From d2e762efdd57adb29605d24cb0f32e34e2d20c2b Mon Sep 17 00:00:00 2001 From: Anish Pallati Date: Thu, 16 Jul 2026 06:56:33 -0400 Subject: [PATCH 1/2] feat: add secretspec-action for GitHub and Forgejo Actions Signed-off-by: Anish Pallati Assisted-by: Claude Fable 5 --- secretspec-action/README.md | 37 +++++++++++++++++++++++++++ secretspec-action/action.yml | 48 ++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 secretspec-action/README.md create mode 100644 secretspec-action/action.yml diff --git a/secretspec-action/README.md b/secretspec-action/README.md new file mode 100644 index 00000000..c348224d --- /dev/null +++ b/secretspec-action/README.md @@ -0,0 +1,37 @@ +# secretspec-action + +Resolve the secrets declared in `secretspec.toml` and expose them to the rest of a GitHub or Forgejo Actions job. + +The action installs the `secretspec` CLI and runs `secretspec export --format gha`, which masks every value in the runner log (`::add-mask::`) and appends `KEY=value` to `$GITHUB_ENV`. Every later step in the job sees the secrets as ordinary environment variables. + +## Usage + +```yaml +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: cachix/secretspec/secretspec-action@main + with: + profile: production + provider: env + - run: ./deploy.sh # DATABASE_URL etc. are in the environment +``` + +## Inputs + +| Input | Default | Description | +|-------|---------|-------------| +| `profile` | project default | secretspec profile to resolve | +| `provider` | user/project config | provider name, alias, or URI to resolve from | +| `version` | `latest` | secretspec release tag to install, or `latest` | +| `working-directory` | `.` | directory containing `secretspec.toml` | + +A missing required secret fails the step, so the action doubles as a CI gate. + +## Requirements + +- A secretspec release that includes `secretspec export`. +- Linux, macOS, or Windows runners. The prebuilt Linux binary links glibc and libdbus (for the keyring provider); GitHub-hosted runner images ship both, but a minimal `container:` image (alpine, distroless) will not. +- Provider credentials must already be available to the job, e.g. `provider: env` with repo secrets, or a Vault/OpenBao token obtained from an earlier OIDC exchange step. diff --git a/secretspec-action/action.yml b/secretspec-action/action.yml new file mode 100644 index 00000000..dfd27916 --- /dev/null +++ b/secretspec-action/action.yml @@ -0,0 +1,48 @@ +name: secretspec +description: >- + Resolve secrets declared in secretspec.toml and expose them to the rest of + the job. Values are masked in the runner log and appended to $GITHUB_ENV, so + later steps see them as environment variables. + +inputs: + profile: + description: secretspec profile to resolve + required: false + default: "" + provider: + description: provider name, alias, or URI to resolve from + required: false + default: "" + version: + description: secretspec release tag to install, or "latest" + required: false + default: latest + working-directory: + description: directory containing secretspec.toml + required: false + default: "." + +runs: + using: composite + steps: + - name: Install secretspec + shell: bash + env: + VERSION: ${{ inputs.version }} + run: | + if [ "$VERSION" = latest ]; then + curl --proto '=https' --tlsv1.2 -sSfL https://install.secretspec.dev | sh + else + # cargo-dist publishes a pinned installer per release tag + curl --proto '=https' --tlsv1.2 -sSfL \ + "https://github.com/cachix/secretspec/releases/download/v${VERSION#v}/secretspec-installer.sh" | sh + fi + echo "${CARGO_HOME:-$HOME/.cargo}/bin" >> "$GITHUB_PATH" + + - name: Export secrets to the job environment + shell: bash + working-directory: ${{ inputs.working-directory }} + env: + SECRETSPEC_PROFILE: ${{ inputs.profile }} + SECRETSPEC_PROVIDER: ${{ inputs.provider }} + run: secretspec export --format gha From eece83d4bdab6e095ba15cd919c7ae3af4ab17e9 Mon Sep 17 00:00:00 2001 From: Anish Pallati Date: Sun, 19 Jul 2026 02:03:59 -0400 Subject: [PATCH 2/2] docs: add continuous integration guide Signed-off-by: Anish Pallati --- docs/astro.config.ts | 5 +++- docs/src/content/docs/ci.md | 54 +++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 docs/src/content/docs/ci.md diff --git a/docs/astro.config.ts b/docs/astro.config.ts index 812378fe..78d3e336 100644 --- a/docs/astro.config.ts +++ b/docs/astro.config.ts @@ -139,7 +139,10 @@ Secrets can be stored in: keyring (default), dotenv files, environment variables sidebar: [ { label: "Getting Started", - items: [{ label: "Quick Start", slug: "quick-start" }], + items: [ + { label: "Quick Start", slug: "quick-start" }, + { label: "Continuous Integration", slug: "ci" }, + ], }, { label: "Concepts", diff --git a/docs/src/content/docs/ci.md b/docs/src/content/docs/ci.md new file mode 100644 index 00000000..56aa9c40 --- /dev/null +++ b/docs/src/content/docs/ci.md @@ -0,0 +1,54 @@ +--- +title: Continuous Integration +description: Resolve secrets from secretspec.toml in GitHub Actions, Forgejo Actions, and other CI systems +--- + +In a GitHub or Forgejo Actions job, `secretspec-action` installs the CLI and runs `secretspec export --format gha`, which masks every value in the runner log and appends `KEY=value` to `$GITHUB_ENV`. Every later step, including third-party actions, then sees the secrets as ordinary environment variables. + +```yaml +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: cachix/secretspec/secretspec-action@main + with: + profile: production + - run: ./deploy.sh +``` + +A missing required secret fails the step before the job runs anything with an incomplete environment. + +## Fetching from a secret manager + +For secrets kept in a dedicated store, resolve them on the runner with the matching provider, shown here with [Vault or OpenBao](/providers/vault/). Other stores plug in the same way with their own credentials. + +Grant the job `id-token: write` and select `?auth=jwt` with a `role`. Vault exchanges the runner's OIDC token for a client token, so nothing is stored on the platform. + +```yaml + - uses: cachix/secretspec/secretspec-action@main + with: + profile: production + provider: vault://vault.example.com:8200/secret?auth=jwt&role=ci +``` + +Without an OIDC identity to draw on, select `?auth=approle` instead and pass `VAULT_ROLE_ID` and `VAULT_SECRET_ID` as CI secrets. + +```yaml + - uses: cachix/secretspec/secretspec-action@main + with: + profile: production + provider: vault://vault.example.com:8200/secret?auth=approle + env: + VAULT_ROLE_ID: ${{ secrets.VAULT_ROLE_ID }} + VAULT_SECRET_ID: ${{ secrets.VAULT_SECRET_ID }} +``` + +## Other CI systems + +`secretspec-action` is a convenience wrapper around commands that work anywhere the CLI is installed. + +- `secretspec run -- ` runs a single command with the secrets confined to its environment. +- `secretspec export` writes the resolved secrets to stdout for a tool that cannot be wrapped, such as a containerized pipeline. `eval "$(secretspec export)"` loads them into the current shell, while `--format dotenv` and `--format json` feed other consumers. + +Both resolve through the same provider chain and fail on a missing required secret.