diff --git a/.agents/skills/watch-github-actions/SKILL.md b/.agents/skills/watch-github-actions/SKILL.md index 6a5893a234..33652398e5 100644 --- a/.agents/skills/watch-github-actions/SKILL.md +++ b/.agents/skills/watch-github-actions/SKILL.md @@ -145,6 +145,11 @@ The public diagnostic intentionally omits them; detailed review needs an approved private destination. Helpers must come from the workflow revision, not from the candidate under scan. +`setup-nix` always uses read-only Cachix access. `Populate Nix Cache` is the only +cache writer and runs after pushes to `main`. If it fails because its credential +is missing, follow the environment setup in `CI.md#nix-cache-access`; never add +a Cachix write token to repository or organization secrets. + `setup-nix` retries development-shell preparation once when `prepare-shell` is enabled. Inspect both attempts in the job log; `setup-rust` assumes the shell has already been prepared. Cargo, lint, and test commands are not retried. diff --git a/.github/actions/setup-nix/action.yml b/.github/actions/setup-nix/action.yml index 5ad0a071fa..3ca4b14f70 100644 --- a/.github/actions/setup-nix/action.yml +++ b/.github/actions/setup-nix/action.yml @@ -2,14 +2,9 @@ # SPDX-License-Identifier: Apache-2.0 name: Setup Nix -description: Install Determinate Nix, configure Cachix, and optionally prepare the development shell +description: Install Determinate Nix, configure read-only Cachix access, and optionally prepare the development shell inputs: - cachix-auth-token: - description: Token used to write build outputs to Cachix - required: false - default: "" - prepare-shell: description: Prepare the development shell, retrying once on failure required: false @@ -30,8 +25,7 @@ runs: - uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17 with: name: openshell - authToken: ${{ inputs.cachix-auth-token }} - skipPush: ${{ inputs.cachix-auth-token == '' }} + skipPush: true - name: Prepare Nix development shell if: inputs.prepare-shell == 'true' diff --git a/.github/workflows/branch-checks.yml b/.github/workflows/branch-checks.yml index 075cf5b2c8..a25e3198a9 100644 --- a/.github/workflows/branch-checks.yml +++ b/.github/workflows/branch-checks.yml @@ -192,7 +192,6 @@ jobs: - uses: ./.github/actions/setup-nix with: - cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} prepare-shell: "true" shell-installable: .#devShells.x86_64-linux.default @@ -211,7 +210,6 @@ jobs: - uses: ./.github/actions/setup-nix with: prepare-shell: "true" - cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} - name: Format shell: nix develop -c bash -euo pipefail {0} @@ -233,7 +231,6 @@ jobs: - uses: ./.github/actions/setup-nix with: prepare-shell: "true" - cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} - name: Verify Cargo lockfiles shell: nix develop -c bash -euo pipefail {0} @@ -261,7 +258,6 @@ jobs: - uses: ./.github/actions/setup-nix with: prepare-shell: "true" - cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} - uses: ./.github/actions/setup-rust with: @@ -298,7 +294,6 @@ jobs: - uses: ./.github/actions/setup-nix with: prepare-shell: "true" - cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} - uses: ./.github/actions/setup-rust with: @@ -329,7 +324,6 @@ jobs: - uses: ./.github/actions/setup-nix with: prepare-shell: "true" - cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} - uses: ./.github/actions/setup-rust with: @@ -365,7 +359,6 @@ jobs: - uses: ./.github/actions/setup-nix with: prepare-shell: "true" - cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} - uses: ./.github/actions/setup-rust with: @@ -415,7 +408,6 @@ jobs: - uses: ./.github/actions/setup-nix with: prepare-shell: "true" - cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} - uses: ./.github/actions/setup-rust with: @@ -440,7 +432,6 @@ jobs: - uses: ./.github/actions/setup-nix with: prepare-shell: "true" - cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} - uses: ./.github/actions/setup-rust with: @@ -467,7 +458,6 @@ jobs: - uses: ./.github/actions/setup-nix with: prepare-shell: "true" - cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} - uses: ./.github/actions/setup-rust with: @@ -524,6 +514,7 @@ jobs: - name: Test release qualification summary run: mise run test:qualification-summary + go: name: Go SDK needs: pr_metadata diff --git a/.github/workflows/branch-e2e.yml b/.github/workflows/branch-e2e.yml index db4c96b936..6a997bdc66 100644 --- a/.github/workflows/branch-e2e.yml +++ b/.github/workflows/branch-e2e.yml @@ -108,8 +108,6 @@ jobs: uses: ./.github/workflows/build-binaries.yml with: cargo-version: ${{ needs.version.outputs.cargo }} - secrets: - CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} build-gateway-plain: name: openshell-gateway driver-free (${{ matrix.triple }}) @@ -134,7 +132,6 @@ jobs: - uses: ./.github/actions/setup-nix with: prepare-shell: "true" - cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} - uses: ./.github/actions/build-rust-binary with: package: openshell-gateway @@ -175,7 +172,6 @@ jobs: - uses: ./.github/actions/setup-nix with: prepare-shell: "true" - cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} - uses: ./.github/actions/build-rust-binary with: package: ${{ matrix.package }} @@ -191,8 +187,6 @@ jobs: uses: ./.github/workflows/build-vm-driver.yml with: cargo-version: ${{ needs.version.outputs.cargo }} - secrets: - CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} build-images: needs: [pr_metadata, build-binaries] diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml index 1289968d14..0eba8b96c5 100644 --- a/.github/workflows/build-binaries.yml +++ b/.github/workflows/build-binaries.yml @@ -17,9 +17,6 @@ on: required: false type: string default: "" - secrets: - CACHIX_AUTH_TOKEN: - required: true permissions: contents: read @@ -52,7 +49,6 @@ jobs: - uses: ./.github/actions/setup-nix with: prepare-shell: "true" - cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} - uses: ./.github/actions/build-rust-binary with: package: ${{ env.PACKAGE }} diff --git a/.github/workflows/build-vm-driver.yml b/.github/workflows/build-vm-driver.yml index a93f6928da..635d4a97ec 100644 --- a/.github/workflows/build-vm-driver.yml +++ b/.github/workflows/build-vm-driver.yml @@ -17,9 +17,6 @@ on: required: false type: string default: "" - secrets: - CACHIX_AUTH_TOKEN: - required: true permissions: contents: read @@ -54,7 +51,6 @@ jobs: - uses: ./.github/actions/setup-nix with: prepare-shell: "true" - cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} - name: Download openshell-sandbox uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 diff --git a/.github/workflows/cachix.yml b/.github/workflows/cachix.yml new file mode 100644 index 0000000000..80c1cf5247 --- /dev/null +++ b/.github/workflows/cachix.yml @@ -0,0 +1,67 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +name: Populate Nix Cache + +on: + push: + branches: [main] + +permissions: + contents: read + +concurrency: + group: cachix-write + cancel-in-progress: false + +jobs: + populate: + if: github.repository == 'NVIDIA/OpenShell' && github.event_name == 'push' && github.ref == 'refs/heads/main' + # Configure this environment to allow only the main branch BEFORE adding + # its secret. Ref checks in YAML cannot protect a repository-wide secret. + environment: cachix-write + strategy: + fail-fast: false + matrix: + include: + - system: x86_64-linux + runner: ubuntu-24.04 + - system: aarch64-linux + runner: ubuntu-24.04-arm + - system: aarch64-darwin + runner: macos-15-xlarge + # Keep credential-bearing work on fresh GitHub-hosted runners. + runs-on: ${{ matrix.runner }} + timeout-minutes: 90 + steps: + - name: Require the environment credential + env: + CACHIX_WRITE_TOKEN: ${{ secrets.CACHIX_WRITE_TOKEN }} + run: | + if [ -z "$CACHIX_WRITE_TOKEN" ]; then + echo "::error::Configure the cachix-write environment as documented in CI.md." + exit 1 + fi + + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + ref: ${{ github.sha }} + + - uses: DeterminateSystems/determinate-nix-action@e847f25223456b3058e12f7fc9d777b6a6f25060 # v3.23.2 + with: + github-token: ${{ github.token }} + summarize: false + + - uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17 + with: + name: openshell + authToken: ${{ secrets.CACHIX_WRITE_TOKEN }} + + - name: Build the development shell and VM runtime + env: + NIX_SYSTEM: ${{ matrix.system }} + run: | + # HTTP 416 is not retried by Nix; restart the build once on failure. + nix build --no-link ".#devShells.${NIX_SYSTEM}.default" .#vm-runtime || \ + nix build --no-link ".#devShells.${NIX_SYSTEM}.default" .#vm-runtime diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 957d9130ec..8a6babfa80 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -27,9 +27,6 @@ on: source_sha: description: Source commit resolved from the release tag value: ${{ jobs.compute-versions.outputs.source_sha }} - secrets: - CACHIX_AUTH_TOKEN: - required: false permissions: contents: read @@ -116,8 +113,6 @@ jobs: cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} supervisor-image-tag: ${{ needs.compute-versions.outputs.semver }} checkout-ref: ${{ needs.compute-versions.outputs.source_sha }} - secrets: - CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} build-vm-driver: needs: [compute-versions, build-binaries] @@ -128,8 +123,6 @@ jobs: cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} supervisor-image-tag: ${{ needs.compute-versions.outputs.semver }} checkout-ref: ${{ needs.compute-versions.outputs.source_sha }} - secrets: - CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} package-binaries: needs: [compute-versions, build-binaries, build-vm-driver] diff --git a/.github/workflows/release-dev.yml b/.github/workflows/release-dev.yml index aec77972ee..68030237e3 100644 --- a/.github/workflows/release-dev.yml +++ b/.github/workflows/release-dev.yml @@ -68,8 +68,6 @@ jobs: cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} supervisor-image-tag: dev checkout-ref: ${{ github.sha }} - secrets: - CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} build-vm-driver: needs: [compute-versions, build-binaries] @@ -80,8 +78,6 @@ jobs: cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} supervisor-image-tag: dev checkout-ref: ${{ github.sha }} - secrets: - CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} package-binaries: needs: [build-binaries, build-vm-driver] diff --git a/.github/workflows/release-prerelease.yml b/.github/workflows/release-prerelease.yml index 72486958c1..eebdd68543 100644 --- a/.github/workflows/release-prerelease.yml +++ b/.github/workflows/release-prerelease.yml @@ -28,8 +28,6 @@ jobs: with: release-tag: ${{ inputs.tag || github.ref_name }} is-prerelease: true - secrets: - CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} publish: name: Publish @@ -66,7 +64,6 @@ jobs: is-prerelease: true secrets: CODEX_SECURITY_API_KEY: ${{ secrets.CODEX_SECURITY_API_KEY }} - CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} notify-prerelease-success: name: Notify Prerelease Success diff --git a/.github/workflows/release-qualification.yml b/.github/workflows/release-qualification.yml index 3dc0ca3c9e..c1bb2c2069 100644 --- a/.github/workflows/release-qualification.yml +++ b/.github/workflows/release-qualification.yml @@ -21,8 +21,6 @@ on: secrets: CODEX_SECURITY_API_KEY: required: true - CACHIX_AUTH_TOKEN: - required: false permissions: contents: read @@ -51,8 +49,6 @@ jobs: ghcr.io/nvidia/openshell/gateway:${{ inputs.source-sha }} ghcr.io/nvidia/openshell/sandbox:${{ inputs.source-sha }} ghcr.io/nvidia/openshell/supervisor:${{ inputs.source-sha }} - secrets: - CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} prepare-integration: permissions: diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index e3ac6f5947..5d9ae10cbc 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -26,8 +26,6 @@ jobs: with: release-tag: ${{ inputs.tag || github.ref_name }} is-prerelease: false - secrets: - CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} qualify: name: Qualify @@ -45,7 +43,6 @@ jobs: is-prerelease: false secrets: CODEX_SECURITY_API_KEY: ${{ secrets.CODEX_SECURITY_API_KEY }} - CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} publish: name: Publish diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index f224f8ab14..80cb170300 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -40,9 +40,6 @@ on: description: Newline-separated packaged Helm chart OCI references for Trivy default: "" type: string - secrets: - CACHIX_AUTH_TOKEN: - required: false permissions: contents: read @@ -73,8 +70,6 @@ jobs: ignore-unfixed: false images: ${{ inputs.images }} charts: ${{ inputs.charts }} - secrets: - CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} cargo-deny: name: Cargo Deny @@ -95,5 +90,3 @@ jobs: with: candidate_ref: ${{ inputs.candidate_ref }} fail-on-findings: ${{ inputs.fail-on-static-findings }} - secrets: - CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} diff --git a/.github/workflows/trivy-scan.yml b/.github/workflows/trivy-scan.yml index 5b6e0156cc..8afc2f3730 100644 --- a/.github/workflows/trivy-scan.yml +++ b/.github/workflows/trivy-scan.yml @@ -37,9 +37,6 @@ on: description: Upload results to GitHub Code Scanning type: boolean default: true - secrets: - CACHIX_AUTH_TOKEN: - description: Token used to write Nix build outputs to Cachix workflow_dispatch: inputs: @@ -106,7 +103,6 @@ jobs: uses: ./.github/actions/setup-nix with: prepare-shell: "true" - cachix-auth-token: ${{ github.event_name != 'pull_request' && secrets.CACHIX_AUTH_TOKEN || '' }} - name: Test scan reporting run: tasks/scripts/trivy-scan-test.sh diff --git a/.github/workflows/workflow-security.yml b/.github/workflows/workflow-security.yml index c5a7f9ac76..855e263af7 100644 --- a/.github/workflows/workflow-security.yml +++ b/.github/workflows/workflow-security.yml @@ -22,9 +22,6 @@ on: description: Fail on Zizmor HIGH findings; Actionlint remains informational default: false type: boolean - secrets: - CACHIX_AUTH_TOKEN: - required: false permissions: contents: read @@ -55,7 +52,6 @@ jobs: uses: ./.github/actions/setup-nix with: prepare-shell: "true" - cachix-auth-token: ${{ github.event_name != 'pull_request' && secrets.CACHIX_AUTH_TOKEN || '' }} - name: Run Actionlint run: | @@ -143,7 +139,6 @@ jobs: uses: ./.github/actions/setup-nix with: prepare-shell: "true" - cachix-auth-token: ${{ github.event_name != 'pull_request' && secrets.CACHIX_AUTH_TOKEN || '' }} - name: Run Zizmor id: findings diff --git a/CI.md b/CI.md index b886bd70e3..fb1f5a7965 100644 --- a/CI.md +++ b/CI.md @@ -17,6 +17,23 @@ Manual admission does not change the bot's automatic trust policy for ready PRs. Merge queue validation is a second integration gate for `main`. After a PR has passed the required PR-head statuses, a maintainer adds it to the merge queue. GitHub creates a temporary merge-group branch that combines the latest `main`, the queued PR, and any earlier queued PRs. The same required `OpenShell / ...` status contexts are then published against the merge-group SHA before GitHub merges it. +### Nix cache access + +All CI jobs use the public `openshell` Cachix cache read-only. `Populate Nix +Cache` is the sole writer and runs on GitHub-hosted runners after pushes to `main`. + +Before enabling cache writes, a repository administrator must: + +1. Revoke the old Cachix write token and remove `CACHIX_AUTH_TOKEN` from + repository and organization secrets; audit its uploads and invalidate + untrusted entries. +2. Create the `cachix-write` environment, restrict it to the exact protected + `main` branch with no tag rules or administrator bypass, then add a fresh + `CACHIX_WRITE_TOKEN` only to that environment. + +Mirrored PRs can edit workflow YAML, so the GitHub [environment restriction](https://docs.github.com/en/actions/reference/workflows-and-actions/deployments-and-environments) +is the security boundary. Keep writes disabled until this migration is complete. + ### Protobuf API compatibility `Protobuf Compatibility` runs in Branch Checks, Release, and Pre-release through the shared @@ -317,8 +334,6 @@ jobs: ${{ needs.build.outputs.sandbox_image }} charts: ${{ needs.build.outputs.chart_ref }} fail-on-static-findings: true - secrets: - CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} ``` Set `needs: security` on a downstream promotion job to require successful scans. @@ -338,7 +353,7 @@ The summary contains qualification results and policy coverage only; artifact identity belongs in the release manifest. The run ID and attempt distinguish reruns without overwriting earlier evidence. -`CACHIX_AUTH_TOKEN` is optional. The parent publishes SARIF from CodeQL, Trivy, +The scan workflows use read-only Nix cache access. The parent publishes SARIF from CodeQL, Trivy, and workflow reports against the candidate tag and commit. Existing standalone triggers stay active. @@ -560,7 +575,8 @@ The bot's full administrator documentation is internal to NVIDIA. The only comma | `.github/workflows/e2e-docker-test.yml`, `e2e-podman-test.yml`, `e2e-vm-test.yml`, `e2e-kubernetes-test.yml` | Reusable runtime lanes called directly by branch and release workflows. Callers select suites and declare only the artifacts each runtime consumes. | | `.github/actions/setup-e2e-*` | Shared artifact, Podman, KVM, and kind setup used by the runtime lanes. | | `.github/workflows/helm-lint.yml` | Helm chart validation. PR mirror pushes skip lint jobs unless Helm inputs changed; merge groups always validate Helm because they represent the final integration state. | -| `.github/actions/setup-nix/action.yml` | Installs Nix and configures the OpenShell Cachix cache, using read-only cache access when no authentication token is available. | +| `.github/actions/setup-nix/action.yml` | Installs Nix and configures read-only access to the OpenShell Cachix cache. | +| `.github/workflows/cachix.yml` | Populates the Nix cache from `main` using the protected `cachix-write` environment. | | `.github/actions/pr-gate/action.yml` | Composite action that resolves PR metadata and verifies the required label is set for PR mirror pushes. Non-push events are allowed through. | | `.github/actions/pr-merge-base/action.yml` | Composite action that resolves and fetches the merge-base commit for `pull-request/` push workflows. | | `.github/workflows/required-ci-gates.yml` | Posts required PR-head and merge-group statuses for gated CI workflows. This is what branch protection and merge queue should require. |