Skip to content
Browse the docs

CI/CD

Any CI with a service token

Give a pipeline exactly the environments it needs with a service token, install crunu with checksums, and keep production secrets out of CI altogether.

Last updated

On this page

A CI pipeline gets its variables from Crunu through a service token and the crunu command. This works on any CI system that can run a shell command on Linux or macOS. GitHub Actions, GitLab CI and Bitbucket Pipelines have their own pages with ready-made wrappers and OIDC binding.

Principles

  1. One service token per pipeline, with the smallest grant. A token crunu_svc_… gets access only to the environments you choose, for example web-app/staging. The key of a production environment is never issued to it, so even a leaked token can't decrypt production.
  2. Production is fetched by the production machine. The agent runs on the production server and the pipeline only triggers the deploy. Production secrets never pass through CI.
  3. If CI really needs a production secret, for example to sync variables into a hosting platform, use a separate production token with a CI binding. It then works only in a step of the right repository, branch and deployment environment.
  4. Preview environments get short-lived tokens with a lease measured in hours.

Step 1. Create a service token

In the web app, open Service tokens, then New token. Give it a name such as github-actions-web-app, choose its environments and delivery, set the lease and, if you like, a usage limit and networks. See Service tokens for what each setting does.

The token is shown only once. The server knows only its public keys and can't recover it. Store it right away in your CI's protected variables as CRUNU_TOKEN.

The token page also shows a CI setup block with the values you need for installation: CRUNU_SERVER, CRUNU_VERSION, CRUNU_INSTALL_URL, CRUNU_INSTALL_SHA256 and CRUNU_BINARY_SHA256. They come from the signed release manifest. Store them as ordinary CI variables. Only CRUNU_TOKEN is a secret.

Step 2. Install crunu and run your command

Three commands install a pinned version of crunu without root and check both checksums:

Shell
curl -sSf "$CRUNU_INSTALL_URL" -o /tmp/crunu-install.sh
echo "$CRUNU_INSTALL_SHA256  /tmp/crunu-install.sh" | sha256sum -c -
sh /tmp/crunu-install.sh --version "$CRUNU_VERSION" --sha256 "$CRUNU_BINARY_SHA256" --user-install
exec crunu run --env web-app/staging -- npm test

crunu run signs in with CRUNU_TOKEN, checks signatures and certificates against the organization key whose fingerprint is built into the token, decrypts the variables in memory and starts the command with them. Nothing is written to disk.

The image needs sh and curl or wget. The node, python and alpine images have them. On a plain ubuntu or debian image, install curl and ca-certificates first.

The binary checksum in the CI setup block is for Linux amd64. For arm64 or macOS runners, give one checksum per platform from the release's manifest.json, for example amd64=… arm64=….

Keep the token away from your build

crunu run never passes CRUNU_TOKEN to the command it starts. But the shell that runs the step may still hold it:

  • Set CRUNU_TOKEN only on the step that runs crunu, not for the whole job. Job-level variables reach every step and every process.
  • Start crunu with exec. The step's shell is then replaced by crunu, and no other process keeps the token in its environment. exec must be the last command of the step, because nothing after it runs.

This protects against accidental leaks and code without root. On runners where any process can become root, a malicious dependency could still read the token. The real protection is a small grant: only the environment the job needs, never production.

Deploying to a production server

  1. On the production server, install the agent and enroll the machine once, from Machines, then New machine. Approve it with payments-api / production. See Install the agent.

  2. Run the application through the agent so its variables come from the service's memory:

    INI
    [Service]
    User=payments
    ExecStart=/usr/local/bin/crunu run --env payments-api/production --restart on-change -- /opt/payments/bin/server

    The payments user must be in the crunu group.

  3. The pipeline only ships code and restarts the service, for example over SSH with sudo systemctl restart payments-api. It never sees the secrets.

Syncing into a platform's own variables

Some platforms keep environment variables themselves. Crunu has no built-in sync. You can push values from a pipeline with a production token bound to the main branch and the production deployment environment:

Shell
crunu run --env payments-api/production -- sh -c 'printf "%s" "$DATABASE_URL" | vercel env add DATABASE_URL production --force'

Every run appears in the audit log: who, when, from which address and which version it received.

Revoking a token

Open Service tokens, the token, then Revoke…. The next sign-in with the token is refused. A pipeline step that already started finishes with what it already received. If the token may have leaked, revoke it, rotate the keys of the environments it had, and reissue the values inside at their providers. See Rotate environment keys.