Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/astro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
54 changes: 54 additions & 0 deletions docs/src/content/docs/ci.md
Original file line number Diff line number Diff line change
@@ -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 -- <command>` 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.
37 changes: 37 additions & 0 deletions secretspec-action/README.md
Original file line number Diff line number Diff line change
@@ -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.
48 changes: 48 additions & 0 deletions secretspec-action/action.yml
Original file line number Diff line number Diff line change
@@ -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