diff --git a/.github/actions/tf-plan-apply/action.yml b/.github/actions/tf-plan-apply/action.yml new file mode 100644 index 000000000..7924b2476 --- /dev/null +++ b/.github/actions/tf-plan-apply/action.yml @@ -0,0 +1,81 @@ +name: "Terraform Plan & Apply" +description: "Run Terraform plan & Apply for a given component" + +inputs: + aws_assume_role: + description: "AWS IAM Role to assume" + required: true + + aws_region: + description: "AWS Region to use" + required: true + + terraform_version: + description: "Terraform version to use" + required: false + default: "1.13.3" + + backend_conf: + description: "Terraform backend config file" + required: true + + working_directory: + description: "Terraform working directory" + required: false + default: "./infrastructure" + + workspace: + description: "Environment (ndr-dev, test, etc) or Sandbox name [a-z0-9]{1,8}" + required: true + + tf_vars_file: + description: "Terraform variables file" + required: true + + tf_extra_args: + description: "Additional Terraform arguments to pass in" + required: false + default: "" + +runs: + using: "composite" + steps: + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v5 + with: + role-to-assume: ${{ inputs.aws_assume_role }} + role-skip-session-tagging: true + aws-region: ${{ inputs.aws_region }} + mask-aws-account-id: true + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: ${{ inputs.terraform_version }} + terraform_wrapper: false + + - name: Initialise Terraform + run: terraform init -backend-config=${{ inputs.backend_conf }} + working-directory: ${{ inputs.working_directory }} + shell: bash + + - name: Select Terraform Workspace + run: terraform workspace select -or-create ${{ inputs.workspace }} + working-directory: ${{ inputs.working_directory }} + shell: bash + + - name: Check Terraform Formatting + run: terraform fmt -check + working-directory: ${{ inputs.working_directory }} + shell: bash + + - name: Run Terraform Plan + run: | + terraform plan -input=false -no-color -var-file="${{ inputs.tf_vars_file }}" ${{ inputs.tf_extra_args }} -out tf.plan + working-directory: ${{ inputs.working_directory }} + shell: bash + + - name: Run Terraform Apply + run: terraform apply -auto-approve -input=false tf.plan + working-directory: ${{ inputs.working_directory }} + shell: bash diff --git a/.github/workflows/automated-deploy-dev.yml b/.github/workflows/automated-deploy-dev.yml index a9859ff59..3d6d8448e 100644 --- a/.github/workflows/automated-deploy-dev.yml +++ b/.github/workflows/automated-deploy-dev.yml @@ -13,11 +13,35 @@ permissions: actions: read # This is required for Plan comment id-token: write # This is required for requesting the JWT contents: write # This is required for SBOM action - + jobs: + + # Terraform apply of base_iam will only occur on a push (merge request completion) + terraform_plan_apply_base_iam: + if: github.ref == 'refs/heads/main' + name: Terraform Plan/Apply (base_iam) + runs-on: ubuntu-latest + environment: development + steps: + - name: Checkout branch + uses: actions/checkout@v5 + + - name: Apply base_iam + uses: ./.github/actions/tf-plan-apply + with: + aws_assume_role: ${{ secrets.AWS_ASSUME_ROLE }} + aws_region: ${{ vars.AWS_REGION }} + backend_conf: "backend.conf" + working_directory: "./base_iam" # Use separate base_iam directory + workspace: ${{ secrets.AWS_WORKSPACE }} + tf_vars_file: ${{ vars.TF_VARS_FILE }} + terraform_plan_apply: name: Terraform Plan/Apply (ndr-dev) runs-on: ubuntu-latest + needs: terraform_plan_apply_base_iam + # Will run when terraform_plan_apply_base_iam completes or is skipped + if: always() && (needs.terraform_plan_apply_base_iam.result == 'skipped' || needs.terraform_plan_apply_base_iam.result == 'success') environment: development steps: - name: Checkout @@ -73,7 +97,7 @@ jobs: echo "::add-mask::$cert_block" fi done || echo "No certificate blocks found to mask." - + # Mask sensitive URLs in the Terraform Plan output grep -Eo 'https://[a-zA-Z0-9.-]+\.execute-api\.[a-zA-Z0-9.-]+\.amazonaws\.com/[a-zA-Z0-9/._-]*' tfplan.txt | while read -r api_url; do if [ -n "$api_url" ]; then @@ -153,7 +177,7 @@ jobs: // 2. Prepare format of the comment const output = `### Report for environment: ndr-dev - + #### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
Initialization Output @@ -191,7 +215,7 @@ jobs: body: output }) } - + github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, diff --git a/.github/workflows/deploy-pre-prod.yml b/.github/workflows/deploy-pre-prod.yml index bce8e835b..b53b3bdb8 100644 --- a/.github/workflows/deploy-pre-prod.yml +++ b/.github/workflows/deploy-pre-prod.yml @@ -44,6 +44,55 @@ jobs: run: | echo Tag to deploy: ${{ steps.versioning.outputs.tag || github.event.inputs.branch_or_tag }} + + + + # # TODO: Activate this section and delete the existing plan_apply job + # terraform_plan_apply_base_iam: + # name: Terraform Plan/Apply (base_iam) + # runs-on: ubuntu-latest + # needs: ["tag_main"] + # environment: pre-prod + # steps: + # - name: Checkout branch + # uses: actions/checkout@v5 + # with: + # ref: ${{ needs.tag_main.outputs.version }} + + # - name: Apply base_iam + # uses: ./.github/actions/tf-plan-apply + # with: + # aws_assume_role: ${{ secrets.AWS_ASSUME_ROLE }} + # aws_region: ${{ vars.AWS_REGION }} + # backend_conf: "backend-pre-prod.conf" + # working_directory: "./base_iam" # Use separate base_iam directory + # workspace: ${{ secrets.AWS_WORKSPACE }} + # tf_vars_file: ${{ vars.TF_VARS_FILE }} + # tf_extra_args: "-var aws_account_id=${{ secrets.AWS_ACCOUNT_ID }}" + + # terraform_plan_apply: + # name: Terraform Plan/Apply (pre-prod) + # runs-on: ubuntu-latest + # needs: ["tag_main", "terraform_plan_apply_base_iam"] + # environment: pre-prod + # steps: + # - name: Checkout Branch + # uses: actions/checkout@v5 + # with: + # ref: ${{ needs.tag_main.outputs.version }} + + # - name: Apply Branch + # uses: ./.github/actions/tf-plan-apply + # with: + # aws_assume_role: ${{ secrets.AWS_ASSUME_ROLE }} + # aws_region: ${{ vars.AWS_REGION }} + # backend_conf: "backend-pre-prod.conf" + # workspace: ${{ secrets.AWS_WORKSPACE }} + # tf_vars_file: ${{ vars.TF_VARS_FILE }} + + + + terraform_plan_apply: name: Terraform Plan/Apply (pre-prod) runs-on: ubuntu-latest diff --git a/.github/workflows/deploy-sandbox.yml b/.github/workflows/deploy-sandbox.yml index 1afd9ad57..11858c792 100644 --- a/.github/workflows/deploy-sandbox.yml +++ b/.github/workflows/deploy-sandbox.yml @@ -34,10 +34,37 @@ jobs: env: SANDBOX_NAME: ${{ github.event.inputs.sandbox_name }} + +# APPLY base_iam TF (FROM CHOSEN BRANCH) + terraform_plan_apply_base_iam: + name: Terraform Plan/Apply (base_iam) + runs-on: ubuntu-latest + needs: validate_inputs + environment: development + steps: + - name: Checkout branch + uses: actions/checkout@v5 + with: + ref: ${{ github.event.inputs.git_ref}} + + - name: Apply base_iam + uses: ./.github/actions/tf-plan-apply + with: + # TODO: After initial deployment, can be changed to: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ github.event.inputs.sandbox_name}}-github-actions-role + aws_assume_role: ${{ secrets.AWS_ASSUME_ROLE }} + aws_region: ${{ vars.AWS_REGION }} + backend_conf: "backend.conf" + working_directory: "./base_iam" # Use separate base_iam directory + workspace: ${{ github.event.inputs.sandbox_name }} + tf_vars_file: ${{ vars.TF_VARS_FILE }} + tf_extra_args: "-var aws_account_id=${{ secrets.AWS_ACCOUNT_ID }}" + + + # APPLY MAIN terraform_plan_apply_main: name: Terraform Plan/Apply (main) runs-on: ubuntu-latest - needs: validate_inputs + needs: terraform_plan_apply_base_iam environment: development steps: - name: Checkout main @@ -45,6 +72,16 @@ jobs: with: ref: main + # TODO: We can't use this on the main branch yet, until the action is merged to main! + # - name: Apply Main + # uses: ./.github/actions/tf-plan-apply + # with: + # aws_assume_role: ${{ secrets.AWS_ASSUME_ROLE }} + # aws_region: ${{ vars.AWS_REGION }} + # backend_conf: "backend.conf" + # workspace: ${{ github.event.inputs.sandbox_name }} + # tf_vars_file: ${{ vars.TF_VARS_FILE }} + - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v5 with: @@ -53,7 +90,6 @@ jobs: aws-region: ${{ vars.AWS_REGION }} mask-aws-account-id: true - # Install the latest version of Terraform CLI and configure the Terraform CLI configuration file with a Terraform Cloud user API token - name: Setup Terraform uses: hashicorp/setup-terraform@v3 with: @@ -83,6 +119,9 @@ jobs: run: terraform apply -auto-approve -input=false tf-main.plan working-directory: ./infrastructure + + # APPLY CHOSEN BRANCH + # USING THE NEWLY CREATED ROLE terraform_plan_apply_branch: name: Terraform Plan/Apply (branch) if: ${{ github.event.inputs.git_ref != 'main' }} @@ -90,49 +129,16 @@ jobs: needs: terraform_plan_apply_main environment: development steps: - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v5 - with: - role-to-assume: ${{ secrets.AWS_ASSUME_ROLE }} - role-skip-session-tagging: true - aws-region: ${{ vars.AWS_REGION }} - mask-aws-account-id: true - - - name: Setup Terraform - uses: hashicorp/setup-terraform@v3 - with: - terraform_version: 1.13.3 - terraform_wrapper: false - - name: Checkout Branch uses: actions/checkout@v5 with: ref: ${{ github.event.inputs.git_ref}} - # Checks that all Terraform configuration files adhere to a canonical format. - - name: Check Terraform Formatting - run: terraform fmt -check - working-directory: ./infrastructure - - - name: Initialise Terraform - id: init - run: terraform init -backend-config=backend.conf - working-directory: ./infrastructure - shell: bash - - - name: Select Terraform Workspace - id: workspace - run: terraform workspace select ${{ github.event.inputs.sandbox_name}} - working-directory: ./infrastructure - shell: bash - - - name: Run Terraform Plan - id: plan - run: | - terraform plan -input=false -no-color -var-file="${{vars.TF_VARS_FILE}}" -out tf.plan - working-directory: ./infrastructure - shell: bash - - - name: Run Terraform Apply (branch over main) - run: terraform apply -auto-approve -input=false tf.plan - working-directory: ./infrastructure + - name: Apply Branch + uses: ./.github/actions/tf-plan-apply + with: + aws_assume_role: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ github.event.inputs.sandbox_name}}-github-actions-role + aws_region: ${{ vars.AWS_REGION }} + backend_conf: "backend.conf" + workspace: ${{ github.event.inputs.sandbox_name }} + tf_vars_file: ${{ vars.TF_VARS_FILE }} diff --git a/.github/workflows/deploy-test.yml b/.github/workflows/deploy-test.yml index 3c701f8c2..047e740ed 100644 --- a/.github/workflows/deploy-test.yml +++ b/.github/workflows/deploy-test.yml @@ -16,6 +16,51 @@ permissions: contents: read # This is required for actions/checkout jobs: + # TODO: Activate this section and delete the existing job + # terraform_plan_apply_base_iam: + # name: Terraform Plan/Apply (base_iam) + # runs-on: ubuntu-latest + # environment: test + # steps: + # - name: Checkout branch + # uses: actions/checkout@v5 + # with: + # ref: ${{ github.event.inputs.git_ref}} + + # - name: Apply base_iam + # uses: ./.github/actions/tf-plan-apply + # with: + # aws_assume_role: ${{ secrets.AWS_ASSUME_ROLE }} + # aws_region: ${{ vars.AWS_REGION }} + # backend_conf: "backend-test.conf" + # working_directory: "./base_iam" # Use separate base_iam directory + # workspace: ${{ secrets.AWS_WORKSPACE }} + # tf_vars_file: ${{ vars.TF_VARS_FILE }} + # tf_extra_args: "-var aws_account_id=${{ secrets.AWS_ACCOUNT_ID }}" + + # terraform_plan_apply_test: + # name: Terraform Plan/Apply (ndr-test) + # runs-on: ubuntu-latest + # needs: terraform_plan_apply_base_iam + # environment: test + # steps: + # - name: Checkout Branch + # uses: actions/checkout@v5 + # with: + # ref: ${{ github.event.inputs.git_ref}} + + # - name: Apply Branch + # uses: ./.github/actions/tf-plan-apply + # with: + # aws_assume_role: ${{ secrets.AWS_ASSUME_ROLE }} + # aws_region: ${{ vars.AWS_REGION }} + # backend_conf: "backend-test.conf" + # workspace: ${{ secrets.AWS_WORKSPACE }} + # tf_vars_file: ${{ vars.TF_VARS_FILE }} + + + + terraform_plan_apply: name: Terraform Plan/Apply (ndr-test) runs-on: ubuntu-latest diff --git a/.github/workflows/tear-down-sandbox.yml b/.github/workflows/tear-down-sandbox.yml index 70c7d44c7..f93450b31 100644 --- a/.github/workflows/tear-down-sandbox.yml +++ b/.github/workflows/tear-down-sandbox.yml @@ -77,20 +77,11 @@ jobs: with: ref: ${{ inputs.git_ref }} - - name: Setup Python 3.11 - uses: actions/setup-python@v6 - with: - python-version: 3.11 - - - name: Install Python Dependencies - run: | - python3 -m venv ./venv - ./venv/bin/pip3 install --upgrade pip boto3 - - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v5 with: - role-to-assume: ${{ secrets.AWS_ASSUME_ROLE }} + # Use role created in base_iam + role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ github.event.inputs.sandbox_name}}-github-actions-role aws-region: ${{ vars.AWS_REGION }} mask-aws-account-id: true @@ -111,6 +102,72 @@ jobs: run: terraform destroy -auto-approve -var-file="${{ vars.TF_VARS_FILE }}" working-directory: ./infrastructure + terraform_destroy_base_iam: + name: Terraform Destroy (base_iam) + # Only destroy base_iam in a Sandbox environment. Don't tear down in Test or ndr-dev environments. + if: ${{ github.event.inputs.environment == 'development' && github.event.inputs.sandbox_name != 'ndr-dev' }} + runs-on: ubuntu-latest + needs: [terraform_destroy] + environment: ${{ inputs.environment }} + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + ref: ${{ inputs.git_ref }} + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v5 + with: + role-to-assume: ${{ secrets.AWS_ASSUME_ROLE }} + aws-region: ${{ vars.AWS_REGION }} + mask-aws-account-id: true + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: 1.13.3 + + - name: Initialise Terraform + run: terraform init -backend-config=${{ vars.TF_BACKEND_FILE }} + working-directory: ./base_iam + + - name: Select Terraform Workspace + run: terraform workspace select ${{ inputs.sandbox_name }} + working-directory: ./base_iam + + - name: Run Terraform Destroy + run: terraform destroy -auto-approve -var-file="${{ vars.TF_VARS_FILE }}" -var aws_account_id=${{ secrets.AWS_ACCOUNT_ID }} + + working-directory: ./base_iam + + cleanup_resources: + name: Cleanup Resources + runs-on: ubuntu-latest + needs: [terraform_destroy_base_iam] + environment: ${{ inputs.environment }} + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + ref: ${{ inputs.git_ref }} + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v5 + with: + role-to-assume: ${{ secrets.AWS_ASSUME_ROLE }} + aws-region: ${{ vars.AWS_REGION }} + mask-aws-account-id: true + + - name: Setup Python 3.11 + uses: actions/setup-python@v6 + with: + python-version: 3.11 + + - name: Install Python Dependencies + run: | + python3 -m venv ./venv + ./venv/bin/pip3 install --upgrade pip boto3 + - name: Run Cleanup Script (Terraform Workspace) run: ./venv/bin/python3 -u scripts/cleanup_terraform_states.py ${{ inputs.sandbox_name }} diff --git a/base_iam/Makefile b/base_iam/Makefile new file mode 100644 index 000000000..1021bdd4a --- /dev/null +++ b/base_iam/Makefile @@ -0,0 +1,49 @@ +# var-file := $(account).tfvars + +guard-%: + @ if [ "${${*}}" = "" ]; then \ + echo "env var: $* not set"; \ + exit 1; \ + fi + +clean: + find . -type d -name .terraform -exec rm -rf "{}" \; 2>/dev/null || true + find . -type d -name terraform.tfstate.d -exec rm -rf "{}" \; 2>/dev/null || true + find . -name .terraform.plan -type f -delete 2>/dev/null || true + find . -name errored.tfstate -type f -delete 2>/dev/null || true + find . -name .tfplan.exit -type f -delete 2>/dev/null || true + find . -name external-vars.json -type f -delete 2>/dev/null || true + find . -name terraform.tfstate -type f -delete 2>/dev/null || true + +tf-format-check: + terraform fmt -check -recursive + +tf-format: + terraform fmt --recursive + +# init: guard-aws_account_id guard-env +# terraform init -var environment=${env} -var aws_account_id=${aws_account_id} + +init: +# terraform init -var aws_account_id=${aws_account_id} +# terraform init -backend-config=backend.conf + terraform init + +plan: guard-env +# terraform workspace select -or-create ${workspace} + terraform plan -out=tfplan -var environment=${env} + +apply: guard-workspace + terraform workspace select -or-create ${workspace} + terraform apply tfplan + + +# TEMP FUNCTIONS + +import-github-policies: guard-aws_account_id guard-env guard-role + python policy_tool.py import ${aws_account_id} ${env} ${role} + rm -f dummy_import_${env}.tf + +generate-tf-file: guard-aws_account_id guard-env guard-role + python policy_tool.py generate-tf-file ${aws_account_id} ${env} ${role} + mv imported_${env}.tf.txt iam_github_${env}.tf diff --git a/base_iam/backend-pre-prod.conf b/base_iam/backend-pre-prod.conf new file mode 100644 index 000000000..7a3171088 --- /dev/null +++ b/base_iam/backend-pre-prod.conf @@ -0,0 +1 @@ +bucket = "ndr-pre-prod-terraform-state-694282683086" \ No newline at end of file diff --git a/base_iam/backend-prod.conf b/base_iam/backend-prod.conf new file mode 100644 index 000000000..2c3521439 --- /dev/null +++ b/base_iam/backend-prod.conf @@ -0,0 +1 @@ +bucket = "ndr-prod-terraform-state-487224344892" \ No newline at end of file diff --git a/base_iam/backend-test.conf b/base_iam/backend-test.conf new file mode 100644 index 000000000..b189b63a6 --- /dev/null +++ b/base_iam/backend-test.conf @@ -0,0 +1 @@ +bucket = "ndr-test-terraform-state-211125386286" \ No newline at end of file diff --git a/base_iam/backend.conf b/base_iam/backend.conf new file mode 100644 index 000000000..486a8d720 --- /dev/null +++ b/base_iam/backend.conf @@ -0,0 +1 @@ +bucket = "ndr-dev-terraform-state-533825906475" \ No newline at end of file diff --git a/base_iam/dev.tfvars b/base_iam/dev.tfvars new file mode 100644 index 000000000..c5090654d --- /dev/null +++ b/base_iam/dev.tfvars @@ -0,0 +1,2 @@ +environment = "dev" +owner = "nhse/ndr-team" \ No newline at end of file diff --git a/base_iam/iam_github_dev.tf b/base_iam/iam_github_dev.tf new file mode 100644 index 000000000..39cae66aa --- /dev/null +++ b/base_iam/iam_github_dev.tf @@ -0,0 +1,892 @@ +# aws_iam_role.github_role_dev[0]: +resource "aws_iam_role" "github_role_dev" { + count = local.is_sandbox_or_dev ? 1 : 0 + name = "${terraform.workspace}-github-actions-role" + description = "This role is to provide access for GitHub actions to the development environment. " + force_detach_policies = false + max_session_duration = 3600 + name_prefix = null + path = "/" + permissions_boundary = null + tags = {} + assume_role_policy = jsonencode( + { + Statement = [ + { + Action = "sts:AssumeRoleWithWebIdentity" + Condition = { + StringEquals = { + "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com" + } + StringLike = { + "token.actions.githubusercontent.com:sub" = [ + "repo:NHSDigital/national-document-repository-infrastructure:*", + "repo:NHSDigital/national-document-repository:*", + ] + } + } + Effect = "Allow" + Principal = { + Federated = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/token.actions.githubusercontent.com" + } + }, + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/aws-reserved/sso.amazonaws.com/eu-west-2/AWSReservedSSO_DomainCGpit-Administrators_e00623801cb4b59e" + } + }, + ] + Version = "2012-10-17" + } + ) +} + + +# INLINE POLICIES + +resource "aws_iam_role_policy" "cloudtrail_dev" { + count = local.is_sandbox_or_dev ? 1 : 0 + role = aws_iam_role.github_role_dev[0].id + name = "cloudtrail" + policy = jsonencode( + { + Statement = [ + { + Action = [ + "cloudtrail:AddTags", + "cloudtrail:CreateTrail", + "cloudtrail:StartLogging", + "cloudtrail:DeleteTrail", + ] + Effect = "Allow" + Resource = [ + "arn:aws:cloudtrail:eu-west-2:${data.aws_caller_identity.current.account_id}:trail/*", + "arn:aws:cloudtrail:eu-west-2:${data.aws_caller_identity.current.account_id}:eventdatastore/*", + "arn:aws:cloudtrail:eu-west-2:${data.aws_caller_identity.current.account_id}:channel/*", + ] + Sid = "VisualEditor0" + }, + { + Action = "organizations:ListAWSServiceAccessForOrganization" + Effect = "Allow" + Resource = "*" + Sid = "VisualEditor1" + }, + ] + Version = "2012-10-17" + } + ) +} + +resource "aws_iam_role_policy" "cloudwatch_logs_policy_dev" { + count = local.is_sandbox_or_dev ? 1 : 0 + role = aws_iam_role.github_role_dev[0].id + name = "cloudwatch_logs_policy" + policy = jsonencode( + { + Statement = [ + { + Action = [ + "logs:DescribeLogGroups", + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:PutRetentionPolicy", + "logs:PutResourcePolicy", + "logs:DeleteResourcePolicy", + "logs:DeleteRetentionPolicy", + "logs:TagResource", + "logs:UntagResource", + "logs:AssociateKmsKey", + "logs:DisassociateKmsKey", + ] + Effect = "Allow" + Resource = "arn:aws:logs:eu-west-2:${data.aws_caller_identity.current.account_id}:log-group:*" + Sid = "Statement1" + }, + { + Action = [ + "logs:PutDeliverySource", + ] + Effect = "Allow" + Resource = [ + "arn:aws:logs:us-east-1:${data.aws_caller_identity.current.account_id}:delivery-source:*", + ] + Sid = "Statement2" + }, + ] + Version = "2012-10-17" + } + ) +} + +resource "aws_iam_role_policy" "ecs_policy_dev" { + count = local.is_sandbox_or_dev ? 1 : 0 + role = aws_iam_role.github_role_dev[0].id + name = "ecs_policy" + policy = jsonencode( + { + Statement = [ + { + Action = [ + "ecs:UpdateCluster", + "ecs:PutClusterCapacityProviders", + ] + Effect = "Allow" + Resource = "*" + Sid = "VisualEditor0" + }, + ] + Version = "2012-10-17" + } + ) +} + +resource "aws_iam_role_policy" "github_actions_waf_override_dev" { + count = local.is_sandbox_or_dev ? 1 : 0 + role = aws_iam_role.github_role_dev[0].id + name = "github_actions_waf_override" + policy = jsonencode( + { + Statement = [ + { + Action = "apigateway:SetWebACL" + Effect = "Allow" + Resource = "arn:aws:apigateway:eu-west-2::/restapis/*/stages/*" + }, + ] + Version = "2012-10-17" + } + ) +} + +resource "aws_iam_role_policy" "lambda_layer_policy_dev" { + count = local.is_sandbox_or_dev ? 1 : 0 + role = aws_iam_role.github_role_dev[0].id + name = "lambda_layer_policy" + policy = jsonencode( + { + Statement = [ + { + Action = [ + "lambda:GetLayerVersion", + "lambda:PublishLayerVersion", + "lambda:DeleteLayerVersion", + "lambda:ListLayerVersions", + "lambda:ListLayers", + "lambda:AddLayerVersionPermission", + "lambda:GetLayerVersionPolicy", + "lambda:RemoveLayerVersionPermission", + ] + Effect = "Allow" + Resource = "*" + Sid = "VisualEditor0" + }, + ] + Version = "2012-10-17" + } + ) +} + +resource "aws_iam_role_policy" "rum_policy_dev" { + count = local.is_sandbox_or_dev ? 1 : 0 + role = aws_iam_role.github_role_dev[0].id + name = "rum_policy" + policy = jsonencode( + { + Statement = [ + { + Action = [ + "cognito-identity:SetIdentityPoolRoles", + "cognito-identity:CreateIdentityPool", + "cognito-identity:DeleteIdentityPool", + "cognito-identity:UpdateIdentityPool", + ] + Effect = "Allow" + Resource = "arn:aws:cognito-identity:eu-west-2:${data.aws_caller_identity.current.account_id}:identitypool/*" + Sid = "VisualEditor0" + }, + { + Action = [ + "rum:TagResource", + "rum:UntagResource", + "rum:ListTagsForResource", + "iam:PassRole", + "rum:UpdateAppMonitor", + "rum:GetAppMonitor", + "rum:CreateAppMonitor", + "rum:DeleteAppMonitor", + ] + Effect = "Allow" + Resource = "arn:aws:rum:eu-west-2:${data.aws_caller_identity.current.account_id}:appmonitor/*" + Sid = "VisualEditor1" + }, + { + Action = [ + "logs:DeleteLogGroup", + "logs:DeleteResourcePolicy", + "logs:DescribeLogGroups", + ] + Effect = "Allow" + Resource = "arn:aws:logs:eu-west-2:${data.aws_caller_identity.current.account_id}:log-group:*RUMService*" + Sid = "VisualEditor2" + }, + { + Action = [ + "logs:CreateLogDelivery", + "logs:GetLogDelivery", + "logs:UpdateLogDelivery", + "logs:DeleteLogDelivery", + "logs:ListLogDeliveries", + "logs:DescribeResourcePolicies", + ] + Effect = "Allow" + Resource = "*" + Sid = "VisualEditor3" + }, + ] + Version = "2012-10-17" + } + ) +} + +resource "aws_iam_role_policy" "step_functions_dev" { + count = local.is_sandbox_or_dev ? 1 : 0 + role = aws_iam_role.github_role_dev[0].id + name = "step_functions" + policy = jsonencode( + { + Statement = [ + { + Action = [ + "states:DescribeStateMachine", + "states:UpdateStateMachine", + "states:DeleteStateMachine", + "states:CreateStateMachine", + "states:TagResource", + "states:UntagResource", + ] + Effect = "Allow" + Resource = "*" + Sid = "VisualEditor0" + }, + ] + Version = "2012-10-17" + } + ) +} + +resource "aws_iam_role_policy" "github_terraform_tagging_policy_dev" { + count = local.is_sandbox_or_dev ? 1 : 0 + role = aws_iam_role.github_role_dev[0].id + name = "github_terraform_tagging_policy" + policy = jsonencode( + { + Statement = [ + { + Action = [ + "sns:TagResource", + "backup:TagResource", + "resource-groups:GetGroupQuery", + "lambda:TagResource", + "resource-groups:UpdateGroup", + "iam:UntagRole", + "iam:TagRole", + "resource-groups:GetTags", + "sns:UntagResource", + "resource-groups:Untag", + "lambda:UntagResource", + "elasticloadbalancing:RemoveTags", + "cognito-identity:UntagResource", + "resource-groups:GetGroup", + "resource-groups:GetGroupConfiguration", + "backup:UntagResource", + "cognito-identity:TagResource", + "resource-groups:Tag", + "resource-groups:UpdateGroupQuery", + "iam:TagPolicy", + "resource-groups:DeleteGroup", + "events:TagResource", + "elasticloadbalancing:AddTags", + "iam:UntagPolicy", + "resource-groups:ListGroupResources", + "events:UntagResource", + ] + Effect = "Allow" + Resource = [ + "arn:aws:lambda:*:${data.aws_caller_identity.current.account_id}:event-source-mapping:*", + "arn:aws:lambda:*:${data.aws_caller_identity.current.account_id}:function:*", + "arn:aws:lambda:*:${data.aws_caller_identity.current.account_id}:code-signing-config:*", + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/*", + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/*", + "arn:aws:sns:*:${data.aws_caller_identity.current.account_id}:*", + "arn:aws:backup:*:${data.aws_caller_identity.current.account_id}:legal-hold:*", + "arn:aws:backup:*:${data.aws_caller_identity.current.account_id}:framework:*-*", + "arn:aws:backup:*:${data.aws_caller_identity.current.account_id}:backup-vault:*", + "arn:aws:backup:*:${data.aws_caller_identity.current.account_id}:report-plan:*-*", + "arn:aws:backup:*:${data.aws_caller_identity.current.account_id}:backup-plan:*", + "arn:aws:backup:*:${data.aws_caller_identity.current.account_id}:restore-testing-plan:*-*", + "arn:aws:cognito-identity:*:${data.aws_caller_identity.current.account_id}:identitypool/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:loadbalancer/gwy/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:loadbalancer/net/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:loadbalancer/app/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:truststore/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:listener/app/*/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:listener/gwy/*/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:listener-rule/net/*/*/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:listener/net/*/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:listener-rule/app/*/*/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:targetgroup/*/*", + "arn:aws:resource-groups:*:${data.aws_caller_identity.current.account_id}:group/*", + "arn:aws:events:*:${data.aws_caller_identity.current.account_id}:event-bus/*", + "arn:aws:events:*:${data.aws_caller_identity.current.account_id}:rule/*/*", + ] + Sid = "VisualEditor0" + }, + { + Action = [ + "events:TagResource", + "elasticloadbalancing:RemoveTags", + "elasticloadbalancing:AddTags", + "events:UntagResource", + ] + Effect = "Allow" + Resource = [ + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:loadbalancer/gwy/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:truststore/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:listener/app/*/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:listener/gwy/*/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:listener/net/*/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:listener-rule/net/*/*/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:listener-rule/app/*/*/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:targetgroup/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:loadbalancer/net/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:loadbalancer/app/*/*", + "arn:aws:events:*:${data.aws_caller_identity.current.account_id}:rule/*", + ] + Sid = "VisualEditor1" + }, + { + Action = [ + "resource-groups:SearchResources", + "resource-groups:CreateGroup", + "resource-groups:ListGroups", + ] + Effect = "Allow" + Resource = "*" + Sid = "VisualEditor2" + }, + ] + Version = "2012-10-17" + } + ) +} + + +# ATTACHED POLICIES + +resource "aws_iam_role_policy_attachment" "ReadOnlyAccess_dev" { + count = local.is_sandbox_or_dev ? 1 : 0 + role = aws_iam_role.github_role_dev[0].name + policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess" +} + +resource "aws_iam_role_policy_attachment" "github_actions_terraform_full_dev" { + count = local.is_sandbox_or_dev ? 1 : 0 + role = aws_iam_role.github_role_dev[0].name + policy_arn = aws_iam_policy.github_actions_terraform_full_dev[0].arn +} + +# aws_iam_policy.github_actions_terraform_full_dev[0]: +resource "aws_iam_policy" "github_actions_terraform_full_dev" { + count = local.is_sandbox_or_dev ? 1 : 0 + description = "All permissions required for Terraform to do its thing." + name = "${terraform.workspace}-github_actions_terraform_full" + name_prefix = null + path = "/" + policy = jsonencode( + { + Statement = [ + { + Action = [ + "ec2:AuthorizeSecurityGroupIngress", + "ec2:DeleteVpcEndpoints", + "ec2:AttachInternetGateway", + "iam:PutRolePolicy", + "ecr:DeleteRepository", + "scheduler:DeleteSchedule", + "ec2:CreateRoute", + "cloudwatch:ListTagsForResource", + "ecr:TagResource", + "dynamodb:DescribeContinuousBackups", + "events:RemoveTargets", + "lambda:DeleteFunction", + "iam:ListRolePolicies", + "ecs:TagResource", + "ecr:GetLifecyclePolicy", + "iam:GetRole", + "dynamodb:BatchWriteItem", + "elasticloadbalancing:CreateTargetGroup", + "ecr:GetAuthorizationToken", + "application-autoscaling:DeleteScalingPolicy", + "kms:RetireGrant", + "elasticloadbalancing:AddTags", + "ec2:DeleteNatGateway", + "lambda:PublishVersion", + "apigateway:POST", + "lambda:DeleteEventSourceMapping", + "elasticloadbalancing:ModifyLoadBalancerAttributes", + "dynamodb:UpdateTable", + "ec2:ModifyVpcEndpoint", + "logs:ListTagsLogGroup", + "kms:PutKeyPolicy", + "events:PutRule", + "ec2:CreateVpc", + "dynamodb:ListTagsOfResource", + "iam:PassRole", + "logs:DeleteMetricFilter", + "sqs:createqueue", + "iam:DeleteRolePolicy", + "application-autoscaling:TagResource", + "ec2:ReleaseAddress", + "lambda:UpdateEventSourceMapping", + "elasticloadbalancing:CreateLoadBalancer", + "apigateway:PUT", + "route53:ListTagsForResource", + "ec2:DescribeSecurityGroups", + "iam:CreatePolicy", + "sqs:TagQueue", + "iam:CreateServiceLinkedRole", + "kms:CreateAlias", + "elasticloadbalancing:DescribeTargetGroups", + "route53:AssociateVPCWithHostedZone", + "elasticloadbalancing:DeleteListener", + "iam:UpdateAssumeRolePolicy", + "iam:GetPolicyVersion", + "wafv2:AssociateWebACL", + "ec2:DeleteSubnet", + "elasticloadbalancing:SetWebACL", + "ecs:UpdateService", + "elasticloadbalancing:DescribeLoadBalancers", + "ssm:DeleteParameter", + "cloudfront:*", + "kms:GetKeyRotationStatus", + "dynamodb:DescribeTable", + "ssm:AddTagsToResource", + "ecs:RegisterTaskDefinition", + "route53:ListResourceRecordSets", + "ecr:CreateRepository", + "ecs:DeleteService", + "application-autoscaling:UntagResource", + "ec2:DescribePrefixLists", + "backup:CreateBackupVault", + "backup:UpdateBackupPlan", + "sqs:DeleteQueue", + "ec2:DeleteVpc", + "kms:DeleteAlias", + "sns:DeleteTopic", + "wafv2:DeleteWebACL", + "dynamodb:DeleteItem", + "iam:DeletePolicy", + "sns:SetTopicAttributes", + "ses:VerifyDomainDkim", + "lambda:PutFunctionConcurrency", + "dynamodb:UpdateContinuousBackups", + "ecs:CreateService", + "elasticloadbalancing:CreateListener", + "kms:ScheduleKeyDeletion", + "ecr:DescribeRepositories", + "ecs:DescribeServices", + "iam:CreatePolicyVersion", + "ecs:UntagResource", + "sqs:ListQueues", + "wafv2:UpdateWebACL", + "dynamodb:DescribeTimeToLive", + "kms:UpdateAlias", + "backup:GetBackupSelection", + "kms:ListKeys", + "events:PutTargets", + "lambda:AddPermission", + "ecr:SetRepositoryPolicy", + "ec2:DeleteSecurityGroup", + "application-autoscaling:DeregisterScalableTarget", + "backup:DeleteBackupPlan", + "ses:SetIdentityMailFromDomain", + "lambda:CreateFunction", + "sqs:DeleteMessage", + "elasticloadbalancing:ModifyListener", + "cloudwatch:DeleteAlarms", + "secretsmanager:DeleteSecret", + "wafv2:CreateRegexPatternSet", + "wafv2:CreateWebACL", + "dynamodb:DeleteTable", + "ecs:DescribeTaskDefinition", + "ec2:DeleteRouteTable", + "ec2:CreateInternetGateway", + "ec2:RevokeSecurityGroupEgress", + "sns:Subscribe", + "ec2:DeleteInternetGateway", + "wafv2:TagResource", + "dynamodb:UpdateTimeToLive", + "iam:GetPolicy", + "ec2:CreateTags", + "sns:CreateTopic", + "ecs:DeleteCluster", + "iam:UpdateRoleDescription", + "iam:DeleteRole", + "ec2:DisassociateRouteTable", + "backup:GetBackupPlan", + "wafv2:DeleteRegexPatternSet", + "dynamodb:CreateTable", + "ec2:RevokeSecurityGroupIngress", + "lambda:UpdateFunctionCode", + "ec2:CreateDefaultVpc", + "ec2:CreateSubnet", + "ec2:DescribeSubnets", + "iam:GetRolePolicy", + "sqs:setqueueattributes", + "ec2:DisassociateAddress", + "kms:UntagResource", + "ec2:CreateNatGateway", + "kms:ListResourceTags", + "ecr:ListTagsForResource", + "ses:VerifyDomainIdentity", + "ecs:DeregisterTaskDefinition", + "apigateway:DELETE", + "apigateway:SetWebACL", + "backup:CreateBackupSelection", + "scheduler:UpdateSchedule", + "ec2:DescribeAvailabilityZones", + "kms:CreateKey", + "kms:EnableKeyRotation", + "ecr:PutLifecyclePolicy", + "s3:*", + "kms:GetKeyPolicy", + "route53:ListHostedZones", + "backup:DeleteBackupVault", + "lambda:UpdateFunctionConfiguration", + "elasticloadbalancing:DeleteTargetGroup", + "events:DeleteRule", + "backup:DescribeBackupVault", + "ec2:DescribeVpcs", + "kms:ListAliases", + "backup:CreateBackupPlan", + "ses:DeleteIdentity", + "lambda:RemovePermission", + "backup:ListTags", + "route53:GetHostedZone", + "sns:Unsubscribe", + "iam:CreateRole", + "iam:AttachRolePolicy", + "lambda:EnableReplication", + "ec2:AssociateRouteTable", + "elasticloadbalancing:DeleteLoadBalancer", + "ec2:DescribeInternetGateways", + "backup:DeleteBackupSelection", + "iam:DetachRolePolicy", + "cloudwatch:UntagResource", + "iam:ListAttachedRolePolicies", + "dynamodb:GetItem", + "elasticloadbalancing:ModifyTargetGroupAttributes", + "ec2:DescribeRouteTables", + "application-autoscaling:RegisterScalableTarget", + "dynamodb:PutItem", + "ecs:CreateCluster", + "route53:ChangeResourceRecordSets", + "ec2:CreateRouteTable", + "ec2:DetachInternetGateway", + "ecr:DeleteLifecyclePolicy", + "logs:CreateLogGroup", + "backup-storage:MountCapsule", + "ecs:DescribeClusters", + "ssm:PutParameter", + "elasticloadbalancing:DescribeLoadBalancerAttributes", + "logs:CreateLogDelivery", + "logs:PutMetricFilter", + "elasticloadbalancing:DescribeTargetGroupAttributes", + "ec2:DescribeSecurityGroupRules", + "application-autoscaling:PutScalingPolicy", + "ec2:DescribeVpcEndpoints", + "route53:GetChange", + "ec2:DeleteTags", + "lambda:GetLayerVersion", + "lambda:CreateEventSourceMapping", + "kms:TagResource", + "elasticloadbalancing:DescribeListeners", + "dynamodb:TagResource", + "ec2:CreateSecurityGroup", + "apigateway:PATCH", + "kms:DescribeKey", + "application-autoscaling:ListTagsForResource", + "ec2:ModifyVpcAttribute", + "ecr:DeleteRepositoryPolicy", + "ec2:AuthorizeSecurityGroupEgress", + "elasticloadbalancing:ModifyListenerAttributes", + "kms:UpdateKeyDescription", + "logs:DescribeLogGroups", + "logs:DeleteLogGroup", + "elasticloadbalancing:DescribeTags", + "ec2:DeleteRoute", + "backup:DeleteRecoveryPoint", + "ec2:AllocateAddress", + "cloudwatch:PutMetricAlarm", + "cloudwatch:TagResource", + "ec2:CreateVpcEndpoint", + "elasticloadbalancing:SetSecurityGroups", + "scheduler:CreateSchedule", + "logs:PutRetentionPolicy", + "lambda:GetPolicy", + "iam:DeletePolicyVersion", + "ecr:GetRepositoryPolicy", + "cognito-idp:*", + ] + Effect = "Allow" + Resource = "*" + Sid = "VisualEditor0" + }, + ] + Version = "2012-10-17" + } + ) + tags = {} +} + + + +resource "aws_iam_role_policy_attachment" "github_actions_extended_dev" { + count = local.is_sandbox_or_dev ? 1 : 0 + role = aws_iam_role.github_role_dev[0].name + policy_arn = aws_iam_policy.github_actions_extended_dev[0].arn +} + +# aws_iam_policy github_actions_extended +# Incorporates permissions from: +# config_policy +# ecr_github_access_policy +# github_mtls_gateway +# (github_terraform_tagging_policy - Moved to inline) +# lambda_github_access_policy +# repo_app_config +# terraform_github_dynamodb_access_policy +# terraform_github_s3_access_policy +resource "aws_iam_policy" "github_actions_extended_dev" { + count = local.is_sandbox_or_dev ? 1 : 0 + description = null + name = "${terraform.workspace}-github_actions_extended" + name_prefix = null + path = "/" + policy = jsonencode( + { + Statement = [ + { + Action = [ + "config:DeleteDeliveryChannel", + "config:PutConfigurationRecorder", + "config:StopConfigurationRecorder", + "config:StartConfigurationRecorder", + "config:PutDeliveryChannel", + "config:DeleteConfigurationRecorder", + "config:DescribeConfigurationRecorderStatus", + ] + Effect = "Allow" + Resource = "*" + Sid = "ConfigPolicy1" + }, + + + { + Action = [ + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage", + "ecr:CompleteLayerUpload", + "ecr:UploadLayerPart", + "ecr:InitiateLayerUpload", + "ecr:BatchCheckLayerAvailability", + "ecr:PutImage", + ] + Effect = "Allow" + Resource = "arn:aws:ecr:eu-west-2:*:repository/*" + Sid = "EcrGithubAccessPolicy1" + }, + + + { + Action = [ + "acm:RequestCertificate", + "route53:ListHostedZones", + "acm:ListCertificates", + ] + Effect = "Allow" + Resource = "*" + Sid = "GithubMtlsGateway1" + }, + { + Action = "apigateway:AddCertificateToDomain" + Effect = "Allow" + Resource = "arn:aws:apigateway:eu-west-2::/domainnames" + Sid = "GithubMtlsGateway2" + }, + { + Action = [ + "acm:DeleteCertificate", + "acm:DescribeCertificate", + "acm:GetCertificate", + "route53:GetHostedZone", + "route53:ChangeResourceRecordSets", + "apigateway:AddCertificateToDomain", + "acm:AddTagsToCertificate", + "apigateway:RemoveCertificateFromDomain", + "acm:ListTagsForCertificate", + ] + Effect = "Allow" + Resource = [ + "arn:aws:apigateway:eu-west-2::/domainnames", + "arn:aws:apigateway:eu-west-2::/domainnames/*", + "arn:aws:route53:::hostedzone/*", + "arn:aws:acm:eu-west-2:${data.aws_caller_identity.current.account_id}:certificate/*", + ] + Sid = "GithubMtlsGateway3" + }, + { + Action = [ + "apigateway:AddCertificateToDomain", + "apigateway:RemoveCertificateFromDomain", + ] + Effect = "Allow" + Resource = [ + "arn:aws:apigateway:eu-west-2::/domainnames/*", + "arn:aws:apigateway:eu-west-2::/domainnames", + ] + Sid = "GithubMtlsGateway4" + }, + { + Action = "apigateway:AddCertificateToDomain" + Effect = "Allow" + Resource = "arn:aws:apigateway:eu-west-2::/domainnames" + Sid = "GithubMtlsGateway5" + }, + + + + + { + Action = [ + "lambda:CreateFunction", + "s3:PutObject", + "lambda:UpdateFunctionCode", + "kms:TagResource", + "kms:UntagResource", + "kms:Encrypt", + "kms:Decrypt", + "lambda:InvokeFunction", + "lambda:GetFunction", + "lambda:UpdateFunctionConfiguration", + "lambda:GetFunctionConfiguration", + "lambda:DeleteFunctionConcurrency", + "kms:CreateGrant", + ] + Effect = "Allow" + Resource = [ + "arn:aws:kms:*:${data.aws_caller_identity.current.account_id}:key/*", + "arn:aws:lambda:eu-west-2:*:function:*", + ] + Sid = "LambdaGithubAccessPolicy1" + }, + { + Action = "iam:ListRoles" + Effect = "Allow" + Resource = "arn:aws:lambda:eu-west-2:*:function:*" + Sid = "LambdaGithubAccessPolicy2" + }, + + + { + Action = [ + "appconfig:ListTagsForResource", + "appconfig:StartDeployment", + "appconfig:DeleteApplication", + "appconfig:GetLatestConfiguration", + "appconfig:TagResource", + "appconfig:CreateConfigurationProfile", + "appconfig:CreateExtensionAssociation", + "appconfig:DeleteConfigurationProfile", + "appconfig:CreateDeploymentStrategy", + "appconfig:CreateApplication", + "appconfig:GetDeploymentStrategy", + "appconfig:GetHostedConfigurationVersion", + "appconfig:ListExtensionAssociations", + "appconfig:ListDeploymentStrategies", + "appconfig:CreateHostedConfigurationVersion", + "appconfig:DeleteEnvironment", + "appconfig:UntagResource", + "appconfig:ListHostedConfigurationVersions", + "appconfig:ListEnvironments", + "appconfig:UpdateDeploymentStrategy", + "appconfig:GetExtensionAssociation", + "appconfig:GetExtension", + "appconfig:ListDeployments", + "appconfig:GetDeployment", + "appconfig:ListExtensions", + "appconfig:DeleteHostedConfigurationVersion", + "appconfig:StopDeployment", + "appconfig:CreateEnvironment", + "appconfig:UpdateEnvironment", + "appconfig:GetEnvironment", + "appconfig:ListConfigurationProfiles", + "appconfig:DeleteDeploymentStrategy", + "appconfig:ListApplications", + "appconfig:UpdateApplication", + "appconfig:CreateExtension", + "appconfig:GetConfiguration", + "appconfig:GetApplication", + "appconfig:UpdateConfigurationProfile", + "appconfig:GetConfigurationProfile", + ] + Effect = "Allow" + Resource = "*" + Sid = "RepoAppConfig1" + }, + + + { + Action = [ + "dynamodb:DescribeTable", + "dynamodb:GetItem", + "dynamodb:PutItem", + "dynamodb:DeleteItem", + "dynamodb:UpdateTimeToLive", + ] + Effect = "Allow" + Resource = "arn:aws:dynamodb:*:*:table/ndr-terraform-locks" + Sid = "TerraformGithubDynamodbAccessPolicy1" + }, + + + { + Action = "s3:ListBucket" + Effect = "Allow" + Resource = "arn:aws:s3:::ndr-dev-terraform-state-${data.aws_caller_identity.current.account_id}" + Sid = "TerraformGithubS3AccessPolicy1" + }, + { + Action = [ + "s3:GetObject", + "s3:PutObject", + "s3:DeleteObject", + "s3:DeleteBucketPolicy", + "s3:PutBucketPolicy", + ] + Effect = "Allow" + Resource = "arn:aws:s3:::ndr-dev-terraform-state-${data.aws_caller_identity.current.account_id}/ndr/terraform.tfstate" + Sid = "TerraformGithubS3AccessPolicy2" + }, + + + ] + Version = "2012-10-17" + } + ) + tags = {} +} diff --git a/base_iam/iam_github_pre-prod.tf b/base_iam/iam_github_pre-prod.tf new file mode 100644 index 000000000..992ef4a4a --- /dev/null +++ b/base_iam/iam_github_pre-prod.tf @@ -0,0 +1,856 @@ +# aws_iam_role.github_role_pre-prod[0]: +resource "aws_iam_role" "github_role_pre_prod" { + count = local.is_pre_production ? 1 : 0 + description = "This role is to provide access for GitHub actions to the pre-prod environment. " + force_detach_policies = false + max_session_duration = 3600 + name = "Github-Actions-pre-prod-role" + name_prefix = null + path = "/" + permissions_boundary = null + tags = {} + assume_role_policy = jsonencode( + { + Statement = [ + { + Action = "sts:AssumeRoleWithWebIdentity" + Condition = { + StringEquals = { + "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com" + } + StringLike = { + "token.actions.githubusercontent.com:sub" = [ + "repo:NHSDigital/national-document-repository-infrastructure:*", + "repo:NHSDigital/national-document-repository:*", + ] + } + } + Effect = "Allow" + Principal = { + Federated = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/token.actions.githubusercontent.com" + } + }, + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + AWS = "arn:aws:sts::${data.aws_caller_identity.current.account_id}:assumed-role/AWSReservedSSO_DomainCGpit-Administrators_3f00be4c22ce78e5/ABKH2@hscic.gov.uk" + } + }, + ] + Version = "2012-10-17" + } + ) +} + + +# INLINE POLICIES + +resource "aws_iam_role_policy" "cloudfront_policy_pre_prod" { + count = local.is_pre_production ? 1 : 0 + role = aws_iam_role.github_role_pre_prod[0].id + name = "cloudfront_policy" + policy = jsonencode( + { + Statement = [ + { + Action = [ + "cloudfront:CreateCachePolicy", + "cloudfront:DeleteCachePolicy", + "cloudfront:CreateOriginAccessControl", + "cloudfront:CreateDistribution", + "cloudfront:TagResource", + "cloudfront:UntagResource", + "cloudfront:DeleteDistribution", + "lambda:EnableReplication", + "cloudfront:UpdateDistribution", + "cloudfront:DeleteOriginAccessControl", + "cloudfront:CreateInvalidation", + "cloudfront:UpdateOriginAccessControl", + "cloudfront:CreateOriginRequestPolicy", + ] + Effect = "Allow" + Resource = "*" + Sid = "VisualEditor0" + }, + ] + Version = "2012-10-17" + } + ) +} + +resource "aws_iam_role_policy" "cloudwatch_logs_policy_pre_prod" { + count = local.is_pre_production ? 1 : 0 + role = aws_iam_role.github_role_pre_prod[0].id + name = "cloudwatch_logs_policy" + policy = jsonencode( + { + Statement = [ + { + Action = [ + "logs:ListTagsLogGroup", + "logs:CreateLogDelivery", + "logs:PutMetricFilter", + "logs:DeleteMetricFilter", + "logs:DescribeLogGroups", + "logs:PutRetentionPolicy", + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:PutResourcePolicy", + ] + Effect = "Allow" + Resource = "*" + Sid = "AllowLogGroup" + }, + ] + Version = "2012-10-17" + } + ) +} + +resource "aws_iam_role_policy" "ecr_policy_pre_prod" { + count = local.is_pre_production ? 1 : 0 + role = aws_iam_role.github_role_pre_prod[0].id + name = "ecr_policy" + policy = jsonencode( + { + Statement = [ + { + Action = [ + "ecr:InitiateLayerUpload", + "ecr:BatchDeleteImage", + "ecr:CompleteLayerUpload", + "ecr:InitiateLayerUpload", + "ecr:PutImage", + "ecr:UploadLayerPart", + ] + Effect = "Allow" + Resource = [ + "arn:aws:ecr:eu-west-2:${data.aws_caller_identity.current.account_id}:repository/ndr-pre-prod-app", + "arn:aws:ecr:eu-west-2:${data.aws_caller_identity.current.account_id}:repository/pre-prod-data-collection", + ] + Sid = "AllowAppAndOdsUpdate" + }, + ] + Version = "2012-10-17" + } + ) +} + +resource "aws_iam_role_policy" "ecs_policy_pre_prod" { + count = local.is_pre_production ? 1 : 0 + role = aws_iam_role.github_role_pre_prod[0].id + name = "ecs_policy" + policy = jsonencode( + { + Statement = [ + { + Action = [ + "ecs:UpdateCluster", + "ecs:PutClusterCapacityProviders", + ] + Effect = "Allow" + Resource = "*" + Sid = "VisualEditor0" + }, + ] + Version = "2012-10-17" + } + ) +} + +resource "aws_iam_role_policy" "github_extended_policy_virus_scanner_pre_prod" { + count = local.is_pre_production ? 1 : 0 + role = aws_iam_role.github_role_pre_prod[0].id + name = "github-extended-policy-virus-scanner" + policy = jsonencode( + { + Statement = [ + { + Action = [ + "ssm:CreateDocument", + "iam:TagRole", + "SNS:TagResource", + "cognito-idp:CreateUserPool", + "cognito-idp:TagResource", + "cognito-idp:SetUserPoolMfaConfig", + "iam:CreateInstanceProfile", + "iam:AddRoleToInstanceProfile", + "iam:DeleteInstanceProfile", + "cloudformation:CreateResource", + "cognito-idp:DeleteUserPool", + "cognito-idp:CreateGroup", + "cognito-idp:AdminCreateUser", + "cognito-idp:CreateUserPoolClient", + "cognito-idp:AdminAddUserToGroup", + ] + Effect = "Allow" + Resource = "*" + Sid = "Statement1" + }, + ] + Version = "2012-10-17" + } + ) +} + +resource "aws_iam_role_policy" "lambda_pre_prod" { + count = local.is_pre_production ? 1 : 0 + role = aws_iam_role.github_role_pre_prod[0].id + name = "lambda" + policy = jsonencode( + { + Statement = [ + { + Action = [ + "lambda:CreateFunction", + "lambda:DeleteFunctionConcurrency", + "lambda:GetFunction", + "lambda:GetFunctionConfiguration", + "lambda:InvokeFunction", + "lambda:UpdateFunctionCode", + "lambda:UpdateFunctionConfiguration", + "kms:CreateGrant", + "kms:Decrypt", + "kms:Encrypt", + "kms:TagResource", + "kms:UntagResource", + "s3:PutObject", + ] + Effect = "Allow" + Resource = [ + "arn:aws:kms:*:${data.aws_caller_identity.current.account_id}:key/*", + "arn:aws:lambda:eu-west-2:*:function:*", + ] + Sid = "VisualEditor0" + }, + ] + Version = "2012-10-17" + } + ) +} + +resource "aws_iam_role_policy" "mtls_gateway_pre_prod" { + count = local.is_pre_production ? 1 : 0 + role = aws_iam_role.github_role_pre_prod[0].id + name = "mtls-gateway" + policy = jsonencode( + { + Statement = [ + { + Action = [ + "acm:RequestCertificate", + "route53:ListHostedZones", + "acm:ListCertificates", + ] + Effect = "Allow" + Resource = "*" + Sid = "VisualEditor0" + }, + { + Action = "apigateway:AddCertificateToDomain" + Effect = "Allow" + Resource = "arn:aws:apigateway:eu-west-2::/domainnames" + Sid = "VisualEditor1" + }, + { + Action = [ + "acm:DeleteCertificate", + "acm:DescribeCertificate", + "acm:GetCertificate", + "route53:GetHostedZone", + "route53:ChangeResourceRecordSets", + "apigateway:AddCertificateToDomain", + "acm:AddTagsToCertificate", + "apigateway:RemoveCertificateFromDomain", + "acm:ListTagsForCertificate", + ] + Effect = "Allow" + Resource = [ + "arn:aws:apigateway:eu-west-2::/domainnames", + "arn:aws:apigateway:eu-west-2::/domainnames/*", + "arn:aws:route53:::hostedzone/*", + "arn:aws:acm:eu-west-2:${data.aws_caller_identity.current.account_id}:certificate/*", + ] + Sid = "VisualEditor2" + }, + { + Action = [ + "apigateway:AddCertificateToDomain", + "apigateway:RemoveCertificateFromDomain", + ] + Effect = "Allow" + Resource = [ + "arn:aws:apigateway:eu-west-2::/domainnames/*", + "arn:aws:apigateway:eu-west-2::/domainnames", + ] + Sid = "VisualEditor3" + }, + { + Action = "apigateway:AddCertificateToDomain" + Effect = "Allow" + Resource = "arn:aws:apigateway:eu-west-2::/domainnames" + Sid = "VisualEditor4" + }, + ] + Version = "2012-10-17" + } + ) +} + +resource "aws_iam_role_policy" "resource_tagging_pre_prod" { + count = local.is_pre_production ? 1 : 0 + role = aws_iam_role.github_role_pre_prod[0].id + name = "resource_tagging" + policy = jsonencode( + { + Statement = [ + { + Action = [ + "resource-groups:GetGroupQuery", + "backup:TagResource", + "sns:TagResource", + "lambda:TagResource", + "resource-groups:UpdateGroup", + "iam:UntagRole", + "iam:TagRole", + "resource-groups:GetTags", + "sns:UntagResource", + "resource-groups:Untag", + "lambda:UntagResource", + "elasticloadbalancing:RemoveTags", + "cognito-identity:UntagResource", + "resource-groups:GetGroup", + "resource-groups:GetGroupConfiguration", + "backup:UntagResource", + "cognito-identity:TagResource", + "resource-groups:Tag", + "logs:UntagResource", + "resource-groups:UpdateGroupQuery", + "iam:TagPolicy", + "logs:TagResource", + "events:TagResource", + "resource-groups:DeleteGroup", + "elasticloadbalancing:AddTags", + "iam:UntagPolicy", + "resource-groups:ListGroupResources", + "iam:UntagInstanceProfile", + "events:UntagResource", + "iam:TagInstanceProfile", + ] + Effect = "Allow" + Resource = [ + "arn:aws:events:*:${data.aws_caller_identity.current.account_id}:event-bus/*", + "arn:aws:events:*:${data.aws_caller_identity.current.account_id}:rule/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:loadbalancer/gwy/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:loadbalancer/net/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:loadbalancer/app/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:truststore/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:listener/app/*/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:listener/gwy/*/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:listener-rule/net/*/*/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:listener/net/*/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:listener-rule/app/*/*/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:targetgroup/*/*", + "arn:aws:lambda:*:${data.aws_caller_identity.current.account_id}:event-source-mapping:*", + "arn:aws:lambda:*:${data.aws_caller_identity.current.account_id}:code-signing-config:*", + "arn:aws:lambda:*:${data.aws_caller_identity.current.account_id}:function:*", + "arn:aws:cognito-identity:*:${data.aws_caller_identity.current.account_id}:identitypool/*", + "arn:aws:resource-groups:*:${data.aws_caller_identity.current.account_id}:group/*", + "arn:aws:backup:*:${data.aws_caller_identity.current.account_id}:backup-plan:*", + "arn:aws:backup:*:${data.aws_caller_identity.current.account_id}:report-plan:*-*", + "arn:aws:backup:*:${data.aws_caller_identity.current.account_id}:restore-testing-plan:*-*", + "arn:aws:backup:*:${data.aws_caller_identity.current.account_id}:backup-vault:*", + "arn:aws:backup:*:${data.aws_caller_identity.current.account_id}:legal-hold:*", + "arn:aws:backup:*:${data.aws_caller_identity.current.account_id}:framework:*-*", + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/*", + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:instance-profile/*", + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/*", + "arn:aws:sns:*:${data.aws_caller_identity.current.account_id}:*", + "arn:aws:logs:*:${data.aws_caller_identity.current.account_id}:log-group:*", + "arn:aws:logs:*:${data.aws_caller_identity.current.account_id}:delivery-source:*", + "arn:aws:logs:*:${data.aws_caller_identity.current.account_id}:delivery:*", + "arn:aws:logs:*:${data.aws_caller_identity.current.account_id}:destination:*", + "arn:aws:logs:*:${data.aws_caller_identity.current.account_id}:delivery-destination:*", + "arn:aws:logs:*:${data.aws_caller_identity.current.account_id}:anomaly-detector:*", + ] + Sid = "VisualEditor0" + }, + { + Action = [ + "events:TagResource", + "elasticloadbalancing:RemoveTags", + "elasticloadbalancing:AddTags", + "events:UntagResource", + ] + Effect = "Allow" + Resource = [ + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:loadbalancer/app/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:loadbalancer/net/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:targetgroup/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:truststore/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:loadbalancer/gwy/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:listener/gwy/*/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:listener/app/*/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:listener/net/*/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:listener-rule/app/*/*/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:listener-rule/net/*/*/*/*", + "arn:aws:events:*:${data.aws_caller_identity.current.account_id}:rule/*", + ] + Sid = "VisualEditor1" + }, + { + Action = [ + "elasticloadbalancing:RemoveTags", + "elasticloadbalancing:AddTags", + ] + Effect = "Allow" + Resource = [ + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:truststore/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:listener/app/*/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:listener/gwy/*/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:listener/net/*/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:listener-rule/net/*/*/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:listener-rule/app/*/*/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:targetgroup/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:loadbalancer/gwy/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:loadbalancer/net/*/*", + "arn:aws:elasticloadbalancing:*:${data.aws_caller_identity.current.account_id}:loadbalancer/app/*/*", + ] + Sid = "VisualEditor2" + }, + { + Action = [ + "resource-groups:SearchResources", + "resource-groups:CreateGroup", + "resource-groups:ListGroups", + ] + Effect = "Allow" + Resource = "*" + Sid = "VisualEditor3" + }, + ] + Version = "2012-10-17" + } + ) +} + + +############################################################################################################## +# ATTACHED POLICIES + +resource "aws_iam_role_policy_attachment" "ReadOnlyAccess_pre_prod" { + count = local.is_pre_production ? 1 : 0 + role = aws_iam_role.github_role_pre_prod[0].name + policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess" +} + +resource "aws_iam_role_policy_attachment" "github_actions_policy_pre_prod" { + count = local.is_pre_production ? 1 : 0 + role = aws_iam_role.github_role_pre_prod[0].name + policy_arn = aws_iam_policy.github_actions_policy_pre_prod[0].arn +} + +# aws_iam_policy.github_actions_policy_pre-prod[0]: +resource "aws_iam_policy" "github_actions_policy_pre_prod" { + count = local.is_pre_production ? 1 : 0 + description = null + name = "github-actions-policy" + name_prefix = null + path = "/" + policy = jsonencode( + { + Statement = [ + { + Action = [ + "apigateway:DELETE", + "apigateway:PATCH", + "apigateway:POST", + "apigateway:PUT", + "cloudwatch:DeleteAlarms", + "cloudwatch:PutMetricAlarm", + "dynamodb:CreateTable", + "dynamodb:DeleteItem", + "dynamodb:DeleteTable", + "dynamodb:DescribeContinuousBackups", + "dynamodb:DescribeTable", + "dynamodb:DescribeTimeToLive", + "dynamodb:GetItem", + "dynamodb:ListTagsOfResource", + "dynamodb:PutItem", + "dynamodb:TagResource", + "dynamodb:UpdateTimeToLive", + "ec2:AssociateRouteTable", + "ec2:AttachInternetGateway", + "ec2:AuthorizeSecurityGroupEgress", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:CreateDefaultVpc", + "ec2:CreateInternetGateway", + "ec2:CreateRoute", + "ec2:CreateRouteTable", + "ec2:CreateSecurityGroup", + "ec2:CreateSubnet", + "ec2:CreateTags", + "ec2:CreateVpc", + "ec2:CreateVpcEndpoint", + "ec2:DeleteInternetGateway", + "ec2:DeleteRoute", + "ec2:DeleteRouteTable", + "ec2:DeleteSecurityGroup", + "ec2:DeleteSubnet", + "ec2:DeleteVpc", + "ec2:DeleteVpcEndpoints", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeInternetGateways", + "ec2:DescribePrefixLists", + "ec2:DescribeRouteTables", + "ec2:DescribeSecurityGroupRules", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeVpcEndpoints", + "ec2:DescribeVpcs", + "ec2:DetachInternetGateway", + "ec2:DisassociateRouteTable", + "ec2:ModifyVpcAttribute", + "ec2:ModifyVpcEndpoint", + "ec2:RevokeSecurityGroupEgress", + "ec2:RevokeSecurityGroupIngress", + "ecr:CreateRepository", + "ecr:DeleteLifecyclePolicy", + "ecr:DeleteRepository", + "ecr:DeleteRepositoryPolicy", + "ecr:DescribeRepositories", + "ecr:GetAuthorizationToken", + "ecr:GetLifecyclePolicy", + "ecr:GetRepositoryPolicy", + "ecr:ListTagsForResource", + "ecr:PutLifecyclePolicy", + "ecr:SetRepositoryPolicy", + "ecr:TagResource", + "ecs:CreateCluster", + "ecs:CreateService", + "ecs:DeleteCluster", + "ecs:DeleteService", + "ecs:DeregisterTaskDefinition", + "ecs:DescribeClusters", + "ecs:DescribeServices", + "ecs:DescribeTaskDefinition", + "ecs:RegisterTaskDefinition", + "ecs:UpdateService", + "elasticloadbalancing:AddTags", + "elasticloadbalancing:CreateListener", + "elasticloadbalancing:CreateLoadBalancer", + "elasticloadbalancing:CreateTargetGroup", + "elasticloadbalancing:DeleteListener", + "elasticloadbalancing:DeleteLoadBalancer", + "elasticloadbalancing:DeleteTargetGroup", + "elasticloadbalancing:DescribeListeners", + "elasticloadbalancing:DescribeLoadBalancerAttributes", + "elasticloadbalancing:DescribeLoadBalancers", + "elasticloadbalancing:DescribeTags", + "elasticloadbalancing:DescribeTargetGroupAttributes", + "elasticloadbalancing:DescribeTargetGroups", + "elasticloadbalancing:ModifyLoadBalancerAttributes", + "elasticloadbalancing:ModifyTargetGroupAttributes", + "elasticloadbalancing:SetSecurityGroups", + "events:PutRule", + "events:PutTargets", + "iam:AttachRolePolicy", + "iam:CreatePolicy", + "iam:CreatePolicyVersion", + "iam:CreateRole", + "iam:DeletePolicy", + "iam:DeletePolicyVersion", + "iam:DeleteRole", + "iam:DeleteRolePolicy", + "iam:DetachRolePolicy", + "iam:GetPolicy", + "iam:GetPolicyVersion", + "iam:GetRole", + "iam:GetRolePolicy", + "iam:ListAttachedRolePolicies", + "iam:ListRolePolicies", + "iam:PassRole", + "iam:PutRolePolicy", + "kms:RetireGrant", + "lambda:AddPermission", + "lambda:CreateEventSourceMapping", + "lambda:DeleteEventSourceMapping", + "lambda:DeleteFunction", + "lambda:GetPolicy", + "lambda:RemovePermission", + "logs:CreateLogGroup", + "logs:DeleteLogGroup", + "logs:DescribeLogGroups", + "logs:ListTagsLogGroup", + "route53:AssociateVPCWithHostedZone", + "route53:ChangeResourceRecordSets", + "route53:GetChange", + "route53:GetHostedZone", + "route53:ListHostedZones", + "route53:ListResourceRecordSets", + "route53:ListTagsForResource", + "s3:CreateBucket", + "s3:DeleteBucket", + "s3:DeleteBucketPolicy", + "s3:DeleteObject", + "s3:DeleteObjectTagging", + "s3:DeleteObjectVersion", + "s3:DeleteObjectVersionTagging", + "s3:GetAccelerateConfiguration", + "s3:GetBucketAcl", + "s3:GetBucketCORS", + "s3:GetBucketLogging", + "s3:GetBucketObjectLockConfiguration", + "s3:GetBucketOwnershipControls", + "s3:GetBucketPolicy", + "s3:GetBucketRequestPayment", + "s3:GetBucketTagging", + "s3:GetBucketVersioning", + "s3:GetBucketWebsite", + "s3:GetEncryptionConfiguration", + "s3:GetLifecycleConfiguration", + "s3:GetObject", + "s3:GetReplicationConfiguration", + "s3:ListBucket", + "s3:PutBucketAcl", + "s3:PutBucketCORS", + "s3:PutBucketOwnershipControls", + "s3:PutBucketPolicy", + "s3:PutBucketTagging", + "s3:PutLifecycleConfiguration", + "s3:PutObject", + "secretsmanager:DeleteSecret", + "sns:CreateTopic", + "sns:DeleteTopic", + "sns:SetTopicAttributes", + "sns:Subscribe", + "sns:Unsubscribe", + "sqs:DeleteMessage", + "sqs:DeleteQueue", + "sqs:ListQueues", + "sqs:createqueue", + "sqs:setqueueattributes", + "ssm:AddTagsToResource", + "ssm:DeleteParameter", + "ssm:PutParameter", + "events:RemoveTargets", + "wafv2:CreateRegexPatternSet", + "wafv2:TagResource", + "wafv2:CreateWebACL", + "wafv2:AssociateWebACL", + "elasticloadbalancing:SetWebACL", + "events:DeleteRule", + "wafv2:DeleteRegexPatternSet", + "wafv2:DeleteWebACL", + "s3:PutIntelligentTieringConfiguration", + "ecs:UntagResource", + "lambda:UpdateFunctionConfiguration", + "lambda:UpdateFunctionCode", + "sqs:tagqueue", + "kms:TagResource", + "wafv2:UpdateWebACL", + "dynamodb:UpdateTable", + "kms:CreateKey", + "dynamodb:UpdateContinuousBackups", + "backup:CreateBackupVault", + "application-autoscaling:RegisterScalableTarget", + "application-autoscaling:TagResource", + "s3:PutBucketVersioning", + "kms:CreateAlias", + "kms:DeleteAlias", + "kms:DescribeKey", + "kms:EnableKeyRotation", + "kms:GetKeyPolicy", + "kms:GetKeyRotationStatus", + "kms:ListAliases", + "kms:ListKeys", + "kms:ListResourceTags", + "kms:PutKeyPolicy", + "kms:UntagResource", + "kms:UpdateAlias", + "kms:UpdateKeyDescription", + "kms:ScheduleKeyDeletion", + "application-autoscaling:PutScalingPolicy", + "application-autoscaling:DeleteScalingPolicy", + "application-autoscaling:DeregisterScalableTarget", + "application-autoscaling:UntagResource", + "application-autoscaling:ListTagsForResource", + "cloudwatch:TagResource", + "cloudwatch:UntagResource", + "cloudwatch:ListTagsForResource", + "backup-storage:MountCapsule", + "backup:CreateBackupPlan", + "lambda:PutFunctionConcurrency", + "backup:CreateBackupSelection", + "backup:UpdateBackupPlan", + "backup:DescribeBackupJob", + "backup:ListTags", + "backup:TagResource", + "backup:DeleteBackupVault", + "backup:DeleteBackupSelection", + "iam:UpdateRoleDescription", + "logs:PutMetricFilter", + "ec2:AllocateAddress", + "ec2:CreateNatGateway", + "scheduler:CreateSchedule", + "scheduler:UpdateSchedule", + ] + Effect = "Allow" + Resource = "*" + Sid = "Statement1" + }, + ] + Version = "2012-10-17" + } + ) + tags = {} +} + +resource "aws_iam_role_policy_attachment" "github_extended_policy_1_pre_prod" { + count = local.is_pre_production ? 1 : 0 + role = aws_iam_role.github_role_pre_prod[0].name + policy_arn = aws_iam_policy.github_extended_policy_1_pre_prod[0].arn +} + +# aws_iam_policy.github_extended_policy_1_pre-prod[0]: +resource "aws_iam_policy" "github_extended_policy_1_pre_prod" { + count = local.is_pre_production ? 1 : 0 + description = "more required items for GitHub access" + name = "github-extended-policy-1" + name_prefix = null + path = "/" + policy = jsonencode( + { + Statement = [ + { + Action = [ + "ses:SetIdentityMailFromDomain", + "lambda:CreateFunction", + "appconfig:StartDeployment", + "elasticloadbalancing:ModifyListener", + "appconfig:TagResource", + "appconfig:CreateDeploymentStrategy", + "lambda:ListLayers", + "ecs:TagResource", + "appconfig:DeleteHostedConfigurationVersion", + "lambda:PublishVersion", + "dynamodb:UpdateTable", + "ec2:DisassociateAddress", + "kms:ListResourceTags", + "ecr:ListTagsForResource", + "lambda:RemoveLayerVersionPermission", + "ses:VerifyDomainIdentity", + "ecs:DeregisterTaskDefinition", + "apigateway:DELETE", + "logs:DeleteMetricFilter", + "apigateway:SetWebACL", + "ec2:DescribeAvailabilityZones", + "backup:CreateBackupSelection", + "kms:CreateKey", + "ec2:ReleaseAddress", + "kms:EnableKeyRotation", + "ecr:PutLifecyclePolicy", + "lambda:UpdateEventSourceMapping", + "backup:DeleteBackupVault", + "kms:GetKeyPolicy", + "route53:ListHostedZones", + "elasticloadbalancing:DeleteTargetGroup", + "appconfig:CreateEnvironment", + "backup:DescribeBackupVault", + "events:DeleteRule", + "iam:CreateServiceLinkedRole", + "appconfig:DeleteDeploymentStrategy", + "ec2:DescribeVpcs", + "kms:ListAliases", + "backup:CreateBackupPlan", + "ses:DeleteIdentity", + "lambda:RemovePermission", + "backup:ListTags", + "route53:GetHostedZone", + "sns:Unsubscribe", + "iam:CreateRole", + "iam:AttachRolePolicy", + "appconfig:CreateApplication", + "ec2:AssociateRouteTable", + "ec2:DescribeInternetGateways", + "elasticloadbalancing:DeleteLoadBalancer", + "backup:DeleteBackupSelection", + "iam:DetachRolePolicy", + "cloudwatch:UntagResource", + "iam:ListAttachedRolePolicies", + "dynamodb:GetItem", + "lambda:ListLayerVersions", + "ec2:DescribeRouteTables", + "elasticloadbalancing:ModifyTargetGroupAttributes", + "application-autoscaling:RegisterScalableTarget", + "dynamodb:PutItem", + "ecs:CreateCluster", + "ec2:CreateRouteTable", + "route53:ChangeResourceRecordSets", + "lambda:AddLayerVersionPermission", + "ec2:DetachInternetGateway", + "logs:CreateLogGroup", + "ecr:DeleteLifecyclePolicy", + "backup-storage:MountCapsule", + "ecs:DescribeClusters", + "ssm:PutParameter", + "elasticloadbalancing:DescribeLoadBalancerAttributes", + "logs:PutMetricFilter", + "ec2:DescribeSecurityGroupRules", + "elasticloadbalancing:DescribeTargetGroupAttributes", + "s3:PutBucketLogging", + "application-autoscaling:PutScalingPolicy", + "ec2:DescribeVpcEndpoints", + "appconfig:CreateConfigurationProfile", + "route53:GetChange", + "lambda:GetLayerVersion", + "lambda:PublishLayerVersion", + "ses:VerifyDomainDkim", + "lambda:CreateEventSourceMapping", + "lambda:GetLayerVersionPolicy", + "kms:TagResource", + "dynamodb:TagResource", + "elasticloadbalancing:DescribeListeners", + "ec2:CreateSecurityGroup", + "apigateway:PATCH", + "appconfig:CreateHostedConfigurationVersion", + "lambda:DeleteLayerVersion", + "application-autoscaling:ListTagsForResource", + "kms:DescribeKey", + "ec2:ModifyVpcAttribute", + "ecs:UntagResource", + "ecr:DeleteRepositoryPolicy", + "s3:GetBucketPublicAccessBlock", + "ec2:AuthorizeSecurityGroupEgress", + "elasticloadbalancing:ModifyListenerAttributes", + "s3:PutBucketPublicAccessBlock", + "logs:DescribeLogGroups", + "kms:UpdateKeyDescription", + "logs:DeleteLogGroup", + "elasticloadbalancing:DescribeTags", + "ec2:DeleteRoute", + "backup:DeleteRecoveryPoint", + "ec2:AllocateAddress", + "cloudwatch:PutMetricAlarm", + "cloudwatch:TagResource", + "ec2:CreateVpcEndpoint", + "elasticloadbalancing:SetSecurityGroups", + "lambda:DeleteFunctionConcurrency", + "lambda:GetPolicy", + "iam:DeletePolicyVersion", + "ecr:GetRepositoryPolicy", + "s3:PutBucketNotification", + "iam:UpdateAssumeRolePolicy", + ] + Effect = "Allow" + Resource = "*" + Sid = "VisualEditor0" + }, + ] + Version = "2012-10-17" + } + ) + tags = {} +} diff --git a/base_iam/iam_github_test.tf b/base_iam/iam_github_test.tf new file mode 100644 index 000000000..0dddc44f3 --- /dev/null +++ b/base_iam/iam_github_test.tf @@ -0,0 +1,633 @@ +# aws_iam_role.github_role_test[0]: +resource "aws_iam_role" "github_role_test" { + count = local.is_testing ? 1 : 0 + description = "This role is for the deployment of infrastructure and code from GitHub" + force_detach_policies = false + max_session_duration = 3600 + name = "github-action-role" + name_prefix = null + path = "/" + permissions_boundary = null + tags = {} + assume_role_policy = jsonencode( + { + Statement = [ + { + Action = "sts:AssumeRoleWithWebIdentity" + Condition = { + StringEquals = { + "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com" + } + StringLike = { + "token.actions.githubusercontent.com:sub" = [ + "repo:NHSDigital/national-document-repository-infrastructure:*", + "repo:NHSDigital/national-document-repository:*", + ] + } + } + Effect = "Allow" + Principal = { + Federated = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/token.actions.githubusercontent.com" + } + }, + ] + Version = "2012-10-17" + } + ) +} + +# INLINE POLICIES + + +resource "aws_iam_role_policy" "cloudfront_policies_test" { + count = local.is_testing ? 1 : 0 + role = aws_iam_role.github_role_test[0].id + name = "cloudfront_policies" + policy = jsonencode( + { + Statement = [ + { + Action = [ + "cloudfront:CreateCachePolicy", + "cloudfront:DeleteCachePolicy", + "cloudfront:CreateOriginAccessControl", + "cloudfront:CreateDistribution", + "cloudfront:TagResource", + "cloudfront:UntagResource", + "cloudfront:DeleteDistribution", + "lambda:EnableReplication", + "cloudfront:UpdateDistribution", + "cloudfront:DeleteOriginAccessControl", + "cloudfront:CreateInvalidation", + "cloudfront:CreateOriginRequestPolicy", + "cloudfront:DeleteOriginRequestPolicy", + "cloudfront:UpdateOriginRequestPolicy", + ] + Effect = "Allow" + Resource = "*" + Sid = "VisualEditor0" + }, + ] + Version = "2012-10-17" + } + ) +} + +resource "aws_iam_role_policy" "cloudwatch_logs_policy_test" { + count = local.is_testing ? 1 : 0 + role = aws_iam_role.github_role_test[0].id + name = "cloudwatch_logs_policy" + policy = jsonencode( + { + Statement = [ + { + Action = [ + "logs:DescribeLogGroups", + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:PutRetentionPolicy", + "logs:PutResourcePolicy", + "logs:DeleteResourcePolicy", + "logs:DeleteRetentionPolicy", + "logs:TagResource", + "logs:UntagResource", + "logs:AssociateKmsKey", + "logs:DisassociateKmsKey", + ] + Effect = "Allow" + Resource = "arn:aws:logs:eu-west-2:${data.aws_caller_identity.current.account_id}:log-group:*" + Sid = "Statement1" + }, + ] + Version = "2012-10-17" + } + ) +} + +resource "aws_iam_role_policy" "resource_tagging_test" { + count = local.is_testing ? 1 : 0 + role = aws_iam_role.github_role_test[0].id + name = "resource_tagging" + policy = jsonencode( + { + Statement = [ + { + Action = [ + "resource-groups:SearchResources", + "resource-groups:CreateGroup", + "resource-groups:ListGroups", + ] + Effect = "Allow" + Resource = "*" + Sid = "VisualEditor3" + }, + ] + Version = "2012-10-17" + } + ) +} + +resource "aws_iam_role_policy" "rum_policy_test" { + count = local.is_testing ? 1 : 0 + role = aws_iam_role.github_role_test[0].id + name = "rum_policy" + policy = jsonencode( + { + Statement = [ + { + Action = [ + "cognito-identity:SetIdentityPoolRoles", + "cognito-identity:CreateIdentityPool", + "cognito-identity:DeleteIdentityPool", + "cognito-identity:UpdateIdentityPool", + ] + Effect = "Allow" + Resource = "arn:aws:cognito-identity:eu-west-2:${data.aws_caller_identity.current.account_id}:identitypool/*" + Sid = "VisualEditor0" + }, + { + Action = [ + "rum:TagResource", + "rum:UntagResource", + "rum:ListTagsForResource", + "iam:PassRole", + "rum:UpdateAppMonitor", + "rum:GetAppMonitor", + "rum:CreateAppMonitor", + "rum:DeleteAppMonitor", + ] + Effect = "Allow" + Resource = "arn:aws:rum:eu-west-2:${data.aws_caller_identity.current.account_id}:appmonitor/*" + Sid = "VisualEditor1" + }, + { + Action = [ + "logs:DeleteLogGroup", + "logs:DeleteResourcePolicy", + "logs:DescribeLogGroups", + ] + Effect = "Allow" + Resource = "arn:aws:logs:eu-west-2:${data.aws_caller_identity.current.account_id}:log-group:*RUMService*" + Sid = "VisualEditor2" + }, + { + Action = [ + "logs:CreateLogDelivery", + "logs:GetLogDelivery", + "logs:UpdateLogDelivery", + "logs:DeleteLogDelivery", + "logs:ListLogDeliveries", + "logs:DescribeResourcePolicies", + ] + Effect = "Allow" + Resource = "*" + Sid = "VisualEditor3" + }, + ] + Version = "2012-10-17" + } + ) +} + +resource "aws_iam_role_policy" "scheduler_policy_test" { + count = local.is_testing ? 1 : 0 + role = aws_iam_role.github_role_test[0].id + name = "scheduler-policy" + policy = jsonencode( + { + Statement = [ + { + Action = [ + "scheduler:TagResource", + "scheduler:CreateSchedule", + "scheduler:UntagResource", + "scheduler:DeleteSchedule", + "scheduler:UpdateSchedule", + ] + Effect = "Allow" + Resource = "*" + Sid = "VisualEditor0" + }, + ] + Version = "2012-10-17" + } + ) +} + +resource "aws_iam_role_policy" "virus_scan_cognito_test" { + count = local.is_testing ? 1 : 0 + role = aws_iam_role.github_role_test[0].id + name = "virus-scan-cognito" + policy = jsonencode( + { + Statement = [ + { + Action = [ + "cognito-idp:TagResource", + "cognito-idp:DeleteUserPool", + "cognito-idp:AdminCreateUser", + "cognito-idp:CreateUserPoolClient", + "cognito-idp:CreateGroup", + "cognito-idp:CreateUserPool", + "cognito-idp:SetUserPoolMfaConfig", + "cognito-idp:AdminAddUserToGroup", + "cloudformation:CreateResource", + "cloudformation:DeleteResource", + "cognito-idp:DeleteGroup", + "appconfig:DeleteEnvironment", + "appconfig:DeleteConfigurationProfile", + "iam:RemoveRoleFromInstanceProfile", + "cognito-idp:DeleteUserPoolClient", + "cognito-idp:AdminRemoveUserFromGroup", + "cognito-idp:AdminDeleteUser", + ] + Effect = "Allow" + Resource = "*" + Sid = "VisualEditor0" + }, + ] + Version = "2012-10-17" + } + ) +} + +############################################################### +# ATTACHED POLICIES + +resource "aws_iam_role_policy_attachment" "ReadOnlyAccess_test" { + count = local.is_testing ? 1 : 0 + role = aws_iam_role.github_role_test[0].name + policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess" +} + +resource "aws_iam_role_policy_attachment" "github_action_policy_test" { + count = local.is_testing ? 1 : 0 + role = aws_iam_role.github_role_test[0].name + policy_arn = aws_iam_policy.github_action_policy_test[0].arn +} + +# aws_iam_policy.github_action_policy_test[0]: +resource "aws_iam_policy" "github_action_policy_test" { + count = local.is_testing ? 1 : 0 + description = null + name = "github-action-policy" + name_prefix = null + path = "/" + policy = jsonencode( + { + Statement = [ + { + Action = [ + "ec2:AuthorizeSecurityGroupIngress", + "ec2:DeleteVpcEndpoints", + "ec2:AttachInternetGateway", + "iam:PutRolePolicy", + "ecr:DeleteRepository", + "ec2:CreateRoute", + "cloudwatch:ListTagsForResource", + "ecr:TagResource", + "dynamodb:DescribeContinuousBackups", + "events:RemoveTargets", + "lambda:DeleteFunction", + "iam:ListRolePolicies", + "ecs:TagResource", + "ecr:GetLifecyclePolicy", + "iam:GetRole", + "elasticloadbalancing:CreateTargetGroup", + "ecr:GetAuthorizationToken", + "application-autoscaling:DeleteScalingPolicy", + "kms:RetireGrant", + "elasticloadbalancing:AddTags", + "ec2:DeleteNatGateway", + "apigateway:POST", + "lambda:DeleteEventSourceMapping", + "elasticloadbalancing:ModifyLoadBalancerAttributes", + "ec2:ModifyVpcEndpoint", + "logs:ListTagsLogGroup", + "kms:PutKeyPolicy", + "events:PutRule", + "ec2:CreateVpc", + "dynamodb:ListTagsOfResource", + "iam:PassRole", + "sqs:createqueue", + "iam:DeleteRolePolicy", + "application-autoscaling:TagResource", + "elasticloadbalancing:CreateLoadBalancer", + "lambda:UpdateEventSourceMapping", + "apigateway:PUT", + "route53:ListTagsForResource", + "ec2:DescribeSecurityGroups", + "iam:CreatePolicy", + "sqs:TagQueue", + "kms:CreateAlias", + "elasticloadbalancing:DescribeTargetGroups", + "route53:AssociateVPCWithHostedZone", + "elasticloadbalancing:DeleteListener", + "iam:GetPolicyVersion", + "wafv2:AssociateWebACL", + "ec2:DeleteSubnet", + "elasticloadbalancing:SetWebACL", + "elasticloadbalancing:DescribeLoadBalancers", + "ecs:UpdateService", + "ssm:DeleteParameter", + "kms:GetKeyRotationStatus", + "dynamodb:DescribeTable", + "ssm:AddTagsToResource", + "ecs:RegisterTaskDefinition", + "route53:ListResourceRecordSets", + "ecr:CreateRepository", + "ecs:DeleteService", + "application-autoscaling:UntagResource", + "ec2:DescribePrefixLists", + "backup:CreateBackupVault", + "backup:UpdateBackupPlan", + "sqs:DeleteQueue", + "ec2:DeleteVpc", + "kms:DeleteAlias", + "sns:DeleteTopic", + "wafv2:DeleteWebACL", + "dynamodb:DeleteItem", + "iam:DeletePolicy", + "sns:SetTopicAttributes", + "lambda:PutFunctionConcurrency", + "dynamodb:UpdateContinuousBackups", + "elasticloadbalancing:CreateListener", + "ecs:CreateService", + "kms:ScheduleKeyDeletion", + "ecs:DescribeServices", + "ecr:DescribeRepositories", + "iam:CreatePolicyVersion", + "ecs:UntagResource", + "sqs:ListQueues", + "wafv2:UpdateWebACL", + "dynamodb:DescribeTimeToLive", + "kms:UpdateAlias", + "backup:GetBackupSelection", + "events:PutTargets", + "kms:ListKeys", + "lambda:AddPermission", + "ec2:DeleteSecurityGroup", + "ecr:SetRepositoryPolicy", + "application-autoscaling:DeregisterScalableTarget", + "backup:DeleteBackupPlan", + "sqs:DeleteMessage", + "cloudwatch:DeleteAlarms", + "secretsmanager:DeleteSecret", + "wafv2:CreateRegexPatternSet", + "wafv2:CreateWebACL", + "dynamodb:DeleteTable", + "ecs:DescribeTaskDefinition", + "ec2:DeleteRouteTable", + "ec2:CreateInternetGateway", + "ec2:RevokeSecurityGroupEgress", + "sns:Subscribe", + "ec2:DeleteInternetGateway", + "wafv2:TagResource", + "dynamodb:UpdateTimeToLive", + "iam:GetPolicy", + "ec2:CreateTags", + "sns:CreateTopic", + "ecs:DeleteCluster", + "iam:UpdateRoleDescription", + "iam:DeleteRole", + "ec2:DisassociateRouteTable", + "backup:GetBackupPlan", + "wafv2:DeleteRegexPatternSet", + "ec2:RevokeSecurityGroupIngress", + "dynamodb:CreateTable", + "ec2:CreateDefaultVpc", + "ec2:CreateSubnet", + "ec2:DescribeSubnets", + "iam:GetRolePolicy", + "sqs:setqueueattributes", + "kms:UntagResource", + "ec2:CreateNatGateway", + "kms:ListResourceTags", + "ecr:ListTagsForResource", + "ecs:DeregisterTaskDefinition", + "apigateway:DELETE", + "backup:CreateBackupSelection", + "ec2:DescribeAvailabilityZones", + "kms:CreateKey", + "kms:EnableKeyRotation", + "ecr:PutLifecyclePolicy", + "s3:*", + "backup:DeleteBackupVault", + "kms:GetKeyPolicy", + "route53:ListHostedZones", + "elasticloadbalancing:DeleteTargetGroup", + "events:DeleteRule", + "backup:DescribeBackupVault", + "ec2:DescribeVpcs", + "kms:ListAliases", + "backup:CreateBackupPlan", + "lambda:RemovePermission", + "backup:ListTags", + "route53:GetHostedZone", + "iam:CreateRole", + "sns:Unsubscribe", + "iam:AttachRolePolicy", + "ec2:AssociateRouteTable", + "elasticloadbalancing:DeleteLoadBalancer", + "ec2:DescribeInternetGateways", + "iam:DetachRolePolicy", + "backup:DeleteBackupSelection", + "cloudwatch:UntagResource", + "iam:ListAttachedRolePolicies", + "dynamodb:GetItem", + "elasticloadbalancing:ModifyTargetGroupAttributes", + "ec2:DescribeRouteTables", + "application-autoscaling:RegisterScalableTarget", + "dynamodb:PutItem", + "ecs:CreateCluster", + "ec2:CreateRouteTable", + "route53:ChangeResourceRecordSets", + "ec2:DetachInternetGateway", + "logs:CreateLogGroup", + "ecr:DeleteLifecyclePolicy", + "backup-storage:MountCapsule", + "ecs:DescribeClusters", + "elasticloadbalancing:DescribeLoadBalancerAttributes", + "ssm:PutParameter", + "elasticloadbalancing:DescribeTargetGroupAttributes", + "ec2:DescribeSecurityGroupRules", + "application-autoscaling:PutScalingPolicy", + "ec2:DescribeVpcEndpoints", + "route53:GetChange", + "lambda:CreateEventSourceMapping", + "kms:TagResource", + "elasticloadbalancing:DescribeListeners", + "dynamodb:TagResource", + "ec2:CreateSecurityGroup", + "apigateway:PATCH", + "application-autoscaling:ListTagsForResource", + "kms:DescribeKey", + "ec2:ModifyVpcAttribute", + "ecr:DeleteRepositoryPolicy", + "ec2:AuthorizeSecurityGroupEgress", + "logs:DescribeLogGroups", + "kms:UpdateKeyDescription", + "logs:DeleteLogGroup", + "elasticloadbalancing:DescribeTags", + "ec2:DeleteRoute", + "backup:DeleteRecoveryPoint", + "cloudwatch:PutMetricAlarm", + "cloudwatch:TagResource", + "ec2:CreateVpcEndpoint", + "elasticloadbalancing:SetSecurityGroups", + "iam:DeletePolicyVersion", + "lambda:GetPolicy", + "ecr:GetRepositoryPolicy", + "ec2:AllocateAddress", + "ec2:ReleaseAddress", + "ec2:DisassociateAddress", + "logs:PutMetricFilter", + "logs:DeleteMetricFilter", + "ses:VerifyDomainIdentity", + "ses:VerifyDomainDkim", + "ses:DeleteIdentity", + "ses:SetIdentityMailFromDomain", + "dynamodb:UpdateTable", + "elasticloadbalancing:ModifyListener", + "lambda:GetLayerVersion", + "iam:CreatePolicyVersion", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage", + "ecr:CompleteLayerUpload", + "ecr:UploadLayerPart", + "ecr:InitiateLayerUpload", + "ecr:BatchCheckLayerAvailability", + "s3:PutObject", + "iam:ListRoles", + "lambda:UpdateFunctionCode", + "lambda:CreateFunction", + "lambda:GetFunction", + "lambda:UpdateFunctionConfiguration", + "lambda:GetFunctionConfiguration", + "appconfig:ListTagsForResource", + "appconfig:StartDeployment", + "appconfig:DeleteApplication", + "appconfig:GetLatestConfiguration", + "ecr:PutImage", + ] + Effect = "Allow" + Resource = [ + "*", + ] + Sid = "Statement1" + }, + ] + Version = "2012-10-17" + } + ) + tags = {} +} + +resource "aws_iam_role_policy_attachment" "github_action_policy_2_test" { + count = local.is_testing ? 1 : 0 + role = aws_iam_role.github_role_test[0].name + policy_arn = aws_iam_policy.github_action_policy_2_test[0].arn +} + +# aws_iam_policy.github_action_policy_2_test[0]: +resource "aws_iam_policy" "github_action_policy_2_test" { + count = local.is_testing ? 1 : 0 + description = null + name = "github-action-policy-2" + name_prefix = null + path = "/" + policy = jsonencode( + { + Statement = [ + { + Action = [ + "acm:RequestCertificate", + "acm:AddTagsToCertificate", + "ecs:PutClusterCapacityProviders", + "backup:ListRecoveryPointsByBackupVault", + "appconfig:TagResource", + "appconfig:CreateConfigurationProfile", + "appconfig:CreateExtensionAssociation", + "appconfig:DeleteConfigurationProfile", + "appconfig:CreateDeploymentStrategy", + "appconfig:CreateApplication", + "appconfig:GetDeploymentStrategy", + "appconfig:GetHostedConfigurationVersion", + "appconfig:ListExtensionAssociations", + "appconfig:ListDeploymentStrategies", + "appconfig:CreateHostedConfigurationVersion", + "appconfig:DeleteEnvironment", + "appconfig:UntagResource", + "appconfig:ListHostedConfigurationVersions", + "appconfig:ListEnvironments", + "appconfig:UpdateDeploymentStrategy", + "appconfig:GetExtensionAssociation", + "appconfig:GetExtension", + "appconfig:ListDeployments", + "appconfig:GetDeployment", + "appconfig:ListExtensions", + "appconfig:DeleteHostedConfigurationVersion", + "appconfig:StopDeployment", + "appconfig:CreateEnvironment", + "appconfig:UpdateEnvironment", + "appconfig:GetEnvironment", + "appconfig:ListConfigurationProfiles", + "appconfig:DeleteDeploymentStrategy", + "appconfig:ListApplications", + "appconfig:UpdateApplication", + "appconfig:CreateExtension", + "appconfig:GetConfiguration", + "appconfig:GetApplication", + "appconfig:UpdateConfigurationProfile", + "appconfig:GetConfigurationProfile", + "dynamodb:DescribeTable", + "dynamodb:GetItem", + "dynamodb:PutItem", + "dynamodb:DeleteItem", + "dynamodb:UpdateTimeToLive", + "s3:GetObject", + "s3:PutObject", + "s3:DeleteObject", + "lambda:GetLayerVersion", + "lambda:PublishLayerVersion", + "lambda:DeleteLayerVersion", + "lambda:ListLayerVersions", + "lambda:ListLayers", + "lambda:AddLayerVersionPermission", + "lambda:GetLayerVersionPolicy", + "lambda:RemoveLayerVersionPermission", + "lambda:DeleteFunctionConcurrency", + "lambda:PublishVersion", + "iam:CreateServiceLinkedRole", + "iam:UpdateAssumeRolePolicy", + "elasticloadbalancing:ModifyListenerAttributes", + "apigateway:SetWebACL", + "backup:ListRecoveryPointsByBackupVault", + "iam:UpdateAssumeRolePolicy", + "iam:TagRole", + "iam:CreateInstanceProfile", + "iam:AddRoleToInstanceProfile", + "iam:DeleteInstanceProfile", + "iam:TagPolicy", + "ssm:CreateDocument", + "ssm:DeleteDocument", + "sns:TagResource", + "ec2:DeleteNetworkInterface", + "resource-groups:DeleteGroup", + "events:TagResource", + "kms:Encrypt", + "kms:CreateGrant", + ] + Effect = "Allow" + Resource = [ + "*", + ] + Sid = "Statement1" + }, + ] + Version = "2012-10-17" + } + ) + tags = {} +} diff --git a/base_iam/main.tf b/base_iam/main.tf new file mode 100644 index 000000000..8fc4b38cc --- /dev/null +++ b/base_iam/main.tf @@ -0,0 +1 @@ +data "aws_caller_identity" "current" {} diff --git a/base_iam/policy_tool.py b/base_iam/policy_tool.py new file mode 100644 index 000000000..6f65ab812 --- /dev/null +++ b/base_iam/policy_tool.py @@ -0,0 +1,98 @@ +import json +import os +import subprocess +import sys + + +def get_policy_names(env, role_name): + filename = f"../infrastructure/iam_roles/{env}_{role_name}.json" + with open(filename, 'r') as file: + policies = json.load(file) + attached_policies = [p for p in policies["attached"] if p != "ReadOnlyAccess"] + return sorted(attached_policies) + + +def create_dummy_resources(env, policy_names): + filename = f"dummy_import_{env}.tf" + with open(filename, 'w') as file: + file.write(f'resource "aws_iam_role" "github_role_{env}" {{\n') + file.write(f' count = var.environment == "{env}" ? 1 : 0\n') + file.write('}\n\n') + + for policy_name in policy_names: + file.write(f'resource "aws_iam_policy" "{policy_name.replace("-", "_")}_{env}" {{\n') + file.write(f' count = var.environment == "{env}" ? 1 : 0\n') + file.write('}\n\n') + + +def run_command(command): + print(f"Running command: {command}") + result = os.system(command) + if result != 0: + print(f"Command failed with exit code {result}") + sys.exit(result) + + +def import_resources(aws_account_id, env, role_name, policy_names): + run_command(f'terraform import -var environment={env} aws_iam_role.github_role_{env}[0] {role_name} ') + for policy_name in policy_names: + resource_name = policy_name.replace("-", "_") + run_command(f'terraform import -var environment={env} aws_iam_policy.{resource_name}_{env}[0] arn:aws:iam::{aws_account_id}:policy/{policy_name}') + + +def tidy_resource_file(aws_account_id, env, source): + ignore_lines = [ + "arn", + "attachment_count", + "policy_id", + "id", + "create_date", + "unique_id", +] + output = [] + for line in source.split("\n"): + if [i for i in ignore_lines if f" {i} " in line]: + continue + + if line.startswith("resource "): + output.append(line.rstrip()) + output.append(f' count = var.environment == "{env}" ? 1 : 0') + continue + + output.append(line.replace(aws_account_id, "${var.aws_account_id}")) + + return "\n".join(output) + + +def generate_tf_file(aws_account_id, env, role_name, policy_names): + filename = f"imported_{env}.tf.txt" + with open(filename, 'w') as file: + command = f"terraform state show -no-color aws_iam_role.github_role_{env}[0]" + result = subprocess.run(command.split(" "), stdout=subprocess.PIPE).stdout.decode('utf-8') + file.write(f"{tidy_resource_file(aws_account_id, env, result)}\n\n") + + for policy_name in policy_names: + command = f"terraform state show -no-color aws_iam_policy.{policy_name.replace('-', '_')}_{env}[0]" + result = subprocess.run(command.split(" "), stdout=subprocess.PIPE).stdout.decode('utf-8') + file.write(f"{tidy_resource_file(aws_account_id, env, result)}\n\n") + + + + +command, aws_account_id, env, role_name = sys.argv[1:] +print(f"AWS Account ID: {aws_account_id}") +print(f"Command: {command}") + +if command == "import": + print("Importing policies...") + policy_names = get_policy_names(env, role_name) + print(policy_names) + if not os.path.exists(f"dummy_import_{env}.tf") and not os.path.exists(f"iam_github_{env}.tf"): + create_dummy_resources(env, policy_names) + import_resources(aws_account_id, env, role_name, policy_names) + +if command == "generate-tf-file": + print("Generating TF file...") + policy_names = get_policy_names(env, role_name) + print(policy_names) + generate_tf_file(aws_account_id, env, role_name, policy_names) \ No newline at end of file diff --git a/base_iam/preprod.tfvars b/base_iam/preprod.tfvars new file mode 100644 index 000000000..8681ad845 --- /dev/null +++ b/base_iam/preprod.tfvars @@ -0,0 +1,2 @@ +environment = "pre-prod" +owner = "nhse/ndr-team" diff --git a/base_iam/prod.tfvars b/base_iam/prod.tfvars new file mode 100644 index 000000000..c0cfc43c5 --- /dev/null +++ b/base_iam/prod.tfvars @@ -0,0 +1,2 @@ +environment = "prod" +owner = "nhse/ndr-team" diff --git a/base_iam/providers.tf b/base_iam/providers.tf new file mode 100644 index 000000000..1fbd2fe22 --- /dev/null +++ b/base_iam/providers.tf @@ -0,0 +1,13 @@ +provider "aws" { + region = var.region + allowed_account_ids = [ + var.aws_account_id + ] + default_tags { + tags = { + Owner = var.owner + Environment = var.environment + Workspace = terraform.workspace + } + } +} \ No newline at end of file diff --git a/base_iam/terraform.tf b/base_iam/terraform.tf new file mode 100644 index 000000000..d5512a934 --- /dev/null +++ b/base_iam/terraform.tf @@ -0,0 +1,17 @@ +# Based on settings in infrastructure/main.tf +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } + + + backend "s3" { + use_lockfile = true + region = "eu-west-2" + key = "ndr_base_iam/terraform.tfstate" + encrypt = true + } +} \ No newline at end of file diff --git a/base_iam/test.tfvars b/base_iam/test.tfvars new file mode 100644 index 000000000..77cdb18da --- /dev/null +++ b/base_iam/test.tfvars @@ -0,0 +1,2 @@ +environment = "test" +owner = "nhse/ndr-team" diff --git a/base_iam/variables.tf b/base_iam/variables.tf new file mode 100644 index 000000000..2b6e0d9e9 --- /dev/null +++ b/base_iam/variables.tf @@ -0,0 +1,32 @@ +variable "environment" { + description = "Environment to bootstrap (dev, pre-prod, prod, etc)" + type = string +} + +variable "region" { + type = string + default = "eu-west-2" + description = "The region to be used for bootstrapping" +} + +variable "owner" { + description = "Identifies the team or person responsible for the resource (used for tagging)." + type = string + default = "nhse/ndr-team" +} + +variable "aws_account_id" { + type = string + description = "The AWS Account ID (numeric)" +} + +locals { + is_sandbox = !contains(["ndr-dev", "ndr-test", "pre-prod", "prod"], terraform.workspace) + is_production = contains(["pre-prod", "prod"], terraform.workspace) + + is_sandbox_or_dev = !contains(["ndr-test", "pre-prod", "prod"], terraform.workspace) + is_development = terraform.workspace == "ndr-dev" + is_testing = terraform.workspace == "ndr-test" + is_pre_production = terraform.workspace == "pre-prod" + is_prod = terraform.workspace == "prod" +} \ No newline at end of file