BLOG/CONFIGURATION MANAGEMENT
THEME
CONFIGURATION MANAGEMENT

Rotate secrets on a schedule

Make credential rotation a task a timer can call: mint a new value, activate it, verify, and write an audit record. Keep the secret out of the workflow with a resolver, and let cron own the schedule.

May 6, 2026 OrchStep Team 7 minROLE: Security EngineerSCALE: Enterprise
RUNNABLE DEMO
Full source for this post: blog/credential-rotation
VIEW SOURCE

Secret rotation is the security task everyone agrees on and nobody automates. It sits in a runbook titled "rotate the signing key (quarterly)", the quarter passes, and the key is two years old when an audit finds it. The work isn't hard — mint, activate, verify, record — it just needs to be a thing a timer can run unattended.

So make it one task with a clean exit code, keep the actual secret out of the file, and let cron own the calendar.

The rotation as a task

Each phase is a step. The new value is minted, activated with retry because key stores rate-limit, verified, and then an audit line is written so there's a record of what changed and when.

orchstep.yml
name: rotation
defaults:
  key_name: api-signing-key
  store: vault
  stage: production

tasks:
  # orchstep run rotate --output json
  rotate:
    desc: "Rotate one credential and record an audit entry"
    steps:
      - name: mint
        func: shell
        do: echo "rotated-{{ vars.key_name }}"
        outputs:
          version: "{{ result.output }}"

      - name: activate
        func: shell
        do: echo "activating {{ steps.mint.version }} in {{ vars.store }} for {{ vars.stage }}"
        retry:
          max_attempts: 3
          interval: "2s"
          backoff_rate: 2.0

      - name: verify
        func: assert
        args:
          condition: '{{ ne steps.mint.version "" }}'
          message: "a new version must be minted before activation"

      - name: audit
        func: shell
        do: echo "AUDIT key={{ vars.key_name }} version={{ steps.mint.version }} store={{ vars.store }} stage={{ vars.stage }}"

      - name: retire
        func: shell
        do: echo "scheduling the previous {{ vars.key_name }} for revocation"

The mint step captures its result as version and every later step reads that minted value back through steps.mint.version — so the activate, the audit, and the retire all talk about the same new value. The verify assert makes "we never audited a rotation that didn't actually mint" a property the workflow enforces, not a thing you hope is true.

Keep the secret out of the file

The value you're rotating should never be a literal in the workflow. OrchStep resolves secrets through a resolver and treats them as tainted — they're pulled in at run time and scrubbed from logs and the context that gets persisted, so the audit line records the version, not the material:

dotenv: ["rotation.env?"]
env:
  STORE_TOKEN: <required>

The <required> sentinel means the run refuses to start without the token, and the trailing ? on the dotenv file makes it optional so the same workflow runs in CI where the file isn't present. The secret flows through; it never lands in the YAML.

Let the timer own the schedule

The task is built to be called unattended. A cron line or a systemd timer runs it, and the exit code is the signal:

orchstep run rotate --output json

If any step fails — the store is down, the verify trips — the run exits non-zero and the timer records a failure you can alert on. --output json gives the timer's log collector something structured to ingest. And before you trust it to a schedule, dry-run it once to see the whole sequence resolve without touching the store:

orchstep run rotate --dry-run

What you gained

ConcernQuarterly runbookScheduled task
When it runswhen someone rememberscron / systemd timer
The secretpasted aroundresolver, tainted, scrubbed
Same value everywherehopeone steps.mint.version
Did it mint?trustassert before audit
Record of changemaybe a ticketAUDIT line + exit code
Failure visibilitysilentnon-zero exit, alertable

Where this isn't enough

If your platform already does native rotation — a managed database that rotates its own credentials, a cloud KMS with automatic key rotation — let it. This pattern is for the credentials that don't rotate themselves: signing keys, third-party API tokens, the long tail of secrets that otherwise age in place because rotating them is a manual chore.

Where to go next

The project above is a runnable, echo-only demo — no real store is touched. Wire in the resolver and your key store to make it live.

#SECRETS#ROTATION#SECURITY#AUDIT
Try it in two minutes — one binary, no signup.
curl -fsSL https://orchstep.dev/install.sh | sh

RELATED — CONFIGURATION MANAGEMENT