From 794b399b9f0abf0e9543057da69b9728d2391f5e Mon Sep 17 00:00:00 2001 From: ssvoss Date: Sun, 8 Nov 2026 19:14:56 -0500 Subject: [PATCH] CORE-6214: let Terraform Checks fetch private git modules over SSH run_terraform_checks took no secrets, so init:terraform couldn't download module sources from private github.com repositories, such as git@github.com:OpenSesame/devops-terraform-modules.git//... that identity-infrastructure uses. Repo Checks' Terraform job would always fail at Initialize there, and a contract exception doesn't help because CI still runs init. run_terraform_checks now takes an optional ORG_READ_ONLY_SSH_KEY. When it is set, configure-github-ssh 1.0.0 installs it with GitHub's pinned host keys just before Initialize, as run_repo_validation and tf_plan_single_root do, and the key and SSH config are removed right after, so Validate, tflint and Checkov never run with the org key installed. If SSH setup fails, Initialize and Validate are skipped. Without the secret the job behaves as before. run_repo_checks passes the secret it already declares for Standard through to the Terraform job. Releases run_terraform_checks 1.1.0 and run_repo_checks 1.1.0, which calls it from the same commit. Co-Authored-By: Claude Opus 5.5 --- .../workflows/CHANGELOGS/run_repo_checks.md | 8 +++++ .../CHANGELOGS/run_terraform_checks.md | 10 ++++++ .github/workflows/READMES/run_repo_checks.md | 10 ++++-- .../workflows/READMES/run_terraform_checks.md | 26 +++++++++++--- .../TESTS/run_repo_checks.unit.test.js | 21 +++++++++++- .../TESTS/run_terraform_checks.unit.test.js | 34 +++++++++++++++++++ .github/workflows/run_repo_checks.yml | 4 ++- .github/workflows/run_terraform_checks.yml | 19 ++++++++++- 8 files changed, 121 insertions(+), 11 deletions(-) diff --git a/.github/workflows/CHANGELOGS/run_repo_checks.md b/.github/workflows/CHANGELOGS/run_repo_checks.md index 57c43c4..d2c9140 100644 --- a/.github/workflows/CHANGELOGS/run_repo_checks.md +++ b/.github/workflows/CHANGELOGS/run_repo_checks.md @@ -2,6 +2,14 @@ All notable changes to the **run_repo_checks** reusable workflow are documented in this file. +## 1.1.0 + +### Added + +- Passes the optional `ORG_READ_ONLY_SSH_KEY` secret to the Terraform job, which calls + `run_terraform_checks` 1.1.0 from the same commit, so Terraform roots can use private git module + sources over SSH. + ## 1.0.4 ### Fixed diff --git a/.github/workflows/CHANGELOGS/run_terraform_checks.md b/.github/workflows/CHANGELOGS/run_terraform_checks.md index 09b6b47..cb86e56 100644 --- a/.github/workflows/CHANGELOGS/run_terraform_checks.md +++ b/.github/workflows/CHANGELOGS/run_terraform_checks.md @@ -2,6 +2,16 @@ All notable changes to the **run_terraform_checks** reusable workflow are documented in this file. +## 1.1.0 + +### Added + +- Optional `ORG_READ_ONLY_SSH_KEY` secret. When it is set, the job sets it up with + `configure-github-ssh` 1.0.0 before Initialize, so `init:terraform` can download private git module + sources over SSH, such as `git@github.com:OpenSesame/devops-terraform-modules.git//...`. It removes + the key and SSH config right after Initialize, so Validate, Lint, Format and Scan run without it. + If SSH setup fails, Initialize and Validate are skipped. Without the secret, nothing changes. + ## 1.0.3 ### Fixed diff --git a/.github/workflows/READMES/run_repo_checks.md b/.github/workflows/READMES/run_repo_checks.md index 47fc3fa..0cb5e1a 100644 --- a/.github/workflows/READMES/run_repo_checks.md +++ b/.github/workflows/READMES/run_repo_checks.md @@ -15,7 +15,7 @@ jobs: name: Repo Checks needs: ci-context if: ${{ needs.ci-context.outputs.should-run-ci == 'true' }} - uses: OpenSesame/core-github-actions/.github/workflows/run_repo_checks.yml@ # workflows/run_repo_checks/1.0.4 + uses: OpenSesame/core-github-actions/.github/workflows/run_repo_checks.yml@ # workflows/run_repo_checks/1.1.0 permissions: contents: read # Required to check out the code to lint, format-check, scan and validate. with: @@ -26,7 +26,7 @@ jobs: ``` Add `repo-checks` to `ci-result`'s needs. Replace `` with the full commit SHA of the -tag: `git rev-list -n1 workflows/run_repo_checks/1.0.4`. +tag: `git rev-list -n1 workflows/run_repo_checks/1.1.0`. ## Jobs @@ -90,6 +90,10 @@ Repository validation needs the validator kit at `.core-services/generated/valid `ORG_READ_ONLY_SSH_KEY` so it can warn when a newer validator release exists; without it, it validates without that check. +The Terraform job passes `ORG_READ_ONLY_SSH_KEY` to `run_terraform_checks`, which installs it only +while `init:terraform` runs, so roots can use private git module sources from github.com over SSH, +such as `git@github.com:OpenSesame/devops-terraform-modules.git//...`. + ## Summary The Summary job writes a table of each technology's file count, init, validate, lint, format and scan @@ -121,7 +125,7 @@ several: | Secret | Required | Description | | ------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------- | -| `ORG_READ_ONLY_SSH_KEY` | No | Read-only SSH key that can read OpenSesame/core-services-ai-tooling | +| `ORG_READ_ONLY_SSH_KEY` | No | Read-only SSH key that can read OpenSesame/core-services-ai-tooling and the private repositories the Terraform modules come from | | `ORG_GITHUB_PACKAGES_READ_ONLY_TOKEN` | No | Read-only token for the org's GitHub Packages npm registry, passed to the Markdown and JS checks for private `@opensesame` packages | ## Contribution diff --git a/.github/workflows/READMES/run_terraform_checks.md b/.github/workflows/READMES/run_terraform_checks.md index b343bff..5bd386b 100644 --- a/.github/workflows/READMES/run_terraform_checks.md +++ b/.github/workflows/READMES/run_terraform_checks.md @@ -13,11 +13,13 @@ jobs: name: Terraform needs: ci-context if: ${{ needs.ci-context.outputs.should-run-ci == 'true' }} - uses: OpenSesame/core-github-actions/.github/workflows/run_terraform_checks.yml@ # workflows/run_terraform_checks/1.0.3 + uses: OpenSesame/core-github-actions/.github/workflows/run_terraform_checks.yml@ # workflows/run_terraform_checks/1.1.0 permissions: contents: read # Required to check out the code to check. with: commit-identifier: ${{ needs.ci-context.outputs.commit-identifier }} + secrets: + ORG_READ_ONLY_SSH_KEY: ${{ secrets.ORG_READ_ONLY_SSH_KEY }} ``` `ci-context` is the caller's [`resolve_ci_context`](resolve_ci_context.md) job, which picks the commit @@ -49,10 +51,18 @@ each mise task with `mise run`. It doesn't need Node, `package.json` or `npm ci` - Keep a matching `package.json` script for each task, such as `"lint:terraform": "mise run lint:terraform"`, so `npm run check` still runs them locally. CI doesn't use those scripts. -- The job has no secrets or cloud credentials, so the `init:terraform` and `validate:terraform` tasks - must work without them: initialize with `-backend=false`, and use module sources that download - without credentials, such as public registries or public git. Roots with private git, S3 or other - authenticated module sources fail here. Plan stays in [`tf_plan_env_roots`](tf_plan_env_roots.md). +- The job has no cloud credentials, so the `init:terraform` and `validate:terraform` tasks must work + without them: initialize with `-backend=false`. Plan stays in + [`tf_plan_env_roots`](tf_plan_env_roots.md). +- Module sources must download without credentials, such as public registries or public git, or + over SSH from private github.com repositories, such as + `git@github.com:OpenSesame/devops-terraform-modules.git//...`. For those, pass + `ORG_READ_ONLY_SSH_KEY`. The job then sets it up with + [`configure-github-ssh`](../../actions/configure-github-ssh/README.md) just before Initialize, which + also pins GitHub's host keys, and removes the key and SSH config right after Initialize, so + Validate, Lint, Format and Scan run without it. Without the secret, the job sets up no SSH, and + roots with private git modules fail Initialize. If SSH setup fails, Initialize and Validate are + skipped. Roots with S3 or other authenticated module sources fail here. - The job fails if any step failed. ## Inputs @@ -61,6 +71,12 @@ each mise task with `mise run`. It doesn't need Node, `package.json` or `npm ci` | ------------------- | --------------------- | ----------------------------------- | | `commit-identifier` | Triggering ref or SHA | Commit SHA, tag, or branch to check | +## Secrets + +| Secret | Required | Description | +| ----------------------- | -------- | --------------------------------------------------------------------------------------------------- | +| `ORG_READ_ONLY_SSH_KEY` | No | Read-only SSH key that can read the private github.com repositories the Terraform modules come from | + ## Outputs | Output | Value | diff --git a/.github/workflows/TESTS/run_repo_checks.unit.test.js b/.github/workflows/TESTS/run_repo_checks.unit.test.js index d888d98..e400be9 100644 --- a/.github/workflows/TESTS/run_repo_checks.unit.test.js +++ b/.github/workflows/TESTS/run_repo_checks.unit.test.js @@ -1,6 +1,11 @@ const fs = require('node:fs'); const path = require('node:path'); -const { createRepository, extractRunScript, runScript } = require('./workflow-test-helpers'); +const { + createRepository, + extractRunScript, + readWorkflow, + runScript, +} = require('./workflow-test-helpers'); const workflowFile = 'run_repo_checks.yml'; @@ -213,3 +218,17 @@ describe('run_repo_checks Summary', () => { ); }); }); + +describe('run_repo_checks secrets', () => { + test('passes ORG_READ_ONLY_SSH_KEY to the Terraform job', () => { + const workflow = readWorkflow(workflowFile); + const terraform = workflow.indexOf(' terraform:'); + const markdown = workflow.indexOf(' markdown:'); + + expect(terraform).toBeGreaterThan(-1); + expect(markdown).toBeGreaterThan(terraform); + expect(workflow.slice(terraform, markdown)).toContain( + 'ORG_READ_ONLY_SSH_KEY: ${{ secrets.ORG_READ_ONLY_SSH_KEY }}' + ); + }); +}); diff --git a/.github/workflows/TESTS/run_terraform_checks.unit.test.js b/.github/workflows/TESTS/run_terraform_checks.unit.test.js index fc0010d..bddcd8f 100644 --- a/.github/workflows/TESTS/run_terraform_checks.unit.test.js +++ b/.github/workflows/TESTS/run_terraform_checks.unit.test.js @@ -38,3 +38,37 @@ describe('run_terraform_checks baselined count', () => { expect(result.outputs.count).toBe('0'); }); }); + +describe('run_terraform_checks SSH key lifetime', () => { + const workflow = readWorkflow(workflowFile); + const step = name => workflow.indexOf(` - name: ${name}`); + + test('sets up SSH only when the secret is set, and only for Initialize', () => { + const configure = step('Configure GitHub SSH credentials'); + const init = step('Initialize'); + + expect(configure).toBeGreaterThan(step('Set up mise')); + expect(init).toBeGreaterThan(configure); + expect(workflow.slice(configure, init)).toContain( + "if: ${{ env.HAS_ORG_READ_ONLY_SSH_KEY == 'true' }}" + ); + expect(workflow.slice(init, step('Validate'))).toContain("steps.ssh.outcome != 'failure'"); + }); + + test('removes the SSH key after Initialize and before Validate, Lint and Scan run', () => { + const init = step('Initialize'); + const cleanup = step('Remove GitHub SSH credentials'); + const validate = step('Validate'); + + expect(cleanup).toBeGreaterThan(init); + expect(validate).toBeGreaterThan(cleanup); + expect(step('Lint')).toBeGreaterThan(cleanup); + expect(step('Scan')).toBeGreaterThan(cleanup); + expect(workflow.slice(cleanup, validate)).toContain( + "if: ${{ always() && steps.ssh.outcome == 'success' }}" + ); + expect(workflow.slice(cleanup, validate)).toContain( + 'rm -rf "$RUNNER_TEMP/configure-github-ssh" ~/.ssh/config' + ); + }); +}); diff --git a/.github/workflows/run_repo_checks.yml b/.github/workflows/run_repo_checks.yml index 3a68a8d..c186306 100644 --- a/.github/workflows/run_repo_checks.yml +++ b/.github/workflows/run_repo_checks.yml @@ -10,7 +10,7 @@ on: default: '' secrets: ORG_READ_ONLY_SSH_KEY: - description: Read-only SSH key for OpenSesame/core-services-ai-tooling, passed to run_repo_validation for the latest validator release lookup + description: Read-only SSH key for the org's private repositories, passed to run_terraform_checks for private git module sources and to run_repo_validation for the latest validator release lookup required: false ORG_GITHUB_PACKAGES_READ_ONLY_TOKEN: description: Read-only token for the org's GitHub Packages npm registry, passed to the Markdown and JS checks for private @opensesame packages @@ -120,6 +120,8 @@ jobs: contents: read # Required by run_terraform_checks to check out the Terraform code. with: commit-identifier: ${{ inputs.commit-identifier }} + secrets: + ORG_READ_ONLY_SSH_KEY: ${{ secrets.ORG_READ_ONLY_SSH_KEY }} markdown: name: Markdown diff --git a/.github/workflows/run_terraform_checks.yml b/.github/workflows/run_terraform_checks.yml index 87ac6ae..361509a 100644 --- a/.github/workflows/run_terraform_checks.yml +++ b/.github/workflows/run_terraform_checks.yml @@ -8,6 +8,10 @@ on: type: string required: false default: '' + secrets: + ORG_READ_ONLY_SSH_KEY: + description: Read-only SSH key for the org's private repositories, used only by the initialize step to download private git module sources + required: false outputs: init: description: Outcome of the initialize step (success, failure, cancelled or skipped) @@ -40,6 +44,8 @@ jobs: runs-on: ubuntu-26.04 permissions: contents: read # Required to check out the Terraform code. + env: + HAS_ORG_READ_ONLY_SSH_KEY: ${{ secrets.ORG_READ_ONLY_SSH_KEY != '' }} outputs: init: ${{ steps.init.outcome }} validate: ${{ steps.validate.outcome }} @@ -62,11 +68,22 @@ jobs: version: 2026.10.25 sha256: 5d362ecc578dc7c146679b841dda949d6472cbf86ff5635db7afdca1b6b3fd87 + - name: Configure GitHub SSH credentials + id: ssh + if: ${{ env.HAS_ORG_READ_ONLY_SSH_KEY == 'true' }} + uses: OpenSesame/core-github-actions/.github/actions/configure-github-ssh@6d23fbcf9eb4c24c5f3404ef8bd175371f7e69a9 # actions/configure-github-ssh/1.0.0 + with: + ssh-private-key: ${{ secrets.ORG_READ_ONLY_SSH_KEY }} + - name: Initialize id: init - if: ${{ !cancelled() && steps.mise.outcome == 'success' }} + if: ${{ !cancelled() && steps.mise.outcome == 'success' && steps.ssh.outcome != 'failure' }} run: mise run init:terraform + - name: Remove GitHub SSH credentials + if: ${{ always() && steps.ssh.outcome == 'success' }} + run: rm -rf "$RUNNER_TEMP/configure-github-ssh" ~/.ssh/config + - name: Validate id: validate if: ${{ !cancelled() && steps.init.outcome == 'success' }}