Skip to content

Harden service-repo workflows: pin actions, fix Semgrep findings, add docs#40

Merged
akuzminsky merged 2 commits into
mainfrom
harden-service-repo-workflows
Jul 4, 2026
Merged

Harden service-repo workflows: pin actions, fix Semgrep findings, add docs#40
akuzminsky merged 2 commits into
mainfrom
harden-service-repo-workflows

Conversation

@akuzminsky

Copy link
Copy Markdown
Member

Why

The vuln-scanner-pr.yml sast-check job (Semgrep) that this module injects into every service repo reports 26 blocking findings against the workflow templates this same module ships — surfaced in aws-service-infrahouse-app#25. Since those files are Terraform-managed, the fix belongs here in modules/service-repo/templates/ and propagates to every service repo on the next apply.

What

Semgrep findings (all fixed):

  • github-actions-mutable-action-tag — every third-party action pinned to a full 40-char commit SHA with a # vN comment. trufflesecurity/trufflehog@main (a mutable branch ref) pinned to v3.95.8. Same-repo reusable-workflow calls (./.github/workflows/...) left unpinned.
  • gha-workflow-env-secret (vuln-scanner-pr.yml) — GITHUB_TOKEN moved from workflow-level env: to the two steps that use it.
  • secrets-inherit (terraform-drift-wrapper.yml) — dropped secrets: inherit. The called workflow only uses the automatic GITHUB_TOKEN, always available to reusable workflows and not listable in a secrets: map (reserved GITHUB_ prefix).

Keeping pins current:

  • Added renovate.jsonservice-repo previously injected none. Uses helpers:pinGitHubActionDigests (bumps SHA + comment on new releases), dashboard-approval gated, no automerge.

Incidental lint fix:

  • Environment matrices now render as YAML block sequences instead of jsonencode(...) flow arrays, so rendered output passes yamllint's commas rule. Verified equivalent via terraform console. Added the missing truthy disable on the wrapper's on: key.

Docs:

  • Ship .github/README.md into each service repo (repo-README.md) explaining the managed workflows + renovate.json — the natural home for this since renovate.json (strict JSON) can't carry a managed-by banner. Placed under .github/ so it doesn't shadow a hand-written root README.
  • Added a maintainer-facing templates/README.md resource map.

Notes

  • The .tftpl renders were verified locally with terraform console; the real plan runs in CI.
  • If a service repo has no root README.md, GitHub will surface .github/README.md as its homepage.

🤖 Generated with Claude Code

… docs

