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' }}