├── .gitignore ├── LICENSE ├── README.md ├── TerraformInit-IAM-Policy.json ├── accounts_only.sh ├── deploy.sh ├── destroy.sh └── src ├── accounts ├── ap-southeast-2 │ ├── deployment │ │ ├── _env │ │ │ └── private-bucket.hcl │ │ ├── account.hcl │ │ ├── artifacts-bucket │ │ │ └── terragrunt.hcl │ │ ├── codebuild-apply │ │ │ └── terragrunt.hcl │ │ ├── codebuild-plan │ │ │ └── terragrunt.hcl │ │ ├── codecommit │ │ │ └── terragrunt.hcl │ │ ├── codepipeline │ │ │ └── terragrunt.hcl │ │ ├── logging-bucket │ │ │ └── terragrunt.hcl │ │ └── random-pipeline-prefix │ │ │ └── terragrunt.hcl │ ├── development │ │ └── account.hcl │ ├── logging │ │ ├── account.hcl │ │ └── cloudtrail-bucket │ │ │ └── terragrunt.hcl │ ├── management │ │ ├── account.hcl │ │ └── cloudtrail │ │ │ └── terragrunt.hcl │ ├── region.hcl │ ├── security │ │ └── account.hcl │ └── shared-services │ │ └── account.hcl ├── global │ ├── deployment │ │ ├── account-alias │ │ │ └── terragrunt.hcl │ │ └── account.hcl │ ├── development │ │ ├── account-alias │ │ │ └── terragrunt.hcl │ │ └── account.hcl │ ├── logging │ │ ├── account-alias │ │ │ └── terragrunt.hcl │ │ └── account.hcl │ ├── management │ │ ├── account-alias │ │ │ └── terragrunt.hcl │ │ └── account.hcl │ ├── security │ │ ├── account-alias │ │ │ └── terragrunt.hcl │ │ └── account.hcl │ └── shared-services │ │ ├── account-alias │ │ └── terragrunt.hcl │ │ └── account.hcl └── us-east-1 │ ├── management │ ├── account.hcl │ └── billing-alarm │ │ └── terragrunt.hcl │ └── region.hcl ├── bootstrap ├── organization │ ├── account-deployment.tf │ ├── account-development.tf │ ├── account-logging.tf │ ├── account-security.tf │ ├── account-shared-services.tf │ ├── context.tf │ ├── main.tf │ ├── outputs.tf │ ├── overrides │ │ └── backend-local-override.tf │ ├── policies.tf │ ├── service-control-policies.tf │ ├── terragrunt-local.hcl │ ├── terragrunt.hcl │ ├── variables.tf │ └── versions.tf ├── temp-admin │ ├── .terraform.lock.hcl │ ├── context.tf │ ├── main.tf │ ├── outputs.tf │ ├── terragrunt.hcl │ ├── variables.tf │ └── versions.tf └── terragrunt.hcl ├── buildspec-apply.yaml ├── buildspec-plan.yaml ├── common_vars.yaml ├── modules ├── account-alias │ ├── context.tf │ ├── main.tf │ ├── outputs.tf │ ├── variables.tf │ └── versions.tf ├── assume-role-policy │ ├── context.tf │ ├── main.tf │ ├── outputs.tf │ ├── variables.tf │ └── versions.tf ├── cloudtrail-bucket │ ├── context.tf │ ├── main.tf │ ├── outputs.tf │ ├── variables.tf │ └── versions.tf ├── cloudtrail │ ├── context.tf │ ├── main.tf │ ├── variables.tf │ └── versions.tf ├── codecommit │ ├── context.tf │ ├── main.tf │ ├── outputs.tf │ ├── variables.tf │ └── versions.tf ├── cross-account-role │ ├── context.tf │ ├── main.tf │ ├── outputs.tf │ ├── variables.tf │ └── versions.tf ├── email-billing-alarm │ ├── context.tf │ ├── main.tf │ ├── outputs.tf │ ├── variables.tf │ └── versions.tf ├── random-string │ ├── main.tf │ ├── outputs.tf │ ├── variables.tf │ └── versions.tf ├── terraform-codebuild │ ├── context.tf │ ├── main.tf │ ├── outputs.tf │ ├── variables.tf │ └── versions.tf └── terraform-codepipeline │ ├── context.tf │ ├── main.tf │ ├── outputs.tf │ ├── variables.tf │ └── versions.tf └── terragrunt.hcl /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/terraform,terragrunt,visualstudiocode 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=terraform,terragrunt,visualstudiocode 3 | 4 | ### Terraform ### 5 | # Local .terraform directories 6 | **/.terraform/* 7 | 8 | # .tfstate files 9 | *.tfstate 10 | *.tfstate.* 11 | 12 | # Crash log files 13 | crash.log 14 | crash.*.log 15 | 16 | # Exclude all .tfvars files, which are likely to contain sensitive data, such as 17 | # password, private keys, and other secrets. These should not be part of version 18 | # control as they are data points which are potentially sensitive and subject 19 | # to change depending on the environment. 20 | *.tfvars 21 | *.tfvars.json 22 | 23 | # Ignore override files as they are usually used to override resources locally and so 24 | # are not checked in 25 | override.tf 26 | override.tf.json 27 | *_override.tf 28 | *_override.tf.json 29 | 30 | # Include override files you do wish to add to version control using negated pattern 31 | # !example_override.tf 32 | 33 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 34 | # example: *tfplan* 35 | 36 | # Ignore CLI configuration files 37 | .terraformrc 38 | terraform.rc 39 | 40 | ### Terragrunt ### 41 | # terragrunt cache directories 42 | **/.terragrunt-cache/* 43 | 44 | # Terragrunt debug output file (when using `--terragrunt-debug` option) 45 | # See: https://terragrunt.gruntwork.io/docs/reference/cli-options/#terragrunt-debug 46 | terragrunt-debug.tfvars.json 47 | 48 | ### VisualStudioCode ### 49 | .vscode/* 50 | !.vscode/settings.json 51 | !.vscode/tasks.json 52 | !.vscode/launch.json 53 | !.vscode/extensions.json 54 | !.vscode/*.code-snippets 55 | 56 | # Local History for Visual Studio Code 57 | .history/ 58 | 59 | # Built Visual Studio Code Extensions 60 | *.vsix 61 | 62 | ### VisualStudioCode Patch ### 63 | # Ignore all local history of files 64 | .history 65 | .ionide 66 | 67 | # End of https://www.toptal.com/developers/gitignore/api/terraform,terragrunt,visualstudiocode 68 | 69 | accounts.json 70 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Oliver Schenk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /TerraformInit-IAM-Policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "AllowOrgManagement", 6 | "Effect": "Allow", 7 | "Action": [ 8 | "organizations:*" 9 | ], 10 | "Resource": [ 11 | "*" 12 | ] 13 | }, 14 | { 15 | "Sid": "AllowAliasAdmin", 16 | "Effect": "Allow", 17 | "Action": [ 18 | "iam:ListAccountAliases", 19 | "iam:CreateAccountAlias", 20 | "iam:DeleteAccountAlias" 21 | ], 22 | "Resource": [ 23 | "*" 24 | ] 25 | }, 26 | { 27 | "Sid": "AllowServiceLinkedRoleCreation", 28 | "Effect": "Allow", 29 | "Action": [ 30 | "iam:CreateServiceLinkedRole" 31 | ], 32 | "Resource": [ 33 | "arn:aws:iam::*:role/aws-service-role/*" 34 | ] 35 | }, 36 | { 37 | "Sid": "AllowRoleManagement", 38 | "Effect": "Allow", 39 | "Action": [ 40 | "iam:GetRole", 41 | "iam:CreateRole", 42 | "iam:DetachRolePolicy", 43 | "iam:DeleteRole", 44 | "iam:ListAttachedRolePolicies", 45 | "iam:ListInstanceProfilesForRole", 46 | "iam:ListRolePolicies", 47 | "iam:AttachRolePolicy", 48 | "iam:UpdateAssumeRolePolicy", 49 | "iam:TagRole" 50 | ], 51 | "Resource": [ 52 | "arn:aws:iam::*:role/Billing", 53 | "arn:aws:iam::*:role/*Terraform*" 54 | ] 55 | }, 56 | { 57 | "Sid": "AllowIamCreateTerraformPolicy", 58 | "Effect": "Allow", 59 | "Action": [ 60 | "iam:CreatePolicy", 61 | "iam:CreatePolicyVersion", 62 | "iam:DeletePolicy", 63 | "iam:DeletePolicyVersion", 64 | "iam:ListPolicyVersions", 65 | "iam:GetPolicy", 66 | "iam:GetPolicyVersion" 67 | ], 68 | "Resource": [ 69 | "arn:aws:iam::*:policy/*Terraform*" 70 | ] 71 | }, 72 | { 73 | "Sid": "AllowIamAttachTerraformPolicy", 74 | "Effect": "Allow", 75 | "Action": [ 76 | "iam:GetUser", 77 | "iam:AttachUserPolicy", 78 | "iam:DetachUserPolicy", 79 | "iam:ListAttachedUserPolicies", 80 | "iam:ListInstanceProfilesForRole" 81 | ], 82 | "Resource": [ 83 | "arn:aws:iam::*:user/terraform-init" 84 | ] 85 | }, 86 | { 87 | "Sid": "AllowGuardDutyManagement", 88 | "Effect": "Allow", 89 | "Action": [ 90 | "guardduty:EnableOrganizationAdminAccount", 91 | "guardduty:CreateDetector", 92 | "guardduty:ListDetectors", 93 | "guardduty:DisableOrganizationAdminAccount", 94 | "guardduty:ListOrganizationAdminAccounts" 95 | ], 96 | "Resource": [ 97 | "*" 98 | ] 99 | }, 100 | { 101 | "Sid": "AllowGuardDutyDetectorManagement", 102 | "Effect": "Allow", 103 | "Action": [ 104 | "guardduty:DeleteMembers", 105 | "guardduty:GetMasterAccount", 106 | "guardduty:UpdateDetector", 107 | "guardduty:DisassociateMembers", 108 | "guardduty:GetMembers", 109 | "guardduty:UpdateOrganizationConfiguration", 110 | "guardduty:GetDetector", 111 | "guardduty:AcceptInvitation", 112 | "guardduty:StopMonitoringMembers", 113 | "guardduty:StartMonitoringMembers", 114 | "guardduty:CreateMembers", 115 | "guardduty:DescribeOrganizationConfiguration", 116 | "guardduty:DeleteDetector", 117 | "guardduty:InviteMembers", 118 | "guardduty:ListMembers" 119 | ], 120 | "Resource": [ 121 | "arn:aws:guardduty:*:*:detector/*" 122 | ] 123 | }, 124 | { 125 | "Sid": "AllowRoleAssumptionToOrgAccounts", 126 | "Effect": "Allow", 127 | "Action": [ 128 | "sts:AssumeRole" 129 | ], 130 | "Resource": [ 131 | "arn:aws:iam::*:role/OrganizationAccountAccessRole" 132 | ] 133 | } 134 | ] 135 | } 136 | -------------------------------------------------------------------------------- /accounts_only.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | DEFAULT_REGION='ap-southeast-2' 5 | 6 | function usage { 7 | echo "DESCRIPTION:" 8 | echo " Creates a temp-admin user in Deployment account and the deploys the resources in the" 9 | echo " accounts folder. It does not deploy the bootstrapping resources." 10 | echo "" 11 | echo " *** MUST BE RUN WITH ADMIN CREDENTIALS FOR terraform-init USER IN THE MANAGEMENT ACCOUNT ***" 12 | echo "" 13 | echo "USAGE:" 14 | echo " accounts_only.sh -a terraform_init_access_key -s terraform_init_secret_key -k keybase_profile" 15 | echo " [-r default_region] [-t tf_account_id] " 16 | echo "" 17 | echo "OPTIONS" 18 | echo " -r the default region for this deployment" 19 | echo " -t account ID where Terraform state is stored, used with -l option" 20 | } 21 | 22 | function pushd () { 23 | command pushd "$@" > /dev/null 24 | } 25 | 26 | function popd () { 27 | command popd "$@" > /dev/null 28 | } 29 | 30 | while getopts "a:s:k:r:t:h" option; do 31 | case ${option} in 32 | a ) ACCESS_KEY=$OPTARG;; 33 | s ) SECRET_KEY=$OPTARG;; 34 | k ) KEYBASE_PROFILE=$OPTARG;; 35 | r ) DEFAULT_REGION=$OPTARG;; 36 | t ) TF_AWS_ACCT=$OPTARG;; 37 | h ) 38 | usage 39 | exit 0 40 | ;; 41 | \? ) 42 | echo "Invalid option: -$OPTARG" 1>&2 43 | usage 44 | exit 1 45 | ;; 46 | esac 47 | done 48 | 49 | if [[ -z "${ACCESS_KEY}" ]]; then 50 | echo "Please provide the terraform-init user's AWS access key as -a key" 1>&2 51 | VALIDATION_ERROR=1 52 | fi 53 | if [[ -z "${SECRET_KEY}" ]]; then 54 | echo "Please provide the terraform-init user's AWS secret access key as -s secret " 1>&2 55 | VALIDATION_ERROR=1 56 | fi 57 | if [[ -z "${KEYBASE_PROFILE}" ]]; then 58 | echo "Please provide the keybase profile as -k profile " 1>&2 59 | VALIDATION_ERROR=1 60 | fi 61 | if [[ -z "${TF_AWS_ACCT}" ]]; then 62 | echo "Please provide the account ID where Terraform state is stored as -t tf_account_id " 1>&2 63 | VALIDATION_ERROR=1 64 | fi 65 | if [[ -n "${VALIDATION_ERROR}" ]]; then 66 | usage 67 | exit 1 68 | fi 69 | 70 | export AWS_DEFAULT_REGION=${DEFAULT_REGION} 71 | echo "Set default region: $AWS_DEFAULT_REGION" 72 | echo "" 73 | 74 | function export_management_keys { 75 | echo "USING MANAGEMENT CREDENTIALS" 76 | echo "" 77 | export AWS_ACCESS_KEY_ID=${ACCESS_KEY} 78 | export AWS_SECRET_ACCESS_KEY=${SECRET_KEY} 79 | } 80 | 81 | function export_admin_keys { 82 | echo "USING ADMIN CREDENTIALS" 83 | echo "" 84 | export AWS_ACCESS_KEY_ID=${ADMIN_ACCESS_KEY} 85 | export AWS_SECRET_ACCESS_KEY=${ADMIN_SECRET_KEY} 86 | } 87 | 88 | export_management_keys 89 | 90 | export TF_AWS_ACCT=${TF_AWS_ACCT} 91 | echo "Terraform state account is set to: $TF_AWS_ACCT" 92 | echo "" 93 | 94 | echo "=== CREATING temp-admin USER ===" 95 | pushd ./src/bootstrap/temp-admin 96 | echo "Running terragrunt init" 97 | terragrunt init 98 | echo "Running terragrunt apply using Terraform state account $TF_AWS_ACCT and keybase profile $KEYBASE_PROFILE" 99 | terragrunt apply -var terraform_state_account_id=${TF_AWS_ACCT} -var keybase=${KEYBASE_PROFILE} 100 | 101 | echo "Storing and decrypting temp-admin access keys" 102 | ADMIN_ACCESS_KEY=$(terraform output -raw temp_admin_access_key) 103 | ADMIN_SECRET_KEY=$(terraform output -raw temp_admin_secret_key | base64 --decode | keybase --pinentry=none pgp decrypt) 104 | popd 105 | 106 | echo "Sleeping for 10 seconds to allow AWS keys to be ready" 107 | sleep 10 # give AWS some time for the new access key to be ready 108 | 109 | echo "=== APPLYING ALL ACCOUNT RESOURCES ===" 110 | export_admin_keys 111 | 112 | echo "=== APPLYING INFRASTRUCTURE FOR EACH ACCOUNT ===" 113 | pushd ./src/accounts 114 | terragrunt run-all init 115 | terragrunt run-all apply 116 | 117 | popd 118 | 119 | echo "=== DESTROYING temp-admin USER ===" 120 | pushd ./src/bootstrap/temp-admin 121 | export_management_keys 122 | terragrunt destroy -var terraform_state_account_id=${TF_AWS_ACCT} -var keybase=${KEYBASE_PROFILE} 123 | popd 124 | echo "=== temp-admin USER DESTROYED ===" 125 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | DEFAULT_REGION='ap-southeast-2' 5 | 6 | function usage { 7 | echo "DESCRIPTION:" 8 | echo " Script for initializing an AWS account structure. See README for more details." 9 | echo " *** MUST BE RUN WITH ADMIN CREDENTIALS FOR terraform-init USER IN THE MANAGEMENT ACCOUNT ***" 10 | echo "" 11 | echo "USAGE:" 12 | echo " deploy.sh -a terraform_init_access_key -s terraform_init_secret_key -k keybase_profile" 13 | echo " [-r default_region] [-l] [-t tf_account_id] [-p]" 14 | echo "" 15 | echo "OPTIONS" 16 | echo " -l skip using local state, can be used after the inital run, must provide tf_account_id" 17 | echo " -p push code to remote repo, should only be used once to bootstrap CodeCommit" 18 | echo " -r the default region for this deployment" 19 | echo " -t account ID where Terraform state is stored, used with -l option" 20 | } 21 | 22 | function pushd () { 23 | command pushd "$@" > /dev/null 24 | } 25 | 26 | function popd () { 27 | command popd "$@" > /dev/null 28 | } 29 | 30 | while getopts "a:s:k:r:lt:ph" option; do 31 | case ${option} in 32 | a ) ACCESS_KEY=$OPTARG;; 33 | s ) SECRET_KEY=$OPTARG;; 34 | k ) KEYBASE_PROFILE=$OPTARG;; 35 | r ) DEFAULT_REGION=$OPTARG;; 36 | l ) SKIP_LOCAL_STATE=1;; 37 | t ) TF_AWS_ACCT=$OPTARG;; 38 | p ) PUSH_CODE=1;; 39 | h ) 40 | usage 41 | exit 0 42 | ;; 43 | \? ) 44 | echo "Invalid option: -$OPTARG" 1>&2 45 | usage 46 | exit 1 47 | ;; 48 | esac 49 | done 50 | 51 | if [[ -z "${ACCESS_KEY}" ]]; then 52 | echo "Please provide the terraform-init user's AWS access key as -a key" 1>&2 53 | VALIDATION_ERROR=1 54 | fi 55 | if [[ -z "${SECRET_KEY}" ]]; then 56 | echo "Please provide the terraform-init user's AWS secret access key as -s secret " 1>&2 57 | VALIDATION_ERROR=1 58 | fi 59 | if [[ -z "${KEYBASE_PROFILE}" ]]; then 60 | echo "Please provide the keybase profile as -k profile " 1>&2 61 | VALIDATION_ERROR=1 62 | fi 63 | if [[ -n "${SKIP_LOCAL_STATE}" && -z "${TF_AWS_ACCT}" ]]; then 64 | echo "Please provide the account ID where Terraform state is stored as -t tf_account_id " 1>&2 65 | VALIDATION_ERROR=1 66 | fi 67 | if [[ -n "${VALIDATION_ERROR}" ]]; then 68 | usage 69 | exit 1 70 | fi 71 | 72 | export AWS_DEFAULT_REGION=${DEFAULT_REGION} 73 | echo "Set default region: $AWS_DEFAULT_REGION" 74 | echo "" 75 | 76 | function export_management_keys { 77 | echo "USING MANAGEMENT CREDENTIALS" 78 | echo "" 79 | export AWS_ACCESS_KEY_ID=${ACCESS_KEY} 80 | export AWS_SECRET_ACCESS_KEY=${SECRET_KEY} 81 | } 82 | 83 | function export_admin_keys { 84 | echo "USING ADMIN CREDENTIALS" 85 | echo "" 86 | export AWS_ACCESS_KEY_ID=${ADMIN_ACCESS_KEY} 87 | export AWS_SECRET_ACCESS_KEY=${ADMIN_SECRET_KEY} 88 | } 89 | 90 | pushd ./src 91 | 92 | pushd ./bootstrap/organization 93 | 94 | export_management_keys 95 | 96 | if [[ -n "${SKIP_LOCAL_STATE}" ]]; then 97 | echo "=== RUNNING ORG CONFIGS WITH REMOTE STATE ===" 98 | export TF_AWS_ACCT=${TF_AWS_ACCT} 99 | 100 | echo "Running terragrunt init" 101 | terragrunt init 102 | echo "Running terragrunt apply" 103 | terragrunt apply 104 | else 105 | echo "=== RUNNING ORG CONFIGS WITH LOCAL STATE ===" 106 | 107 | echo "Copying local state override file from overrides/backend-local.override.tf" 108 | cp overrides/backend-local-override.tf . 109 | echo "" 110 | 111 | echo "Running terragrunt init using local state" 112 | terragrunt init --terragrunt-config terragrunt-local.hcl 113 | echo "Running terragrunt apply using local state" 114 | terragrunt apply --terragrunt-config terragrunt-local.hcl 115 | 116 | echo "Exporting Terraform state account" 117 | DEPLOYMENT_AWS_ACCT=$(terraform output -json account_ids | jq -r '."deployment"') 118 | export TF_AWS_ACCT=${DEPLOYMENT_AWS_ACCT} 119 | 120 | echo "=== COPYING LOCAL STATE TO S3 ===" 121 | echo "Removing backend-local-override.tf" 122 | rm ./backend-local-override.tf || true 123 | sleep 10 # give AWS some time for the IAM policy to take effect 124 | echo "Running terragrunt init to initialise Terraform state S3 backend" 125 | terragrunt init 126 | fi 127 | 128 | echo "Terraform state account is set to: $TF_AWS_ACCT" 129 | echo "" 130 | 131 | echo "Outputting account IDs into accounts.json" 132 | terraform output -json account_ids | jq > ../../accounts.json 133 | cat ../../accounts.json 134 | echo "" 135 | popd 136 | 137 | echo "=== CREATING temp-admin USER ===" 138 | pushd ./bootstrap/temp-admin 139 | echo "Running terragrunt init" 140 | terragrunt init 141 | echo "Running terragrunt apply using Terraform state account $TF_AWS_ACCT and keybase profile $KEYBASE_PROFILE" 142 | terragrunt apply -var terraform_state_account_id=${TF_AWS_ACCT} -var keybase=${KEYBASE_PROFILE} 143 | 144 | echo "Storing and decrypting temp-admin access keys" 145 | ADMIN_ACCESS_KEY=$(terraform output -raw temp_admin_access_key) 146 | ADMIN_SECRET_KEY=$(terraform output -raw temp_admin_secret_key | base64 --decode | keybase --pinentry=none pgp decrypt) 147 | popd 148 | 149 | echo "Sleeping for 10 seconds to allow AWS keys to be ready" 150 | sleep 10 # give AWS some time for the new access key to be ready 151 | 152 | echo "=== APPLYING ALL ACCOUNT RESOURCES ===" 153 | export_admin_keys 154 | 155 | echo "=== APPLYING INFRASTRUCTURE FOR EACH ACCOUNT ===" 156 | pushd ./accounts 157 | terragrunt run-all init 158 | terragrunt run-all apply 159 | 160 | pushd ./ap-southeast-2/deployment/codecommit 161 | REPO_NAME=$(terragrunt output -raw repository_name) 162 | echo "Stored CodeCommit repository name: $REPO_NAME" 163 | popd 164 | 165 | popd 166 | 167 | popd # go to root folder of this repo 168 | 169 | if [[ -n "${PUSH_CODE}" ]]; then 170 | echo "=== PREPARING TO PUSH CODE TO CODE COMMIT REPOSITORY ===" 171 | echo "Cleaning up Terragrunt and Terraform cache" 172 | find . -type d -name ".terragrunt-cache" -prune -exec rm -rf {} \; # clean Terragrunt cache data 173 | find . -type d -name ".terraform" -prune -exec rm -rf {} \; # clean Terraform cache data 174 | echo "Preparing source code folder" 175 | rm -rf repository # clean if already exists 176 | mkdir repository 177 | echo "Cloning CodeCommig repository: codecommit://$REPO_NAME in to repository folder" 178 | git clone codecommit://$REPO_NAME repository 179 | echo "Transfering source code into cloned repository folder" 180 | rsync -av . ./repository --exclude repository 181 | pushd ./repository 182 | echo "Running git add and commit for initial commit" 183 | git --git-dir=.git add . 184 | git --git-dir=.git commit -m "Bootstrap commit" 185 | echo "Pushing to remote repository" 186 | git --git-dir=.git push 187 | popd 188 | echo "Cleaning up repository folder" 189 | rm -rf repository 190 | fi 191 | 192 | echo "=== DESTROYING temp-admin USER ===" 193 | pushd ./src/bootstrap/temp-admin 194 | export_management_keys 195 | terragrunt destroy -var terraform_state_account_id=${TF_AWS_ACCT} -var keybase=${KEYBASE_PROFILE} 196 | popd 197 | echo "=== temp-admin USER DESTROYED ===" 198 | 199 | echo "You can now clone the repository into a new folder and work with git going forward." 200 | echo "The deployed CI/CD pipeline will take care of the build and deployment process." 201 | echo "" 202 | echo "See README for more information about how to best access the CodeCommit repository using git." 203 | -------------------------------------------------------------------------------- /destroy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | DEFAULT_REGION='ap-southeast-2' 5 | 6 | function usage { 7 | echo "DESCRIPTION:" 8 | echo " Script for initializing an AWS account structure. See README for more details." 9 | echo " *** MUST BE RUN WITH ADMIN CREDENTIALS FOR terraform-init USER IN THE MANAGEMENT ACCOUNT ***" 10 | echo "" 11 | echo "USAGE:" 12 | echo " destroy.sh -a terraform_init_access_key -s terraform_init_secret_key -k keybase_profile" 13 | echo " [-r default_region] [-t tf_account_id]" 14 | echo "" 15 | echo "OPTIONS" 16 | echo " -r the default region for this deployment" 17 | echo " -t account ID where Terraform state is stored, used with -l option" 18 | } 19 | 20 | function pushd () { 21 | command pushd "$@" > /dev/null 22 | } 23 | 24 | function popd () { 25 | command popd "$@" > /dev/null 26 | } 27 | 28 | while getopts "a:s:k:r:t:h" option; do 29 | case ${option} in 30 | a ) ACCESS_KEY=$OPTARG;; 31 | s ) SECRET_KEY=$OPTARG;; 32 | k ) KEYBASE_PROFILE=$OPTARG;; 33 | r ) DEFAULT_REGION=$OPTARG;; 34 | t ) TF_AWS_ACCT=$OPTARG;; 35 | h ) 36 | usage 37 | exit 0 38 | ;; 39 | \? ) 40 | echo "Invalid option: -$OPTARG" 1>&2 41 | usage 42 | exit 1 43 | ;; 44 | esac 45 | done 46 | 47 | if [[ -z "${ACCESS_KEY}" ]]; then 48 | echo "Please provide the terraform-init user's AWS access key as -a key" 1>&2 49 | VALIDATION_ERROR=1 50 | fi 51 | if [[ -z "${SECRET_KEY}" ]]; then 52 | echo "Please provide the terraform-init user's AWS secret access key as -s secret " 1>&2 53 | VALIDATION_ERROR=1 54 | fi 55 | if [[ -z "${KEYBASE_PROFILE}" ]]; then 56 | echo "Please provide the keybase profile as -k profile " 1>&2 57 | VALIDATION_ERROR=1 58 | fi 59 | if [[ -z "${TF_AWS_ACCT}" ]]; then 60 | echo "Please provide the account ID where Terraform state is stored as -t tf_account_id " 1>&2 61 | VALIDATION_ERROR=1 62 | fi 63 | if [[ -n "${VALIDATION_ERROR}" ]]; then 64 | usage 65 | exit 1 66 | fi 67 | 68 | export AWS_DEFAULT_REGION=${DEFAULT_REGION} 69 | echo "Set default region: $AWS_DEFAULT_REGION" 70 | echo "" 71 | 72 | function export_management_keys { 73 | echo "USING MANAGEMENT CREDENTIALS" 74 | echo "" 75 | export AWS_ACCESS_KEY_ID=${ACCESS_KEY} 76 | export AWS_SECRET_ACCESS_KEY=${SECRET_KEY} 77 | } 78 | 79 | function export_admin_keys { 80 | echo "USING ADMIN CREDENTIALS" 81 | echo "" 82 | export AWS_ACCESS_KEY_ID=${ADMIN_ACCESS_KEY} 83 | export AWS_SECRET_ACCESS_KEY=${ADMIN_SECRET_KEY} 84 | } 85 | 86 | export TF_AWS_ACCT=${TF_AWS_ACCT} 87 | 88 | pushd ./src 89 | 90 | echo "Terraform state account is set to: $TF_AWS_ACCT" 91 | echo "" 92 | 93 | export_management_keys 94 | 95 | echo "=== CREATING temp-admin USER ===" 96 | pushd ./bootstrap/temp-admin 97 | echo "Running terragrunt init" 98 | terragrunt init 99 | echo "Running terragrunt apply using Terraform state account $TF_AWS_ACCT and keybase profile $KEYBASE_PROFILE" 100 | terragrunt apply -var terraform_state_account_id=${TF_AWS_ACCT} -var keybase=${KEYBASE_PROFILE} 101 | 102 | echo "Storing and decrypting temp-admin access keys" 103 | ADMIN_ACCESS_KEY=$(terraform output -raw temp_admin_access_key) 104 | ADMIN_SECRET_KEY=$(terraform output -raw temp_admin_secret_key | base64 --decode | keybase --pinentry=none pgp decrypt) 105 | popd 106 | 107 | echo "Sleeping for 10 seconds to allow AWS keys to be ready" 108 | sleep 10 # give AWS some time for the new access key to be ready 109 | 110 | echo "=== DESTROYING ALL ACCOUNT RESOURCES ===" 111 | export_admin_keys 112 | 113 | echo "=== DESTROYING INFRASTRUCTURE FOR EACH ACCOUNT ===" 114 | pushd ./accounts 115 | terragrunt run-all plan -destroy 116 | 117 | popd 118 | 119 | export_management_keys 120 | 121 | echo "=== DESTROYING temp-admin USER ===" 122 | pushd ./bootstrap/temp-admin 123 | terragrunt destroy -var terraform_state_account_id=${TF_AWS_ACCT} -var keybase=${KEYBASE_PROFILE} 124 | popd 125 | echo "=== temp-admin USER DESTROYED ===" 126 | 127 | pushd ./bootstrap/organization 128 | echo "=== DESTROYING ORGANISATION CONFIGS ===" 129 | terragrunt plan -destroy 130 | popd 131 | 132 | echo "Resources destroyed" -------------------------------------------------------------------------------- /src/accounts/ap-southeast-2/deployment/_env/private-bucket.hcl: -------------------------------------------------------------------------------- 1 | terraform { 2 | source = "github.com/terraform-aws-modules/terraform-aws-s3-bucket//.?ref=v3.8.2" 3 | } 4 | 5 | locals { 6 | common_vars = yamldecode(file(find_in_parent_folders("common_vars.yaml"))) 7 | region_vars = read_terragrunt_config(find_in_parent_folders("region.hcl")) 8 | id = "${local.common_vars.namespace}-${local.region_vars.locals.aws_region}-${local.common_vars.name}" 9 | } 10 | 11 | inputs = { 12 | acl = "private" 13 | force_destroy = true 14 | 15 | # S3 bucket-level Public Access Block configuration 16 | block_public_acls = true 17 | block_public_policy = true 18 | ignore_public_acls = true 19 | restrict_public_buckets = true 20 | 21 | lifecycle_rule = [ 22 | { 23 | id = "${local.id}-lifecycle-rule" 24 | enabled = true 25 | 26 | expiration = { 27 | days = 30 28 | } 29 | } 30 | ] 31 | 32 | versioning = { 33 | enabled = true 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/accounts/ap-southeast-2/deployment/account.hcl: -------------------------------------------------------------------------------- 1 | locals { 2 | stage = "deployment" 3 | account_name = "deployment" 4 | } -------------------------------------------------------------------------------- /src/accounts/ap-southeast-2/deployment/artifacts-bucket/terragrunt.hcl: -------------------------------------------------------------------------------- 1 | include "root" { 2 | path = find_in_parent_folders() 3 | } 4 | 5 | include "private_bucket" { 6 | path = "../_env/private-bucket.hcl" 7 | expose = true 8 | } 9 | 10 | inputs = { 11 | bucket = "${include.private_bucket.locals.id}-pipeline-artifacts" 12 | } 13 | -------------------------------------------------------------------------------- /src/accounts/ap-southeast-2/deployment/codebuild-apply/terragrunt.hcl: -------------------------------------------------------------------------------- 1 | include "root" { 2 | path = find_in_parent_folders() 3 | } 4 | 5 | terraform { 6 | source = "../../../../modules//terraform-codebuild" 7 | } 8 | 9 | locals { 10 | common_vars = yamldecode(file(find_in_parent_folders("common_vars.yaml"))) 11 | account_ids = jsondecode(file(find_in_parent_folders("accounts.json"))) 12 | 13 | tf_deployment_roles = formatlist("arn:aws:iam::%s:role/${local.common_vars.terraform_deployment_role_name}", values(local.account_ids)) 14 | tf_state_roles = [ 15 | "arn:aws:iam::${local.account_ids.deployment}:role/TerraformAdministrator", 16 | "arn:aws:iam::${local.account_ids.deployment}:role/TerraformReader", 17 | ] 18 | } 19 | 20 | dependency "pipeline_prefix" { 21 | config_path = "../random-pipeline-prefix" 22 | mock_outputs = { 23 | result = "abc123" 24 | } 25 | } 26 | 27 | dependency "logging_bucket" { 28 | config_path = "../logging-bucket" 29 | mock_outputs = { 30 | s3_bucket_id = "mock-logging-bucket-name" 31 | } 32 | } 33 | 34 | dependency "artifacts_bucket" { 35 | config_path = "../artifacts-bucket" 36 | mock_outputs = { 37 | s3_bucket_id = "mock-artifacts-bucket-name" 38 | } 39 | } 40 | 41 | inputs = { 42 | pipeline_prefix = dependency.pipeline_prefix.outputs.result 43 | 44 | codebuild_project_name = "accounts-terraform-apply" 45 | buildspec = "src/buildspec-apply.yaml" 46 | 47 | assume_role_arns = concat(local.tf_deployment_roles, local.tf_state_roles) 48 | 49 | logging_bucket_id = dependency.logging_bucket.outputs.s3_bucket_id 50 | artifacts_bucket_id = dependency.artifacts_bucket.outputs.s3_bucket_id 51 | } 52 | -------------------------------------------------------------------------------- /src/accounts/ap-southeast-2/deployment/codebuild-plan/terragrunt.hcl: -------------------------------------------------------------------------------- 1 | include "root" { 2 | path = find_in_parent_folders() 3 | } 4 | 5 | terraform { 6 | source = "../../../../modules//terraform-codebuild" 7 | } 8 | 9 | locals { 10 | common_vars = yamldecode(file(find_in_parent_folders("common_vars.yaml"))) 11 | account_ids = jsondecode(file(find_in_parent_folders("accounts.json"))) 12 | region_vars = read_terragrunt_config(find_in_parent_folders("region.hcl")) 13 | account_vars = read_terragrunt_config(find_in_parent_folders("account.hcl")) 14 | 15 | tf_deployment_roles = formatlist("arn:aws:iam::%s:role/${local.common_vars.terraform_deployment_role_name}", values(local.account_ids)) 16 | tf_state_roles = [ 17 | "arn:aws:iam::${local.account_ids.deployment}:role/TerraformAdministrator", 18 | "arn:aws:iam::${local.account_ids.deployment}:role/TerraformReader", 19 | ] 20 | } 21 | 22 | dependency "pipeline_prefix" { 23 | config_path = "../random-pipeline-prefix" 24 | mock_outputs = { 25 | result = "abc123" 26 | } 27 | } 28 | 29 | dependency "logging_bucket" { 30 | config_path = "../logging-bucket" 31 | mock_outputs = { 32 | s3_bucket_id = "mock-logging-bucket-name" 33 | } 34 | } 35 | 36 | dependency "artifacts_bucket" { 37 | config_path = "../artifacts-bucket" 38 | mock_outputs = { 39 | s3_bucket_id = "mock-artifacts-bucket-name" 40 | } 41 | } 42 | 43 | inputs = { 44 | pipeline_prefix = dependency.pipeline_prefix.outputs.result 45 | 46 | codebuild_project_name = "accounts-terraform-plan" 47 | buildspec = "src/buildspec-plan.yaml" 48 | 49 | assume_role_arns = concat(local.tf_deployment_roles, local.tf_state_roles) 50 | 51 | logging_bucket_id = dependency.logging_bucket.outputs.s3_bucket_id 52 | artifacts_bucket_id = dependency.artifacts_bucket.outputs.s3_bucket_id 53 | 54 | secondary_artifacts = [ 55 | { 56 | artifact_identifier = "TerraformVisual" 57 | bucket_owner_access = "READ_ONLY" 58 | type = "S3" 59 | packaging = "ZIP" 60 | location = dependency.artifacts_bucket.outputs.s3_bucket_id 61 | path = "${local.common_vars.namespace}-${local.region_vars.locals.aws_region}-${local.common_vars.name}-${local.account_vars.locals.stage}-${local.common_vars.pipeline_name}" 62 | namespace_type = "BUILD_ID" 63 | name = "terraform-visual" 64 | }, 65 | { 66 | artifact_identifier = "Plans" 67 | bucket_owner_access = "READ_ONLY" 68 | type = "S3" 69 | packaging = "NONE" 70 | location = dependency.artifacts_bucket.outputs.s3_bucket_id 71 | path = "${local.common_vars.namespace}-${local.region_vars.locals.aws_region}-${local.common_vars.name}-${local.account_vars.locals.stage}-${local.common_vars.pipeline_name}" 72 | namespace_type = "BUILD_ID" 73 | name = "plan" 74 | } 75 | ] 76 | } -------------------------------------------------------------------------------- /src/accounts/ap-southeast-2/deployment/codecommit/terragrunt.hcl: -------------------------------------------------------------------------------- 1 | include "root" { 2 | path = find_in_parent_folders() 3 | } 4 | 5 | terraform { 6 | source = "../../../../modules//codecommit" 7 | } 8 | 9 | locals { 10 | common_vars = yamldecode(file(find_in_parent_folders("common_vars.yaml"))) 11 | } 12 | 13 | inputs = { 14 | repository_name = "repository" 15 | repository_description = "Code repository for ${title(local.common_vars.namespace)} ${title(local.common_vars.name)} infrastructure" 16 | } 17 | -------------------------------------------------------------------------------- /src/accounts/ap-southeast-2/deployment/codepipeline/terragrunt.hcl: -------------------------------------------------------------------------------- 1 | include "root" { 2 | path = find_in_parent_folders() 3 | } 4 | 5 | terraform { 6 | source = "../../../../modules//terraform-codepipeline" 7 | } 8 | 9 | dependency "random_prefix" { 10 | config_path = "../random-pipeline-prefix" 11 | mock_outputs = { 12 | result = "abc123" 13 | } 14 | } 15 | 16 | dependency "artifacts_bucket" { 17 | config_path = "../artifacts-bucket" 18 | mock_outputs = { 19 | s3_bucket_id = "mock-artifacts-bucket-name" 20 | } 21 | } 22 | 23 | dependency "codebuild_plan" { 24 | config_path = "../codebuild-plan" 25 | mock_outputs = { 26 | codebuild_project_name = "mock-codebuild-project-name" 27 | } 28 | } 29 | 30 | dependency "codebuild_apply" { 31 | config_path = "../codebuild-apply" 32 | mock_outputs = { 33 | codebuild_project_name = "mock-codebuild-project-name" 34 | } 35 | } 36 | 37 | dependency "codecommit" { 38 | config_path = "../codecommit" 39 | mock_outputs = { 40 | repository_name = "mock-repository-name" 41 | } 42 | } 43 | 44 | locals { 45 | common_vars = yamldecode(file(find_in_parent_folders("common_vars.yaml"))) 46 | 47 | environment_common = { 48 | NAME = local.common_vars.name 49 | NAMESPACE = local.common_vars.namespace 50 | } 51 | 52 | env_vars = merge( 53 | local.common_vars.default_environment, 54 | local.environment_common 55 | ) 56 | } 57 | 58 | inputs = { 59 | pipeline_prefix = dependency.random_prefix.outputs.result 60 | pipeline_name = local.common_vars.pipeline_name 61 | 62 | codebuild_project_name_plan = dependency.codebuild_plan.outputs.codebuild_project_name 63 | codebuild_project_name_apply = dependency.codebuild_apply.outputs.codebuild_project_name 64 | 65 | repository_name = dependency.codecommit.outputs.repository_name 66 | branch_name = "master" 67 | 68 | environment_variables_plan = local.env_vars 69 | environment_variables_apply = local.env_vars 70 | 71 | source_artifact_name = local.common_vars.source_artifact_name 72 | plan_artifact_name = local.common_vars.plan_artifact_name 73 | 74 | approval_stage = { 75 | enabled = true 76 | notification_email_address = local.common_vars.build_notification_email 77 | } 78 | 79 | artifacts_bucket_id = dependency.artifacts_bucket.outputs.s3_bucket_id 80 | } 81 | -------------------------------------------------------------------------------- /src/accounts/ap-southeast-2/deployment/logging-bucket/terragrunt.hcl: -------------------------------------------------------------------------------- 1 | include "root" { 2 | path = find_in_parent_folders() 3 | } 4 | 5 | include "private_bucket" { 6 | path = "../_env/private-bucket.hcl" 7 | expose = true 8 | } 9 | 10 | inputs = { 11 | bucket = "${include.private_bucket.locals.id}-pipeline-logging" 12 | } 13 | -------------------------------------------------------------------------------- /src/accounts/ap-southeast-2/deployment/random-pipeline-prefix/terragrunt.hcl: -------------------------------------------------------------------------------- 1 | include "root" { 2 | path = find_in_parent_folders() 3 | } 4 | 5 | terraform { 6 | source = "../../../../modules//random-string" 7 | } 8 | 9 | inputs = { 10 | length = 6 11 | } 12 | -------------------------------------------------------------------------------- /src/accounts/ap-southeast-2/development/account.hcl: -------------------------------------------------------------------------------- 1 | locals { 2 | stage = "dev" 3 | account_name = "development" 4 | } -------------------------------------------------------------------------------- /src/accounts/ap-southeast-2/logging/account.hcl: -------------------------------------------------------------------------------- 1 | locals { 2 | stage = "logging" 3 | account_name = "logging" 4 | } -------------------------------------------------------------------------------- /src/accounts/ap-southeast-2/logging/cloudtrail-bucket/terragrunt.hcl: -------------------------------------------------------------------------------- 1 | include "root" { 2 | path = find_in_parent_folders() 3 | } 4 | 5 | terraform { 6 | source = "../../../../modules//cloudtrail-bucket" 7 | } 8 | 9 | locals { 10 | common_vars = yamldecode(file(find_in_parent_folders("common_vars.yaml"))) 11 | region_vars = read_terragrunt_config(find_in_parent_folders("region.hcl")) 12 | id = "${local.common_vars.namespace}-${local.region_vars.locals.aws_region}-${local.common_vars.name}" 13 | 14 | lifecycle_configuration_rule = { 15 | enabled = true 16 | id = "${local.id}-organization-trail-lifecycle-rule" 17 | 18 | abort_incomplete_multipart_upload_days = 1 19 | 20 | filter_and = null 21 | expiration = { 22 | days = 90 23 | } 24 | 25 | transition = [] 26 | noncurrent_version_expiration = null 27 | noncurrent_version_transition = [] 28 | } 29 | } 30 | 31 | inputs = { 32 | enabled = true 33 | 34 | name = "${local.common_vars.name}-organization-trail" 35 | 36 | versioning_enabled = true 37 | lifecycle_configuration_rules = [local.lifecycle_configuration_rule] 38 | } 39 | 40 | -------------------------------------------------------------------------------- /src/accounts/ap-southeast-2/management/account.hcl: -------------------------------------------------------------------------------- 1 | locals { 2 | stage = "management" 3 | account_name = "management" 4 | } -------------------------------------------------------------------------------- /src/accounts/ap-southeast-2/management/cloudtrail/terragrunt.hcl: -------------------------------------------------------------------------------- 1 | include "root" { 2 | path = find_in_parent_folders() 3 | } 4 | 5 | terraform { 6 | source = "github.com/cloudposse/terraform-aws-cloudtrail//.?ref=0.22.0" 7 | } 8 | 9 | dependency "cloudtrail_bucket" { 10 | config_path = "../../logging/cloudtrail-bucket" 11 | mock_outputs = { 12 | bucket_id = "mock-bucket-id" 13 | } 14 | } 15 | 16 | inputs = { 17 | enable_log_file_validation = true 18 | include_global_service_events = true 19 | is_multi_region_trail = true 20 | is_organization_trail = true 21 | enable_logging = true 22 | s3_bucket_name = dependency.cloudtrail_bucket.outputs.bucket_id 23 | } 24 | -------------------------------------------------------------------------------- /src/accounts/ap-southeast-2/region.hcl: -------------------------------------------------------------------------------- 1 | locals { 2 | aws_region = "ap-southeast-2" 3 | environment = "ap-southeast-2" 4 | } -------------------------------------------------------------------------------- /src/accounts/ap-southeast-2/security/account.hcl: -------------------------------------------------------------------------------- 1 | locals { 2 | stage = "sec" 3 | account_name = "security" 4 | } -------------------------------------------------------------------------------- /src/accounts/ap-southeast-2/shared-services/account.hcl: -------------------------------------------------------------------------------- 1 | locals { 2 | stage = "shared-services" 3 | account_name = "shared-services" 4 | } -------------------------------------------------------------------------------- /src/accounts/global/deployment/account-alias/terragrunt.hcl: -------------------------------------------------------------------------------- 1 | include "root" { 2 | path = find_in_parent_folders() 3 | } 4 | 5 | terraform { 6 | source = "../../../../modules//account-alias" 7 | } 8 | 9 | inputs = { 10 | account_alias = "deployment" 11 | } -------------------------------------------------------------------------------- /src/accounts/global/deployment/account.hcl: -------------------------------------------------------------------------------- 1 | locals { 2 | stage = "deployment" 3 | account_name = "deployment" 4 | } -------------------------------------------------------------------------------- /src/accounts/global/development/account-alias/terragrunt.hcl: -------------------------------------------------------------------------------- 1 | include "root" { 2 | path = find_in_parent_folders() 3 | } 4 | 5 | terraform { 6 | source = "../../../../modules//account-alias" 7 | } 8 | 9 | inputs = { 10 | account_alias = "development" 11 | } -------------------------------------------------------------------------------- /src/accounts/global/development/account.hcl: -------------------------------------------------------------------------------- 1 | locals { 2 | stage = "dev" 3 | account_name = "development" 4 | } -------------------------------------------------------------------------------- /src/accounts/global/logging/account-alias/terragrunt.hcl: -------------------------------------------------------------------------------- 1 | include "root" { 2 | path = find_in_parent_folders() 3 | } 4 | 5 | terraform { 6 | source = "../../../../modules//account-alias" 7 | } 8 | 9 | inputs = { 10 | account_alias = "logging" 11 | } -------------------------------------------------------------------------------- /src/accounts/global/logging/account.hcl: -------------------------------------------------------------------------------- 1 | locals { 2 | stage = "logging" 3 | account_name = "logging" 4 | } -------------------------------------------------------------------------------- /src/accounts/global/management/account-alias/terragrunt.hcl: -------------------------------------------------------------------------------- 1 | include "root" { 2 | path = find_in_parent_folders() 3 | } 4 | 5 | terraform { 6 | source = "../../../../modules//account-alias" 7 | } 8 | 9 | inputs = { 10 | account_alias = "management" 11 | } -------------------------------------------------------------------------------- /src/accounts/global/management/account.hcl: -------------------------------------------------------------------------------- 1 | locals { 2 | stage = "management" 3 | account_name = "management" 4 | } -------------------------------------------------------------------------------- /src/accounts/global/security/account-alias/terragrunt.hcl: -------------------------------------------------------------------------------- 1 | include "root" { 2 | path = find_in_parent_folders() 3 | } 4 | 5 | terraform { 6 | source = "../../../../modules//account-alias" 7 | } 8 | 9 | inputs = { 10 | account_alias = "security" 11 | } -------------------------------------------------------------------------------- /src/accounts/global/security/account.hcl: -------------------------------------------------------------------------------- 1 | locals { 2 | stage = "sec" 3 | account_name = "security" 4 | } -------------------------------------------------------------------------------- /src/accounts/global/shared-services/account-alias/terragrunt.hcl: -------------------------------------------------------------------------------- 1 | include "root" { 2 | path = find_in_parent_folders() 3 | } 4 | 5 | terraform { 6 | source = "../../../../modules//account-alias" 7 | } 8 | 9 | inputs = { 10 | account_alias = "shared-services" 11 | } -------------------------------------------------------------------------------- /src/accounts/global/shared-services/account.hcl: -------------------------------------------------------------------------------- 1 | locals { 2 | stage = "shared-services" 3 | account_name = "shared-services" 4 | } -------------------------------------------------------------------------------- /src/accounts/us-east-1/management/account.hcl: -------------------------------------------------------------------------------- 1 | locals { 2 | stage = "management" 3 | account_name = "management" 4 | } -------------------------------------------------------------------------------- /src/accounts/us-east-1/management/billing-alarm/terragrunt.hcl: -------------------------------------------------------------------------------- 1 | include "root" { 2 | path = find_in_parent_folders() 3 | } 4 | 5 | terraform { 6 | source = "../../../../modules//email-billing-alarm" 7 | } 8 | 9 | locals { 10 | common_vars = yamldecode(file(find_in_parent_folders("common_vars.yaml"))) 11 | } 12 | 13 | inputs = { 14 | monthly_billing_threshold = local.common_vars.billing_monthly_threshold 15 | currency = local.common_vars.billing_currency 16 | notification_email_address = local.common_vars.billing_alarm_notification_email 17 | } 18 | -------------------------------------------------------------------------------- /src/accounts/us-east-1/region.hcl: -------------------------------------------------------------------------------- 1 | locals { 2 | aws_region = "us-east-1" 3 | environment = "us-east-1" 4 | } -------------------------------------------------------------------------------- /src/bootstrap/organization/account-deployment.tf: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Everything in this document is deployed into the Deployment account from 3 | # the management account context using the OrganizationAccountAccessRole role 4 | ############################################################################### 5 | provider "aws" { 6 | alias = "assume_deployment" 7 | 8 | assume_role { 9 | role_arn = "arn:aws:iam::${aws_organizations_account.deployment.id}:role/OrganizationAccountAccessRole" 10 | } 11 | 12 | region = var.management_aws_region 13 | } 14 | 15 | ############################################################################### 16 | # Create a Terraform deployment role based on the default AWS Administrator 17 | # policy and allow it to be assumed by a role in the Deployment account 18 | ############################################################################### 19 | module "deployment_assume_deployment_terraform_deployment_role" { 20 | source = "../../modules/cross-account-role" 21 | 22 | providers = { 23 | aws = aws.assume_deployment 24 | } 25 | 26 | assume_role_policy_json = data.aws_iam_policy_document.crossaccount_assume_from_deployment_account.json 27 | role_name = var.terraform_deployment_role_name 28 | role_policy_arn = var.administrator_default_arn 29 | 30 | tags = module.this.tags 31 | } 32 | 33 | ############################################################################### 34 | # Policy that allows the Deployment account to assume the Terraform 35 | # deployment role in the Deployment account. 36 | ############################################################################### 37 | module "assume_role_policy_deployment_terraform_deploy" { 38 | source = "../../modules/assume-role-policy" 39 | 40 | providers = { 41 | aws = aws.assume_deployment 42 | } 43 | 44 | account_name = aws_organizations_account.deployment.name 45 | account_id = aws_organizations_account.deployment.id 46 | role_name = module.deployment_assume_deployment_terraform_deployment_role.role_name 47 | 48 | tags = module.this.tags 49 | } 50 | 51 | ############################################################################### 52 | # Policy that allows the Deployment account to assume the Terraform 53 | # deployment role in the Management account. 54 | ############################################################################### 55 | module "assume_role_policy_management_terraform_deploy" { 56 | source = "../../modules/assume-role-policy" 57 | 58 | providers = { 59 | aws = aws.assume_deployment 60 | } 61 | 62 | account_name = "Management" 63 | account_id = data.aws_caller_identity.current.account_id 64 | role_name = module.deployment_assume_management_terraform_deployment_role.role_name 65 | 66 | tags = module.this.tags 67 | } 68 | 69 | ############################################################################### 70 | # Policy that allows the Deployment account to assume the Terraform 71 | # deployment role in the Security account. 72 | ############################################################################### 73 | module "assume_role_policy_security_terraform_deploy" { 74 | source = "../../modules/assume-role-policy" 75 | 76 | providers = { 77 | aws = aws.assume_deployment 78 | } 79 | 80 | account_name = aws_organizations_account.security.name 81 | account_id = aws_organizations_account.security.id 82 | role_name = module.deployment_assume_security_terraform_deployment_role.role_name 83 | 84 | tags = module.this.tags 85 | } 86 | 87 | ############################################################################### 88 | # Policy that allows the Deployment account to assume the Terraform 89 | # deployment role in the Shared Services account. 90 | ############################################################################### 91 | module "assume_role_policy_shared_services_terraform_deploy" { 92 | source = "../../modules/assume-role-policy" 93 | 94 | providers = { 95 | aws = aws.assume_deployment 96 | } 97 | 98 | account_name = aws_organizations_account.shared_services.name 99 | account_id = aws_organizations_account.shared_services.id 100 | role_name = module.deployment_assume_shared_services_terraform_deployment_role.role_name 101 | 102 | tags = module.this.tags 103 | } 104 | 105 | ############################################################################### 106 | # Policy that allows the Deployment account to assume the Terraform 107 | # deployment role in the Development account. 108 | ############################################################################### 109 | module "assume_role_policy_development_terraform_deploy" { 110 | source = "../../modules/assume-role-policy" 111 | 112 | providers = { 113 | aws = aws.assume_deployment 114 | } 115 | 116 | account_name = aws_organizations_account.development.name 117 | account_id = aws_organizations_account.development.id 118 | role_name = module.deployment_assume_development_terraform_deployment_role.role_name 119 | 120 | tags = module.this.tags 121 | } 122 | 123 | ############################################################################### 124 | # Policy that allows the Deployment account to assume the Terraform 125 | # deployment role in the Logging account. 126 | ############################################################################### 127 | module "assume_role_policy_logging_terraform_deploy" { 128 | source = "../../modules/assume-role-policy" 129 | 130 | providers = { 131 | aws = aws.assume_deployment 132 | } 133 | 134 | account_name = aws_organizations_account.logging.name 135 | account_id = aws_organizations_account.logging.id 136 | role_name = module.deployment_assume_logging_terraform_deployment_role.role_name 137 | 138 | tags = module.this.tags 139 | } 140 | 141 | ############################################################################### 142 | # Create a TerraformAdminAccess policy for Terraform read/write access based 143 | # on the terraform_admin policy document 144 | ############################################################################### 145 | resource "aws_iam_policy" "terraform_admin" { 146 | name = "TerraformAdminAccess" 147 | policy = data.aws_iam_policy_document.terraform_admin.json 148 | description = "Grants permissions needed by Terraform to manage Terraform remote state" 149 | provider = aws.assume_deployment 150 | 151 | tags = module.this.tags 152 | } 153 | 154 | ############################################################################### 155 | # Create a TerraformAdmin role in the Deployment account that is 156 | # allowed to be assumed from the Deployment account 157 | ############################################################################### 158 | module "cross_account_role_terraform_admin" { 159 | source = "../../modules/cross-account-role" 160 | 161 | providers = { 162 | aws = aws.assume_deployment 163 | } 164 | 165 | assume_role_policy_json = data.aws_iam_policy_document.crossaccount_assume_from_deployment_account.json 166 | role_name = "TerraformAdministrator" 167 | role_policy_arn = aws_iam_policy.terraform_admin.arn 168 | 169 | tags = module.this.tags 170 | } 171 | 172 | ############################################################################### 173 | # Create a TerraformReadAccess policy for Terraform read access based on the 174 | # terraform_reader policy document 175 | ############################################################################### 176 | resource "aws_iam_policy" "terraform_reader" { 177 | name = "TerraformReadAccess" 178 | policy = data.aws_iam_policy_document.terraform_reader.json 179 | description = "Grants permissions to read Terraform remote state" 180 | provider = aws.assume_deployment 181 | 182 | tags = module.this.tags 183 | } 184 | 185 | ############################################################################### 186 | # Create a TerraformReader role in the Deployment account that is 187 | # allowed to be assumed from the Deployment accounts. 188 | ############################################################################### 189 | module "cross_account_role_terraform_reader" { 190 | source = "../../modules/cross-account-role" 191 | 192 | providers = { 193 | aws = aws.assume_deployment 194 | } 195 | 196 | assume_role_policy_json = data.aws_iam_policy_document.crossaccount_assume_from_deployment_account.json 197 | role_name = "TerraformReader" 198 | role_policy_arn = aws_iam_policy.terraform_reader.arn 199 | 200 | tags = module.this.tags 201 | } 202 | 203 | ############################################################################### 204 | # Policy that allows the Deployment account to assume the TerraformAdmin 205 | # role in the Deployment account 206 | ############################################################################### 207 | module "assume_role_policy_terraform_admin" { 208 | source = "../../modules/assume-role-policy" 209 | 210 | providers = { 211 | aws = aws.assume_deployment 212 | } 213 | 214 | account_name = aws_organizations_account.deployment.name 215 | account_id = aws_organizations_account.deployment.id 216 | role_name = module.cross_account_role_terraform_admin.role_name 217 | 218 | tags = module.this.tags 219 | } 220 | 221 | ############################################################################### 222 | # Policy that allows the Deployment account to assume the TerraformReader 223 | # role in the Deployment account 224 | ############################################################################### 225 | module "assume_role_policy_terraform_reader" { 226 | source = "../../modules/assume-role-policy" 227 | 228 | providers = { 229 | aws = aws.assume_deployment 230 | } 231 | 232 | account_name = aws_organizations_account.deployment.name 233 | account_id = aws_organizations_account.deployment.id 234 | role_name = module.cross_account_role_terraform_reader.role_name 235 | 236 | tags = module.this.tags 237 | } 238 | -------------------------------------------------------------------------------- /src/bootstrap/organization/account-development.tf: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Everything in this document is deployed into the Development account from the 3 | # management account context using the OrganizationAccountAccessRole role 4 | ############################################################################### 5 | provider "aws" { 6 | alias = "assume_development" 7 | 8 | assume_role { 9 | role_arn = "arn:aws:iam::${aws_organizations_account.development.id}:role/OrganizationAccountAccessRole" 10 | } 11 | 12 | region = var.management_aws_region 13 | } 14 | 15 | ############################################################################### 16 | # Create a Terraform deployment role based on the default AWS Administrator 17 | # policy and allow it to be assumed by the Deployment account 18 | ############################################################################### 19 | module "deployment_assume_development_terraform_deployment_role" { 20 | source = "../../modules/cross-account-role" 21 | 22 | providers = { 23 | aws = aws.assume_development 24 | } 25 | 26 | assume_role_policy_json = data.aws_iam_policy_document.crossaccount_assume_from_deployment_account.json 27 | role_name = var.terraform_deployment_role_name 28 | role_policy_arn = var.administrator_default_arn 29 | 30 | tags = module.this.tags 31 | } -------------------------------------------------------------------------------- /src/bootstrap/organization/account-logging.tf: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Everything in this document is deployed into the Logging account from the 3 | # management account context using the OrganizationAccountAccessRole role 4 | ############################################################################### 5 | provider "aws" { 6 | alias = "assume_logging" 7 | 8 | assume_role { 9 | role_arn = "arn:aws:iam::${aws_organizations_account.logging.id}:role/OrganizationAccountAccessRole" 10 | } 11 | 12 | region = var.management_aws_region 13 | } 14 | 15 | ############################################################################### 16 | # Create a Terraform deployment role based on the default AWS Administrator 17 | # policy and allow it to be assumed by the Deployment account 18 | ############################################################################### 19 | module "deployment_assume_logging_terraform_deployment_role" { 20 | source = "../../modules/cross-account-role" 21 | 22 | providers = { 23 | aws = aws.assume_logging 24 | } 25 | 26 | assume_role_policy_json = data.aws_iam_policy_document.crossaccount_assume_from_deployment_account.json 27 | role_name = var.terraform_deployment_role_name 28 | role_policy_arn = var.administrator_default_arn 29 | 30 | tags = module.this.tags 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/bootstrap/organization/account-security.tf: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Everything in this document is deployed into the Security account from the 3 | # management account context using the OrganizationAccountAccessRole role 4 | ############################################################################### 5 | provider "aws" { 6 | alias = "assume_security" 7 | 8 | assume_role { 9 | role_arn = "arn:aws:iam::${aws_organizations_account.security.id}:role/OrganizationAccountAccessRole" 10 | } 11 | 12 | region = var.management_aws_region 13 | } 14 | 15 | ############################################################################### 16 | # Create a Terraform deployment role based on the default AWS Administrator 17 | # policy and allow it to be assumed by the Deployment account 18 | ############################################################################### 19 | module "deployment_assume_security_terraform_deployment_role" { 20 | source = "../../modules/cross-account-role" 21 | 22 | providers = { 23 | aws = aws.assume_security 24 | } 25 | 26 | assume_role_policy_json = data.aws_iam_policy_document.crossaccount_assume_from_deployment_account.json 27 | role_name = var.terraform_deployment_role_name 28 | role_policy_arn = var.administrator_default_arn 29 | 30 | tags = module.this.tags 31 | } 32 | -------------------------------------------------------------------------------- /src/bootstrap/organization/account-shared-services.tf: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Everything in this document is deployed into the Shared Services account from 3 | # the management account context using the OrganizationAccountAccessRole role 4 | ############################################################################### 5 | provider "aws" { 6 | alias = "assume_shared_services" 7 | 8 | assume_role { 9 | role_arn = "arn:aws:iam::${aws_organizations_account.shared_services.id}:role/OrganizationAccountAccessRole" 10 | } 11 | 12 | region = var.management_aws_region 13 | } 14 | 15 | ############################################################################### 16 | # Create a Terraform deployment role based on the default AWS Administrator 17 | # policy and allow it to be assumed by the Deployment account 18 | ############################################################################### 19 | module "deployment_assume_shared_services_terraform_deployment_role" { 20 | source = "../../modules/cross-account-role" 21 | 22 | providers = { 23 | aws = aws.assume_shared_services 24 | } 25 | 26 | assume_role_policy_json = data.aws_iam_policy_document.crossaccount_assume_from_deployment_account.json 27 | role_name = var.terraform_deployment_role_name 28 | role_policy_arn = var.administrator_default_arn 29 | 30 | tags = module.this.tags 31 | } 32 | -------------------------------------------------------------------------------- /src/bootstrap/organization/context.tf: -------------------------------------------------------------------------------- 1 | # 2 | # ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label 3 | # All other instances of this file should be a copy of that one 4 | # 5 | # 6 | # Copy this file from https://github.com/cloudposse/terraform-null-label/blob/management/exports/context.tf 7 | # and then place it in your Terraform module to automatically get 8 | # Cloud Posse's standard configuration inputs suitable for passing 9 | # to Cloud Posse modules. 10 | # 11 | # curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/management/exports/context.tf -o context.tf 12 | # 13 | # Modules should access the whole context as `module.this.context` 14 | # to get the input variables with nulls for defaults, 15 | # for example `context = module.this.context`, 16 | # and access individual variables as `module.this.`, 17 | # with final values filled in. 18 | # 19 | # For example, when using defaults, `module.this.context.delimiter` 20 | # will be null, and `module.this.delimiter` will be `-` (hyphen). 21 | # 22 | 23 | module "this" { 24 | source = "cloudposse/label/null" 25 | version = "0.25.0" 26 | 27 | enabled = var.enabled 28 | namespace = var.namespace 29 | tenant = var.tenant 30 | environment = var.environment 31 | stage = var.stage 32 | name = var.name 33 | delimiter = var.delimiter 34 | attributes = var.attributes 35 | tags = var.tags 36 | additional_tag_map = var.additional_tag_map 37 | label_order = var.label_order 38 | regex_replace_chars = var.regex_replace_chars 39 | id_length_limit = var.id_length_limit 40 | label_key_case = var.label_key_case 41 | label_value_case = var.label_value_case 42 | descriptor_formats = var.descriptor_formats 43 | labels_as_tags = var.labels_as_tags 44 | 45 | context = var.context 46 | } 47 | 48 | # Copy contents of cloudposse/terraform-null-label/variables.tf here 49 | 50 | variable "context" { 51 | type = any 52 | default = { 53 | enabled = true 54 | namespace = null 55 | tenant = null 56 | environment = null 57 | stage = null 58 | name = null 59 | delimiter = null 60 | attributes = [] 61 | tags = {} 62 | additional_tag_map = {} 63 | regex_replace_chars = null 64 | label_order = [] 65 | id_length_limit = null 66 | label_key_case = null 67 | label_value_case = null 68 | descriptor_formats = {} 69 | # Note: we have to use [] instead of null for unset lists due to 70 | # https://github.com/hashicorp/terraform/issues/28137 71 | # which was not fixed until Terraform 1.0.0, 72 | # but we want the default to be all the labels in `label_order` 73 | # and we want users to be able to prevent all tag generation 74 | # by setting `labels_as_tags` to `[]`, so we need 75 | # a different sentinel to indicate "default" 76 | labels_as_tags = ["unset"] 77 | } 78 | description = <<-EOT 79 | Single object for setting entire context at once. 80 | See description of individual variables for details. 81 | Leave string and numeric variables as `null` to use default value. 82 | Individual variable settings (non-null) override settings in context object, 83 | except for attributes, tags, and additional_tag_map, which are merged. 84 | EOT 85 | 86 | validation { 87 | condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) 88 | error_message = "Allowed values: `lower`, `title`, `upper`." 89 | } 90 | 91 | validation { 92 | condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) 93 | error_message = "Allowed values: `lower`, `title`, `upper`, `none`." 94 | } 95 | } 96 | 97 | variable "enabled" { 98 | type = bool 99 | default = null 100 | description = "Set to false to prevent the module from creating any resources" 101 | } 102 | 103 | variable "namespace" { 104 | type = string 105 | default = null 106 | description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" 107 | } 108 | 109 | variable "tenant" { 110 | type = string 111 | default = null 112 | description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" 113 | } 114 | 115 | variable "environment" { 116 | type = string 117 | default = null 118 | description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" 119 | } 120 | 121 | variable "stage" { 122 | type = string 123 | default = null 124 | description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" 125 | } 126 | 127 | variable "name" { 128 | type = string 129 | default = null 130 | description = <<-EOT 131 | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. 132 | This is the only ID element not also included as a `tag`. 133 | The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. 134 | EOT 135 | } 136 | 137 | variable "delimiter" { 138 | type = string 139 | default = null 140 | description = <<-EOT 141 | Delimiter to be used between ID elements. 142 | Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. 143 | EOT 144 | } 145 | 146 | variable "attributes" { 147 | type = list(string) 148 | default = [] 149 | description = <<-EOT 150 | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, 151 | in the order they appear in the list. New attributes are appended to the 152 | end of the list. The elements of the list are joined by the `delimiter` 153 | and treated as a single ID element. 154 | EOT 155 | } 156 | 157 | variable "labels_as_tags" { 158 | type = set(string) 159 | default = ["default"] 160 | description = <<-EOT 161 | Set of labels (ID elements) to include as tags in the `tags` output. 162 | Default is to include all labels. 163 | Tags with empty values will not be included in the `tags` output. 164 | Set to `[]` to suppress all generated tags. 165 | **Notes:** 166 | The value of the `name` tag, if included, will be the `id`, not the `name`. 167 | Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be 168 | changed in later chained modules. Attempts to change it will be silently ignored. 169 | EOT 170 | } 171 | 172 | variable "tags" { 173 | type = map(string) 174 | default = {} 175 | description = <<-EOT 176 | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). 177 | Neither the tag keys nor the tag values will be modified by this module. 178 | EOT 179 | } 180 | 181 | variable "additional_tag_map" { 182 | type = map(string) 183 | default = {} 184 | description = <<-EOT 185 | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. 186 | This is for some rare cases where resources want additional configuration of tags 187 | and therefore take a list of maps with tag key, value, and additional configuration. 188 | EOT 189 | } 190 | 191 | variable "label_order" { 192 | type = list(string) 193 | default = null 194 | description = <<-EOT 195 | The order in which the labels (ID elements) appear in the `id`. 196 | Defaults to ["namespace", "environment", "stage", "name", "attributes"]. 197 | You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. 198 | EOT 199 | } 200 | 201 | variable "regex_replace_chars" { 202 | type = string 203 | default = null 204 | description = <<-EOT 205 | Terraform regular expression (regex) string. 206 | Characters matching the regex will be removed from the ID elements. 207 | If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. 208 | EOT 209 | } 210 | 211 | variable "id_length_limit" { 212 | type = number 213 | default = null 214 | description = <<-EOT 215 | Limit `id` to this many characters (minimum 6). 216 | Set to `0` for unlimited length. 217 | Set to `null` for keep the existing setting, which defaults to `0`. 218 | Does not affect `id_full`. 219 | EOT 220 | validation { 221 | condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 222 | error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." 223 | } 224 | } 225 | 226 | variable "label_key_case" { 227 | type = string 228 | default = null 229 | description = <<-EOT 230 | Controls the letter case of the `tags` keys (label names) for tags generated by this module. 231 | Does not affect keys of tags passed in via the `tags` input. 232 | Possible values: `lower`, `title`, `upper`. 233 | Default value: `title`. 234 | EOT 235 | 236 | validation { 237 | condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) 238 | error_message = "Allowed values: `lower`, `title`, `upper`." 239 | } 240 | } 241 | 242 | variable "label_value_case" { 243 | type = string 244 | default = null 245 | description = <<-EOT 246 | Controls the letter case of ID elements (labels) as included in `id`, 247 | set as tag values, and output by this module individually. 248 | Does not affect values of tags passed in via the `tags` input. 249 | Possible values: `lower`, `title`, `upper` and `none` (no transformation). 250 | Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. 251 | Default value: `lower`. 252 | EOT 253 | 254 | validation { 255 | condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) 256 | error_message = "Allowed values: `lower`, `title`, `upper`, `none`." 257 | } 258 | } 259 | 260 | variable "descriptor_formats" { 261 | type = any 262 | default = {} 263 | description = <<-EOT 264 | Describe additional descriptors to be output in the `descriptors` output map. 265 | Map of maps. Keys are names of descriptors. Values are maps of the form 266 | `{ 267 | format = string 268 | labels = list(string) 269 | }` 270 | (Type is `any` so the map values can later be enhanced to provide additional options.) 271 | `format` is a Terraform format string to be passed to the `format()` function. 272 | `labels` is a list of labels, in order, to pass to `format()` function. 273 | Label values will be normalized before being passed to `format()` so they will be 274 | identical to how they appear in `id`. 275 | Default is `{}` (`descriptors` output will be empty). 276 | EOT 277 | } 278 | 279 | #### End of copy of cloudposse/terraform-null-label/variables.tf -------------------------------------------------------------------------------- /src/bootstrap/organization/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | backend "s3" {} 3 | } 4 | 5 | ############################################################################### 6 | # Gets the data object that represents the currently running identity context 7 | ############################################################################### 8 | data "aws_caller_identity" "current" {} 9 | 10 | ############################################################################### 11 | # Create a new organisation if use_existing_organization is false 12 | ############################################################################### 13 | resource "aws_organizations_organization" "org" { 14 | enabled_policy_types = ["SERVICE_CONTROL_POLICY"] 15 | feature_set = "ALL" 16 | 17 | aws_service_access_principals = [ 18 | "cloudtrail.amazonaws.com", 19 | "sso.amazonaws.com" 20 | ] 21 | 22 | lifecycle { 23 | prevent_destroy = true 24 | } 25 | } 26 | 27 | ############################################################################### 28 | # Create OU for Security aspects 29 | ############################################################################### 30 | resource "aws_organizations_organizational_unit" "security" { 31 | name = "Security" 32 | parent_id = aws_organizations_organization.org.roots[0].id 33 | } 34 | 35 | ############################################################################### 36 | # Create OU for Infrastructure aspects 37 | ############################################################################### 38 | resource "aws_organizations_organizational_unit" "infrastructure" { 39 | name = "Infrastructure" 40 | parent_id = aws_organizations_organization.org.roots[0].id 41 | } 42 | 43 | ############################################################################### 44 | # Create OU for Sandbox aspects 45 | ############################################################################### 46 | resource "aws_organizations_organizational_unit" "sandbox" { 47 | name = "Sandbox" 48 | parent_id = aws_organizations_organization.org.roots[0].id 49 | } 50 | 51 | ############################################################################### 52 | # Create OU for Deployment aspects 53 | ############################################################################### 54 | resource "aws_organizations_organizational_unit" "deployment" { 55 | name = "Deployment" 56 | parent_id = aws_organizations_organization.org.roots[0].id 57 | } 58 | 59 | ############################################################################### 60 | # Create OU for Workload aspects 61 | ############################################################################### 62 | resource "aws_organizations_organizational_unit" "workloads" { 63 | name = "Workloads" 64 | parent_id = aws_organizations_organization.org.roots[0].id 65 | } 66 | 67 | ############################################################################### 68 | # Create OU for Production Workload aspects 69 | ############################################################################### 70 | resource "aws_organizations_organizational_unit" "workloads_prod" { 71 | name = "Prod" 72 | parent_id = aws_organizations_organizational_unit.workloads.id 73 | } 74 | 75 | ############################################################################### 76 | # Create OU for Test Workload aspects 77 | ############################################################################### 78 | resource "aws_organizations_organizational_unit" "workloads_test" { 79 | name = "Test" 80 | parent_id = aws_organizations_organizational_unit.workloads.id 81 | } 82 | 83 | ############################################################################### 84 | # Create a Security account 85 | # ------------------------- 86 | # The security account hosts the tools used by the Security team. 87 | ############################################################################### 88 | resource "aws_organizations_account" "security" { 89 | name = "Core Security Account" 90 | email = var.security_account_email 91 | 92 | parent_id = aws_organizations_organizational_unit.security.id 93 | 94 | lifecycle { 95 | prevent_destroy = true 96 | } 97 | } 98 | 99 | ############################################################################### 100 | # Create a Development account 101 | # ---------------------------- 102 | # The development account is a development sandbox. 103 | ############################################################################### 104 | resource "aws_organizations_account" "development" { 105 | name = "Core Development Account" 106 | email = var.development_account_email 107 | 108 | parent_id = aws_organizations_organizational_unit.sandbox.id 109 | 110 | lifecycle { 111 | prevent_destroy = true 112 | } 113 | } 114 | 115 | ############################################################################### 116 | # Create a Shared Services account 117 | # -------------------------------- 118 | # The shared services account is responsible for identity management 119 | ############################################################################### 120 | resource "aws_organizations_account" "shared_services" { 121 | name = "Core Shared Services Account" 122 | email = var.shared_services_account_email 123 | 124 | parent_id = aws_organizations_organizational_unit.infrastructure.id 125 | 126 | lifecycle { 127 | prevent_destroy = true 128 | } 129 | } 130 | 131 | ############################################################################### 132 | # Create a Deployment account 133 | # -------------------------------- 134 | # The deployment account holds the code repositorties and CI/CD pipelines. 135 | ############################################################################### 136 | resource "aws_organizations_account" "deployment" { 137 | name = "Core Deployment Account" 138 | email = var.deployment_account_email 139 | 140 | parent_id = aws_organizations_organizational_unit.deployment.id 141 | 142 | lifecycle { 143 | prevent_destroy = true 144 | } 145 | } 146 | 147 | ############################################################################### 148 | # Create a Logging account 149 | # -------------------------------- 150 | # The logging account collects logs from all member accounts. 151 | ############################################################################### 152 | resource "aws_organizations_account" "logging" { 153 | name = "Core Logging Account" 154 | email = var.logging_account_email 155 | 156 | parent_id = aws_organizations_organizational_unit.security.id 157 | 158 | lifecycle { 159 | prevent_destroy = true 160 | } 161 | } 162 | 163 | ############################################################################### 164 | # Create a Terraform deployment role based on the default AWS Administrator 165 | # policy and allow it to be assumed by the Deployment account 166 | ############################################################################### 167 | module "deployment_assume_management_terraform_deployment_role" { 168 | source = "../../modules/cross-account-role" 169 | 170 | assume_role_policy_json = data.aws_iam_policy_document.crossaccount_assume_from_deployment_account.json 171 | role_name = var.terraform_deployment_role_name 172 | role_policy_arn = var.administrator_default_arn 173 | 174 | tags = module.this.tags 175 | } 176 | -------------------------------------------------------------------------------- /src/bootstrap/organization/outputs.tf: -------------------------------------------------------------------------------- 1 | output "account_ids" { 2 | value = { 3 | management = data.aws_caller_identity.current.account_id 4 | security = aws_organizations_account.security.id 5 | deployment = aws_organizations_account.deployment.id 6 | development = aws_organizations_account.development.id 7 | logging = aws_organizations_account.logging.id 8 | shared-services = aws_organizations_account.shared_services.id 9 | } 10 | description = "Map of accounts created in the organization including the management account" 11 | } 12 | 13 | output "crossaccount_assume_from_deployment_account_policy_json" { 14 | value = data.aws_iam_policy_document.crossaccount_assume_from_deployment_account.json 15 | } 16 | 17 | output "security_terraform_deploy_role_policy_arn" { 18 | value = module.assume_role_policy_security_terraform_deploy.policy_arn 19 | } 20 | 21 | output "deployment_terraform_deploy_role_policy_arn" { 22 | value = module.assume_role_policy_deployment_terraform_deploy.policy_arn 23 | } 24 | 25 | output "development_terraform_deploy_role_policy_arn" { 26 | value = module.assume_role_policy_development_terraform_deploy.policy_arn 27 | } 28 | 29 | output "shared_services_terraform_deploy_role_policy_arn" { 30 | value = module.assume_role_policy_shared_services_terraform_deploy.policy_arn 31 | } 32 | 33 | output "management_terraform_deploy_role_policy_arn" { 34 | value = module.assume_role_policy_management_terraform_deploy.policy_arn 35 | } 36 | 37 | output "logging_terraform_deploy_role_policy_arn" { 38 | value = module.assume_role_policy_logging_terraform_deploy.policy_arn 39 | } 40 | 41 | output "terraform_admin_role_policy_arn" { 42 | value = module.assume_role_policy_terraform_admin.policy_arn 43 | } 44 | 45 | output "terraform_reader_role_policy_arn" { 46 | value = module.assume_role_policy_terraform_reader.policy_arn 47 | } 48 | -------------------------------------------------------------------------------- /src/bootstrap/organization/overrides/backend-local-override.tf: -------------------------------------------------------------------------------- 1 | // should only be used by init.sh for the initial setup 2 | terraform { 3 | backend "local" {} 4 | } 5 | -------------------------------------------------------------------------------- /src/bootstrap/organization/policies.tf: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Policy document that assigns the Deployment account as principal in an 3 | # assume role policy for cross account access 4 | ############################################################################### 5 | data "aws_iam_policy_document" "crossaccount_assume_from_deployment_account" { 6 | statement { 7 | sid = "AssumeFromDeploymentAccount" 8 | actions = ["sts:AssumeRole"] 9 | 10 | principals { 11 | type = "AWS" 12 | identifiers = [aws_organizations_account.deployment.id] 13 | } 14 | } 15 | } 16 | 17 | ############################################################################### 18 | # A policy document that provides read/write access to the necessary resources to 19 | # manage Terraform state in S3 and locking in DynamoDB 20 | ############################################################################### 21 | data "aws_iam_policy_document" "terraform_admin" { 22 | statement { 23 | sid = "AllowS3ActionsOnTerraformBucket" 24 | 25 | actions = ["s3:*"] 26 | 27 | resources = [ 28 | "arn:aws:s3:::${var.terraform_state_bucket_name}", 29 | "arn:aws:s3:::${var.terraform_state_bucket_name}/*", 30 | ] 31 | } 32 | 33 | statement { 34 | sid = "AllowCreateAndUpdateDynamoDBActionsOnTerraformLockTable" 35 | 36 | actions = [ 37 | "dynamodb:PutItem", 38 | "dynamodb:GetItem", 39 | "dynamodb:DeleteItem", 40 | "dynamodb:DescribeTable", 41 | "dynamodb:CreateTable", 42 | ] 43 | 44 | resources = [ 45 | "arn:aws:dynamodb:*:${aws_organizations_account.deployment.id}:table/${var.terraform_state_dynamodb_table}", 46 | ] 47 | } 48 | 49 | statement { 50 | sid = "AllowTagAndUntagDynamoDBActions" 51 | 52 | actions = [ 53 | "dynamodb:TagResource", 54 | "dynamodb:UntagResource", 55 | ] 56 | 57 | resources = [ 58 | "*", 59 | ] 60 | } 61 | } 62 | 63 | ############################################################################### 64 | # A policy document that provides read access to the necessary resources to 65 | # list and read Terraform state from S3 66 | ############################################################################### 67 | data "aws_iam_policy_document" "terraform_reader" { 68 | statement { 69 | sid = "AllowListS3ActionsOnTerraformBucket" 70 | 71 | actions = [ 72 | "s3:ListBucket", 73 | ] 74 | 75 | resources = [ 76 | "arn:aws:s3:::${var.terraform_state_bucket_name}", 77 | ] 78 | } 79 | 80 | statement { 81 | sid = "AllowGetS3ActionsOnTerraformBucketPath" 82 | 83 | actions = [ 84 | "s3:GetObject", 85 | ] 86 | 87 | resources = [ 88 | "arn:aws:s3:::${var.terraform_state_bucket_name}/*", 89 | ] 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/bootstrap/organization/service-control-policies.tf: -------------------------------------------------------------------------------- 1 | resource "aws_organizations_policy" "regional_restrictions" { 2 | name = "DenyAllOutsideAURegions" 3 | description = "Prevent resources from being created outside of the AU region" 4 | 5 | content = <`, 17 | # with final values filled in. 18 | # 19 | # For example, when using defaults, `module.this.context.delimiter` 20 | # will be null, and `module.this.delimiter` will be `-` (hyphen). 21 | # 22 | 23 | module "this" { 24 | source = "cloudposse/label/null" 25 | version = "0.25.0" 26 | 27 | enabled = var.enabled 28 | namespace = var.namespace 29 | tenant = var.tenant 30 | environment = var.environment 31 | stage = var.stage 32 | name = var.name 33 | delimiter = var.delimiter 34 | attributes = var.attributes 35 | tags = var.tags 36 | additional_tag_map = var.additional_tag_map 37 | label_order = var.label_order 38 | regex_replace_chars = var.regex_replace_chars 39 | id_length_limit = var.id_length_limit 40 | label_key_case = var.label_key_case 41 | label_value_case = var.label_value_case 42 | descriptor_formats = var.descriptor_formats 43 | labels_as_tags = var.labels_as_tags 44 | 45 | context = var.context 46 | } 47 | 48 | # Copy contents of cloudposse/terraform-null-label/variables.tf here 49 | 50 | variable "context" { 51 | type = any 52 | default = { 53 | enabled = true 54 | namespace = null 55 | tenant = null 56 | environment = null 57 | stage = null 58 | name = null 59 | delimiter = null 60 | attributes = [] 61 | tags = {} 62 | additional_tag_map = {} 63 | regex_replace_chars = null 64 | label_order = [] 65 | id_length_limit = null 66 | label_key_case = null 67 | label_value_case = null 68 | descriptor_formats = {} 69 | # Note: we have to use [] instead of null for unset lists due to 70 | # https://github.com/hashicorp/terraform/issues/28137 71 | # which was not fixed until Terraform 1.0.0, 72 | # but we want the default to be all the labels in `label_order` 73 | # and we want users to be able to prevent all tag generation 74 | # by setting `labels_as_tags` to `[]`, so we need 75 | # a different sentinel to indicate "default" 76 | labels_as_tags = ["unset"] 77 | } 78 | description = <<-EOT 79 | Single object for setting entire context at once. 80 | See description of individual variables for details. 81 | Leave string and numeric variables as `null` to use default value. 82 | Individual variable settings (non-null) override settings in context object, 83 | except for attributes, tags, and additional_tag_map, which are merged. 84 | EOT 85 | 86 | validation { 87 | condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) 88 | error_message = "Allowed values: `lower`, `title`, `upper`." 89 | } 90 | 91 | validation { 92 | condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) 93 | error_message = "Allowed values: `lower`, `title`, `upper`, `none`." 94 | } 95 | } 96 | 97 | variable "enabled" { 98 | type = bool 99 | default = null 100 | description = "Set to false to prevent the module from creating any resources" 101 | } 102 | 103 | variable "namespace" { 104 | type = string 105 | default = null 106 | description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" 107 | } 108 | 109 | variable "tenant" { 110 | type = string 111 | default = null 112 | description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" 113 | } 114 | 115 | variable "environment" { 116 | type = string 117 | default = null 118 | description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" 119 | } 120 | 121 | variable "stage" { 122 | type = string 123 | default = null 124 | description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" 125 | } 126 | 127 | variable "name" { 128 | type = string 129 | default = null 130 | description = <<-EOT 131 | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. 132 | This is the only ID element not also included as a `tag`. 133 | The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. 134 | EOT 135 | } 136 | 137 | variable "delimiter" { 138 | type = string 139 | default = null 140 | description = <<-EOT 141 | Delimiter to be used between ID elements. 142 | Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. 143 | EOT 144 | } 145 | 146 | variable "attributes" { 147 | type = list(string) 148 | default = [] 149 | description = <<-EOT 150 | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, 151 | in the order they appear in the list. New attributes are appended to the 152 | end of the list. The elements of the list are joined by the `delimiter` 153 | and treated as a single ID element. 154 | EOT 155 | } 156 | 157 | variable "labels_as_tags" { 158 | type = set(string) 159 | default = ["default"] 160 | description = <<-EOT 161 | Set of labels (ID elements) to include as tags in the `tags` output. 162 | Default is to include all labels. 163 | Tags with empty values will not be included in the `tags` output. 164 | Set to `[]` to suppress all generated tags. 165 | **Notes:** 166 | The value of the `name` tag, if included, will be the `id`, not the `name`. 167 | Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be 168 | changed in later chained modules. Attempts to change it will be silently ignored. 169 | EOT 170 | } 171 | 172 | variable "tags" { 173 | type = map(string) 174 | default = {} 175 | description = <<-EOT 176 | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). 177 | Neither the tag keys nor the tag values will be modified by this module. 178 | EOT 179 | } 180 | 181 | variable "additional_tag_map" { 182 | type = map(string) 183 | default = {} 184 | description = <<-EOT 185 | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. 186 | This is for some rare cases where resources want additional configuration of tags 187 | and therefore take a list of maps with tag key, value, and additional configuration. 188 | EOT 189 | } 190 | 191 | variable "label_order" { 192 | type = list(string) 193 | default = null 194 | description = <<-EOT 195 | The order in which the labels (ID elements) appear in the `id`. 196 | Defaults to ["namespace", "environment", "stage", "name", "attributes"]. 197 | You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. 198 | EOT 199 | } 200 | 201 | variable "regex_replace_chars" { 202 | type = string 203 | default = null 204 | description = <<-EOT 205 | Terraform regular expression (regex) string. 206 | Characters matching the regex will be removed from the ID elements. 207 | If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. 208 | EOT 209 | } 210 | 211 | variable "id_length_limit" { 212 | type = number 213 | default = null 214 | description = <<-EOT 215 | Limit `id` to this many characters (minimum 6). 216 | Set to `0` for unlimited length. 217 | Set to `null` for keep the existing setting, which defaults to `0`. 218 | Does not affect `id_full`. 219 | EOT 220 | validation { 221 | condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 222 | error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." 223 | } 224 | } 225 | 226 | variable "label_key_case" { 227 | type = string 228 | default = null 229 | description = <<-EOT 230 | Controls the letter case of the `tags` keys (label names) for tags generated by this module. 231 | Does not affect keys of tags passed in via the `tags` input. 232 | Possible values: `lower`, `title`, `upper`. 233 | Default value: `title`. 234 | EOT 235 | 236 | validation { 237 | condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) 238 | error_message = "Allowed values: `lower`, `title`, `upper`." 239 | } 240 | } 241 | 242 | variable "label_value_case" { 243 | type = string 244 | default = null 245 | description = <<-EOT 246 | Controls the letter case of ID elements (labels) as included in `id`, 247 | set as tag values, and output by this module individually. 248 | Does not affect values of tags passed in via the `tags` input. 249 | Possible values: `lower`, `title`, `upper` and `none` (no transformation). 250 | Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. 251 | Default value: `lower`. 252 | EOT 253 | 254 | validation { 255 | condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) 256 | error_message = "Allowed values: `lower`, `title`, `upper`, `none`." 257 | } 258 | } 259 | 260 | variable "descriptor_formats" { 261 | type = any 262 | default = {} 263 | description = <<-EOT 264 | Describe additional descriptors to be output in the `descriptors` output map. 265 | Map of maps. Keys are names of descriptors. Values are maps of the form 266 | `{ 267 | format = string 268 | labels = list(string) 269 | }` 270 | (Type is `any` so the map values can later be enhanced to provide additional options.) 271 | `format` is a Terraform format string to be passed to the `format()` function. 272 | `labels` is a list of labels, in order, to pass to `format()` function. 273 | Label values will be normalized before being passed to `format()` so they will be 274 | identical to how they appear in `id`. 275 | Default is `{}` (`descriptors` output will be empty). 276 | EOT 277 | } 278 | 279 | #### End of copy of cloudposse/terraform-null-label/variables.tf -------------------------------------------------------------------------------- /src/bootstrap/temp-admin/main.tf: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # The temp_admin user is created and removed by the init script and thus only 3 | # exists during the execution of the init script. It should only be stored in 4 | # local state. 5 | ############################################################################### 6 | terraform { 7 | backend "local" {} 8 | } 9 | 10 | ############################################################################### 11 | # Get the data object representing the organization state from the Shared 12 | # Services account where Terraform state is stored 13 | ############################################################################### 14 | data "terraform_remote_state" "organization" { 15 | backend = "s3" 16 | 17 | config = { 18 | bucket = var.terraform_state_bucket_name 19 | key = var.management_terraform_state_path 20 | region = var.terraform_state_bucket_region 21 | role_arn = "arn:aws:iam::${var.terraform_state_account_id}:role/OrganizationAccountAccessRole" 22 | } 23 | } 24 | 25 | ############################################################################### 26 | # Define a provider for accessing the Security account using the default 27 | # OrganizationAccountAccessRole role. All the resources in this file are to be 28 | # deployed into the Security account 29 | ############################################################################### 30 | provider "aws" { 31 | alias = "assume_deployment" 32 | 33 | assume_role { 34 | role_arn = "arn:aws:iam::${data.terraform_remote_state.organization.outputs.account_ids["deployment"]}:role/OrganizationAccountAccessRole" 35 | } 36 | 37 | region = var.management_aws_region 38 | } 39 | 40 | ############################################################################### 41 | # Create a temp_admin user 42 | ############################################################################### 43 | resource "aws_iam_user" "temp_admin" { 44 | name = "temp-admin" 45 | force_destroy = true 46 | 47 | provider = aws.assume_deployment 48 | 49 | tags = module.this.tags 50 | } 51 | 52 | ############################################################################### 53 | # Allow the temp_admin user to assume the Terraform deployment role in the 54 | # Security account for deploying initial account resources 55 | ############################################################################### 56 | resource "aws_iam_user_policy_attachment" "assume_terraform_deploy_role_security_account" { 57 | user = aws_iam_user.temp_admin.name 58 | policy_arn = data.terraform_remote_state.organization.outputs.security_terraform_deploy_role_policy_arn 59 | 60 | provider = aws.assume_deployment 61 | } 62 | 63 | ############################################################################### 64 | # Allow the temp_admin user to assume the Terraform deployment role in the 65 | # Deployment account for deploying initial account resources 66 | ############################################################################### 67 | resource "aws_iam_user_policy_attachment" "assume_terraform_deploy_role_deployment_account" { 68 | user = aws_iam_user.temp_admin.name 69 | policy_arn = data.terraform_remote_state.organization.outputs.deployment_terraform_deploy_role_policy_arn 70 | 71 | provider = aws.assume_deployment 72 | } 73 | 74 | ############################################################################### 75 | # Allow the temp_admin user to assume the Terraform deployment role in the 76 | # Development account for deploying initial account resources 77 | ############################################################################### 78 | resource "aws_iam_user_policy_attachment" "assume_terraform_deploy_role_development_account" { 79 | user = aws_iam_user.temp_admin.name 80 | policy_arn = data.terraform_remote_state.organization.outputs.development_terraform_deploy_role_policy_arn 81 | 82 | provider = aws.assume_deployment 83 | } 84 | 85 | ############################################################################### 86 | # Allow the temp_admin user to assume the Terraform deployment role in the 87 | # Shared Services account for deploying initial account resources 88 | ############################################################################### 89 | resource "aws_iam_user_policy_attachment" "assume_terraform_deploy_role_shared_services_account" { 90 | user = aws_iam_user.temp_admin.name 91 | policy_arn = data.terraform_remote_state.organization.outputs.shared_services_terraform_deploy_role_policy_arn 92 | 93 | provider = aws.assume_deployment 94 | } 95 | 96 | ############################################################################### 97 | # Allow the temp_admin user to assume the Terraform deployment role in the 98 | # Management account for deploying initial account resources 99 | ############################################################################### 100 | resource "aws_iam_user_policy_attachment" "assume_terraform_deploy_role_management_account" { 101 | user = aws_iam_user.temp_admin.name 102 | policy_arn = data.terraform_remote_state.organization.outputs.management_terraform_deploy_role_policy_arn 103 | 104 | provider = aws.assume_deployment 105 | } 106 | 107 | ############################################################################### 108 | # Allow the temp_admin user to assume the Terraform deployment role in the 109 | # Management account for deploying initial account resources 110 | ############################################################################### 111 | resource "aws_iam_user_policy_attachment" "assume_terraform_deploy_role_logging_account" { 112 | user = aws_iam_user.temp_admin.name 113 | policy_arn = data.terraform_remote_state.organization.outputs.logging_terraform_deploy_role_policy_arn 114 | 115 | provider = aws.assume_deployment 116 | } 117 | 118 | ############################################################################### 119 | # Allow the temp_admin user to assume the Terraform Admin role in the 120 | # Deployment account for reading/writing Terraform state 121 | ############################################################################### 122 | resource "aws_iam_user_policy_attachment" "assume_role_terraform_admin" { 123 | user = aws_iam_user.temp_admin.name 124 | policy_arn = data.terraform_remote_state.organization.outputs.terraform_admin_role_policy_arn 125 | provider = aws.assume_deployment 126 | } 127 | 128 | ############################################################################### 129 | # Allow the temp_admin user to assume the Terraform Reader role in the 130 | # Deployment account for reading Terraform state 131 | ############################################################################### 132 | resource "aws_iam_user_policy_attachment" "assume_role_terraform_reader" { 133 | user = aws_iam_user.temp_admin.name 134 | policy_arn = data.terraform_remote_state.organization.outputs.terraform_reader_role_policy_arn 135 | provider = aws.assume_deployment 136 | } 137 | 138 | ############################################################################### 139 | # Grant the temp_admin user CodeCommit permissions in order to be able to 140 | # commit the initial repository into CodeCommit 141 | ############################################################################### 142 | resource "aws_iam_user_policy_attachment" "code_commit_access" { 143 | user = aws_iam_user.temp_admin.name 144 | policy_arn = "arn:aws:iam::aws:policy/AWSCodeCommitPowerUser" 145 | 146 | provider = aws.assume_deployment 147 | } 148 | 149 | ############################################################################### 150 | # Create a temporary set of access credentials for the temp_admin user in the 151 | # Deployment account and encrypt the secret access key using Keybase PGP 152 | ############################################################################### 153 | resource "aws_iam_access_key" "temp_admin" { 154 | user = aws_iam_user.temp_admin.name 155 | pgp_key = "keybase:${var.keybase}" 156 | 157 | provider = aws.assume_deployment 158 | } 159 | -------------------------------------------------------------------------------- /src/bootstrap/temp-admin/outputs.tf: -------------------------------------------------------------------------------- 1 | output "temp_admin_access_key" { 2 | value = aws_iam_access_key.temp_admin.id 3 | } 4 | 5 | output "temp_admin_secret_key" { 6 | value = aws_iam_access_key.temp_admin.encrypted_secret 7 | } 8 | -------------------------------------------------------------------------------- /src/bootstrap/temp-admin/terragrunt.hcl: -------------------------------------------------------------------------------- 1 | include { 2 | path = find_in_parent_folders() 3 | } 4 | dependencies { 5 | paths = [ 6 | "../organization" 7 | ] 8 | } 9 | remote_state { 10 | backend = "local" 11 | config = { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/bootstrap/temp-admin/variables.tf: -------------------------------------------------------------------------------- 1 | variable "keybase" { 2 | description = "Enter the keybase profile to encrypt the secret_key (to decrypt: terraform output secret_key | base64 --decode | keybase pgp decrypt)" 3 | } 4 | 5 | variable "management_aws_region" {} 6 | variable "management_terraform_state_path" {} 7 | 8 | variable "terraform_state_account_id" {} 9 | variable "terraform_state_bucket_name" {} 10 | variable "terraform_state_bucket_region" {} 11 | -------------------------------------------------------------------------------- /src/bootstrap/temp-admin/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = "~>1.4.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = "= 4.59.0" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/bootstrap/terragrunt.hcl: -------------------------------------------------------------------------------- 1 | locals { 2 | common_vars = yamldecode(file(find_in_parent_folders("common_vars.yaml"))) 3 | 4 | environment = local.common_vars.management_aws_region 5 | 6 | terraform_state_bucket_name = "${local.common_vars.namespace}-${local.common_vars.name}-${local.common_vars.terraform_state_bucket_region}-deployment-${local.common_vars.terraform_state_bucket}" 7 | } 8 | 9 | remote_state { 10 | backend = "s3" 11 | config = { 12 | bucket = local.terraform_state_bucket_name 13 | key = local.common_vars.management_terraform_state_path 14 | region = local.common_vars.terraform_state_bucket_region 15 | role_arn = "arn:aws:iam::${get_env("TF_AWS_ACCT", "")}:role/OrganizationAccountAccessRole" 16 | encrypt = true 17 | dynamodb_table = local.common_vars.terraform_state_dynamodb_table 18 | s3_bucket_tags = { 19 | owner = "terraform" 20 | name = "Terraform state storage" 21 | } 22 | dynamodb_table_tags = { 23 | owner = "terraform" 24 | name = local.common_vars.terraform_state_dynamodb_table 25 | } 26 | } 27 | } 28 | 29 | inputs = merge( 30 | local.common_vars, 31 | { 32 | terraform_state_bucket_name = local.terraform_state_bucket_name, 33 | environment = local.environment 34 | } 35 | ) 36 | -------------------------------------------------------------------------------- /src/buildspec-apply.yaml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | env: 4 | shell: bash 5 | 6 | variables: 7 | TERRAFORM_VERSION: "1.4.2" 8 | TERRAGRUNT_VERSION: "0.45.0" 9 | 10 | exported-variables: 11 | - BuildID 12 | - BuildTag 13 | 14 | phases: 15 | install: 16 | 17 | commands: 18 | - echo Entered the install phase... 19 | 20 | # install terraform binary 21 | - echo Installing Terraform 22 | - curl -s -qL -o terraform_install.zip https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip 23 | - unzip terraform_install.zip && mv terraform /usr/bin 24 | 25 | # install terragrunt binary 26 | - echo Installing Terragrunt 27 | - curl -Lo terragrunt https://github.com/gruntwork-io/terragrunt/releases/download/v${TERRAGRUNT_VERSION}/terragrunt_linux_amd64 28 | - chmod +x terragrunt 29 | - mv terragrunt /usr/bin 30 | 31 | finally: 32 | # output versions 33 | - terraform --version 34 | - terragrunt --version 35 | 36 | build: 37 | commands: 38 | 39 | # Go to accounts folder 40 | - cd $CODEBUILD_SRC_DIR/src/accounts 41 | 42 | # Find all Terragrunt modules to destroy 43 | - tg_modules_to_destroy=$(find . -maxdepth 4 -name terragrunt.hcl -execdir [ -e .destroy ] \; -printf " --terragrunt-include-dir %h") 44 | 45 | # Destroy modules no longer needed 46 | - | 47 | if [[ -n "${tg_modules_to_destroy}" ]]; then 48 | terragrunt run-all apply $tg_modules_to_destroy -destroy -no-color 49 | fi 50 | 51 | # Find all Terragrunt modules that do not contain a .destroy file 52 | - tg_modules=$(find . -maxdepth 4 -name terragrunt.hcl -execdir [ ! -e .destroy ] \; -printf " --terragrunt-include-dir %h") 53 | 54 | # Apply all changes 55 | - | 56 | if [[ -n "${tg_modules}" ]]; then 57 | terragrunt run-all apply $tg_modules -no-color 58 | fi 59 | -------------------------------------------------------------------------------- /src/buildspec-plan.yaml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | env: 4 | shell: bash 5 | 6 | variables: 7 | TERRAFORM_VERSION: "1.4.2" 8 | TERRAGRUNT_VERSION: "0.45.0" 9 | 10 | exported-variables: 11 | - BuildID 12 | - BuildTag 13 | 14 | phases: 15 | install: 16 | runtime-versions: 17 | nodejs: 16 18 | 19 | commands: 20 | - echo Entered the install phase... 21 | 22 | # install terraform binary 23 | - echo Installing Terraform 24 | - curl -s -qL -o terraform_install.zip https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip 25 | - unzip terraform_install.zip && mv terraform /usr/bin 26 | - rm terraform_install.zip 27 | 28 | # install terragrunt binary 29 | - echo Installing Terragrunt 30 | - curl -Lo terragrunt https://github.com/gruntwork-io/terragrunt/releases/download/v${TERRAGRUNT_VERSION}/terragrunt_linux_amd64 31 | - chmod +x terragrunt 32 | - mv terragrunt /usr/bin 33 | 34 | # install terraform visual 35 | - npm install -g @terraform-visual/cli 36 | 37 | finally: 38 | # output versions 39 | - terraform --version 40 | - terragrunt --version 41 | 42 | build: 43 | commands: 44 | 45 | # Go to accounts folder 46 | - cd $CODEBUILD_SRC_DIR/src/accounts 47 | 48 | # Create a reports folder for terraform visual plans 49 | - reports_artifacts_path=$CODEBUILD_SRC_DIR/reports/terraform-visual 50 | - mkdir -p $reports_artifacts_path/plans 51 | 52 | # Find all Terragrunt modules to destroy 53 | - tg_modules_to_destroy=$(find . -maxdepth 4 -name terragrunt.hcl -execdir [ -e .destroy ] \; -printf " --terragrunt-include-dir %h") 54 | 55 | # Plan to destroy modules no longer needed 56 | - | 57 | if [[ -n "${tg_modules_to_destroy}" ]]; then 58 | terragrunt run-all plan $tg_modules_to_destroy -destroy -no-color -out=tfplan.binary 59 | fi 60 | 61 | # Find all Terragrunt modules that do not contain a .destroy file 62 | - tg_modules=$(find . -maxdepth 4 -name terragrunt.hcl -execdir [ ! -e .destroy ] \; -printf " --terragrunt-include-dir %h") 63 | 64 | # Plan all changes 65 | - | 66 | if [[ -n "${tg_modules}" ]]; then 67 | terragrunt run-all plan $tg_modules -no-color -out=tfplan.binary 68 | fi 69 | 70 | # Find all tfplan.binary files 71 | - tg_binaries=$(find . -name 'tfplan.binary') 72 | 73 | # Process artifacts 74 | - | 75 | for module_path in ${tg_binaries[@]}; 76 | do 77 | module_dir=$(dirname $module_path) 78 | module_dir=$(echo "$module_dir" | sed 's/\(.*\)\/\.terragrunt-cache\/.*/\1/') 79 | 80 | module=$(basename $module_dir) 81 | account_dir=$(dirname $module_dir) 82 | account=$(basename $account_dir) 83 | region_dir=$(dirname $account_dir) 84 | region=$(basename $region_dir) 85 | 86 | # Convert binary plan file to JSON file 87 | echo "Running terragrunt show for $module" 88 | terragrunt show -json $(basename $module_path) --terragrunt-working-dir $module_dir --terragrunt-no-auto-init > $module_dir/plan.json 89 | 90 | # Create terraform visual plan 91 | terraform-visual --plan $module_dir/plan.json --out $module_dir 92 | 93 | # Prefix that will be used when renaming Terraform Visual reports 94 | prefix=$region-$account-$module 95 | # The path where Terraform Visual reports are currently stored 96 | report_dir=$module_dir/terraform-visual-report 97 | 98 | # Rename the index.html file to include prefix 99 | mv $report_dir/index.html $report_dir/$prefix-terraform-visual.html 100 | 101 | # Rename the plan.js file and copy into plans subfolder 102 | mkdir $report_dir/plans 103 | mv $report_dir/plan.js $report_dir/plans/$prefix-plan.js 104 | 105 | # Rename plan.js reference inside the terraform visual HTML file 106 | sed -i -e "s@\./plan\.js@\./plans/$prefix\-plan\.js@g" $report_dir/$prefix-terraform-visual.html 107 | 108 | # Copy all terraform visual files in current directory 109 | # to a common reporting folder and overwrite any common files 110 | cp -a $report_dir/* $reports_artifacts_path/ 111 | 112 | done 113 | 114 | - "export BuildID=`echo $CODEBUILD_BUILD_ID | cut -d: -f1`" 115 | - "export BuildTag=`echo $CODEBUILD_BUILD_ID | cut -d: -f2`" 116 | 117 | artifacts: 118 | files: 119 | - "**/*" 120 | exclude-paths: 121 | - "**/terraform-visual-report/**/*" 122 | - "reports/**/*" 123 | secondary-artifacts: 124 | Plans: 125 | files: 126 | - "**/plan.json" 127 | base-directory: $CODEBUILD_SRC_DIR/src/accounts 128 | TerraformVisual: 129 | files: 130 | - "**/*" 131 | name: terraform-visual-$CODEBUILD_BUILD_ID 132 | base-directory: $CODEBUILD_SRC_DIR/reports/terraform-visual 133 | -------------------------------------------------------------------------------- /src/common_vars.yaml: -------------------------------------------------------------------------------- 1 | # general settings 2 | namespace: "your_namespace" 3 | name: "your_project_name" 4 | 5 | # Terraform configuration 6 | # must match the remote_state config in terragrunt.hcl 7 | terraform_state_bucket: "terraform-state" 8 | terraform_state_bucket_region: "ap-southeast-2" 9 | terraform_state_dynamodb_table: "TerraformLock" 10 | 11 | # CloudTrail configuration 12 | cloudtrail_bucket: "organization-trail" 13 | 14 | # the region where the terraform state is deployed 15 | management_aws_region: "ap-southeast-2" 16 | 17 | # the path of the Organization management account Terraform state 18 | management_terraform_state_path: "management/organization/terraform.tfstate" 19 | 20 | # Administrator default role policy ARN 21 | administrator_default_arn: "arn:aws:iam::aws:policy/AdministratorAccess" 22 | # Terraform deployment role 23 | terraform_deployment_role_name: "TerraformDeployment" 24 | 25 | # details of accounts to be created 26 | security_account_email: "aws+security@your_email.com" 27 | deployment_account_email: "aws+deployment@your_email.com" 28 | development_account_email: "aws+development@your_email.com" 29 | logging_account_email: "aws+logging@your_email.com" 30 | shared_services_account_email: "aws+shared-services@your_email.com" 31 | 32 | # notifications 33 | build_notification_email: "aws+build-notifications@your_email.com" 34 | billing_alarm_notification_email: "aws+billing-alarm-notification@your_email.com" 35 | billing_monthly_threshold: 100 36 | billing_currency: "AUD" 37 | 38 | # CI/CD pipeline 39 | pipeline_name: "pipeline" 40 | source_artifact_name: "SourceArtifact" 41 | plan_artifact_name: "PlanArtifact" 42 | 43 | # configuration for automation 44 | default_environment: 45 | TF_IN_AUTOMATION: 1 46 | TF_INPUT: 0 47 | -------------------------------------------------------------------------------- /src/modules/account-alias/context.tf: -------------------------------------------------------------------------------- 1 | # 2 | # ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label 3 | # All other instances of this file should be a copy of that one 4 | # 5 | # 6 | # Copy this file from https://github.com/cloudposse/terraform-null-label/blob/management/exports/context.tf 7 | # and then place it in your Terraform module to automatically get 8 | # Cloud Posse's standard configuration inputs suitable for passing 9 | # to Cloud Posse modules. 10 | # 11 | # curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/management/exports/context.tf -o context.tf 12 | # 13 | # Modules should access the whole context as `module.this.context` 14 | # to get the input variables with nulls for defaults, 15 | # for example `context = module.this.context`, 16 | # and access individual variables as `module.this.`, 17 | # with final values filled in. 18 | # 19 | # For example, when using defaults, `module.this.context.delimiter` 20 | # will be null, and `module.this.delimiter` will be `-` (hyphen). 21 | # 22 | 23 | module "this" { 24 | source = "cloudposse/label/null" 25 | version = "0.25.0" 26 | 27 | enabled = var.enabled 28 | namespace = var.namespace 29 | tenant = var.tenant 30 | environment = var.environment 31 | stage = var.stage 32 | name = var.name 33 | delimiter = var.delimiter 34 | attributes = var.attributes 35 | tags = var.tags 36 | additional_tag_map = var.additional_tag_map 37 | label_order = var.label_order 38 | regex_replace_chars = var.regex_replace_chars 39 | id_length_limit = var.id_length_limit 40 | label_key_case = var.label_key_case 41 | label_value_case = var.label_value_case 42 | descriptor_formats = var.descriptor_formats 43 | labels_as_tags = var.labels_as_tags 44 | 45 | context = var.context 46 | } 47 | 48 | # Copy contents of cloudposse/terraform-null-label/variables.tf here 49 | 50 | variable "context" { 51 | type = any 52 | default = { 53 | enabled = true 54 | namespace = null 55 | tenant = null 56 | environment = null 57 | stage = null 58 | name = null 59 | delimiter = null 60 | attributes = [] 61 | tags = {} 62 | additional_tag_map = {} 63 | regex_replace_chars = null 64 | label_order = [] 65 | id_length_limit = null 66 | label_key_case = null 67 | label_value_case = null 68 | descriptor_formats = {} 69 | # Note: we have to use [] instead of null for unset lists due to 70 | # https://github.com/hashicorp/terraform/issues/28137 71 | # which was not fixed until Terraform 1.0.0, 72 | # but we want the default to be all the labels in `label_order` 73 | # and we want users to be able to prevent all tag generation 74 | # by setting `labels_as_tags` to `[]`, so we need 75 | # a different sentinel to indicate "default" 76 | labels_as_tags = ["unset"] 77 | } 78 | description = <<-EOT 79 | Single object for setting entire context at once. 80 | See description of individual variables for details. 81 | Leave string and numeric variables as `null` to use default value. 82 | Individual variable settings (non-null) override settings in context object, 83 | except for attributes, tags, and additional_tag_map, which are merged. 84 | EOT 85 | 86 | validation { 87 | condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) 88 | error_message = "Allowed values: `lower`, `title`, `upper`." 89 | } 90 | 91 | validation { 92 | condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) 93 | error_message = "Allowed values: `lower`, `title`, `upper`, `none`." 94 | } 95 | } 96 | 97 | variable "enabled" { 98 | type = bool 99 | default = null 100 | description = "Set to false to prevent the module from creating any resources" 101 | } 102 | 103 | variable "namespace" { 104 | type = string 105 | default = null 106 | description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" 107 | } 108 | 109 | variable "tenant" { 110 | type = string 111 | default = null 112 | description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" 113 | } 114 | 115 | variable "environment" { 116 | type = string 117 | default = null 118 | description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" 119 | } 120 | 121 | variable "stage" { 122 | type = string 123 | default = null 124 | description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" 125 | } 126 | 127 | variable "name" { 128 | type = string 129 | default = null 130 | description = <<-EOT 131 | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. 132 | This is the only ID element not also included as a `tag`. 133 | The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. 134 | EOT 135 | } 136 | 137 | variable "delimiter" { 138 | type = string 139 | default = null 140 | description = <<-EOT 141 | Delimiter to be used between ID elements. 142 | Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. 143 | EOT 144 | } 145 | 146 | variable "attributes" { 147 | type = list(string) 148 | default = [] 149 | description = <<-EOT 150 | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, 151 | in the order they appear in the list. New attributes are appended to the 152 | end of the list. The elements of the list are joined by the `delimiter` 153 | and treated as a single ID element. 154 | EOT 155 | } 156 | 157 | variable "labels_as_tags" { 158 | type = set(string) 159 | default = ["default"] 160 | description = <<-EOT 161 | Set of labels (ID elements) to include as tags in the `tags` output. 162 | Default is to include all labels. 163 | Tags with empty values will not be included in the `tags` output. 164 | Set to `[]` to suppress all generated tags. 165 | **Notes:** 166 | The value of the `name` tag, if included, will be the `id`, not the `name`. 167 | Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be 168 | changed in later chained modules. Attempts to change it will be silently ignored. 169 | EOT 170 | } 171 | 172 | variable "tags" { 173 | type = map(string) 174 | default = {} 175 | description = <<-EOT 176 | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). 177 | Neither the tag keys nor the tag values will be modified by this module. 178 | EOT 179 | } 180 | 181 | variable "additional_tag_map" { 182 | type = map(string) 183 | default = {} 184 | description = <<-EOT 185 | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. 186 | This is for some rare cases where resources want additional configuration of tags 187 | and therefore take a list of maps with tag key, value, and additional configuration. 188 | EOT 189 | } 190 | 191 | variable "label_order" { 192 | type = list(string) 193 | default = null 194 | description = <<-EOT 195 | The order in which the labels (ID elements) appear in the `id`. 196 | Defaults to ["namespace", "environment", "stage", "name", "attributes"]. 197 | You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. 198 | EOT 199 | } 200 | 201 | variable "regex_replace_chars" { 202 | type = string 203 | default = null 204 | description = <<-EOT 205 | Terraform regular expression (regex) string. 206 | Characters matching the regex will be removed from the ID elements. 207 | If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. 208 | EOT 209 | } 210 | 211 | variable "id_length_limit" { 212 | type = number 213 | default = null 214 | description = <<-EOT 215 | Limit `id` to this many characters (minimum 6). 216 | Set to `0` for unlimited length. 217 | Set to `null` for keep the existing setting, which defaults to `0`. 218 | Does not affect `id_full`. 219 | EOT 220 | validation { 221 | condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 222 | error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." 223 | } 224 | } 225 | 226 | variable "label_key_case" { 227 | type = string 228 | default = null 229 | description = <<-EOT 230 | Controls the letter case of the `tags` keys (label names) for tags generated by this module. 231 | Does not affect keys of tags passed in via the `tags` input. 232 | Possible values: `lower`, `title`, `upper`. 233 | Default value: `title`. 234 | EOT 235 | 236 | validation { 237 | condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) 238 | error_message = "Allowed values: `lower`, `title`, `upper`." 239 | } 240 | } 241 | 242 | variable "label_value_case" { 243 | type = string 244 | default = null 245 | description = <<-EOT 246 | Controls the letter case of ID elements (labels) as included in `id`, 247 | set as tag values, and output by this module individually. 248 | Does not affect values of tags passed in via the `tags` input. 249 | Possible values: `lower`, `title`, `upper` and `none` (no transformation). 250 | Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. 251 | Default value: `lower`. 252 | EOT 253 | 254 | validation { 255 | condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) 256 | error_message = "Allowed values: `lower`, `title`, `upper`, `none`." 257 | } 258 | } 259 | 260 | variable "descriptor_formats" { 261 | type = any 262 | default = {} 263 | description = <<-EOT 264 | Describe additional descriptors to be output in the `descriptors` output map. 265 | Map of maps. Keys are names of descriptors. Values are maps of the form 266 | `{ 267 | format = string 268 | labels = list(string) 269 | }` 270 | (Type is `any` so the map values can later be enhanced to provide additional options.) 271 | `format` is a Terraform format string to be passed to the `format()` function. 272 | `labels` is a list of labels, in order, to pass to `format()` function. 273 | Label values will be normalized before being passed to `format()` so they will be 274 | identical to how they appear in `id`. 275 | Default is `{}` (`descriptors` output will be empty). 276 | EOT 277 | } 278 | 279 | #### End of copy of cloudposse/terraform-null-label/variables.tf -------------------------------------------------------------------------------- /src/modules/account-alias/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_iam_account_alias" "alias" { 2 | account_alias = "${module.this.namespace}-${module.this.name}-${var.account_alias}" 3 | } 4 | -------------------------------------------------------------------------------- /src/modules/account-alias/outputs.tf: -------------------------------------------------------------------------------- 1 | output "account_alias" { 2 | value = aws_iam_account_alias.alias.account_alias 3 | } 4 | -------------------------------------------------------------------------------- /src/modules/account-alias/variables.tf: -------------------------------------------------------------------------------- 1 | variable "account_alias" {} -------------------------------------------------------------------------------- /src/modules/account-alias/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = "~>1.4.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = "= 4.59.0" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/modules/assume-role-policy/context.tf: -------------------------------------------------------------------------------- 1 | # 2 | # ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label 3 | # All other instances of this file should be a copy of that one 4 | # 5 | # 6 | # Copy this file from https://github.com/cloudposse/terraform-null-label/blob/management/exports/context.tf 7 | # and then place it in your Terraform module to automatically get 8 | # Cloud Posse's standard configuration inputs suitable for passing 9 | # to Cloud Posse modules. 10 | # 11 | # curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/management/exports/context.tf -o context.tf 12 | # 13 | # Modules should access the whole context as `module.this.context` 14 | # to get the input variables with nulls for defaults, 15 | # for example `context = module.this.context`, 16 | # and access individual variables as `module.this.`, 17 | # with final values filled in. 18 | # 19 | # For example, when using defaults, `module.this.context.delimiter` 20 | # will be null, and `module.this.delimiter` will be `-` (hyphen). 21 | # 22 | 23 | module "this" { 24 | source = "cloudposse/label/null" 25 | version = "0.25.0" 26 | 27 | enabled = var.enabled 28 | namespace = var.namespace 29 | tenant = var.tenant 30 | environment = var.environment 31 | stage = var.stage 32 | name = var.name 33 | delimiter = var.delimiter 34 | attributes = var.attributes 35 | tags = var.tags 36 | additional_tag_map = var.additional_tag_map 37 | label_order = var.label_order 38 | regex_replace_chars = var.regex_replace_chars 39 | id_length_limit = var.id_length_limit 40 | label_key_case = var.label_key_case 41 | label_value_case = var.label_value_case 42 | descriptor_formats = var.descriptor_formats 43 | labels_as_tags = var.labels_as_tags 44 | 45 | context = var.context 46 | } 47 | 48 | # Copy contents of cloudposse/terraform-null-label/variables.tf here 49 | 50 | variable "context" { 51 | type = any 52 | default = { 53 | enabled = true 54 | namespace = null 55 | tenant = null 56 | environment = null 57 | stage = null 58 | name = null 59 | delimiter = null 60 | attributes = [] 61 | tags = {} 62 | additional_tag_map = {} 63 | regex_replace_chars = null 64 | label_order = [] 65 | id_length_limit = null 66 | label_key_case = null 67 | label_value_case = null 68 | descriptor_formats = {} 69 | # Note: we have to use [] instead of null for unset lists due to 70 | # https://github.com/hashicorp/terraform/issues/28137 71 | # which was not fixed until Terraform 1.0.0, 72 | # but we want the default to be all the labels in `label_order` 73 | # and we want users to be able to prevent all tag generation 74 | # by setting `labels_as_tags` to `[]`, so we need 75 | # a different sentinel to indicate "default" 76 | labels_as_tags = ["unset"] 77 | } 78 | description = <<-EOT 79 | Single object for setting entire context at once. 80 | See description of individual variables for details. 81 | Leave string and numeric variables as `null` to use default value. 82 | Individual variable settings (non-null) override settings in context object, 83 | except for attributes, tags, and additional_tag_map, which are merged. 84 | EOT 85 | 86 | validation { 87 | condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) 88 | error_message = "Allowed values: `lower`, `title`, `upper`." 89 | } 90 | 91 | validation { 92 | condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) 93 | error_message = "Allowed values: `lower`, `title`, `upper`, `none`." 94 | } 95 | } 96 | 97 | variable "enabled" { 98 | type = bool 99 | default = null 100 | description = "Set to false to prevent the module from creating any resources" 101 | } 102 | 103 | variable "namespace" { 104 | type = string 105 | default = null 106 | description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" 107 | } 108 | 109 | variable "tenant" { 110 | type = string 111 | default = null 112 | description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" 113 | } 114 | 115 | variable "environment" { 116 | type = string 117 | default = null 118 | description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" 119 | } 120 | 121 | variable "stage" { 122 | type = string 123 | default = null 124 | description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" 125 | } 126 | 127 | variable "name" { 128 | type = string 129 | default = null 130 | description = <<-EOT 131 | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. 132 | This is the only ID element not also included as a `tag`. 133 | The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. 134 | EOT 135 | } 136 | 137 | variable "delimiter" { 138 | type = string 139 | default = null 140 | description = <<-EOT 141 | Delimiter to be used between ID elements. 142 | Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. 143 | EOT 144 | } 145 | 146 | variable "attributes" { 147 | type = list(string) 148 | default = [] 149 | description = <<-EOT 150 | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, 151 | in the order they appear in the list. New attributes are appended to the 152 | end of the list. The elements of the list are joined by the `delimiter` 153 | and treated as a single ID element. 154 | EOT 155 | } 156 | 157 | variable "labels_as_tags" { 158 | type = set(string) 159 | default = ["default"] 160 | description = <<-EOT 161 | Set of labels (ID elements) to include as tags in the `tags` output. 162 | Default is to include all labels. 163 | Tags with empty values will not be included in the `tags` output. 164 | Set to `[]` to suppress all generated tags. 165 | **Notes:** 166 | The value of the `name` tag, if included, will be the `id`, not the `name`. 167 | Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be 168 | changed in later chained modules. Attempts to change it will be silently ignored. 169 | EOT 170 | } 171 | 172 | variable "tags" { 173 | type = map(string) 174 | default = {} 175 | description = <<-EOT 176 | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). 177 | Neither the tag keys nor the tag values will be modified by this module. 178 | EOT 179 | } 180 | 181 | variable "additional_tag_map" { 182 | type = map(string) 183 | default = {} 184 | description = <<-EOT 185 | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. 186 | This is for some rare cases where resources want additional configuration of tags 187 | and therefore take a list of maps with tag key, value, and additional configuration. 188 | EOT 189 | } 190 | 191 | variable "label_order" { 192 | type = list(string) 193 | default = null 194 | description = <<-EOT 195 | The order in which the labels (ID elements) appear in the `id`. 196 | Defaults to ["namespace", "environment", "stage", "name", "attributes"]. 197 | You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. 198 | EOT 199 | } 200 | 201 | variable "regex_replace_chars" { 202 | type = string 203 | default = null 204 | description = <<-EOT 205 | Terraform regular expression (regex) string. 206 | Characters matching the regex will be removed from the ID elements. 207 | If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. 208 | EOT 209 | } 210 | 211 | variable "id_length_limit" { 212 | type = number 213 | default = null 214 | description = <<-EOT 215 | Limit `id` to this many characters (minimum 6). 216 | Set to `0` for unlimited length. 217 | Set to `null` for keep the existing setting, which defaults to `0`. 218 | Does not affect `id_full`. 219 | EOT 220 | validation { 221 | condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 222 | error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." 223 | } 224 | } 225 | 226 | variable "label_key_case" { 227 | type = string 228 | default = null 229 | description = <<-EOT 230 | Controls the letter case of the `tags` keys (label names) for tags generated by this module. 231 | Does not affect keys of tags passed in via the `tags` input. 232 | Possible values: `lower`, `title`, `upper`. 233 | Default value: `title`. 234 | EOT 235 | 236 | validation { 237 | condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) 238 | error_message = "Allowed values: `lower`, `title`, `upper`." 239 | } 240 | } 241 | 242 | variable "label_value_case" { 243 | type = string 244 | default = null 245 | description = <<-EOT 246 | Controls the letter case of ID elements (labels) as included in `id`, 247 | set as tag values, and output by this module individually. 248 | Does not affect values of tags passed in via the `tags` input. 249 | Possible values: `lower`, `title`, `upper` and `none` (no transformation). 250 | Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. 251 | Default value: `lower`. 252 | EOT 253 | 254 | validation { 255 | condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) 256 | error_message = "Allowed values: `lower`, `title`, `upper`, `none`." 257 | } 258 | } 259 | 260 | variable "descriptor_formats" { 261 | type = any 262 | default = {} 263 | description = <<-EOT 264 | Describe additional descriptors to be output in the `descriptors` output map. 265 | Map of maps. Keys are names of descriptors. Values are maps of the form 266 | `{ 267 | format = string 268 | labels = list(string) 269 | }` 270 | (Type is `any` so the map values can later be enhanced to provide additional options.) 271 | `format` is a Terraform format string to be passed to the `format()` function. 272 | `labels` is a list of labels, in order, to pass to `format()` function. 273 | Label values will be normalized before being passed to `format()` so they will be 274 | identical to how they appear in `id`. 275 | Default is `{}` (`descriptors` output will be empty). 276 | EOT 277 | } 278 | 279 | #### End of copy of cloudposse/terraform-null-label/variables.tf -------------------------------------------------------------------------------- /src/modules/assume-role-policy/main.tf: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # A policy document that grants role assumption to a given role name in a given 3 | # account. 4 | ############################################################################### 5 | data "aws_iam_policy_document" "assume_role" { 6 | statement { 7 | sid = "Assume${replace(title(var.account_name), "/-| /", "")}${replace(title(var.role_name), "/-| /", "")}Role" 8 | actions = [ 9 | "sts:AssumeRole" 10 | ] 11 | 12 | resources = [ 13 | "arn:aws:iam::${var.account_id}:role/${var.role_name}", 14 | ] 15 | } 16 | } 17 | 18 | ############################################################################### 19 | # A policy that grants role assumption to a given role name in a given account 20 | # using the assume_role policy document defined above 21 | ############################################################################### 22 | resource "aws_iam_policy" "assume_role" { 23 | name = "${replace(title(var.account_name), "/-| /", "")}${replace(title(var.role_name), "/-| /", "")}RoleAccess" 24 | policy = data.aws_iam_policy_document.assume_role.json 25 | description = "Grants role assumption for the ${title(var.role_name)} role in the ${title(var.account_name)} account" 26 | 27 | tags = module.this.tags 28 | } 29 | -------------------------------------------------------------------------------- /src/modules/assume-role-policy/outputs.tf: -------------------------------------------------------------------------------- 1 | output "policy_arn" { 2 | value = aws_iam_policy.assume_role.arn 3 | } 4 | -------------------------------------------------------------------------------- /src/modules/assume-role-policy/variables.tf: -------------------------------------------------------------------------------- 1 | variable "account_name" {} 2 | variable "account_id" {} 3 | variable "role_name" {} 4 | -------------------------------------------------------------------------------- /src/modules/assume-role-policy/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = "~>1.4.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = "= 4.59.0" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/modules/cloudtrail-bucket/context.tf: -------------------------------------------------------------------------------- 1 | # 2 | # ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label 3 | # All other instances of this file should be a copy of that one 4 | # 5 | # 6 | # Copy this file from https://github.com/cloudposse/terraform-null-label/blob/management/exports/context.tf 7 | # and then place it in your Terraform module to automatically get 8 | # Cloud Posse's standard configuration inputs suitable for passing 9 | # to Cloud Posse modules. 10 | # 11 | # curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/management/exports/context.tf -o context.tf 12 | # 13 | # Modules should access the whole context as `module.this.context` 14 | # to get the input variables with nulls for defaults, 15 | # for example `context = module.this.context`, 16 | # and access individual variables as `module.this.`, 17 | # with final values filled in. 18 | # 19 | # For example, when using defaults, `module.this.context.delimiter` 20 | # will be null, and `module.this.delimiter` will be `-` (hyphen). 21 | # 22 | 23 | module "this" { 24 | source = "cloudposse/label/null" 25 | version = "0.25.0" 26 | 27 | enabled = var.enabled 28 | namespace = var.namespace 29 | tenant = var.tenant 30 | environment = var.environment 31 | stage = var.stage 32 | name = var.name 33 | delimiter = var.delimiter 34 | attributes = var.attributes 35 | tags = var.tags 36 | additional_tag_map = var.additional_tag_map 37 | label_order = var.label_order 38 | regex_replace_chars = var.regex_replace_chars 39 | id_length_limit = var.id_length_limit 40 | label_key_case = var.label_key_case 41 | label_value_case = var.label_value_case 42 | descriptor_formats = var.descriptor_formats 43 | labels_as_tags = var.labels_as_tags 44 | 45 | context = var.context 46 | } 47 | 48 | # Copy contents of cloudposse/terraform-null-label/variables.tf here 49 | 50 | variable "context" { 51 | type = any 52 | default = { 53 | enabled = true 54 | namespace = null 55 | tenant = null 56 | environment = null 57 | stage = null 58 | name = null 59 | delimiter = null 60 | attributes = [] 61 | tags = {} 62 | additional_tag_map = {} 63 | regex_replace_chars = null 64 | label_order = [] 65 | id_length_limit = null 66 | label_key_case = null 67 | label_value_case = null 68 | descriptor_formats = {} 69 | # Note: we have to use [] instead of null for unset lists due to 70 | # https://github.com/hashicorp/terraform/issues/28137 71 | # which was not fixed until Terraform 1.0.0, 72 | # but we want the default to be all the labels in `label_order` 73 | # and we want users to be able to prevent all tag generation 74 | # by setting `labels_as_tags` to `[]`, so we need 75 | # a different sentinel to indicate "default" 76 | labels_as_tags = ["unset"] 77 | } 78 | description = <<-EOT 79 | Single object for setting entire context at once. 80 | See description of individual variables for details. 81 | Leave string and numeric variables as `null` to use default value. 82 | Individual variable settings (non-null) override settings in context object, 83 | except for attributes, tags, and additional_tag_map, which are merged. 84 | EOT 85 | 86 | validation { 87 | condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) 88 | error_message = "Allowed values: `lower`, `title`, `upper`." 89 | } 90 | 91 | validation { 92 | condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) 93 | error_message = "Allowed values: `lower`, `title`, `upper`, `none`." 94 | } 95 | } 96 | 97 | variable "enabled" { 98 | type = bool 99 | default = null 100 | description = "Set to false to prevent the module from creating any resources" 101 | } 102 | 103 | variable "namespace" { 104 | type = string 105 | default = null 106 | description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" 107 | } 108 | 109 | variable "tenant" { 110 | type = string 111 | default = null 112 | description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" 113 | } 114 | 115 | variable "environment" { 116 | type = string 117 | default = null 118 | description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" 119 | } 120 | 121 | variable "stage" { 122 | type = string 123 | default = null 124 | description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" 125 | } 126 | 127 | variable "name" { 128 | type = string 129 | default = null 130 | description = <<-EOT 131 | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. 132 | This is the only ID element not also included as a `tag`. 133 | The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. 134 | EOT 135 | } 136 | 137 | variable "delimiter" { 138 | type = string 139 | default = null 140 | description = <<-EOT 141 | Delimiter to be used between ID elements. 142 | Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. 143 | EOT 144 | } 145 | 146 | variable "attributes" { 147 | type = list(string) 148 | default = [] 149 | description = <<-EOT 150 | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, 151 | in the order they appear in the list. New attributes are appended to the 152 | end of the list. The elements of the list are joined by the `delimiter` 153 | and treated as a single ID element. 154 | EOT 155 | } 156 | 157 | variable "labels_as_tags" { 158 | type = set(string) 159 | default = ["default"] 160 | description = <<-EOT 161 | Set of labels (ID elements) to include as tags in the `tags` output. 162 | Default is to include all labels. 163 | Tags with empty values will not be included in the `tags` output. 164 | Set to `[]` to suppress all generated tags. 165 | **Notes:** 166 | The value of the `name` tag, if included, will be the `id`, not the `name`. 167 | Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be 168 | changed in later chained modules. Attempts to change it will be silently ignored. 169 | EOT 170 | } 171 | 172 | variable "tags" { 173 | type = map(string) 174 | default = {} 175 | description = <<-EOT 176 | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). 177 | Neither the tag keys nor the tag values will be modified by this module. 178 | EOT 179 | } 180 | 181 | variable "additional_tag_map" { 182 | type = map(string) 183 | default = {} 184 | description = <<-EOT 185 | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. 186 | This is for some rare cases where resources want additional configuration of tags 187 | and therefore take a list of maps with tag key, value, and additional configuration. 188 | EOT 189 | } 190 | 191 | variable "label_order" { 192 | type = list(string) 193 | default = null 194 | description = <<-EOT 195 | The order in which the labels (ID elements) appear in the `id`. 196 | Defaults to ["namespace", "environment", "stage", "name", "attributes"]. 197 | You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. 198 | EOT 199 | } 200 | 201 | variable "regex_replace_chars" { 202 | type = string 203 | default = null 204 | description = <<-EOT 205 | Terraform regular expression (regex) string. 206 | Characters matching the regex will be removed from the ID elements. 207 | If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. 208 | EOT 209 | } 210 | 211 | variable "id_length_limit" { 212 | type = number 213 | default = null 214 | description = <<-EOT 215 | Limit `id` to this many characters (minimum 6). 216 | Set to `0` for unlimited length. 217 | Set to `null` for keep the existing setting, which defaults to `0`. 218 | Does not affect `id_full`. 219 | EOT 220 | validation { 221 | condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 222 | error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." 223 | } 224 | } 225 | 226 | variable "label_key_case" { 227 | type = string 228 | default = null 229 | description = <<-EOT 230 | Controls the letter case of the `tags` keys (label names) for tags generated by this module. 231 | Does not affect keys of tags passed in via the `tags` input. 232 | Possible values: `lower`, `title`, `upper`. 233 | Default value: `title`. 234 | EOT 235 | 236 | validation { 237 | condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) 238 | error_message = "Allowed values: `lower`, `title`, `upper`." 239 | } 240 | } 241 | 242 | variable "label_value_case" { 243 | type = string 244 | default = null 245 | description = <<-EOT 246 | Controls the letter case of ID elements (labels) as included in `id`, 247 | set as tag values, and output by this module individually. 248 | Does not affect values of tags passed in via the `tags` input. 249 | Possible values: `lower`, `title`, `upper` and `none` (no transformation). 250 | Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. 251 | Default value: `lower`. 252 | EOT 253 | 254 | validation { 255 | condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) 256 | error_message = "Allowed values: `lower`, `title`, `upper`, `none`." 257 | } 258 | } 259 | 260 | variable "descriptor_formats" { 261 | type = any 262 | default = {} 263 | description = <<-EOT 264 | Describe additional descriptors to be output in the `descriptors` output map. 265 | Map of maps. Keys are names of descriptors. Values are maps of the form 266 | `{ 267 | format = string 268 | labels = list(string) 269 | }` 270 | (Type is `any` so the map values can later be enhanced to provide additional options.) 271 | `format` is a Terraform format string to be passed to the `format()` function. 272 | `labels` is a list of labels, in order, to pass to `format()` function. 273 | Label values will be normalized before being passed to `format()` so they will be 274 | identical to how they appear in `id`. 275 | Default is `{}` (`descriptors` output will be empty). 276 | EOT 277 | } 278 | 279 | #### End of copy of cloudposse/terraform-null-label/variables.tf -------------------------------------------------------------------------------- /src/modules/cloudtrail-bucket/main.tf: -------------------------------------------------------------------------------- 1 | module "s3_bucket" { 2 | source = "cloudposse/s3-log-storage/aws" 3 | version = "1.1.0" 4 | enabled = module.this.enabled 5 | 6 | acl = var.acl 7 | source_policy_documents = data.aws_iam_policy_document.default.*.json 8 | force_destroy = var.force_destroy 9 | versioning_enabled = var.versioning_enabled 10 | lifecycle_configuration_rules = var.lifecycle_configuration_rules 11 | sse_algorithm = var.sse_algorithm 12 | kms_master_key_arn = var.kms_master_key_arn 13 | block_public_acls = var.block_public_acls 14 | block_public_policy = var.block_public_policy 15 | ignore_public_acls = var.ignore_public_acls 16 | restrict_public_buckets = var.restrict_public_buckets 17 | access_log_bucket_name = local.access_log_bucket_name 18 | allow_ssl_requests_only = var.allow_ssl_requests_only 19 | bucket_notifications_enabled = var.bucket_notifications_enabled 20 | bucket_notifications_type = var.bucket_notifications_type 21 | bucket_notifications_prefix = var.bucket_notifications_prefix 22 | 23 | context = module.this.context 24 | } 25 | 26 | module "s3_access_log_bucket" { 27 | source = "cloudposse/s3-log-storage/aws" 28 | version = "1.1.0" 29 | enabled = module.this.enabled && var.create_access_log_bucket 30 | 31 | acl = var.acl 32 | force_destroy = var.force_destroy 33 | versioning_enabled = var.versioning_enabled 34 | lifecycle_configuration_rules = var.lifecycle_configuration_rules 35 | sse_algorithm = var.sse_algorithm 36 | kms_master_key_arn = var.kms_master_key_arn 37 | block_public_acls = var.block_public_acls 38 | block_public_policy = var.block_public_policy 39 | ignore_public_acls = var.ignore_public_acls 40 | restrict_public_buckets = var.restrict_public_buckets 41 | access_log_bucket_name = "" 42 | allow_ssl_requests_only = var.allow_ssl_requests_only 43 | 44 | attributes = ["access-logs"] 45 | context = module.this.context 46 | } 47 | 48 | data "aws_iam_policy_document" "default" { 49 | count = module.this.enabled ? 1 : 0 50 | 51 | statement { 52 | sid = "AWSCloudTrailAclCheck" 53 | 54 | principals { 55 | type = "Service" 56 | identifiers = ["cloudtrail.amazonaws.com"] 57 | } 58 | 59 | actions = [ 60 | "s3:GetBucketAcl", 61 | ] 62 | 63 | resources = [ 64 | "${local.arn_format}:s3:::${module.this.id}", 65 | ] 66 | } 67 | 68 | statement { 69 | sid = "AWSCloudTrailWrite" 70 | 71 | principals { 72 | type = "Service" 73 | identifiers = ["cloudtrail.amazonaws.com", "config.amazonaws.com"] 74 | } 75 | 76 | actions = [ 77 | "s3:PutObject", 78 | ] 79 | 80 | resources = [ 81 | "${local.arn_format}:s3:::${module.this.id}/*", 82 | ] 83 | 84 | condition { 85 | test = "StringEquals" 86 | variable = "s3:x-amz-acl" 87 | 88 | values = [ 89 | "bucket-owner-full-control", 90 | ] 91 | } 92 | } 93 | } 94 | 95 | data "aws_partition" "current" {} 96 | 97 | locals { 98 | access_log_bucket_name = var.create_access_log_bucket == true ? module.s3_access_log_bucket.bucket_id : var.access_log_bucket_name 99 | arn_format = "arn:${data.aws_partition.current.partition}" 100 | } -------------------------------------------------------------------------------- /src/modules/cloudtrail-bucket/outputs.tf: -------------------------------------------------------------------------------- 1 | output "bucket_domain_name" { 2 | value = module.s3_bucket.bucket_domain_name 3 | description = "FQDN of bucket" 4 | } 5 | 6 | output "bucket_id" { 7 | value = module.s3_bucket.bucket_id 8 | description = "Bucket ID" 9 | } 10 | 11 | output "bucket_arn" { 12 | value = module.s3_bucket.bucket_arn 13 | description = "Bucket ARN" 14 | } 15 | 16 | output "prefix" { 17 | value = module.s3_bucket.prefix 18 | description = "Prefix configured for lifecycle rules" 19 | } 20 | 21 | output "bucket_notifications_sqs_queue_arn" { 22 | value = module.s3_bucket.bucket_notifications_sqs_queue_arn 23 | description = "Notifications SQS queue ARN" 24 | } -------------------------------------------------------------------------------- /src/modules/cloudtrail-bucket/variables.tf: -------------------------------------------------------------------------------- 1 | variable "acl" { 2 | type = string 3 | description = "The canned ACL to apply. We recommend log-delivery-write for compatibility with AWS services" 4 | default = "log-delivery-write" 5 | } 6 | 7 | variable "force_destroy" { 8 | type = bool 9 | description = "(Optional, Default:false ) A boolean that indicates all objects should be deleted from the bucket so that the bucket can be destroyed without error. These objects are not recoverable" 10 | default = false 11 | } 12 | 13 | variable "versioning_enabled" { 14 | type = bool 15 | description = "A state of versioning. Versioning is a means of keeping multiple variants of an object in the same bucket" 16 | default = false 17 | } 18 | 19 | variable "lifecycle_configuration_rules" { 20 | type = list(object({ 21 | enabled = bool 22 | id = string 23 | 24 | abort_incomplete_multipart_upload_days = number 25 | 26 | # `filter_and` is the `and` configuration block inside the `filter` configuration. 27 | # This is the only place you should specify a prefix. 28 | filter_and = any 29 | expiration = any 30 | transition = list(any) 31 | 32 | noncurrent_version_expiration = any 33 | noncurrent_version_transition = list(any) 34 | })) 35 | default = [] 36 | description = "A list of lifecycle V2 rules" 37 | } 38 | 39 | variable "sse_algorithm" { 40 | type = string 41 | description = "The server-side encryption algorithm to use. Valid values are AES256 and aws:kms" 42 | default = "AES256" 43 | } 44 | 45 | variable "kms_master_key_arn" { 46 | type = string 47 | description = "The AWS KMS master key ARN used for the SSE-KMS encryption. This can only be used when you set the value of sse_algorithm as aws:kms. The default aws/s3 AWS KMS master key is used if this element is absent while the sse_algorithm is aws:kms" 48 | default = "" 49 | } 50 | 51 | variable "block_public_acls" { 52 | type = bool 53 | default = true 54 | description = "Set to `false` to disable the blocking of new public access lists on the bucket" 55 | } 56 | 57 | variable "block_public_policy" { 58 | type = bool 59 | default = true 60 | description = "Set to `false` to disable the blocking of new public policies on the bucket" 61 | } 62 | 63 | variable "ignore_public_acls" { 64 | type = bool 65 | default = true 66 | description = "Set to `false` to disable the ignoring of public access lists on the bucket" 67 | } 68 | 69 | variable "restrict_public_buckets" { 70 | type = bool 71 | default = true 72 | description = "Set to `false` to disable the restricting of making the bucket public" 73 | } 74 | 75 | variable "access_log_bucket_name" { 76 | type = string 77 | default = "" 78 | description = "Name of the S3 bucket where s3 access log will be sent to" 79 | } 80 | 81 | variable "create_access_log_bucket" { 82 | type = bool 83 | default = false 84 | description = "A flag to indicate if a bucket for s3 access logs should be created" 85 | } 86 | 87 | variable "allow_ssl_requests_only" { 88 | type = bool 89 | default = true 90 | description = "Set to `true` to require requests to use Secure Socket Layer (HTTPS/SSL). This will explicitly deny access to HTTP requests" 91 | } 92 | 93 | variable "bucket_notifications_enabled" { 94 | type = bool 95 | description = "Send notifications for the object created events. Used for 3rd-party log collection from a bucket. This does not affect access log bucket created by this module. To enable bucket notifications on the access log bucket, create it separately using the cloudposse/s3-log-storage/aws" 96 | default = false 97 | } 98 | 99 | variable "bucket_notifications_type" { 100 | type = string 101 | description = "Type of the notification configuration. Only SQS is supported." 102 | default = "SQS" 103 | } 104 | 105 | variable "bucket_notifications_prefix" { 106 | type = string 107 | description = "Prefix filter. Used to manage object notifications" 108 | default = "" 109 | } -------------------------------------------------------------------------------- /src/modules/cloudtrail-bucket/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = "~>1.4.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = "= 4.59.0" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/modules/cloudtrail/context.tf: -------------------------------------------------------------------------------- 1 | # 2 | # ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label 3 | # All other instances of this file should be a copy of that one 4 | # 5 | # 6 | # Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf 7 | # and then place it in your Terraform module to automatically get 8 | # Cloud Posse's standard configuration inputs suitable for passing 9 | # to Cloud Posse modules. 10 | # 11 | # Modules should access the whole context as `module.this.context` 12 | # to get the input variables with nulls for defaults, 13 | # for example `context = module.this.context`, 14 | # and access individual variables as `module.this.`, 15 | # with final values filled in. 16 | # 17 | # For example, when using defaults, `module.this.context.delimiter` 18 | # will be null, and `module.this.delimiter` will be `-` (hyphen). 19 | # 20 | 21 | module "this" { 22 | source = "cloudposse/label/null" 23 | version = "0.24.1" # requires Terraform >= 0.13.0 24 | 25 | enabled = var.enabled 26 | namespace = var.namespace 27 | environment = var.environment 28 | stage = var.stage 29 | name = var.name 30 | delimiter = var.delimiter 31 | attributes = var.attributes 32 | tags = var.tags 33 | additional_tag_map = var.additional_tag_map 34 | label_order = var.label_order 35 | regex_replace_chars = var.regex_replace_chars 36 | id_length_limit = var.id_length_limit 37 | label_key_case = var.label_key_case 38 | label_value_case = var.label_value_case 39 | 40 | context = var.context 41 | } 42 | 43 | # Copy contents of cloudposse/terraform-null-label/variables.tf here 44 | 45 | variable "context" { 46 | type = any 47 | default = { 48 | enabled = true 49 | namespace = null 50 | environment = null 51 | stage = null 52 | name = null 53 | delimiter = null 54 | attributes = [] 55 | tags = {} 56 | additional_tag_map = {} 57 | regex_replace_chars = null 58 | label_order = [] 59 | id_length_limit = null 60 | label_key_case = null 61 | label_value_case = null 62 | } 63 | description = <<-EOT 64 | Single object for setting entire context at once. 65 | See description of individual variables for details. 66 | Leave string and numeric variables as `null` to use default value. 67 | Individual variable settings (non-null) override settings in context object, 68 | except for attributes, tags, and additional_tag_map, which are merged. 69 | EOT 70 | 71 | validation { 72 | condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) 73 | error_message = "Allowed values: `lower`, `title`, `upper`." 74 | } 75 | 76 | validation { 77 | condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) 78 | error_message = "Allowed values: `lower`, `title`, `upper`, `none`." 79 | } 80 | } 81 | 82 | variable "enabled" { 83 | type = bool 84 | default = null 85 | description = "Set to false to prevent the module from creating any resources" 86 | } 87 | 88 | variable "namespace" { 89 | type = string 90 | default = null 91 | description = "Namespace, which could be your organization name or abbreviation, e.g. 'eg' or 'cp'" 92 | } 93 | 94 | variable "environment" { 95 | type = string 96 | default = null 97 | description = "Environment, e.g. 'uw2', 'us-west-2', OR 'prod', 'staging', 'dev', 'UAT'" 98 | } 99 | 100 | variable "stage" { 101 | type = string 102 | default = null 103 | description = "Stage, e.g. 'prod', 'staging', 'dev', OR 'source', 'build', 'test', 'deploy', 'release'" 104 | } 105 | 106 | variable "name" { 107 | type = string 108 | default = null 109 | description = "Solution name, e.g. 'app' or 'jenkins'" 110 | } 111 | 112 | variable "delimiter" { 113 | type = string 114 | default = null 115 | description = <<-EOT 116 | Delimiter to be used between `namespace`, `environment`, `stage`, `name` and `attributes`. 117 | Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. 118 | EOT 119 | } 120 | 121 | variable "attributes" { 122 | type = list(string) 123 | default = [] 124 | description = "Additional attributes (e.g. `1`)" 125 | } 126 | 127 | variable "tags" { 128 | type = map(string) 129 | default = {} 130 | description = "Additional tags (e.g. `map('BusinessUnit','XYZ')`" 131 | } 132 | 133 | variable "additional_tag_map" { 134 | type = map(string) 135 | default = {} 136 | description = "Additional tags for appending to tags_as_list_of_maps. Not added to `tags`." 137 | } 138 | 139 | variable "label_order" { 140 | type = list(string) 141 | default = null 142 | description = <<-EOT 143 | The naming order of the id output and Name tag. 144 | Defaults to ["namespace", "environment", "stage", "name", "attributes"]. 145 | You can omit any of the 5 elements, but at least one must be present. 146 | EOT 147 | } 148 | 149 | variable "regex_replace_chars" { 150 | type = string 151 | default = null 152 | description = <<-EOT 153 | Regex to replace chars with empty string in `namespace`, `environment`, `stage` and `name`. 154 | If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. 155 | EOT 156 | } 157 | 158 | variable "id_length_limit" { 159 | type = number 160 | default = null 161 | description = <<-EOT 162 | Limit `id` to this many characters (minimum 6). 163 | Set to `0` for unlimited length. 164 | Set to `null` for default, which is `0`. 165 | Does not affect `id_full`. 166 | EOT 167 | validation { 168 | condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 169 | error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." 170 | } 171 | } 172 | 173 | variable "label_key_case" { 174 | type = string 175 | default = null 176 | description = <<-EOT 177 | The letter case of label keys (`tag` names) (i.e. `name`, `namespace`, `environment`, `stage`, `attributes`) to use in `tags`. 178 | Possible values: `lower`, `title`, `upper`. 179 | Default value: `title`. 180 | EOT 181 | 182 | validation { 183 | condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) 184 | error_message = "Allowed values: `lower`, `title`, `upper`." 185 | } 186 | } 187 | 188 | variable "label_value_case" { 189 | type = string 190 | default = null 191 | description = <<-EOT 192 | The letter case of output label values (also used in `tags` and `id`). 193 | Possible values: `lower`, `title`, `upper` and `none` (no transformation). 194 | Default value: `lower`. 195 | EOT 196 | 197 | validation { 198 | condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) 199 | error_message = "Allowed values: `lower`, `title`, `upper`, `none`." 200 | } 201 | } 202 | #### End of copy of cloudposse/terraform-null-label/variables.tf -------------------------------------------------------------------------------- /src/modules/cloudtrail/main.tf: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Create CloudTrail organization trail 3 | ############################################################################### 4 | resource "aws_cloudtrail" "organization_trail" { 5 | name = "${module.this.id}-organization-trail" 6 | s3_bucket_name = var.cloudtrail_bucket_id 7 | 8 | include_global_service_events = true 9 | is_organization_trail = true 10 | is_multi_region_trail = true 11 | 12 | tags = module.this.tags 13 | } 14 | -------------------------------------------------------------------------------- /src/modules/cloudtrail/variables.tf: -------------------------------------------------------------------------------- 1 | variable "cloudtrail_bucket_id" {} 2 | 3 | variable "is_organization_trail" { 4 | type = bool 5 | default = false 6 | } 7 | 8 | variable "is_multi_region_trail" { 9 | type = bool 10 | default = false 11 | } -------------------------------------------------------------------------------- /src/modules/cloudtrail/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = "~>1.4.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = "= 4.59.0" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/modules/codecommit/context.tf: -------------------------------------------------------------------------------- 1 | # 2 | # ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label 3 | # All other instances of this file should be a copy of that one 4 | # 5 | # 6 | # Copy this file from https://github.com/cloudposse/terraform-null-label/blob/management/exports/context.tf 7 | # and then place it in your Terraform module to automatically get 8 | # Cloud Posse's standard configuration inputs suitable for passing 9 | # to Cloud Posse modules. 10 | # 11 | # curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/management/exports/context.tf -o context.tf 12 | # 13 | # Modules should access the whole context as `module.this.context` 14 | # to get the input variables with nulls for defaults, 15 | # for example `context = module.this.context`, 16 | # and access individual variables as `module.this.`, 17 | # with final values filled in. 18 | # 19 | # For example, when using defaults, `module.this.context.delimiter` 20 | # will be null, and `module.this.delimiter` will be `-` (hyphen). 21 | # 22 | 23 | module "this" { 24 | source = "cloudposse/label/null" 25 | version = "0.25.0" 26 | 27 | enabled = var.enabled 28 | namespace = var.namespace 29 | tenant = var.tenant 30 | environment = var.environment 31 | stage = var.stage 32 | name = var.name 33 | delimiter = var.delimiter 34 | attributes = var.attributes 35 | tags = var.tags 36 | additional_tag_map = var.additional_tag_map 37 | label_order = var.label_order 38 | regex_replace_chars = var.regex_replace_chars 39 | id_length_limit = var.id_length_limit 40 | label_key_case = var.label_key_case 41 | label_value_case = var.label_value_case 42 | descriptor_formats = var.descriptor_formats 43 | labels_as_tags = var.labels_as_tags 44 | 45 | context = var.context 46 | } 47 | 48 | # Copy contents of cloudposse/terraform-null-label/variables.tf here 49 | 50 | variable "context" { 51 | type = any 52 | default = { 53 | enabled = true 54 | namespace = null 55 | tenant = null 56 | environment = null 57 | stage = null 58 | name = null 59 | delimiter = null 60 | attributes = [] 61 | tags = {} 62 | additional_tag_map = {} 63 | regex_replace_chars = null 64 | label_order = [] 65 | id_length_limit = null 66 | label_key_case = null 67 | label_value_case = null 68 | descriptor_formats = {} 69 | # Note: we have to use [] instead of null for unset lists due to 70 | # https://github.com/hashicorp/terraform/issues/28137 71 | # which was not fixed until Terraform 1.0.0, 72 | # but we want the default to be all the labels in `label_order` 73 | # and we want users to be able to prevent all tag generation 74 | # by setting `labels_as_tags` to `[]`, so we need 75 | # a different sentinel to indicate "default" 76 | labels_as_tags = ["unset"] 77 | } 78 | description = <<-EOT 79 | Single object for setting entire context at once. 80 | See description of individual variables for details. 81 | Leave string and numeric variables as `null` to use default value. 82 | Individual variable settings (non-null) override settings in context object, 83 | except for attributes, tags, and additional_tag_map, which are merged. 84 | EOT 85 | 86 | validation { 87 | condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) 88 | error_message = "Allowed values: `lower`, `title`, `upper`." 89 | } 90 | 91 | validation { 92 | condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) 93 | error_message = "Allowed values: `lower`, `title`, `upper`, `none`." 94 | } 95 | } 96 | 97 | variable "enabled" { 98 | type = bool 99 | default = null 100 | description = "Set to false to prevent the module from creating any resources" 101 | } 102 | 103 | variable "namespace" { 104 | type = string 105 | default = null 106 | description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" 107 | } 108 | 109 | variable "tenant" { 110 | type = string 111 | default = null 112 | description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" 113 | } 114 | 115 | variable "environment" { 116 | type = string 117 | default = null 118 | description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" 119 | } 120 | 121 | variable "stage" { 122 | type = string 123 | default = null 124 | description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" 125 | } 126 | 127 | variable "name" { 128 | type = string 129 | default = null 130 | description = <<-EOT 131 | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. 132 | This is the only ID element not also included as a `tag`. 133 | The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. 134 | EOT 135 | } 136 | 137 | variable "delimiter" { 138 | type = string 139 | default = null 140 | description = <<-EOT 141 | Delimiter to be used between ID elements. 142 | Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. 143 | EOT 144 | } 145 | 146 | variable "attributes" { 147 | type = list(string) 148 | default = [] 149 | description = <<-EOT 150 | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, 151 | in the order they appear in the list. New attributes are appended to the 152 | end of the list. The elements of the list are joined by the `delimiter` 153 | and treated as a single ID element. 154 | EOT 155 | } 156 | 157 | variable "labels_as_tags" { 158 | type = set(string) 159 | default = ["default"] 160 | description = <<-EOT 161 | Set of labels (ID elements) to include as tags in the `tags` output. 162 | Default is to include all labels. 163 | Tags with empty values will not be included in the `tags` output. 164 | Set to `[]` to suppress all generated tags. 165 | **Notes:** 166 | The value of the `name` tag, if included, will be the `id`, not the `name`. 167 | Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be 168 | changed in later chained modules. Attempts to change it will be silently ignored. 169 | EOT 170 | } 171 | 172 | variable "tags" { 173 | type = map(string) 174 | default = {} 175 | description = <<-EOT 176 | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). 177 | Neither the tag keys nor the tag values will be modified by this module. 178 | EOT 179 | } 180 | 181 | variable "additional_tag_map" { 182 | type = map(string) 183 | default = {} 184 | description = <<-EOT 185 | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. 186 | This is for some rare cases where resources want additional configuration of tags 187 | and therefore take a list of maps with tag key, value, and additional configuration. 188 | EOT 189 | } 190 | 191 | variable "label_order" { 192 | type = list(string) 193 | default = null 194 | description = <<-EOT 195 | The order in which the labels (ID elements) appear in the `id`. 196 | Defaults to ["namespace", "environment", "stage", "name", "attributes"]. 197 | You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. 198 | EOT 199 | } 200 | 201 | variable "regex_replace_chars" { 202 | type = string 203 | default = null 204 | description = <<-EOT 205 | Terraform regular expression (regex) string. 206 | Characters matching the regex will be removed from the ID elements. 207 | If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. 208 | EOT 209 | } 210 | 211 | variable "id_length_limit" { 212 | type = number 213 | default = null 214 | description = <<-EOT 215 | Limit `id` to this many characters (minimum 6). 216 | Set to `0` for unlimited length. 217 | Set to `null` for keep the existing setting, which defaults to `0`. 218 | Does not affect `id_full`. 219 | EOT 220 | validation { 221 | condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 222 | error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." 223 | } 224 | } 225 | 226 | variable "label_key_case" { 227 | type = string 228 | default = null 229 | description = <<-EOT 230 | Controls the letter case of the `tags` keys (label names) for tags generated by this module. 231 | Does not affect keys of tags passed in via the `tags` input. 232 | Possible values: `lower`, `title`, `upper`. 233 | Default value: `title`. 234 | EOT 235 | 236 | validation { 237 | condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) 238 | error_message = "Allowed values: `lower`, `title`, `upper`." 239 | } 240 | } 241 | 242 | variable "label_value_case" { 243 | type = string 244 | default = null 245 | description = <<-EOT 246 | Controls the letter case of ID elements (labels) as included in `id`, 247 | set as tag values, and output by this module individually. 248 | Does not affect values of tags passed in via the `tags` input. 249 | Possible values: `lower`, `title`, `upper` and `none` (no transformation). 250 | Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. 251 | Default value: `lower`. 252 | EOT 253 | 254 | validation { 255 | condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) 256 | error_message = "Allowed values: `lower`, `title`, `upper`, `none`." 257 | } 258 | } 259 | 260 | variable "descriptor_formats" { 261 | type = any 262 | default = {} 263 | description = <<-EOT 264 | Describe additional descriptors to be output in the `descriptors` output map. 265 | Map of maps. Keys are names of descriptors. Values are maps of the form 266 | `{ 267 | format = string 268 | labels = list(string) 269 | }` 270 | (Type is `any` so the map values can later be enhanced to provide additional options.) 271 | `format` is a Terraform format string to be passed to the `format()` function. 272 | `labels` is a list of labels, in order, to pass to `format()` function. 273 | Label values will be normalized before being passed to `format()` so they will be 274 | identical to how they appear in `id`. 275 | Default is `{}` (`descriptors` output will be empty). 276 | EOT 277 | } 278 | 279 | #### End of copy of cloudposse/terraform-null-label/variables.tf -------------------------------------------------------------------------------- /src/modules/codecommit/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_codecommit_repository" "repository" { 2 | repository_name = "${module.this.namespace}-${module.this.name}-${var.aws_region}-${var.repository_name}" 3 | description = var.repository_description 4 | 5 | tags = module.this.tags 6 | } 7 | -------------------------------------------------------------------------------- /src/modules/codecommit/outputs.tf: -------------------------------------------------------------------------------- 1 | output "repository_id" { 2 | value = aws_codecommit_repository.repository.repository_id 3 | } 4 | 5 | output "repository_name" { 6 | value = aws_codecommit_repository.repository.repository_name 7 | } 8 | 9 | output "repository_arn" { 10 | value = aws_codecommit_repository.repository.arn 11 | } 12 | 13 | output "clone_url_http" { 14 | value = aws_codecommit_repository.repository.clone_url_http 15 | } 16 | 17 | output "clone_url_ssh" { 18 | value = aws_codecommit_repository.repository.clone_url_ssh 19 | } 20 | -------------------------------------------------------------------------------- /src/modules/codecommit/variables.tf: -------------------------------------------------------------------------------- 1 | variable "aws_region" {} 2 | 3 | variable "repository_name" {} 4 | variable "repository_description" {} 5 | -------------------------------------------------------------------------------- /src/modules/codecommit/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = "~>1.4.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = "= 4.59.0" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/modules/cross-account-role/context.tf: -------------------------------------------------------------------------------- 1 | # 2 | # ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label 3 | # All other instances of this file should be a copy of that one 4 | # 5 | # 6 | # Copy this file from https://github.com/cloudposse/terraform-null-label/blob/management/exports/context.tf 7 | # and then place it in your Terraform module to automatically get 8 | # Cloud Posse's standard configuration inputs suitable for passing 9 | # to Cloud Posse modules. 10 | # 11 | # curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/management/exports/context.tf -o context.tf 12 | # 13 | # Modules should access the whole context as `module.this.context` 14 | # to get the input variables with nulls for defaults, 15 | # for example `context = module.this.context`, 16 | # and access individual variables as `module.this.`, 17 | # with final values filled in. 18 | # 19 | # For example, when using defaults, `module.this.context.delimiter` 20 | # will be null, and `module.this.delimiter` will be `-` (hyphen). 21 | # 22 | 23 | module "this" { 24 | source = "cloudposse/label/null" 25 | version = "0.25.0" 26 | 27 | enabled = var.enabled 28 | namespace = var.namespace 29 | tenant = var.tenant 30 | environment = var.environment 31 | stage = var.stage 32 | name = var.name 33 | delimiter = var.delimiter 34 | attributes = var.attributes 35 | tags = var.tags 36 | additional_tag_map = var.additional_tag_map 37 | label_order = var.label_order 38 | regex_replace_chars = var.regex_replace_chars 39 | id_length_limit = var.id_length_limit 40 | label_key_case = var.label_key_case 41 | label_value_case = var.label_value_case 42 | descriptor_formats = var.descriptor_formats 43 | labels_as_tags = var.labels_as_tags 44 | 45 | context = var.context 46 | } 47 | 48 | # Copy contents of cloudposse/terraform-null-label/variables.tf here 49 | 50 | variable "context" { 51 | type = any 52 | default = { 53 | enabled = true 54 | namespace = null 55 | tenant = null 56 | environment = null 57 | stage = null 58 | name = null 59 | delimiter = null 60 | attributes = [] 61 | tags = {} 62 | additional_tag_map = {} 63 | regex_replace_chars = null 64 | label_order = [] 65 | id_length_limit = null 66 | label_key_case = null 67 | label_value_case = null 68 | descriptor_formats = {} 69 | # Note: we have to use [] instead of null for unset lists due to 70 | # https://github.com/hashicorp/terraform/issues/28137 71 | # which was not fixed until Terraform 1.0.0, 72 | # but we want the default to be all the labels in `label_order` 73 | # and we want users to be able to prevent all tag generation 74 | # by setting `labels_as_tags` to `[]`, so we need 75 | # a different sentinel to indicate "default" 76 | labels_as_tags = ["unset"] 77 | } 78 | description = <<-EOT 79 | Single object for setting entire context at once. 80 | See description of individual variables for details. 81 | Leave string and numeric variables as `null` to use default value. 82 | Individual variable settings (non-null) override settings in context object, 83 | except for attributes, tags, and additional_tag_map, which are merged. 84 | EOT 85 | 86 | validation { 87 | condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) 88 | error_message = "Allowed values: `lower`, `title`, `upper`." 89 | } 90 | 91 | validation { 92 | condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) 93 | error_message = "Allowed values: `lower`, `title`, `upper`, `none`." 94 | } 95 | } 96 | 97 | variable "enabled" { 98 | type = bool 99 | default = null 100 | description = "Set to false to prevent the module from creating any resources" 101 | } 102 | 103 | variable "namespace" { 104 | type = string 105 | default = null 106 | description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" 107 | } 108 | 109 | variable "tenant" { 110 | type = string 111 | default = null 112 | description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" 113 | } 114 | 115 | variable "environment" { 116 | type = string 117 | default = null 118 | description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" 119 | } 120 | 121 | variable "stage" { 122 | type = string 123 | default = null 124 | description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" 125 | } 126 | 127 | variable "name" { 128 | type = string 129 | default = null 130 | description = <<-EOT 131 | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. 132 | This is the only ID element not also included as a `tag`. 133 | The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. 134 | EOT 135 | } 136 | 137 | variable "delimiter" { 138 | type = string 139 | default = null 140 | description = <<-EOT 141 | Delimiter to be used between ID elements. 142 | Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. 143 | EOT 144 | } 145 | 146 | variable "attributes" { 147 | type = list(string) 148 | default = [] 149 | description = <<-EOT 150 | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, 151 | in the order they appear in the list. New attributes are appended to the 152 | end of the list. The elements of the list are joined by the `delimiter` 153 | and treated as a single ID element. 154 | EOT 155 | } 156 | 157 | variable "labels_as_tags" { 158 | type = set(string) 159 | default = ["default"] 160 | description = <<-EOT 161 | Set of labels (ID elements) to include as tags in the `tags` output. 162 | Default is to include all labels. 163 | Tags with empty values will not be included in the `tags` output. 164 | Set to `[]` to suppress all generated tags. 165 | **Notes:** 166 | The value of the `name` tag, if included, will be the `id`, not the `name`. 167 | Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be 168 | changed in later chained modules. Attempts to change it will be silently ignored. 169 | EOT 170 | } 171 | 172 | variable "tags" { 173 | type = map(string) 174 | default = {} 175 | description = <<-EOT 176 | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). 177 | Neither the tag keys nor the tag values will be modified by this module. 178 | EOT 179 | } 180 | 181 | variable "additional_tag_map" { 182 | type = map(string) 183 | default = {} 184 | description = <<-EOT 185 | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. 186 | This is for some rare cases where resources want additional configuration of tags 187 | and therefore take a list of maps with tag key, value, and additional configuration. 188 | EOT 189 | } 190 | 191 | variable "label_order" { 192 | type = list(string) 193 | default = null 194 | description = <<-EOT 195 | The order in which the labels (ID elements) appear in the `id`. 196 | Defaults to ["namespace", "environment", "stage", "name", "attributes"]. 197 | You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. 198 | EOT 199 | } 200 | 201 | variable "regex_replace_chars" { 202 | type = string 203 | default = null 204 | description = <<-EOT 205 | Terraform regular expression (regex) string. 206 | Characters matching the regex will be removed from the ID elements. 207 | If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. 208 | EOT 209 | } 210 | 211 | variable "id_length_limit" { 212 | type = number 213 | default = null 214 | description = <<-EOT 215 | Limit `id` to this many characters (minimum 6). 216 | Set to `0` for unlimited length. 217 | Set to `null` for keep the existing setting, which defaults to `0`. 218 | Does not affect `id_full`. 219 | EOT 220 | validation { 221 | condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 222 | error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." 223 | } 224 | } 225 | 226 | variable "label_key_case" { 227 | type = string 228 | default = null 229 | description = <<-EOT 230 | Controls the letter case of the `tags` keys (label names) for tags generated by this module. 231 | Does not affect keys of tags passed in via the `tags` input. 232 | Possible values: `lower`, `title`, `upper`. 233 | Default value: `title`. 234 | EOT 235 | 236 | validation { 237 | condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) 238 | error_message = "Allowed values: `lower`, `title`, `upper`." 239 | } 240 | } 241 | 242 | variable "label_value_case" { 243 | type = string 244 | default = null 245 | description = <<-EOT 246 | Controls the letter case of ID elements (labels) as included in `id`, 247 | set as tag values, and output by this module individually. 248 | Does not affect values of tags passed in via the `tags` input. 249 | Possible values: `lower`, `title`, `upper` and `none` (no transformation). 250 | Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. 251 | Default value: `lower`. 252 | EOT 253 | 254 | validation { 255 | condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) 256 | error_message = "Allowed values: `lower`, `title`, `upper`, `none`." 257 | } 258 | } 259 | 260 | variable "descriptor_formats" { 261 | type = any 262 | default = {} 263 | description = <<-EOT 264 | Describe additional descriptors to be output in the `descriptors` output map. 265 | Map of maps. Keys are names of descriptors. Values are maps of the form 266 | `{ 267 | format = string 268 | labels = list(string) 269 | }` 270 | (Type is `any` so the map values can later be enhanced to provide additional options.) 271 | `format` is a Terraform format string to be passed to the `format()` function. 272 | `labels` is a list of labels, in order, to pass to `format()` function. 273 | Label values will be normalized before being passed to `format()` so they will be 274 | identical to how they appear in `id`. 275 | Default is `{}` (`descriptors` output will be empty). 276 | EOT 277 | } 278 | 279 | #### End of copy of cloudposse/terraform-null-label/variables.tf -------------------------------------------------------------------------------- /src/modules/cross-account-role/main.tf: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Create a role with an assume role policy. The assume role policy is used to 3 | # grant permission for another account or service to assume this role. 4 | ############################################################################### 5 | resource "aws_iam_role" "role" { 6 | name = replace(title(var.role_name), "/-| /", "") 7 | assume_role_policy = var.assume_role_policy_json 8 | 9 | tags = module.this.tags 10 | } 11 | 12 | ############################################################################### 13 | # Attach a role policy to the role defined above. The role policy is used to 14 | # grant the role permission to perform actions. 15 | ############################################################################### 16 | resource "aws_iam_role_policy_attachment" "role_policy" { 17 | role = aws_iam_role.role.name 18 | policy_arn = var.role_policy_arn 19 | } 20 | -------------------------------------------------------------------------------- /src/modules/cross-account-role/outputs.tf: -------------------------------------------------------------------------------- 1 | output "role_name" { 2 | value = aws_iam_role.role.name 3 | } 4 | -------------------------------------------------------------------------------- /src/modules/cross-account-role/variables.tf: -------------------------------------------------------------------------------- 1 | variable "role_name" {} 2 | variable "assume_role_policy_json" {} 3 | variable "role_policy_arn" {} 4 | -------------------------------------------------------------------------------- /src/modules/cross-account-role/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = "~>1.4.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = "= 4.59.0" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/modules/email-billing-alarm/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_cloudwatch_metric_alarm" "consolidated_billing_alarm" { 2 | alarm_name = "account-billing-alarm-${lower(var.currency)}" 3 | comparison_operator = "GreaterThanOrEqualToThreshold" 4 | evaluation_periods = "1" 5 | metric_name = "EstimatedCharges" 6 | namespace = "AWS/Billing" 7 | period = "28800" 8 | statistic = "Maximum" 9 | alarm_description = "Consolidated billing alarm >= ${var.currency} ${var.monthly_billing_threshold}" 10 | threshold = var.monthly_billing_threshold 11 | alarm_actions = [aws_sns_topic.consolidated_billing_alarm.arn] 12 | 13 | dimensions = { 14 | Currency = var.currency 15 | } 16 | 17 | tags = module.this.tags 18 | } 19 | 20 | resource "aws_sns_topic" "consolidated_billing_alarm" { 21 | name = "billing-alarm-notification-${lower(var.currency)}" 22 | 23 | tags = module.this.tags 24 | } 25 | 26 | resource "aws_sns_topic_subscription" "consolidated_billing_alarm" { 27 | topic_arn = aws_sns_topic.consolidated_billing_alarm.arn 28 | protocol = "email" 29 | endpoint = var.notification_email_address 30 | } 31 | -------------------------------------------------------------------------------- /src/modules/email-billing-alarm/outputs.tf: -------------------------------------------------------------------------------- 1 | output "sns_topic_arn" { 2 | description = "SNS Topic ARN to be subscribed to in order to delivery the clodwatch billing alarms" 3 | value = aws_sns_topic.consolidated_billing_alarm.arn 4 | } 5 | 6 | output "sns_topic_subscription_arn" { 7 | description = "SNS topic subscription ARN where CloudWatch billing alarms are delivered to" 8 | value = aws_sns_topic_subscription.consolidated_billing_alarm.arn 9 | } 10 | -------------------------------------------------------------------------------- /src/modules/email-billing-alarm/variables.tf: -------------------------------------------------------------------------------- 1 | variable "monthly_billing_threshold" { 2 | description = "The threshold for which estimated monthly charges will trigger the metric alarm." 3 | type = string 4 | } 5 | 6 | variable "currency" { 7 | description = "Short notation for currency type (e.g. USD, CAD, AUD)" 8 | type = string 9 | default = "AUD" 10 | } 11 | 12 | variable "notification_email_address" { 13 | description = "Email address where notifications should be sent" 14 | type = string 15 | } 16 | -------------------------------------------------------------------------------- /src/modules/email-billing-alarm/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = "~>1.4.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = "= 4.59.0" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/modules/random-string/main.tf: -------------------------------------------------------------------------------- 1 | resource "random_string" "this" { 2 | length = var.length 3 | special = false 4 | upper = false 5 | } 6 | -------------------------------------------------------------------------------- /src/modules/random-string/outputs.tf: -------------------------------------------------------------------------------- 1 | output "result" { 2 | description = "The generated random string" 3 | value = random_string.this.result 4 | } 5 | -------------------------------------------------------------------------------- /src/modules/random-string/variables.tf: -------------------------------------------------------------------------------- 1 | variable "length" { 2 | description = "The length of the random string to generate" 3 | type = string 4 | } -------------------------------------------------------------------------------- /src/modules/random-string/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = "~>1.4.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = "= 4.59.0" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/modules/terraform-codebuild/main.tf: -------------------------------------------------------------------------------- 1 | data "aws_caller_identity" "current" {} 2 | 3 | resource "aws_codebuild_project" "codebuild" { 4 | name = "${module.this.id}-${var.codebuild_project_name}" 5 | description = "Build project for ${title(module.this.namespace)} ${title(module.this.name)} ${replace(title(var.codebuild_project_name), "/-| /", "")}" 6 | build_timeout = var.build_timeout 7 | service_role = aws_iam_role.codebuild.arn 8 | 9 | source { 10 | type = "CODEPIPELINE" 11 | buildspec = var.buildspec 12 | } 13 | 14 | artifacts { 15 | type = "CODEPIPELINE" 16 | } 17 | 18 | dynamic secondary_artifacts { 19 | for_each = var.secondary_artifacts 20 | content { 21 | artifact_identifier = secondary_artifacts.value["artifact_identifier"] 22 | bucket_owner_access = secondary_artifacts.value["bucket_owner_access"] 23 | type = secondary_artifacts.value["type"] 24 | packaging = secondary_artifacts.value["packaging"] 25 | location = secondary_artifacts.value["location"] 26 | path = secondary_artifacts.value["path"] 27 | namespace_type = secondary_artifacts.value["namespace_type"] 28 | name = secondary_artifacts.value["name"] 29 | } 30 | } 31 | 32 | environment { 33 | compute_type = "BUILD_GENERAL1_SMALL" 34 | image = "aws/codebuild/standard:6.0" 35 | type = "LINUX_CONTAINER" 36 | image_pull_credentials_type = "CODEBUILD" 37 | } 38 | 39 | logs_config { 40 | cloudwatch_logs { 41 | group_name = "/${module.this.namespace}/${module.this.name}/${var.codebuild_project_name}" 42 | } 43 | 44 | s3_logs { 45 | status = "ENABLED" 46 | bucket_owner_access = "READ_ONLY" 47 | location = "${var.logging_bucket_id}/${module.this.namespace}/${module.this.name}/${var.codebuild_project_name}" 48 | } 49 | } 50 | 51 | tags = module.this.tags 52 | } 53 | 54 | resource "aws_iam_role" "codebuild" { 55 | name = "${local.capitalised_name}CodebuildRole" 56 | 57 | assume_role_policy = < 0 39 | error_message = "A valid notification email address must be a given if approval stage is enabled." 40 | } 41 | description = "Approval stage configuration." 42 | } 43 | 44 | variable "environment_variables_plan" { 45 | type = map(string) 46 | default = {} 47 | description = "A map of environment variables to pass into pipeline for plan stage" 48 | } 49 | 50 | variable "environment_variables_apply" { 51 | type = map(string) 52 | default = {} 53 | description = "A map of environment variables to pass into pipeline for apply stage" 54 | } 55 | 56 | variable "codebuild_project_name_plan" { 57 | type = string 58 | description = "CodeBuild project name to handle Terraform plan stage" 59 | } 60 | 61 | variable "codebuild_project_name_apply" { 62 | type = string 63 | description = "CodeBuild project name to handle Terraform apply stage" 64 | } 65 | 66 | variable "source_artifact_name" { 67 | type = string 68 | description = "The name of the source artifact" 69 | default = "SourceArtifact" 70 | } 71 | 72 | variable "plan_artifact_name" { 73 | type = string 74 | description = "The name of the plan artifact" 75 | default = "PlanArtifact" 76 | } 77 | -------------------------------------------------------------------------------- /src/modules/terraform-codepipeline/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = "~>1.4.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = "= 4.59.0" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/terragrunt.hcl: -------------------------------------------------------------------------------- 1 | locals { 2 | common_vars = yamldecode(file(find_in_parent_folders("common_vars.yaml"))) 3 | 4 | region_vars = read_terragrunt_config(find_in_parent_folders("region.hcl", "ignore"), {locals = { aws_region = local.common_vars.management_aws_region}}) 5 | account_vars = read_terragrunt_config(find_in_parent_folders("account.hcl"), {}) 6 | 7 | account_ids = jsondecode(file("accounts.json")) 8 | 9 | terraform_state_bucket_name = "${local.common_vars.namespace}-${local.common_vars.name}-${local.common_vars.terraform_state_bucket_region}-deployment-${local.common_vars.terraform_state_bucket}" 10 | } 11 | 12 | generate "provider" { 13 | path = "provider.tf" 14 | if_exists = "overwrite" 15 | contents = <