diff --git a/.github/workflows/terraform-standard-iac-pipeline-aws-global-bootstrap.yaml b/.github/workflows/terraform-standard-iac-pipeline-aws-global-bootstrap.yaml index f7ef3346..49de7c4d 100644 --- a/.github/workflows/terraform-standard-iac-pipeline-aws-global-bootstrap.yaml +++ b/.github/workflows/terraform-standard-iac-pipeline-aws-global-bootstrap.yaml @@ -17,22 +17,19 @@ on: inputs: deploy_action: type: choice - options: [plan, apply, destroy] + options: [plan, apply] default: plan env: - TF_WORKDIR: iac-template/terraform-hcl-standard/aws-cloud + TG_ROOT: iac-template/terraform-hcl-standard/aws-cloud/bootstrap DEPLOY_ACTION: ${{ github.event.inputs.deploy_action || 'plan' }} + TG_VERSION: 0.67.14 jobs: bootstrap: name: "Bootstrap Modules" runs-on: ubuntu-latest - strategy: - matrix: - target: [bootstrap/state/, bootstrap/lock, bootstrap/identity] - steps: - uses: actions/checkout@v4 @@ -41,22 +38,20 @@ jobs: cat <<'SUMMARY' >> "$GITHUB_STEP_SUMMARY" ## Bootstrap scope - IAM: create Terraform deploy role and automation user for DevOps - - S3: create remote state bucket (versioned + SSE) - - DynamoDB: create state lock table for Terraform CRUD workflows + - S3: create remote state bucket (versioned + SSE + public access block) + - DynamoDB: create state lock table with encryption + PITR - Resource names and regions follow iac-template/terraform-hcl-standard/aws-cloud/config/accounts/bootstrap.yaml. + Terragrunt orchestrates state → lock → identity. Resource names and regions follow iac-template/terraform-hcl-standard/aws-cloud/config/accounts/bootstrap.yaml. SUMMARY - uses: hashicorp/setup-terraform@v3 with: terraform_version: 1.9.5 - - name: Restore Terraform state - uses: actions/download-artifact@v4 - continue-on-error: true - with: - name: tfstate-${{ matrix.target }} - path: ${{ env.TF_WORKDIR }}/${{ matrix.target }} + - name: Install Terragrunt + run: | + curl -L "https://github.com/gruntwork-io/terragrunt/releases/download/v${TG_VERSION}/terragrunt_linux_amd64" -o terragrunt + sudo install terragrunt /usr/local/bin/terragrunt - name: AWS Credentials uses: aws-actions/configure-aws-credentials@v4 @@ -65,97 +60,28 @@ jobs: aws-secret-access-key: ${{ secrets.AWS_BOOTSTRAP_SECRET_ACCESS_KEY }} aws-region: ap-northeast-1 - - name: Init - working-directory: ${{ env.TF_WORKDIR }}/${{ matrix.target }} - run: make init - - - name: Plan + - name: Terragrunt Plan if: env.DEPLOY_ACTION == 'plan' - working-directory: ${{ env.TF_WORKDIR }}/${{ matrix.target }} - run: make plan + working-directory: ${{ env.TG_ROOT }} + run: terragrunt run-all plan --terragrunt-non-interactive - - name: Apply + - name: Terragrunt Apply if: env.DEPLOY_ACTION == 'apply' - working-directory: ${{ env.TF_WORKDIR }}/${{ matrix.target }} - run: make apply - - - name: Load bootstrap config for destroy - if: env.DEPLOY_ACTION == 'destroy' - run: | - python -m pip install --quiet pyyaml - python - <<'PY' - import yaml - import os - from pathlib import Path - - cfg_path = Path("iac-template/terraform-hcl-standard/aws-cloud/config/accounts/bootstrap.yaml") - cfg = yaml.safe_load(cfg_path.read_text()) - - env_path = Path(os.environ["GITHUB_ENV"]) - current_env = env_path.read_text() if env_path.exists() else "" - env_path.write_text( - current_env - + f"BOOTSTRAP_BUCKET={cfg['state']['bucket_name']}\n" - + f"BOOTSTRAP_REGION={cfg['region']}\n" - + f"BOOTSTRAP_DYNAMODB_TABLE={cfg['state']['dynamodb_table_name']}\n" - + f"BOOTSTRAP_ROLE_NAME={cfg['iam']['role_name']}\n" - + f"BOOTSTRAP_TERRAFORM_USER={cfg['iam']['terraform_user_name']}\n" - ) - PY - - - name: Destroy - if: env.DEPLOY_ACTION == 'destroy' - working-directory: ${{ env.TF_WORKDIR }}/${{ matrix.target }} - env: - AWS_REGION: ${{ env.BOOTSTRAP_REGION }} - run: | - if [ "${{ matrix.target }}" = "bootstrap-s3" ]; then - make destroy bucket_name=${BOOTSTRAP_BUCKET} region=${BOOTSTRAP_REGION} - elif [ "${{ matrix.target }}" = "bootstrap-dynamodb" ]; then - make destroy table_name=${BOOTSTRAP_DYNAMODB_TABLE} region=${BOOTSTRAP_REGION} - else - make destroy role_name=${BOOTSTRAP_ROLE_NAME} terraform_user_name=${BOOTSTRAP_TERRAFORM_USER} - fi + working-directory: ${{ env.TG_ROOT }} + run: terragrunt run-all apply --terragrunt-non-interactive - name: Save Outputs if: env.DEPLOY_ACTION == 'apply' - working-directory: ${{ env.TF_WORKDIR }}/${{ matrix.target }} - run: terraform output -json > ../../outputs_${{ matrix.target }}.json + working-directory: ${{ env.TG_ROOT }} + run: | + mkdir -p outputs + for dir in state lock identity; do + terragrunt output -json --terragrunt-working-dir $dir > outputs/${dir}.json + done - uses: actions/upload-artifact@v4 if: env.DEPLOY_ACTION == 'apply' with: - name: outputs-${{ matrix.target }} - path: iac-template/terraform-hcl-standard/aws-cloud/outputs_${{ matrix.target }}.json + name: bootstrap-outputs + path: ${{ env.TG_ROOT }}/outputs retention-days: 30 - - aggregate: - name: "Aggregate Bootstrap Outputs" - runs-on: ubuntu-latest - needs: bootstrap - - if: ${{ github.event.inputs.deploy_action == 'apply' }} - - steps: - - uses: actions/download-artifact@v4 - with: - path: ./outputs - - - name: Merge Outputs - run: | - shopt -s globstar nullglob - echo "{" > final_bootstrap_outputs.json - f=true - for x in outputs/**/outputs_*.json; do - k=$(basename $x .json | sed 's/outputs_//') - [ "$f" = true ] && f=false || echo "," >> final_bootstrap_outputs.json - echo "\"$k\": $(cat $x)" >> final_bootstrap_outputs.json - done - echo "}" >> final_bootstrap_outputs.json - - - run: cat final_bootstrap_outputs.json - - - uses: actions/upload-artifact@v4 - with: - name: bootstrap-final-output - path: final_bootstrap_outputs.json diff --git a/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/README.md b/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/README.md new file mode 100644 index 00000000..9a85aa41 --- /dev/null +++ b/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/README.md @@ -0,0 +1,72 @@ +# AWS Bootstrap (Terraform + Terragrunt) + +This bootstrap stack provisions the shared primitives required for Terraform automation on AWS using the **terraform-hcl-standard** baseline. It delivers an auditable, deterministic foundation that can be reused across environments. + +## Architecture + +- **state**: Versioned, SSE-encrypted S3 bucket with public access blocked for Terraform state storage. +- **lock**: DynamoDB table with point-in-time recovery (PITR) and server-side encryption for state locking and auditability. +- **identity**: Terraform deploy role plus automation user, wired with least-privilege inline policies stored as external JSON documents. +- **Orchestration**: Terragrunt dependencies guarantee the apply order (state → lock → identity) and propagate outputs (bucket name, region, lock table) automatically. + +## Execution Order + +1. `state`: Creates the S3 backend bucket and exports `bucket_name`, `bucket_arn`, and `region`. +2. `lock`: Creates the DynamoDB lock table in the same region and exports `dynamodb_table_name` and `region`. +3. `identity`: Uses dependency outputs to bind IAM policies to the created state and lock resources. + +Terragrunt `run-all` handles the ordering; no manual sequencing is required. + +## Security Model + +- **Data plane**: S3 bucket enforces AES256 SSE, public access block, and versioning. DynamoDB enables server-side encryption and PITR for forensic recovery. +- **Control plane**: IAM policies are externalized in `identity/policies/*.json` and rendered via `aws_iam_policy_document` to keep Terraform code lean and auditable. +- **Config source of truth**: `config/accounts/bootstrap.yaml` defines canonical names, regions, and tags. Terragrunt passes outputs between modules to avoid drift. + +## How to Run with Terragrunt + +```bash +cd iac-template/terraform-hcl-standard/aws-cloud/bootstrap + +# Plan everything in dependency order +terragrunt run-all plan + +# Apply everything (state -> lock -> identity) +terragrunt run-all apply +``` + +### Targeting a Single Module + +```bash +terragrunt plan --terragrunt-working-dir state +terragrunt apply --terragrunt-working-dir identity +``` + +Terragrunt injects dependency outputs automatically; you do not need to pass bucket or table names manually. + +### Decommissioning Bootstrap Resources + +Bootstrap is intentionally outside day-to-day state management. Avoid `terragrunt destroy` and use the AWS CLI for teardown to keep lifecycle control explicit and auditable. + +```bash +# Remove automation user and deploy role (customize to your account IDs) +aws iam delete-access-key --user-name terraform-automation --access-key-id +aws iam delete-user-policy --user-name terraform-automation --policy-name terraform-automation-inline +aws iam delete-user --user-name terraform-automation +aws iam detach-role-policy --role-name terraform-deploy --policy-arn arn:aws:iam:::policy/terraform-deploy-inline +aws iam delete-role --role-name terraform-deploy + +# Remove lock + state once no stacks depend on them +aws dynamodb delete-table --table-name +aws s3 rb s3:// --force +``` + +Document the teardown in your change log for auditability. + +## CloudNeutral Bootstrap Principles + +- **Separation of concerns**: State, locking, and identity are isolated modules with explicit interfaces. +- **Least privilege by default**: IAM policies grant the minimal scope required for bootstrap lifecycle operations. +- **Idempotent automation**: All configurations are declarative, version-controlled, and runnable via Terragrunt without manual steps. +- **Auditability**: Policies live in external JSON files; DynamoDB PITR and S3 versioning preserve history for compliance. +- **Portability**: Inputs are read from YAML configuration and Terragrunt dependencies, making the stack reusable across accounts and regions. diff --git a/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/identity/locals.tf b/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/identity/locals.tf index 913d3bf2..a9f1bf25 100644 --- a/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/identity/locals.tf +++ b/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/identity/locals.tf @@ -1,15 +1,17 @@ locals { bootstrap = yamldecode(file("${path.module}/../../config/accounts/bootstrap.yaml")) - config_account_name = coalesce(var.account_name, local.bootstrap.account_name) - config_region = coalesce(var.region, local.bootstrap.region) - config_role_name = coalesce(var.role_name, local.bootstrap.iam.role_name) - config_terraform_user = coalesce(var.terraform_user_name, local.bootstrap.iam.terraform_user_name) - environment = coalesce(try(local.bootstrap.environment, null), try(local.bootstrap.iam.environment, null), "bootstrap") - extra_tags = try(local.bootstrap.tags, {}) + config_account_name = coalesce(var.account_name, local.bootstrap.account_name) + config_region = coalesce(var.region, local.bootstrap.region) + config_role_name = coalesce(var.role_name, local.bootstrap.iam.role_name) + config_terraform_user = coalesce(var.terraform_user_name, local.bootstrap.iam.terraform_user_name) + environment = coalesce(try(local.bootstrap.environment, null), try(local.bootstrap.iam.environment, null), "bootstrap") + extra_tags = try(local.bootstrap.tags, {}) - role_name = coalesce(var.existing_role_name, local.config_role_name) + role_name = coalesce(var.existing_role_name, local.config_role_name) terraform_user_name = coalesce(var.existing_user_name, local.config_terraform_user) + state_bucket_name = coalesce(var.state_bucket_name, try(local.bootstrap.state.bucket_name, null)) + lock_table_name = coalesce(var.state_lock_table_name, try(local.bootstrap.state.dynamodb_table_name, null)) } locals { diff --git a/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/identity/main.tf b/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/identity/main.tf index b5ad0915..2143633d 100644 --- a/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/identity/main.tf +++ b/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/identity/main.tf @@ -1,21 +1,21 @@ # # IAM Role: Terraform Deploy Role # ---------------------------------------- +data "aws_iam_policy_document" "terraform_deploy_assume_role" { + source_json = templatefile( + "${path.module}/policies/terraform-deploy-assume-role.json", + { + account_id = local.account.account_id + terraform_user_name = local.config_terraform_user + } + ) +} + resource "aws_iam_role" "terraform_deploy_role" { count = var.create_role ? 1 : 0 - name = local.role_name - - assume_role_policy = jsonencode({ - Version = "2012-10-17" - Statement = [{ - Effect = "Allow" - Principal = { - AWS = "arn:aws:iam::${local.account.account_id}:user/${local.config_terraform_user}" - } - Action = "sts:AssumeRole" - }] - }) + name = local.role_name + assume_role_policy = data.aws_iam_policy_document.terraform_deploy_assume_role.json tags = merge( { @@ -23,91 +23,29 @@ resource "aws_iam_role" "terraform_deploy_role" { Environment = coalesce(try(local.account.environment, null), local.environment) }, try(local.account.tags, {}), - local.extra_tags + local.extra_tags, + ) +} + +data "aws_iam_policy_document" "terraform_deploy_inline" { + source_json = templatefile( + "${path.module}/policies/terraform-deploy-inline-policy.json", + { + account_id = local.account.account_id + bucket_name = local.state_bucket_name + region = local.config_region + role_name = local.role_name + table_name = local.lock_table_name + } ) } resource "aws_iam_role_policy" "terraform_deploy_role_policy" { count = var.create_role ? 1 : 0 - name = "${local.role_name}-bootstrap-minimal" - role = aws_iam_role.terraform_deploy_role[0].id - - policy = jsonencode({ - Version = "2012-10-17", - Statement = [ - # Bootstrap S3 backend (state bucket) - { - Effect = "Allow", - Action = [ - "s3:CreateBucket", - "s3:GetBucketLocation", - "s3:ListBucket", - "s3:PutBucketVersioning", - "s3:PutBucketPolicy", - "s3:PutBucketTagging", - "s3:PutEncryptionConfiguration", - "s3:PutBucketPublicAccessBlock", - ], - Resource = "arn:aws:s3:::${local.bootstrap.state.bucket_name}" - }, - { - Effect = "Allow", - Action = [ - "s3:GetObject", - "s3:PutObject", - "s3:DeleteObject", - "s3:PutObjectTagging" - ], - Resource = "arn:aws:s3:::${local.bootstrap.state.bucket_name}/*" - }, - - # DynamoDB state lock table - { - Effect = "Allow", - Action = [ - "dynamodb:CreateTable", - "dynamodb:DescribeTable", - "dynamodb:UpdateTable", - "dynamodb:TagResource", - "dynamodb:UntagResource" - ], - Resource = "arn:aws:dynamodb:${local.config_region}:${local.account.account_id}:table/${local.bootstrap.state.dynamodb_table_name}" - }, - - # IAM roles needed for bootstrap lifecycle - { - Effect = "Allow", - Action = [ - "iam:GetRole", - "iam:CreateRole", - "iam:DeleteRole", - "iam:UpdateAssumeRolePolicy", - "iam:TagRole", - "iam:UntagRole" - ], - Resource = [ - "arn:aws:iam::${local.account.account_id}:role/${local.role_name}", - "arn:aws:iam::${local.account.account_id}:role/bootstrap-*", - "arn:aws:iam::${local.account.account_id}:role/terraform-*" - ] - }, - { - Effect = "Allow", - Action = [ - "iam:PutRolePolicy", - "iam:DeleteRolePolicy", - "iam:AttachRolePolicy", - "iam:DetachRolePolicy" - ], - Resource = [ - "arn:aws:iam::${local.account.account_id}:role/${local.role_name}", - "arn:aws:iam::${local.account.account_id}:role/bootstrap-*", - "arn:aws:iam::${local.account.account_id}:role/terraform-*" - ] - } - ] - }) + name = "${local.role_name}-bootstrap-minimal" + role = aws_iam_role.terraform_deploy_role[0].id + policy = data.aws_iam_policy_document.terraform_deploy_inline.json } # @@ -122,23 +60,20 @@ resource "aws_iam_user" "terraform_user" { # # IAM User Policy: 最小权限 # ---------------------------------------- +data "aws_iam_policy_document" "terraform_user" { + source_json = templatefile( + "${path.module}/policies/terraform-user-assume-role.json", + { + account_id = local.account.account_id + role_name = local.role_name + } + ) +} + resource "aws_iam_user_policy" "terraform_user_policy" { count = var.create_user ? 1 : 0 - name = "${local.terraform_user_name}-iac-policy" - user = aws_iam_user.terraform_user[0].name - - policy = jsonencode({ - Version = "2012-10-17", - Statement = [ - # 允许 Assume TerraformDeployRole - { - Effect = "Allow", - Action = [ - "sts:AssumeRole" - ], - Resource = var.create_role ? aws_iam_role.terraform_deploy_role[0].arn : var.existing_role_arn - } - ] - }) + name = "${local.terraform_user_name}-iac-policy" + user = aws_iam_user.terraform_user[0].name + policy = data.aws_iam_policy_document.terraform_user.json } diff --git a/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/identity/policies/terraform-deploy-assume-role.json b/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/identity/policies/terraform-deploy-assume-role.json new file mode 100644 index 00000000..3ca92966 --- /dev/null +++ b/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/identity/policies/terraform-deploy-assume-role.json @@ -0,0 +1,12 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "AWS": "arn:aws:iam::${account_id}:user/${terraform_user_name}" + }, + "Action": "sts:AssumeRole" + } + ] +} diff --git a/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/identity/policies/terraform-deploy-inline-policy.json b/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/identity/policies/terraform-deploy-inline-policy.json new file mode 100644 index 00000000..4948075c --- /dev/null +++ b/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/identity/policies/terraform-deploy-inline-policy.json @@ -0,0 +1,75 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "BootstrapStateBucketManagement", + "Effect": "Allow", + "Action": [ + "s3:CreateBucket", + "s3:GetBucketLocation", + "s3:ListBucket", + "s3:PutBucketVersioning", + "s3:PutBucketPolicy", + "s3:PutBucketTagging", + "s3:PutEncryptionConfiguration", + "s3:PutBucketPublicAccessBlock" + ], + "Resource": "arn:aws:s3:::${bucket_name}" + }, + { + "Sid": "BootstrapStateObjectAccess", + "Effect": "Allow", + "Action": [ + "s3:GetObject", + "s3:PutObject", + "s3:DeleteObject", + "s3:PutObjectTagging" + ], + "Resource": "arn:aws:s3:::${bucket_name}/*" + }, + { + "Sid": "TerraformLockTable", + "Effect": "Allow", + "Action": [ + "dynamodb:CreateTable", + "dynamodb:DescribeTable", + "dynamodb:UpdateTable", + "dynamodb:TagResource", + "dynamodb:UntagResource" + ], + "Resource": "arn:aws:dynamodb:${region}:${account_id}:table/${table_name}" + }, + { + "Sid": "BootstrapIamRoleLifecycle", + "Effect": "Allow", + "Action": [ + "iam:GetRole", + "iam:CreateRole", + "iam:DeleteRole", + "iam:UpdateAssumeRolePolicy", + "iam:TagRole", + "iam:UntagRole" + ], + "Resource": [ + "arn:aws:iam::${account_id}:role/${role_name}", + "arn:aws:iam::${account_id}:role/bootstrap-*", + "arn:aws:iam::${account_id}:role/terraform-*" + ] + }, + { + "Sid": "BootstrapIamRolePolicies", + "Effect": "Allow", + "Action": [ + "iam:PutRolePolicy", + "iam:DeleteRolePolicy", + "iam:AttachRolePolicy", + "iam:DetachRolePolicy" + ], + "Resource": [ + "arn:aws:iam::${account_id}:role/${role_name}", + "arn:aws:iam::${account_id}:role/bootstrap-*", + "arn:aws:iam::${account_id}:role/terraform-*" + ] + } + ] +} diff --git a/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/identity/policies/terraform-user-assume-role.json b/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/identity/policies/terraform-user-assume-role.json new file mode 100644 index 00000000..5d77f87e --- /dev/null +++ b/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/identity/policies/terraform-user-assume-role.json @@ -0,0 +1,12 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "sts:AssumeRole" + ], + "Resource": "arn:aws:iam::${account_id}:role/${role_name}" + } + ] +} diff --git a/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/identity/terragrunt.hcl b/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/identity/terragrunt.hcl new file mode 100644 index 00000000..6e92d3fe --- /dev/null +++ b/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/identity/terragrunt.hcl @@ -0,0 +1,39 @@ +include "root" { + path = find_in_parent_folders() +} + +locals { + bootstrap_config = include.root.locals.bootstrap_config +} + +dependency "state" { + config_path = "../state" + + mock_outputs = { + bucket_name = local.bootstrap_config.state.bucket_name + region = local.bootstrap_config.region + } + + mock_outputs_allowed_terraform_commands = ["plan", "validate"] +} + +dependency "lock" { + config_path = "../lock" + + mock_outputs = { + dynamodb_table_name = local.bootstrap_config.state.dynamodb_table_name + region = local.bootstrap_config.region + } + + mock_outputs_allowed_terraform_commands = ["plan", "validate"] +} + +terraform { + source = "./" +} + +inputs = { + region = dependency.state.outputs.region + state_bucket_name = dependency.state.outputs.bucket_name + state_lock_table_name = dependency.lock.outputs.dynamodb_table_name +} diff --git a/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/identity/variables.tf b/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/identity/variables.tf index 85fb07f7..bcfc31ba 100644 --- a/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/identity/variables.tf +++ b/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/identity/variables.tf @@ -61,3 +61,15 @@ variable "create_user" { error_message = "existing_user_name must be provided when create_user is false." } } + +variable "state_bucket_name" { + description = "Name of the Terraform state bucket (overrides bootstrap config when provided)" + type = string + default = null +} + +variable "state_lock_table_name" { + description = "Name of the DynamoDB state lock table (overrides bootstrap config when provided)" + type = string + default = null +} diff --git a/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/lock/main.tf b/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/lock/main.tf index 64f52268..d712c507 100644 --- a/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/lock/main.tf +++ b/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/lock/main.tf @@ -9,6 +9,14 @@ resource "aws_dynamodb_table" "terraform_locks" { type = "S" } + point_in_time_recovery { + enabled = true + } + + server_side_encryption { + enabled = true + } + tags = merge( { Name = local.dynamodb_table_name diff --git a/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/lock/outputs.tf b/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/lock/outputs.tf index 9a5ed80f..6c658525 100644 --- a/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/lock/outputs.tf +++ b/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/lock/outputs.tf @@ -3,3 +3,7 @@ output "dynamodb_table_name" { value = aws_dynamodb_table.terraform_locks.name } +output "region" { + description = "AWS region hosting the DynamoDB lock table" + value = local.region +} diff --git a/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/lock/terragrunt.hcl b/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/lock/terragrunt.hcl new file mode 100644 index 00000000..3f7efa76 --- /dev/null +++ b/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/lock/terragrunt.hcl @@ -0,0 +1,26 @@ +include "root" { + path = find_in_parent_folders() +} + +locals { + bootstrap_config = include.root.locals.bootstrap_config +} + +dependency "state" { + config_path = "../state" + + mock_outputs = { + bucket_name = local.bootstrap_config.state.bucket_name + region = local.bootstrap_config.region + } + + mock_outputs_allowed_terraform_commands = ["plan", "validate"] +} + +terraform { + source = "./" +} + +inputs = { + region = dependency.state.outputs.region +} diff --git a/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/state/main.tf b/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/state/main.tf index 6236fe37..bd58697e 100644 --- a/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/state/main.tf +++ b/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/state/main.tf @@ -27,3 +27,12 @@ resource "aws_s3_bucket_server_side_encryption_configuration" "sse" { } } } + +resource "aws_s3_bucket_public_access_block" "block" { + bucket = aws_s3_bucket.state.id + + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} diff --git a/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/state/outputs.tf b/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/state/outputs.tf index 1c95c75b..2f47008b 100644 --- a/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/state/outputs.tf +++ b/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/state/outputs.tf @@ -1,3 +1,13 @@ output "bucket_name" { value = aws_s3_bucket.state.bucket } + +output "bucket_arn" { + value = aws_s3_bucket.state.arn + description = "ARN of the Terraform state bucket" +} + +output "region" { + value = local.region + description = "AWS region hosting the state bucket" +} diff --git a/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/state/terragrunt.hcl b/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/state/terragrunt.hcl new file mode 100644 index 00000000..b6217778 --- /dev/null +++ b/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/state/terragrunt.hcl @@ -0,0 +1,16 @@ +include "root" { + path = find_in_parent_folders() +} + +locals { + bootstrap_config = include.root.locals.bootstrap_config +} + +terraform { + source = "./" +} + +inputs = { + bucket_name = local.bootstrap_config.state.bucket_name + region = local.bootstrap_config.region +} diff --git a/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/terragrunt.hcl b/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/terragrunt.hcl new file mode 100644 index 00000000..5ee04ef9 --- /dev/null +++ b/iac-template/terraform-hcl-standard/aws-cloud/bootstrap/terragrunt.hcl @@ -0,0 +1,6 @@ +terraform_version_constraint = ">= 1.2.0" +terragrunt_version_constraint = ">= 0.67.14" + +locals { + bootstrap_config = yamldecode(file("${get_terragrunt_dir()}/../config/accounts/bootstrap.yaml")) +}