Resolves the 26 blocking Semgrep SAST findings that the injected
vuln-scanner-pr.yml sast-check job reports against the workflow templates
this module ships (surfaced in aws-service-infrahouse-app PR #25).

Workflow templates:
- Pin every third-party action to a full 40-char commit SHA with a # vN
  comment (github-actions-mutable-action-tag). trufflehog@main, a mutable
  branch ref, is pinned to v3.95.8. Same-repo reusable-workflow calls stay
  unpinned.
- vuln-scanner-pr.yml: move GITHUB_TOKEN out of the workflow-level env into
  the two steps that use it (gha-workflow-env-secret).
- terraform-drift-wrapper.yml: drop `secrets: inherit` (secrets-inherit).
  The called workflow only uses the automatic GITHUB_TOKEN, which is always
  available to reusable workflows and cannot be listed in a secrets: map.
- Render environment matrices as YAML block sequences instead of
  jsonencode(...) flow arrays so output passes yamllint's commas rule; add
  the missing truthy disable on the wrapper's on: key.

Keep the pins current:
- Add renovate.json (service-repo previously injected none) with
  helpers:pinGitHubActionDigests, dashboard-approval gated, no automerge.

Docs:
- Ship .github/README.md into each service repo (repo-README.md) explaining
  the managed workflows and renovate.json, since renovate.json cannot carry
  a managed-by banner comment.
- Add a maintainer-facing templates/README.md resource map.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
infrahouse8
infrahouse8 previously approved these changes Jul 4, 2026
@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown

State s3://infrahouse-github-control-state/terraform.tfstate

Affected resources counts

Success 🟢 Add 🟡 Change Destroy
2 6 0

Affected resources by action

Action Resources
🟢 module.aws_service_infrahouse_app.github_repository_file.renovate[0]
🟢 module.aws_service_infrahouse_app.github_repository_file.repo_readme[0]
🟡 module.aws_service_infrahouse_app.github_repository_file.secrets_scanner[0]
🟡 module.aws_service_infrahouse_app.github_repository_file.terraform_cd[0]
🟡 module.aws_service_infrahouse_app.github_repository_file.terraform_ci[0]
🟡 module.aws_service_infrahouse_app.github_repository_file.terraform_drift[0]
🟡 module.aws_service_infrahouse_app.github_repository_file.terraform_drift_wrapper[0]
🟡 module.aws_service_infrahouse_app.github_repository_file.vuln_scanner[0]
STDOUT
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create
  ~ update in-place

Terraform will perform the following actions:

  # module.aws_service_infrahouse_app.github_repository_file.renovate[0] will be created
  + resource "github_repository_file" "renovate" {
      + autocreate_branch_source_sha = (known after apply)
      + branch                       = (known after apply)
      + commit_message               = "Add renovate.json"
      + commit_sha                   = (known after apply)
      + content                      = jsonencode(
            {
              + "$schema"                   = "https://docs.renovatebot.com/renovate-schema.json"
              + dependencyDashboardApproval = true
              + extends                     = [
                  + "config:recommended",
                  + "helpers:pinGitHubActionDigests",
                ]
              + packageRules                = [
                  + {
                      + automerge     = false
                      + matchManagers = [
                          + "github-actions",
                        ]
                    },
                ]
              + prConcurrentLimit           = 1
              + rebaseWhen                  = "conflicted"
            }
        )
      + file                         = "renovate.json"
      + id                           = (known after apply)
      + overwrite_on_create          = true
      + ref                          = (known after apply)
      + repository                   = "aws-service-infrahouse-app"
      + repository_id                = (known after apply)
      + sha                          = (known after apply)
    }

  # module.aws_service_infrahouse_app.github_repository_file.repo_readme[0] will be created
  + resource "github_repository_file" "repo_readme" {
      + autocreate_branch_source_sha = (known after apply)
      + branch                       = (known after apply)
      + commit_message               = "Add .github/README.md documenting managed CI/CD"
      + commit_sha                   = (known after apply)
      + content                      = <<-EOT
            <!--
            This file is managed by Terraform in the github-control repository.
            Do not edit it here — changes are overwritten on the next apply.
            To change it, open a PR against https://github.com/infrahouse/github-control
            (modules/service-repo/templates/repo-README.md).
            -->
            
            # CI/CD & automation (managed)
            
            This repository's `.github/workflows/` and `renovate.json` are generated and kept in sync by
            [github-control](https://github.com/infrahouse/github-control) through the `service-repo` Terraform
            module. **Do not edit those files here** — any change is reverted on the next `terraform apply`. To
            change how CI/CD behaves, open a PR against github-control.
            
            ## Workflows
            
            ### `terraform-CI.yml` — plan on every PR
            
            Runs on `pull_request`. For each environment it runs `terraform plan`, posts the plan as a PR comment
            (`ih-plan publish`), and uploads the plan file to the state bucket (`ih-plan upload`) so the exact
            reviewed plan can be applied after merge.
            
            ### `terraform-CD.yml` — apply on merge
            
            Runs when a PR is merged. For each environment it downloads the plan CI stored, applies it, removes
            the stored plan, then runs `make smoke`. Environments deploy in the order set by each environment's
            `deploy_order`.
            
            ### `terraform-drift.yml` — reusable drift check
            
            A `workflow_call` reusable workflow. Runs `terraform plan -detailed-exitcode` for one environment; if
            drift is detected it opens a `config-drift`-labeled PR to reconcile.
            
            ### `terraform-drift-wrapper.yml` — scheduled drift sweep
            
            Runs daily (and on demand via `workflow_dispatch`). Calls `terraform-drift.yml` for every environment
            and posts to the `#infra` Slack channel on failure.
            
            ### `secrets-scanner.yml` — leaked-secret scan
            
            Runs on PRs and the merge queue. Uses TruffleHog to scan the diff for verified secrets.
            
            ### `vuln-scanner-pr.yml` — dependency + SAST scan
            
            Runs on PRs and the merge queue. One job runs OSV-Scanner for dependency CVEs; another runs Semgrep
            SAST (`semgrep scan --error`), which blocks the PR on findings.
            
            ## `renovate.json`
            
            Configures [Renovate](https://docs.renovatebot.com/) for this repo. Every GitHub Action in the
            workflows above is pinned to a full commit SHA (with a trailing `# vN` comment) to prevent
            supply-chain attacks from silently repointed tags or branches. Renovate's
            `helpers:pinGitHubActionDigests` preset keeps those pins current — it opens a PR bumping the SHA and
            comment when a new version ships. Updates are queued on the Dependency Dashboard for approval
            (`dependencyDashboardApproval`) and never automerge.
            
            `renovate.json` is strict JSON, so it carries no "managed by Terraform" banner comment, but it is
            managed exactly like the workflows — edit it in github-control, not here.
        EOT
      + file                         = ".github/README.md"
      + id                           = (known after apply)
      + overwrite_on_create          = true
      + ref                          = (known after apply)
      + repository                   = "aws-service-infrahouse-app"
      + repository_id                = (known after apply)
      + sha                          = (known after apply)
    }

  # module.aws_service_infrahouse_app.github_repository_file.secrets_scanner[0] will be updated in-place
  ~ resource "github_repository_file" "secrets_scanner" {
      ~ content             = <<-EOT
            # This file is managed by Terraform in github-control repository
            # Do not edit this file, all changes will be overwritten
            ---
            name: Leaked Secrets Scan
            on:  # yamllint disable-line rule:truthy
              pull_request:
              merge_group:
                branches: [main]
            
            jobs:
              TruffleHog:
                runs-on: ubuntu-latest
                steps:
                  - name: Checkout code
          -         uses: actions/checkout@v6
          +         uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6
                    with:
                      fetch-depth: 0
                  - name: TruffleHog OSS
          -         uses: trufflesecurity/trufflehog@main
          +         uses: trufflesecurity/trufflehog@00155c9dc586f34d189adc83d3ac2698c2ec551f  # v3.95.8
                    with:
                      path: ./
                      base: ${{ github.event.repository.default_branch }}
                      head: HEAD
                      extra_args: --only-verified
        EOT
        id                  = "aws-service-infrahouse-app:.github/workflows/secrets-scanner.yml:main"
        # (9 unchanged attributes hidden)
    }

  # module.aws_service_infrahouse_app.github_repository_file.terraform_cd[0] will be updated in-place
  ~ resource "github_repository_file" "terraform_cd" {
      ~ content             = <<-EOT
            ---
            name: "Terraform CD"
            
            on:  # yamllint disable-line rule:truthy
              pull_request:
                types:
                  - "closed"
            
            permissions:
              id-token: "write"
              contents: "read"
            
            concurrency:
              group: "terraform"
              cancel-in-progress: false
            
            jobs:
              deploy-order-0:
                if: "github.event.pull_request.merged"
                strategy:
                  fail-fast: false
                  matrix:
          -         env: ["sandbox"]
          +         env:
          +           - "sandbox"
                name: "Terraform Apply ${{ matrix.env }}"
                runs-on: ubuntu-24.04
                environment: "live-${{ matrix.env }}"
                timeout-minutes: 60
                env:
                  GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
                  REGION_JSON: "${{ vars.AWS_DEFAULT_REGION }}"
                  ROLE_GITHUB_JSON: "${{ vars.ROLE_GITHUB }}"
                  ROLE_SM_JSON: "${{ vars.ROLE_STATE_MANAGER }}"
                  TF_VAR_environment: "${{ matrix.env }}"
            
                defaults:
                  run:
                    shell: "bash"
            
                steps:
                  - name: "Checkout"
          -         uses: "actions/checkout@v6"
          +         uses: "actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10"  # v6
            
                  - name: "Extract Variables"
                    id: "extract_vars"
                    run: |
                      REGION=$(echo '${{ env.REGION_JSON }}' | jq -r '.${{ matrix.env }}')
                      echo "REGION=$REGION" >> "$GITHUB_OUTPUT"
                      ROLE_GITHUB=$(echo '${{ env.ROLE_GITHUB_JSON }}' | jq -r '.${{ matrix.env }}')
                      echo "ROLE_GITHUB=$ROLE_GITHUB" >> "$GITHUB_OUTPUT"
                      ROLE_SM=$(echo '${{ env.ROLE_SM_JSON }}' | jq -r '.${{ matrix.env }}')
                      echo "ROLE_STATE_MANAGER=$ROLE_SM" >> "$GITHUB_OUTPUT"
            
                  - name: "Configure AWS Credentials"
          -         uses: "aws-actions/configure-aws-credentials@v6"
          +         uses: "aws-actions/configure-aws-credentials@254c19bd240aabef8777f48595e9d2d7b972184b"  # v6
                    with:
                      role-to-assume: "${{ steps.extract_vars.outputs.ROLE_GITHUB }}"
                      role-session-name: "github-actions-${{ matrix.env }}"
                      aws-region: "${{ steps.extract_vars.outputs.REGION }}"
            
                  - name: "Set Terraform version"
                    id: "terraform_version"
                    run: |
                      echo "IH_TF_VERSION=$(cat .terraform-version)" >> "$GITHUB_OUTPUT"
            
                  - name: "Setup Terraform"
          -         uses: "hashicorp/setup-terraform@v4"
          +         uses: "hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e"  # v4
                    with:
                      terraform_version: "${{ steps.terraform_version.outputs.IH_TF_VERSION }}"
            
                  - name: "Set up Python"
          -         uses: "actions/setup-python@v6"
          +         uses: "actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1"  # v6
                    with:
                      python-version: "3.14"
            
                  - name: "Setup Python Environment"
                    working-directory: "environments/${{ matrix.env }}"
                    run: |
                      make bootstrap
            
                  - name: "Download Terraform Plan"
                    working-directory: "environments/${{ matrix.env }}"
                    run: |
                      ih-plan \
                        --aws-assume-role-arn \
                        ${{ steps.extract_vars.outputs.ROLE_STATE_MANAGER }} \
                        download \
                        plans/${{ matrix.env }}/${{ github.event.pull_request.number }}.plan \
                        tf.plan
            
                  - name: "Terraform Init"
                    working-directory: "environments/${{ matrix.env }}"
                    run: |
                      terraform init -input=false
            
                  - name: "Terraform Validate"
                    working-directory: "environments/${{ matrix.env }}"
                    run: |
                      terraform validate -no-color
            
                  - name: "Terraform Apply"
                    working-directory: "environments/${{ matrix.env }}"
                    run: |
                      make apply
            
                  - name: "Remove Plan"
                    working-directory: "environments/${{ matrix.env }}"
                    run: |
                      ih-plan \
                        --aws-assume-role-arn \
                        ${{ steps.extract_vars.outputs.ROLE_STATE_MANAGER }} \
                        remove \
                        plans/${{ matrix.env }}/${{ github.event.pull_request.number }}.plan
            
                  - name: "Smoke Test"
                    working-directory: "environments/${{ matrix.env }}"
                    run: |
                      make smoke ENV=${{ matrix.env }}
              deploy-order-1:
                if: "github.event.pull_request.merged"
                needs: ["deploy-order-0"]
                strategy:
                  fail-fast: false
                  matrix:
          -         env: ["production"]
          +         env:
          +           - "production"
                name: "Terraform Apply ${{ matrix.env }}"
                runs-on: ubuntu-24.04
                environment: "live-${{ matrix.env }}"
                timeout-minutes: 60
                env:
                  GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
                  REGION_JSON: "${{ vars.AWS_DEFAULT_REGION }}"
                  ROLE_GITHUB_JSON: "${{ vars.ROLE_GITHUB }}"
                  ROLE_SM_JSON: "${{ vars.ROLE_STATE_MANAGER }}"
                  TF_VAR_environment: "${{ matrix.env }}"
            
                defaults:
                  run:
                    shell: "bash"
            
                steps:
                  - name: "Checkout"
          -         uses: "actions/checkout@v6"
          +         uses: "actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10"  # v6
            
                  - name: "Extract Variables"
                    id: "extract_vars"
                    run: |
                      REGION=$(echo '${{ env.REGION_JSON }}' | jq -r '.${{ matrix.env }}')
                      echo "REGION=$REGION" >> "$GITHUB_OUTPUT"
                      ROLE_GITHUB=$(echo '${{ env.ROLE_GITHUB_JSON }}' | jq -r '.${{ matrix.env }}')
                      echo "ROLE_GITHUB=$ROLE_GITHUB" >> "$GITHUB_OUTPUT"
                      ROLE_SM=$(echo '${{ env.ROLE_SM_JSON }}' | jq -r '.${{ matrix.env }}')
                      echo "ROLE_STATE_MANAGER=$ROLE_SM" >> "$GITHUB_OUTPUT"
            
                  - name: "Configure AWS Credentials"
          -         uses: "aws-actions/configure-aws-credentials@v6"
          +         uses: "aws-actions/configure-aws-credentials@254c19bd240aabef8777f48595e9d2d7b972184b"  # v6
                    with:
                      role-to-assume: "${{ steps.extract_vars.outputs.ROLE_GITHUB }}"
                      role-session-name: "github-actions-${{ matrix.env }}"
                      aws-region: "${{ steps.extract_vars.outputs.REGION }}"
            
                  - name: "Set Terraform version"
                    id: "terraform_version"
                    run: |
                      echo "IH_TF_VERSION=$(cat .terraform-version)" >> "$GITHUB_OUTPUT"
            
                  - name: "Setup Terraform"
          -         uses: "hashicorp/setup-terraform@v4"
          +         uses: "hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e"  # v4
                    with:
                      terraform_version: "${{ steps.terraform_version.outputs.IH_TF_VERSION }}"
            
                  - name: "Set up Python"
          -         uses: "actions/setup-python@v6"
          +         uses: "actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1"  # v6
                    with:
                      python-version: "3.14"
            
                  - name: "Setup Python Environment"
                    working-directory: "environments/${{ matrix.env }}"
                    run: |
                      make bootstrap
            
                  - name: "Download Terraform Plan"
                    working-directory: "environments/${{ matrix.env }}"
                    run: |
                      ih-plan \
                        --aws-assume-role-arn \
                        ${{ steps.extract_vars.outputs.ROLE_STATE_MANAGER }} \
                        download \
                        plans/${{ matrix.env }}/${{ github.event.pull_request.number }}.plan \
                        tf.plan
            
                  - name: "Terraform Init"
                    working-directory: "environments/${{ matrix.env }}"
                    run: |
                      terraform init -input=false
            
                  - name: "Terraform Validate"
                    working-directory: "environments/${{ matrix.env }}"
                    run: |
                      terraform validate -no-color
            
                  - name: "Terraform Apply"
                    working-directory: "environments/${{ matrix.env }}"
                    run: |
                      make apply
            
                  - name: "Remove Plan"
                    working-directory: "environments/${{ matrix.env }}"
                    run: |
                      ih-plan \
                        --aws-assume-role-arn \
                        ${{ steps.extract_vars.outputs.ROLE_STATE_MANAGER }} \
                        remove \
                        plans/${{ matrix.env }}/${{ github.event.pull_request.number }}.plan
            
                  - name: "Smoke Test"
                    working-directory: "environments/${{ matrix.env }}"
                    run: |
                      make smoke ENV=${{ matrix.env }}
        EOT
        id                  = "aws-service-infrahouse-app:.github/workflows/terraform-CD.yml:main"
        # (9 unchanged attributes hidden)
    }

  # module.aws_service_infrahouse_app.github_repository_file.terraform_ci[0] will be updated in-place
  ~ resource "github_repository_file" "terraform_ci" {
      ~ content             = <<-EOT
            ---
            name: "Terraform CI"
            
            on:  # yamllint disable-line rule:truthy
              pull_request:
            
            permissions:
              id-token: "write"
              contents: "read"
              pull-requests: "write"
            
            concurrency:
              group: "terraform"
              cancel-in-progress: false
            
            jobs:
              plan:
                strategy:
                  fail-fast: false
                  matrix:
          -         env: ["production","sandbox"]
          +         env:
          +           - "production"
          +           - "sandbox"
                name: "Terraform Plan ${{ matrix.env }}"
                runs-on: ubuntu-24.04
                environment: "continuous-integration-${{ matrix.env }}"
                timeout-minutes: 15
                env:
                  GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
                  REGION_JSON: "${{ vars.AWS_DEFAULT_REGION }}"
                  ROLE_GITHUB_JSON: "${{ vars.ROLE_GITHUB }}"
                  ROLE_SM_JSON: "${{ vars.ROLE_STATE_MANAGER }}"
                  TF_VAR_environment: "${{ matrix.env }}"
            
                defaults:
                  run:
                    shell: "bash"
            
                steps:
                  - name: "Checkout"
          -         uses: "actions/checkout@v6"
          +         uses: "actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10"  # v6
            
                  - name: "Extract Variables"
                    id: "extract_vars"
                    run: |
                      REGION=$(echo '${{ env.REGION_JSON }}' | jq -r '.${{ matrix.env }}')
                      echo "REGION=$REGION" >> "$GITHUB_OUTPUT"
                      ROLE_GITHUB=$(echo '${{ env.ROLE_GITHUB_JSON }}' | jq -r '.${{ matrix.env }}')
                      echo "ROLE_GITHUB=$ROLE_GITHUB" >> "$GITHUB_OUTPUT"
                      ROLE_SM=$(echo '${{ env.ROLE_SM_JSON }}' | jq -r '.${{ matrix.env }}')
                      echo "ROLE_STATE_MANAGER=$ROLE_SM" >> "$GITHUB_OUTPUT"
            
                  - name: "Configure AWS Credentials"
          -         uses: "aws-actions/configure-aws-credentials@v6"
          +         uses: "aws-actions/configure-aws-credentials@254c19bd240aabef8777f48595e9d2d7b972184b"  # v6
                    with:
                      role-to-assume: "${{ steps.extract_vars.outputs.ROLE_GITHUB }}"
                      role-session-name: "github-actions-${{ matrix.env }}"
                      aws-region: "${{ steps.extract_vars.outputs.REGION }}"
            
                  - name: "Set Terraform version"
                    id: "terraform_version"
                    run: |
                      echo "IH_TF_VERSION=$(cat .terraform-version)" >> "$GITHUB_OUTPUT"
            
                  - name: "Setup Terraform"
          -         uses: "hashicorp/setup-terraform@v4"
          +         uses: "hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e"  # v4
                    with:
                      terraform_version: "${{ steps.terraform_version.outputs.IH_TF_VERSION }}"
            
                  - name: "Set up Python"
          -         uses: "actions/setup-python@v6"
          +         uses: "actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1"  # v6
                    with:
                      python-version: "3.14"
            
                  - name: "Setup Python Environment"
                    working-directory: "environments/${{ matrix.env }}"
                    run: |
                      make bootstrap
            
                  - name: "Code Style Check"
                    working-directory: "environments/${{ matrix.env }}"
                    run: |
                      make lint
            
                  - name: "Terraform Init"
                    working-directory: "environments/${{ matrix.env }}"
                    run: |
                      terraform init -input=false
            
                  - name: "Terraform Validate"
                    working-directory: "environments/${{ matrix.env }}"
                    run: |
                      terraform validate -no-color
            
                  - name: "Terraform Plan"
                    working-directory: "environments/${{ matrix.env }}"
                    run: |
                      make plan
                      ih-plan publish \
                        ${{ github.repository }} \
                        ${{ github.event.pull_request.number }} \
                        plan.stdout plan.stderr
            
                  - name: "Upload Terraform Plan"
                    working-directory: "environments/${{ matrix.env }}"
                    run: |
                      ih-plan \
                        --aws-assume-role-arn \
                        ${{ steps.extract_vars.outputs.ROLE_STATE_MANAGER }} \
                        upload \
                        --key-name=plans/${{ matrix.env }}/${{ github.event.pull_request.number }}.plan \
                        tf.plan
        EOT
        id                  = "aws-service-infrahouse-app:.github/workflows/terraform-CI.yml:main"
        # (9 unchanged attributes hidden)
    }

  # module.aws_service_infrahouse_app.github_repository_file.terraform_drift[0] will be updated in-place
  ~ resource "github_repository_file" "terraform_drift" {
      ~ content             = <<-EOT
            ---
            name: "Terraform Drift Detection"
            
            on:  # yamllint disable-line rule:truthy
              workflow_call:
                inputs:
                  env:
                    type: "string"
                    required: true
            
            permissions:
              id-token: "write"  # This is required for requesting the JWT
              contents: "write"
              pull-requests: "write"
              issues: "write"  # Required to create and apply labels
            
            concurrency:
              group: "terraform-drift-${{ inputs.env }}"
              cancel-in-progress: false
            
            jobs:
              terraform-drift:
                name: "Terraform Drift Detection"
                runs-on: ubuntu-24.04
                environment: "continuous-integration-${{ inputs.env }}"
                timeout-minutes: 15
                env:
                  GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
                  REGION_JSON: "${{ vars.AWS_DEFAULT_REGION }}"
            
                defaults:
                  run:
                    shell: "bash"
                    working-directory: "environments/${{ inputs.env }}"
            
                steps:
                  - name: "Checkout"
          -         uses: "actions/checkout@v6"
          +         uses: "actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10"  # v6
            
                  - name: "Extract Variables"
                    id: "extract_vars"
                    env:
                      REGION_JSON_CONTENT: ${{ env.REGION_JSON }}
                      TARGET_ENV: ${{ inputs.env }}
                    run: |
                      REGION=$(echo "$REGION_JSON_CONTENT" | jq -r ".${TARGET_ENV}")
                      echo "REGION=$REGION" >> "$GITHUB_OUTPUT"
            
                  - name: "Configure AWS Credentials"
          -         uses: "aws-actions/configure-aws-credentials@v6"
          +         uses: "aws-actions/configure-aws-credentials@254c19bd240aabef8777f48595e9d2d7b972184b"  # v6
                    with:
                      role-to-assume: "${{ vars.ROLE_GITHUB }}"
                      role-session-name: "github-actions-${{ inputs.env }}"
                      aws-region: "${{ steps.extract_vars.outputs.REGION }}"
            
                  - name: "Set Terraform version"
                    id: "terraform_version"
                    run: |
                      echo "IH_TF_VERSION=$(cat .terraform-version)" >> "$GITHUB_OUTPUT"
            
                  - name: "Setup Terraform"
          -         uses: "hashicorp/setup-terraform@v4"
          +         uses: "hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e"  # v4
                    with:
                      terraform_version: "${{ steps.terraform_version.outputs.IH_TF_VERSION }}"
            
                  - name: "Set up Python"
          -         uses: "actions/setup-python@v6"
          +         uses: "actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1"  # v6
                    with:
                      python-version: "3.14"
            
                  - name: "Setup Python Environment"
                    run: |
                      make bootstrap-ci
            
                  - name: "Terraform Init"
                    run: |
                      terraform init -input=false
            
                  - name: "Check if any changes are planned"
                    id: "check_drift"
                    run: |
                      terraform plan -no-color -input=false -detailed-exitcode
            
                  - name: Cleanup working dir
                    run: git clean -df
            
                  - name: "Record Config Drift Log Entry"
                    if: "steps.check_drift.outputs.exitcode == 2"
                    run: |
                      git \
                      -c user.name="${{ github.actor }}" \
                      -c user.email="${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com" \
                      commit -m "Reconcile configuration drift" --allow-empty
            
                  - name: "Check for open PR with label"
                    id: "check_label"
                    if: "steps.check_drift.outputs.exitcode == 2"
                    run: |
                      LABEL="config-drift"
                      REPO="${{ github.repository }}"
                      COUNT=$(gh pr list --repo "$REPO" --label "$LABEL" --state open --json number --jq 'length')
                      echo "Found $COUNT open PR(s) with label '$LABEL'"
                      if [[ $COUNT -gt 0 ]]; then
                        echo "label_exists=true" >> $GITHUB_OUTPUT
                      else
                        echo "label_exists=false" >> $GITHUB_OUTPUT
                      fi
            
                  - name: "Create Pull Request"
                    if: "steps.check_label.outputs.label_exists == 'false'"
          -         uses: "peter-evans/create-pull-request@v8"
          +         uses: "peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1"  # v8
                    with:
                      token: "${{ steps.app-token.outputs.token }}"
                      base: "main"
                      branch: "create-pull-request/config-drift"
                      title: "[config-drift] Reconcile Terraform configuration drift in ${{ inputs.env }}"
                      commit-message: "[config-drift] Configuration drift record for ${{ inputs.env }}"
                      team-reviewers: "devops-members"
                      labels: |
                        config-drift
        EOT
        id                  = "aws-service-infrahouse-app:.github/workflows/terraform-drift.yml:main"
        # (9 unchanged attributes hidden)
    }

  # module.aws_service_infrahouse_app.github_repository_file.terraform_drift_wrapper[0] will be updated in-place
  ~ resource "github_repository_file" "terraform_drift_wrapper" {
      ~ content             = <<-EOT
            ---
            name: "[Meta] Terraform Drift Detection"
            
          - on:
          + on:  # yamllint disable-line rule:truthy
              workflow_dispatch:
              schedule:
                # Runs daily at 10:XX AM UTC
                - cron: "57 10 * * *"
            
            jobs:
              config-drift-check:
                strategy:
                  matrix:
          -         env: ["production","sandbox"]
          +         env:
          +           - "production"
          +           - "sandbox"
                uses: "./.github/workflows/terraform-drift.yml"
                with:
                  env: "${{ matrix.env }}"
          -     secrets: "inherit"
            
              notify_on_failure:
                name: "Notify #infra slack on failure"
                needs: ["config-drift-check"]
                if: ${{ failure() }}
                runs-on: ubuntu-latest
                steps:
                  - name: Post to Slack
          -         uses: "slackapi/slack-github-action@v2.1.1"
          +         uses: "slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a"  # v2.1.1
                    with:
                      method: "chat.postMessage"
                      token: ${{ secrets.SLACK_BOT_TOKEN }}
                      payload: |
                        channel: "${{ vars.INFRA_NOTIFICATION_CHANNEL }}"
                        text: "❌ Terraform Drift Detection failed for ${{ github.repository }}. Check the workflow run for more details: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}."
        EOT
        id                  = "aws-service-infrahouse-app:.github/workflows/terraform-drift-wrapper.yml:main"
        # (9 unchanged attributes hidden)
    }

  # module.aws_service_infrahouse_app.github_repository_file.vuln_scanner[0] will be updated in-place
  ~ resource "github_repository_file" "vuln_scanner" {
      ~ content             = <<-EOT
            # This file is managed by Terraform in github-control repository
            # Do not edit this file, all changes will be overwritten
            ---
            name: OSV-Scanner PR Scan
            
            on:  # yamllint disable-line rule:truthy
              pull_request:
                branches: [main]
              merge_group:
                branches: [main]
            
            permissions:
              contents: read
              pull-requests: write
            
          - env:
          -   GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
          - 
            jobs:
              vulnerability-check:
                runs-on: ubuntu-24.04
                steps:
          -       - uses: actions/checkout@v6
          +       - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6
                  - name: Set up Python
          -         uses: actions/setup-python@v6
          +         uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1  # v6
                    with:
                      python-version: "3.14"
                  - name: Install dependencies
                    run: pip install -r requirements.txt
                  - name: Install osv-scanner
                    run: |
                      curl -fsSL -o /usr/local/bin/osv-scanner "https://github.com/google/osv-scanner/releases/download/v2.2.4/osv-scanner_linux_amd64"
                      chmod +x /usr/local/bin/osv-scanner
                      osv-scanner --version
                  - name: Detect vulnerabilities
          +         env:
          +           GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
                    run: |
                      if [ -n "${{ github.event.pull_request.number }}" ]; then
                        ih-github scan \
                        --repo ${{ github.repository }} \
                        --pull-request ${{ github.event.pull_request.number }}
                      else
                        ih-github scan
                      fi
            
              sast-check:
                runs-on: ubuntu-24.04
                steps:
          -       - uses: actions/checkout@v6
          +       - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6
                  - name: Set up Python
          -         uses: actions/setup-python@v6
          +         uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1  # v6
                    with:
                      python-version: "3.14"
            
                  - name: Install dependencies
                    run: pip install -r requirements.txt
                  - name: SAST
          +         env:
          +           GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
                    run: |
                      if [ -n "${{ github.event.pull_request.number }}" ]; then
                        ih-github run \
                        ${{ github.repository }} \
                        ${{ github.event.pull_request.number }} \
                        semgrep scan --error
                      else
                        semgrep scan --error
                      fi
        EOT
        id                  = "aws-service-infrahouse-app:.github/workflows/vuln-scanner-pr.yml:main"
        # (9 unchanged attributes hidden)
    }

Plan: 2 to add, 6 to change, 0 to destroy.

Warning: Argument is deprecated

  with module.ih_8_repos.github_repository.repo,
  on modules/local-repo/repos.tf line 4, in resource "github_repository" "repo":
   4:   has_downloads        = false

This attribute is no longer in use, but it hasn't been removed yet. It will
be removed in a future version. See
https://github.com/orgs/community/discussions/102145#discussioncomment-8351756

(and 6 more similar warnings elsewhere)

Warning: Deprecated attribute

  on .terraform/modules/actions-runner-pem-493370826424-uw1/data_sources.tf line 11, in data "external" "secret_value":
  11:     "python", "${path.module}/assets/get_secret.py", data.aws_region.current.name, aws_secretsmanager_secret.secret.id, data.aws_iam_role.caller_role.arn

The attribute "name" is deprecated. Refer to the provider documentation for
details.

(and 5 more similar warnings elsewhere)

─────────────────────────────────────────────────────────────────────────────

Saved the plan to: tf.plan

To perform exactly these actions, run the following command to apply:
    terraform apply "tf.plan"
Releasing state lock. This may take a few moments...
metadata
eyJzMzovL2luZnJhaG91c2UtZ2l0aHViLWNvbnRyb2wtc3RhdGUvdGVycmFmb3JtLnRmc3RhdGUiOiB7InN1Y2Nlc3MiOiB0cnVlLCAiYWRkIjogMiwgImNoYW5nZSI6IDYsICJkZXN0cm95IjogMH19

…commit message

The full CI plan surfaced two pre-existing drifts unrelated to the workflow
hardening; since CD applies the whole plan on merge, fix them here so they
don't ride along.

- teams.tf set only `username` in the members block, so Terraform defaulted
  every team member to role "member" and wanted to demote infrahouse8 (the
  out-of-band admin) from maintainer. Add an explicit `team_role` per member
  (infrahouse8 = maintainer, others = member) and set it in the content block.
- service-repo makefile_fragment had commit_message "Add makefiles/Makefile"
  while state held "Update makefiles/Makefile", producing metadata-only churn.
  Match the config to state. Only aws-service-infrahouse-app uses the module.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@akuzminsky akuzminsky merged commit cc8ebd1 into main Jul 4, 2026
2 checks passed
@akuzminsky akuzminsky deleted the harden-service-repo-workflows branch July 4, 2026 21:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants