CI/CD

Bootstrap an ephemeral machine inside your CI/CD pipeline and pull SikkerKey secrets as environment variables at runtime. Ready-made script builders for GitHub Actions, GitLab CI/CD, and Bitbucket Pipelines.

SikkerKey plugs into CI/CD through a pipeline-time bootstrap. The dashboard generates a shell script (sikkerkey-init.sh) and a config snippet for your platform. At runtime the script registers a one-time ephemeral machine bound to an enrollment token, fetches the secrets that token grants, and exposes them as environment variables for the rest of the job.

How the script builder works

  1. In the dashboard, go to Integrations and click Build on your platform's row (GitHub Actions, GitLab CI/CD, or Bitbucket Pipelines).
  2. Paste your enrollment token plaintext. SikkerKey verifies it server-side and renders two files:
    • sikkerkey-init.sh — the bootstrap script. Commit it at the root of your repository.
    • A platform config snippet — paste the relevant pieces into your workflow or pipeline file.
  3. Add the enrollment-token plaintext to your platform's encrypted variable store as SIKKERKEY_ENROLLMENT_TOKEN (steps per platform below).

The script is platform-agnostic: it only reads SIKKERKEY_ENROLLMENT_TOKEN from the environment, so the same file works on any CI system that can run a shell command (see Other CI systems).

Prerequisites

  • An enrollment token scoped to the projects and secrets your pipeline needs
  • Permission to add an encrypted variable to your CI platform
  • A runner image with curl and npm available

GitHub Actions

Add the token under Settings → Secrets and variables → Actions → New repository secret, named SIKKERKEY_ENROLLMENT_TOKEN.

name: Deploy
on: [push]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Fetch SikkerKey secrets
        env:
          SIKKERKEY_ENROLLMENT_TOKEN: ${{ secrets.SIKKERKEY_ENROLLMENT_TOKEN }}
        run: sh ./sikkerkey-init.sh
      - name: Deploy
        run: ./your-deploy-command.sh

The token is scoped to the Fetch SikkerKey secrets step. Later steps in the same job see the granted secrets as environment variables — the script appends them to $GITHUB_ENV and masks each value in the logs via ::add-mask::.

GitLab CI/CD

Add the token under Settings → CI/CD → Variables → Add variable, keyed SIKKERKEY_ENROLLMENT_TOKEN. Mark it Masked and Protected.

deploy:
  image: ubuntu:latest
  before_script:
    - apt-get update && apt-get install -y curl
    - sh ./sikkerkey-init.sh
  script:
    - ./your-deploy-command.sh

GitLab injects SIKKERKEY_ENROLLMENT_TOKEN automatically. After before_script runs, every command in the job sees the granted secrets.

Bitbucket Pipelines

Add the token under Repository settings → Pipelines → Repository variables, named SIKKERKEY_ENROLLMENT_TOKEN and marked Secured.

pipelines:
  default:
    - step:
        name: Deploy
        image: node:20
        script:
          - sh ./sikkerkey-init.sh
          - ./your-deploy-command.sh

Bitbucket injects the variable automatically. After the bootstrap line, every command in the step sees the granted secrets.

What happens at runtime

  1. The script reads SIKKERKEY_ENROLLMENT_TOKEN from the job environment.
  2. It calls the enrollment endpoint, which generates a one-time Ed25519 keypair on the runner and registers an ephemeral machine bound to the token's policy.
  3. It installs the SikkerKey CLI via npm.
  4. It unlocks each project the token grants.
  5. It exports the granted secrets into the job environment for the rest of the job, masking each value in the logs.

Each job runs on a fresh runner, so add the bootstrap step to every job that needs secrets. Each one produces its own ephemeral machine that disappears when the runner is torn down.

Other CI systems

There's no Build button for Jenkins, CircleCI, AWS CodeBuild, and the like, but the model is identical: add SIKKERKEY_ENROLLMENT_TOKEN to that system's secret store, commit a generated sikkerkey-init.sh (build it from any of the three platforms above — the script is the same), and invoke it before your build commands.

# CircleCI
version: 2.1
jobs:
  deploy:
    docker:
      - image: cimg/node:20.0
    steps:
      - checkout
      - run: sh ./sikkerkey-init.sh
      - run: ./your-deploy-command.sh

For long-lived, self-hosted runners where re-enrolling on every run is wasteful, register a persistent machine instead, store its identity in the CI secret store, and fetch with sikkerkey run --all -- ./deploy.sh.

Security model

  • Scoped token. The enrollment token can only register machines bound to its configured policy: projects, secrets, source CIDR, hostname pattern, machine TTL, and max uses. It cannot read secrets directly.
  • Ephemeral machine. Each run produces a unique identity generated on the runner; the private key lives only on the runner's filesystem and is gone when it's torn down.
  • Hash-only token storage. SikkerKey stores only the SHA-256 hash of the enrollment token; the plaintext lives in your CI provider's encrypted variable store.
  • Audit trail. Every enrollment and every secret read is recorded in your audit log, attributed to the parent token along with the runner's IP and timestamp.

Troubleshooting

./sikkerkey-init.sh: Permission denied. The committed script lacks the execute bit. The generated snippets invoke it via sh ./sikkerkey-init.sh, which doesn't require it — restore the sh prefix if you removed it.

Enrollment token not found (404). The plaintext you pasted into the dashboard modal (or your CI variable) doesn't match any token. The plaintext is shown only once at creation; if lost, revoke and create a new token.

Token has been revoked / expired / no remaining enrollments (410). The token can no longer enroll machines. Create a new one from Enrollment Tokens, update the variable in your CI store, and re-run.

Secrets missing in the same step that runs the script. Environment writes take effect for subsequent steps and commands. Put secret-using commands after the bootstrap step (GitHub $GITHUB_ENV) or after the bootstrap line in the same job (GitLab, Bitbucket).

Variable not visible (GitLab / Bitbucket). Protected or secured variables aren't exposed to non-protected branches or external pull-request pipelines by default. Run on a protected branch or adjust the trigger.