From 2bb97a680d1bea236a2a13a04b422af42bf944f5 Mon Sep 17 00:00:00 2001 From: "Philip M. Gollucci" Date: Mon, 25 Nov 2024 22:15:07 -0500 Subject: [PATCH] refactor: move myip¢ralbucket from shell to cdk; functionalize deploy.ts (#66) --- bin/p6lzctl | 114 ++++------- package.json | 1 + pnpm-lock.yaml | 73 +++++++ src/constructs/p6-lz-sra-central-bucket.ts | 2 + src/constructs/p6-lz-sra-org-trail.ts | 23 +++ src/deploy.ts | 209 +-------------------- src/phases.ts | 167 ++++++++++++++++ src/stacks/audit-1.ts | 8 +- src/stacks/audit-2.ts | 7 +- src/stacks/logarchive-2.ts | 8 +- src/stacks/network-2.ts | 8 +- src/stacks/shared-2.ts | 8 +- src/types.ts | 5 - src/util.ts | 60 ++++++ 14 files changed, 386 insertions(+), 307 deletions(-) create mode 100644 src/phases.ts create mode 100644 src/util.ts diff --git a/bin/p6lzctl b/bin/p6lzctl index 0d3dcdd..40be922 100755 --- a/bin/p6lzctl +++ b/bin/p6lzctl @@ -217,11 +217,9 @@ p6_lz_cmd_destroy() { p6_h3 "Cleaning CDK" p6_awscdk_cli_execute destroy - p6_h3 "Reset Context" + p6_h3 "Remove Generated Files and Reset cdk.context.json" p6_file_rmf cdk.context.json p6_file_rmf conf/accounts.yml - p6_file_rmf conf/myip.yml - p6_file_rmf conf/central-bucket.yml p6_return_void } @@ -317,6 +315,29 @@ p6_lz_run_install() { p6_return_void } +###################################################################### +#< +# +# Function: p6_lz_run_generate() +# +# Environment: DNE +#> +###################################################################### +p6_lz_run_generate() { + + p6_h3 "conf/accounts.yml" + p6_file_copy conf/accounts.yml.in conf/accounts.yml + local management_account_name=$(p6_aws_svc_organizations_management_account_name_get) + local pair + for pair in $(p6_aws_svc_organizations_accounts_list_active_ids_and_names); do + local name=$(p6_echo "$pair" | cut -d= -f1 | cut -d- -f 2 | sed -e 's,p6m7g8,management,') + local account_id=$(p6_echo "$pair" | cut -d= -f2) + yq eval -i ".accounts.\"$name\".AccountId = \"$account_id\"" conf/accounts.yml + done + + p6_return_void +} + ###################################################################### #< # @@ -328,17 +349,8 @@ p6_lz_run_build() { p6_h1 "Building" - p6_h2 "Stub Accounts" - p6_file_copy conf/accounts.yml.in conf/accounts.yml - p6_lz_run_phase_2_account_context - - p6_h2 "Stub Central Bucket" - yq eval -n ".logarchiveBucketArn = \"arn:aws:s3:::p6-lz-logarchive-1-p6lzsracentralbucket-DNE\"" >conf/central-bucket.yml - - p6_h2 "My IP" - local my_ip=$(p6_network_ip_public) - local cidr_ip="$my_ip/32" - yq eval -n ".myIp = \"$cidr_ip\"" >conf/myip.yml + p6_h2 "Generating Files" + p6_lz_run_generate p6_h2 "Linting" pnpm eslint . @@ -406,7 +418,7 @@ p6_lz_run() { # Args: # action - # -# Environment: AWS_REGION +# Environment: AWS_REGION CDK #> ###################################################################### p6_lz_run_bootstrap() { @@ -416,17 +428,7 @@ p6_lz_run_bootstrap() { local region=$AWS_REGION p6_h2 "Bootstrapping" - p6_h3 "Bootstrapping: conf/" - p6_file_copy conf/accounts.yml.in conf/accounts.yml - p6_lz_run_phase_2_account_context - - p6_h3 "Stub Central Bucket" - yq eval -n ".logarchiveBucketArn = \"arn:aws:s3:::p6-lz-logarchive-1-p6lzsracentralbucket-DNE\"" >conf/central-bucket.yml - - p6_h3 "My IP" - local my_ip=$(p6_network_ip_public) - local cidr_ip="$my_ip/32" - yq eval -n ".myIp = \"$cidr_ip\"" >conf/myip.yml + p6_lz_run_generate p6_h3 "Bootstrapping: CDK" p6_awscdk_cli_execute "$action" "" "$account_id" "$region" @@ -469,35 +471,11 @@ p6_lz_run_phase_2() { local action="$1" p6_h2 "Phase 2" - p6_lz_run_phase_2_account_context p6_lz_run_phase_2_bootstrap_trust "$action" p6_return_void } -###################################################################### -#< -# -# Function: p6_lz_run_phase_2_account_context() -# -# Environment: _2_ -#> -###################################################################### -p6_lz_run_phase_2_account_context() { - - p6_h3 "Phase 2: Account Context" - p6_file_copy conf/accounts.yml.in conf/accounts.yml - local management_account_name=$(p6_aws_svc_organizations_management_account_name_get) - local pair - for pair in $(p6_aws_svc_organizations_accounts_list_active_ids_and_names); do - local name=$(p6_echo "$pair" | cut -d= -f1 | cut -d- -f 2 | sed -e 's,p6m7g8,management,') - local account_id=$(p6_echo "$pair" | cut -d= -f2) - yq eval -i ".accounts.\"$name\".AccountId = \"$account_id\"" conf/accounts.yml - done - - p6_return_void -} - ###################################################################### #< # @@ -596,11 +574,6 @@ p6_lz_run_phase_3_logarchive_account() { p6_h2 "Phase 3: Logarchive Stack" p6_awscdk_cli_execute $action p6-lz-logarchive-1 - - p6_h3 "Phase 3: Logarchive Add Logarchive Bucket Name to Context" - local logarchive_account_name=$(p6_lz_util_logarchive_account_name) - p6_aws_svc_organizations_sts_run_as $logarchive_account_name p6_lz_util_set_logarchive_bucket $action - p6_awscdk_cli_execute $action p6-lz-logarchive-2 p6_return_void @@ -729,7 +702,7 @@ p6_lz_run_phase_4() { # Args: # action - # -# Environment: _4_ +# Environment: CDK _4_ #> ###################################################################### p6_lz_run_phase_4_sandbox_account() { @@ -803,30 +776,6 @@ p6_lz_run_phase_4_prod_account() { p6_return_void } -###################################################################### -#< -# -# Function: p6_lz_util_cdk_context_add_logarchive_bucket(action) -# -# Args: -# action - -# -#> -###################################################################### -p6_lz_util_set_logarchive_bucket() { - local action="$1" - - if p6_string_eq "$action" "deploy"; then - local logarchive_bucket_name=$(p6_aws_svc_s3_bucket_find_prefix "p6-lz-logarchive-1-p6lzsracentralbucket") - yq eval -n ".logarchiveBucketArn = \"arn:aws:s3:::$logarchive_bucket_name\"" >conf/central-bucket.yml - elif p6_string_eq "$action" "diff"; then - local logarchive_bucket_name="p6-lz-logarchive-1-p6lzsracentralbucket-DNE" - yq eval -n ".logarchiveBucketArn = \"arn:aws:s3:::$logarchive_bucket_name\"" >conf/central-bucket.yml - fi - - p6_return_void -} - # ###################################################################### # #< # # @@ -873,7 +822,10 @@ p6_lz_util_logs_delete() { ###################################################################### #< # -# Function: p6_lz_util_audit_account_id_get() +# Function: str audit_account_id = p6_lz_util_audit_account_id_get() +# +# Returns: +# str - audit_account_id # #> ###################################################################### diff --git a/package.json b/package.json index e4a9921..34b4b58 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "aws-cdk-lib": "2.170.0", "aws-lambda": "^1.0.7", "aws-sdk": "^2.1692.0", + "axios": "^1.7.8", "cdk-iam-floyd": "^0.658.0", "constructs": "^10.4.2", "p6-cdk-namer": "^1.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0f7e780..68ef2d0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,9 @@ importers: aws-sdk: specifier: ^2.1692.0 version: 2.1692.0 + axios: + specifier: ^1.7.8 + version: 1.7.8 cdk-iam-floyd: specifier: ^0.658.0 version: 0.658.0(aws-cdk-lib@2.170.0(constructs@10.4.2))(constructs@10.4.2) @@ -1389,6 +1392,9 @@ packages: async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} @@ -1424,6 +1430,9 @@ packages: resolution: {integrity: sha512-x511uiJ/57FIsbgUe5csJ13k3uzu25uWQE+XqfBis/sB0SFoiElJWXRkgEAUh0U6n40eT3ay5Ue4oPkRMu1LYw==} engines: {node: '>= 10.0.0'} + axios@1.7.8: + resolution: {integrity: sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==} + babel-jest@29.7.0: resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -1580,6 +1589,10 @@ packages: colorspace@1.1.4: resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + commander@3.0.2: resolution: {integrity: sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==} @@ -1674,6 +1687,10 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -2096,9 +2113,22 @@ packages: fn.name@1.1.0: resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + form-data@4.0.1: + resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} + engines: {node: '>= 6'} + fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -2804,6 +2834,14 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -3019,6 +3057,9 @@ packages: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + punycode@1.3.2: resolution: {integrity: sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==} @@ -5487,6 +5528,8 @@ snapshots: async@3.2.6: {} + asynckit@0.4.0: {} + available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.0.0 @@ -5523,6 +5566,14 @@ snapshots: uuid: 8.0.0 xml2js: 0.6.2 + axios@1.7.8: + dependencies: + follow-redirects: 1.15.9 + form-data: 4.0.1 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + babel-jest@29.7.0(@babel/core@7.26.0): dependencies: '@babel/core': 7.26.0 @@ -5705,6 +5756,10 @@ snapshots: color: 3.2.1 text-hex: 1.0.0 + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + commander@3.0.2: {} comment-parser@1.4.1: {} @@ -5794,6 +5849,8 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 + delayed-stream@1.0.0: {} + dequal@2.0.3: {} detect-newline@3.1.0: {} @@ -6363,10 +6420,18 @@ snapshots: fn.name@1.1.0: {} + follow-redirects@1.15.9: {} + for-each@0.3.3: dependencies: is-callable: 1.2.7 + form-data@4.0.1: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + fs.realpath@1.0.0: {} fsevents@2.3.2: @@ -7392,6 +7457,12 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + mimic-fn@2.1.0: {} min-indent@1.0.1: {} @@ -7596,6 +7667,8 @@ snapshots: kleur: 3.0.3 sisteransi: 1.0.5 + proxy-from-env@1.1.0: {} + punycode@1.3.2: {} punycode@2.3.1: {} diff --git a/src/constructs/p6-lz-sra-central-bucket.ts b/src/constructs/p6-lz-sra-central-bucket.ts index 9db5502..da1d748 100644 --- a/src/constructs/p6-lz-sra-central-bucket.ts +++ b/src/constructs/p6-lz-sra-central-bucket.ts @@ -5,6 +5,7 @@ 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 { getCentralBucketName } from '../util' /** * XXX: Danger! CloudFormation CloudTrail support is a literal piece of shit. @@ -42,6 +43,7 @@ export class P6LzSraCentralBucket extends cdk.Resource { }) const bucket = new s3.Bucket(this, 'Bucket', { + bucketName: getCentralBucketName(this), autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, encryption: s3.BucketEncryption.KMS, diff --git a/src/constructs/p6-lz-sra-org-trail.ts b/src/constructs/p6-lz-sra-org-trail.ts index 6be8914..6896ea0 100644 --- a/src/constructs/p6-lz-sra-org-trail.ts +++ b/src/constructs/p6-lz-sra-org-trail.ts @@ -6,6 +6,29 @@ import * as cloudtrail from 'aws-cdk-lib/aws-cloudtrail' import * as iam from 'aws-cdk-lib/aws-iam' import * as kms from 'aws-cdk-lib/aws-kms' +/** + * XXX: Danger! CloudFormation CloudTrail support is a literal piece of shit. + * XXX: You can not set isLogging: true when making an isOrganizationTrail: true + * XXX: The logging enable will fail b/c it thinks the trail doesn't exist yet + * XXX: The aws cdk cloudtrail.Trail resource does not provide a way to not set isLogging: true + * + * This forces us to drop to the L1 CfnTrail Resource *sigh* + * + * XXX: The s3 bucket policy does not work when following the docs nor for what is in the AWS CDK cloudtrail.Trail + * XXX: which are sadly different! + * XXX: Since this bucket is only used for cloudtrail we can hack around it by granting ALL objects in the bucket + * + * Hence the `resources: [bucket.arnForObjects('*')]` for s3:PutObject + * + * XXX: Despite the docs explaining sns:Publish (CDK) or SNS:Publish (AWS Docs) neither works + * XXX: whether you do it via topic.grantPublish(trail) or via iam Policy attach + * + * There seems to be now solution here + * + * XXX: Its simpler to write a post cdk deploy 1-liner than a [aws] custom resource to start logging + * + */ + export interface IP6LzSraOrgTrailProps extends ILogarchiveBucket { logGroup: logs.ILogGroup logRole: iam.IRole diff --git a/src/deploy.ts b/src/deploy.ts index ffbf1fe..d946788 100644 --- a/src/deploy.ts +++ b/src/deploy.ts @@ -1,210 +1,17 @@ #!/usr/bin/env node -import type { IAccountsConfig } from './types' -import fs from 'node:fs' -import process from 'node:process' import * as cdk from 'aws-cdk-lib' -import * as ec2 from 'aws-cdk-lib/aws-ec2' -import yaml from 'js-yaml' -import { AuditAccountStack1 } from './stacks/audit-1' -import { AuditAccountStack2 } from './stacks/audit-2' -import { AuditAccountStack3 } from './stacks/audit-3' -import { DevAccountStack } from './stacks/dev' -import { LogarchiveAccountStack1 } from './stacks/logarchive-1' -import { LogarchiveAccountStack2 } from './stacks/logarchive-2' -import { AvmStack } from './stacks/management-1-avm' -import { OrganizationStack } from './stacks/management-1-organization' -import { ManagementAccountStack3 } from './stacks/management-3' -import { NetworkAccountStack1 } from './stacks/network-1' -import { NetworkAccountStack2 } from './stacks/network-2' -import { ProdAccountStack } from './stacks/prod' -import { QaAccountStack } from './stacks/qa' -import { SandboxAccountStack } from './stacks/sandbox' -import { SharedAccountStack1 } from './stacks/shared-1' -import { SharedAccountStack2 } from './stacks/shared-2' +import { phase1, phase2, phase3, phase4 } from './phases' +import { loadConfig } from './util' -function loadConfig(): any { - const env = { - account: process.env.CDK_DEPLOY_ACCOUNT || process.env.CDK_DEFAULT_ACCOUNT, - region: process.env.CDK_DEPLOY_REGION || process.env.CDK_DEFAULT_REGION, - } - - const config: IAccountsConfig = yaml.load(fs.readFileSync('conf/accounts.yml', 'utf8')) as IAccountsConfig - const myIp: any = (yaml.load(fs.readFileSync('conf/myip.yml', 'utf8')) as any).myIp - const logarchiveBucketArn: any = (yaml.load(fs.readFileSync('conf/central-bucket.yml', 'utf8')) as any).logarchiveBucketArn - - return { - env, - myIp, - logarchiveBucketArn, - accounts: config.accounts, - principals: [ - config.accounts.audit.AccountId, - config.accounts.dev.AccountId, - config.accounts.logarchive.AccountId, - config.accounts.management.AccountId, - config.accounts.network.AccountId, - config.accounts.prod.AccountId, - config.accounts.qa.AccountId, - config.accounts.sandbox.AccountId, - config.accounts.shared.AccountId, - ], - } -} - -function main() { - const config = loadConfig() +async function main() { + const config = await loadConfig() const app = new cdk.App() - - // ----------------------------------- Phase 1 ----------------------------------- - // Mgmt Account - new OrganizationStack(app, 'p6-lz-management-1-organization', { - env: config.env, - accountAlias: config.accounts.management.Name, - }) - new AvmStack(app, 'p6-lz-management-1-avm', { - env: config.env, - }) - - // ----------------------------------- Phase 3 ----------------------------------- - // Logarchive Account - new LogarchiveAccountStack1(app, 'p6-lz-logarchive-1', { - env: { - account: config.accounts.logarchive.AccountId, - region: config.env.region, - }, - accountAlias: config.accounts.logarchive.Name, - principals: config.principals, - }) - - new LogarchiveAccountStack2(app, 'p6-lz-logarchive-2', { - env: { - account: config.accounts.logarchive.AccountId, - region: config.env.region, - }, - principals: config.principals, - centralBucketArn: config.logarchiveBucketArn, - }) - - // Audit Account - new AuditAccountStack1(app, 'p6-lz-audit-1', { - env: { - account: config.accounts.audit.AccountId, - region: config.env.region, - }, - accountAlias: config.accounts.audit.Name, - principals: config.principals, - centralBucketArn: config.logarchiveBucketArn, - }) - - new AuditAccountStack2(app, 'p6-lz-audit-2', { - env: { - account: config.accounts.audit.AccountId, - region: config.env.region, - }, - principals: config.principals, - centralBucketArn: config.logarchiveBucketArn, - }) - - new AuditAccountStack3(app, 'p6-lz-audit-3', { - env: { - account: config.accounts.audit.AccountId, - region: config.env.region, - }, - }) - - // Network Account - new NetworkAccountStack1(app, 'p6-lz-network-1', { - env: { - account: config.accounts.network.AccountId, - region: config.env.region, - }, - accountAlias: config.accounts.network.Name, - principals: config.principals, - }) - - new NetworkAccountStack2(app, 'p6-lz-network-2', { - env: { - account: config.accounts.network.AccountId, - region: config.env.region, - }, - principals: config.principals, - centralBucketArn: config.logarchiveBucketArn, - }) - - // Shared Account - new SharedAccountStack1(app, 'p6-lz-shared-1', { - env: { - account: config.accounts.shared.AccountId, - region: config.env.region, - }, - accountAlias: config.accounts.shared.Name, - principals: config.principals, - }) - - new SharedAccountStack2(app, 'p6-lz-shared-2', { - env: { - account: config.accounts.shared.AccountId, - region: config.env.region, - }, - principals: config.principals, - centralBucketArn: config.logarchiveBucketArn, - }) - - // Management Account - new ManagementAccountStack3(app, 'p6-lz-management-3', { - env: { - account: config.accounts.management.AccountId, - region: config.env.region, - }, - }) - - // ----------------------------------- Phase 4 ----------------------------------- - // Sandbox - new SandboxAccountStack(app, 'p6-lz-sandbox', { - env: { - account: config.accounts.sandbox.AccountId, - region: config.env.region, - }, - accountAlias: config.accounts.sandbox.Name, - cidr: ec2.IpAddresses.cidr(config.accounts.sandbox.Vpc.cidr), - myIp: ec2.Peer.ipv4(config.myIp), - }) - - // Dev Account - new DevAccountStack(app, 'p6-lz-dev', { - env: { - account: config.accounts.dev.AccountId, - region: config.env.region, - }, - accountAlias: config.accounts.dev.Name, - cidr: ec2.IpAddresses.cidr(config.accounts.dev.Vpc.cidr), - myIp: ec2.Peer.ipv4(config.myIp), - }) - - // QA - new QaAccountStack(app, 'p6-lz-qa', { - env: { - account: config.accounts.qa.AccountId, - region: config.env.region, - }, - accountAlias: config.accounts.qa.Name, - cidr: ec2.IpAddresses.cidr(config.accounts.qa.Vpc.cidr), - myIp: ec2.Peer.ipv4(config.myIp), - }) - - // Prod - new ProdAccountStack(app, 'p6-lz-prod', { - env: { - account: config.accounts.prod.AccountId, - region: config.env.region, - }, - accountAlias: config.accounts.prod.Name, - cidr: ec2.IpAddresses.cidr(config.accounts.prod.Vpc.cidr), - myIp: ec2.Peer.ipv4(config.myIp), - }) - + phase1(app, config) + phase2(app, config) + phase3(app, config) + phase4(app, config) app.synth() } diff --git a/src/phases.ts b/src/phases.ts new file mode 100644 index 0000000..8b8f5b3 --- /dev/null +++ b/src/phases.ts @@ -0,0 +1,167 @@ +import type * as cdk from 'aws-cdk-lib' +import * as ec2 from 'aws-cdk-lib/aws-ec2' +import { AuditAccountStack1 } from './stacks/audit-1' +import { AuditAccountStack2 } from './stacks/audit-2' +import { AuditAccountStack3 } from './stacks/audit-3' +import { DevAccountStack } from './stacks/dev' +import { LogarchiveAccountStack1 } from './stacks/logarchive-1' +import { LogarchiveAccountStack2 } from './stacks/logarchive-2' +import { AvmStack } from './stacks/management-1-avm' +import { OrganizationStack } from './stacks/management-1-organization' +import { ManagementAccountStack3 } from './stacks/management-3' +import { NetworkAccountStack1 } from './stacks/network-1' +import { NetworkAccountStack2 } from './stacks/network-2' +import { ProdAccountStack } from './stacks/prod' +import { QaAccountStack } from './stacks/qa' +import { SandboxAccountStack } from './stacks/sandbox' +import { SharedAccountStack1 } from './stacks/shared-1' +import { SharedAccountStack2 } from './stacks/shared-2' + +export function phase1(app: cdk.App, config: any) { + // Mgmt Account + new OrganizationStack(app, 'p6-lz-management-1-organization', { + env: config.env, + accountAlias: config.accounts.management.Name, + }) + new AvmStack(app, 'p6-lz-management-1-avm', { + env: config.env, + }) +} + +export function phase2(app: cdk.App, config: any) { + // Logarchive Account + new LogarchiveAccountStack1(app, 'p6-lz-logarchive-1', { + env: { + account: config.accounts.logarchive.AccountId, + region: config.env.region, + }, + accountAlias: config.accounts.logarchive.Name, + principals: config.principals, + }) +} + +export function phase3(app: cdk.App, config: any) { + // Logarchive Account + new LogarchiveAccountStack2(app, 'p6-lz-logarchive-2', { + env: { + account: config.accounts.logarchive.AccountId, + region: config.env.region, + }, + principals: config.principals, + }) + + // Audit Account + new AuditAccountStack1(app, 'p6-lz-audit-1', { + env: { + account: config.accounts.audit.AccountId, + region: config.env.region, + }, + accountAlias: config.accounts.audit.Name, + principals: config.principals, + }) + + new AuditAccountStack2(app, 'p6-lz-audit-2', { + env: { + account: config.accounts.audit.AccountId, + region: config.env.region, + }, + principals: config.principals, + }) + + new AuditAccountStack3(app, 'p6-lz-audit-3', { + env: { + account: config.accounts.audit.AccountId, + region: config.env.region, + }, + }) + + // Network Account + new NetworkAccountStack1(app, 'p6-lz-network-1', { + env: { + account: config.accounts.network.AccountId, + region: config.env.region, + }, + accountAlias: config.accounts.network.Name, + principals: config.principals, + }) + + new NetworkAccountStack2(app, 'p6-lz-network-2', { + env: { + account: config.accounts.network.AccountId, + region: config.env.region, + }, + principals: config.principals, + }) + + // Shared Account + new SharedAccountStack1(app, 'p6-lz-shared-1', { + env: { + account: config.accounts.shared.AccountId, + region: config.env.region, + }, + accountAlias: config.accounts.shared.Name, + principals: config.principals, + }) + + new SharedAccountStack2(app, 'p6-lz-shared-2', { + env: { + account: config.accounts.shared.AccountId, + region: config.env.region, + }, + principals: config.principals, + }) + + // Management Account + new ManagementAccountStack3(app, 'p6-lz-management-3', { + env: { + account: config.accounts.management.AccountId, + region: config.env.region, + }, + }) +} + +export function phase4(app: cdk.App, config: any) { + // Sandbox + new SandboxAccountStack(app, 'p6-lz-sandbox', { + env: { + account: config.accounts.sandbox.AccountId, + region: config.env.region, + }, + accountAlias: config.accounts.sandbox.Name, + cidr: ec2.IpAddresses.cidr(config.accounts.sandbox.Vpc.cidr), + myIp: config.myIp, + }) + + // Dev Account + new DevAccountStack(app, 'p6-lz-dev', { + env: { + account: config.accounts.dev.AccountId, + region: config.env.region, + }, + accountAlias: config.accounts.dev.Name, + cidr: ec2.IpAddresses.cidr(config.accounts.dev.Vpc.cidr), + myIp: config.myIp, + }) + + // QA Account + new QaAccountStack(app, 'p6-lz-qa', { + env: { + account: config.accounts.qa.AccountId, + region: config.env.region, + }, + accountAlias: config.accounts.qa.Name, + cidr: ec2.IpAddresses.cidr(config.accounts.qa.Vpc.cidr), + myIp: config.myIp, + }) + + // Prod Account + new ProdAccountStack(app, 'p6-lz-prod', { + env: { + account: config.accounts.prod.AccountId, + region: config.env.region, + }, + accountAlias: config.accounts.prod.Name, + cidr: ec2.IpAddresses.cidr(config.accounts.prod.Vpc.cidr), + myIp: config.myIp, + }) +} diff --git a/src/stacks/audit-1.ts b/src/stacks/audit-1.ts index 8fc5fac..9fe6304 100644 --- a/src/stacks/audit-1.ts +++ b/src/stacks/audit-1.ts @@ -1,18 +1,18 @@ import type { Construct } from 'constructs' -import type { IAccountAlias, ILogarchiveBucketArn, IShareWithOrg } from '../types' +import type { IAccountAlias, IShareWithOrg } from '../types' import * as cdk from 'aws-cdk-lib' -import * as s3 from 'aws-cdk-lib/aws-s3' import { P6CDKNamer } from 'p6-cdk-namer' import { P6LzSraCloudWatch } from '../constructs/p6-lz-sra-cloudwatch' import { P6LzSraOrgTrail } from '../constructs/p6-lz-sra-org-trail' +import { getCentralBucket } from '../util' -interface AuditAccountStack1Props extends cdk.StackProps, IAccountAlias, IShareWithOrg, ILogarchiveBucketArn {} +interface AuditAccountStack1Props extends cdk.StackProps, IAccountAlias, IShareWithOrg {} export class AuditAccountStack1 extends cdk.Stack { constructor(scope: Construct, id: string, props: AuditAccountStack1Props) { super(scope, id, props) - const bucket = s3.Bucket.fromBucketArn(this, 'CentralBucket', props.centralBucketArn.toString()) + const bucket = getCentralBucket(this) new P6CDKNamer(this, 'P6CDKNamer', { accountAlias: props.accountAlias, diff --git a/src/stacks/audit-2.ts b/src/stacks/audit-2.ts index 61da0d3..c46dacc 100644 --- a/src/stacks/audit-2.ts +++ b/src/stacks/audit-2.ts @@ -1,19 +1,18 @@ import type { Construct } from 'constructs' import type { IShareWithOrg } from '../types' -import type { ILogarchiveBucketArn } from './../types' import * as cdk from 'aws-cdk-lib' -import * as s3 from 'aws-cdk-lib/aws-s3' import { P6LzSraChatbot } from '../constructs/p6-lz-sra-chatbot' import { P6LzSraConfig } from '../constructs/p6-lz-sra-config' import { P6LzSraSecurityhub } from '../constructs/p6-lz-sra-security-hub' +import { getCentralBucket } from '../util' -interface AuditAccountStack2Props extends cdk.StackProps, IShareWithOrg, ILogarchiveBucketArn {} +interface AuditAccountStack2Props extends cdk.StackProps, IShareWithOrg {} export class AuditAccountStack2 extends cdk.Stack { constructor(scope: Construct, id: string, props: AuditAccountStack2Props) { super(scope, id, props) - const bucket = s3.Bucket.fromBucketArn(this, 'CentralBucket', props.centralBucketArn.toString()) + const bucket = getCentralBucket(this) new P6LzSraConfig(this, 'P6LzSraConfig', { principals: props.principals, diff --git a/src/stacks/logarchive-2.ts b/src/stacks/logarchive-2.ts index f2f0183..ca929e7 100644 --- a/src/stacks/logarchive-2.ts +++ b/src/stacks/logarchive-2.ts @@ -1,16 +1,16 @@ import type { Construct } from 'constructs' -import type { ILogarchiveBucketArn, IShareWithOrg } from '../types' +import type { IShareWithOrg } from '../types' import * as cdk from 'aws-cdk-lib' -import * as s3 from 'aws-cdk-lib/aws-s3' import { P6LzSraConfig } from '../constructs/p6-lz-sra-config' +import { getCentralBucket } from '../util' -interface LogarchiveAccountStack2Props extends cdk.StackProps, ILogarchiveBucketArn, IShareWithOrg {} +interface LogarchiveAccountStack2Props extends cdk.StackProps, IShareWithOrg {} export class LogarchiveAccountStack2 extends cdk.Stack { constructor(scope: Construct, id: string, props: LogarchiveAccountStack2Props) { super(scope, id, props) - const bucket = s3.Bucket.fromBucketArn(this, 'CentralBucket', props.centralBucketArn.toString()) + const bucket = getCentralBucket(this) new P6LzSraConfig(this, 'P6LzSraConfig', { principals: props.principals, diff --git a/src/stacks/network-2.ts b/src/stacks/network-2.ts index d68637a..9dd86a9 100644 --- a/src/stacks/network-2.ts +++ b/src/stacks/network-2.ts @@ -1,16 +1,16 @@ import type { Construct } from 'constructs' -import type { ILogarchiveBucketArn, IShareWithOrg } from './../types' +import type { IShareWithOrg } from './../types' import * as cdk from 'aws-cdk-lib' -import * as s3 from 'aws-cdk-lib/aws-s3' import { P6LzSraConfig } from '../constructs/p6-lz-sra-config' +import { getCentralBucket } from '../util' -interface NetworkAccountStack2Props extends cdk.StackProps, ILogarchiveBucketArn, IShareWithOrg {} +interface NetworkAccountStack2Props extends cdk.StackProps, IShareWithOrg {} export class NetworkAccountStack2 extends cdk.Stack { constructor(scope: Construct, id: string, props: NetworkAccountStack2Props) { super(scope, id, props) - const bucket = s3.Bucket.fromBucketArn(this, 'CentralBucket', props.centralBucketArn.toString()) + const bucket = getCentralBucket(this) new P6LzSraConfig(this, 'P6LzSraConfig', { principals: props.principals, diff --git a/src/stacks/shared-2.ts b/src/stacks/shared-2.ts index 73a463c..408c47a 100644 --- a/src/stacks/shared-2.ts +++ b/src/stacks/shared-2.ts @@ -1,16 +1,16 @@ import type { Construct } from 'constructs' -import type { ILogarchiveBucketArn, IShareWithOrg } from '../types' +import type { IShareWithOrg } from '../types' import * as cdk from 'aws-cdk-lib' -import * as s3 from 'aws-cdk-lib/aws-s3' import { P6LzSraConfig } from '../constructs/p6-lz-sra-config' +import { getCentralBucket } from '../util' -interface SharedAccountStack2Props extends cdk.StackProps, ILogarchiveBucketArn, IShareWithOrg {} +interface SharedAccountStack2Props extends cdk.StackProps, IShareWithOrg {} export class SharedAccountStack2 extends cdk.Stack { constructor(scope: Construct, id: string, props: SharedAccountStack2Props) { super(scope, id, props) - const bucket = s3.Bucket.fromBucketArn(this, 'CentralBucket', props.centralBucketArn.toString()) + const bucket = getCentralBucket(this) new P6LzSraConfig(this, 'P6LzSraConfig', { principals: props.principals, diff --git a/src/types.ts b/src/types.ts index 46751df..c8be1ab 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,4 @@ import type { OrganizationalUnit } from '@aws-sdk/client-organizations' -import type * as cdk from 'aws-cdk-lib' import type { IIpAddresses, IPeer } from 'aws-cdk-lib/aws-ec2' import type * as s3 from 'aws-cdk-lib/aws-s3' @@ -27,10 +26,6 @@ export interface ILogarchiveBucket { centralBucket: s3.IBucket } -export interface ILogarchiveBucketArn { - centralBucketArn: cdk.Arn -} - export interface IVpc { cidr: IIpAddresses myIp: IPeer diff --git a/src/util.ts b/src/util.ts new file mode 100644 index 0000000..05b9ede --- /dev/null +++ b/src/util.ts @@ -0,0 +1,60 @@ +import type { Construct } from 'constructs' +import type { IAccountsConfig } from './types' +import * as fs from 'node:fs' +import * as process from 'node:process' +import * as cdk from 'aws-cdk-lib' +import * as ec2 from 'aws-cdk-lib/aws-ec2' +import * as s3 from 'aws-cdk-lib/aws-s3' +import axios from 'axios' +import yaml from 'js-yaml' + +export function getCentralBucketName(scope: Construct): string { + return `p6-lz-sra-logarchive-central-${cdk.Stack.of(scope).region}` +} + +export function getCentralBucket(scope: Construct): s3.IBucket { + const name = getCentralBucketName(scope) + const arn = `arn:aws:s3:::${name}` + const bucket = s3.Bucket.fromBucketArn(scope, 'CentralBucket', arn) + + return bucket +} + +export async function getPublicIpAsEc2Peer(): Promise { + try { + const response = await axios.get('https://checkip.amazonaws.com', { timeout: 5000 }) + const publicIp = response.data.trim() + return ec2.Peer.ipv4(`${publicIp}/32`) + } + catch (error) { + console.error('Error fetching public IP:', error) + throw new Error('Failed to retrieve public IP address.') + } +} + +export async function loadConfig(): Promise { + const env = { + account: process.env.CDK_DEPLOY_ACCOUNT || process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEPLOY_REGION || process.env.CDK_DEFAULT_REGION, + } + + const config: IAccountsConfig = yaml.load(fs.readFileSync('conf/accounts.yml', 'utf8')) as IAccountsConfig + const myIp: ec2.Peer = await getPublicIpAsEc2Peer() + + return { + env, + myIp, + accounts: config.accounts, + principals: [ + config.accounts.audit.AccountId, + config.accounts.dev.AccountId, + config.accounts.logarchive.AccountId, + config.accounts.management.AccountId, + config.accounts.network.AccountId, + config.accounts.prod.AccountId, + config.accounts.qa.AccountId, + config.accounts.sandbox.AccountId, + config.accounts.shared.AccountId, + ], + } +}