Add encrypted secrets¶
Encrypt application secrets to a quorum bundle, ship them in your enclave image and release them to the verified enclave with holder approvals.
See Key Services for how quorums, holders and the key service work.
Before you start¶
- An application checkout initialized with
caution initand built from a container image. Useunit "default"for the startup command;build.binaryis not supported. - The Caution CLI installed with
make install-cli.send-shardneeds this host-toolchain build, notmake install-cli-stagex. - Holders registered in your organization:
- PGP holders add their public key under Security → PGP keys. It needs signing, encryption and authentication subkeys and must not be expired or revoked.
- Passkey holders must verify at least one registered passkey under Security → Authentication → Verify for quorum approval (PIN or biometric) before being selected. The bundle captures all their registered credentials, including unverified ones; releasing a share requires user verification.
1. Create a quorum bundle¶
Skip this step if you already have a bundle. Reuse it rather than generating a new one: a new bundle is a new key.
- Open Secrets → Create quorum bundle.
- Select members and choose each one's approval method. Pick a PGP key if a member has several. Add PGP holder adds an external holder from a public certificate.
- Set the Quorum threshold. The dashboard defaults to two and allows up to ten holders.
- Approve creation with your passkey. This does not approve any release.
- Download the JSON from Use this bundle and save it as
.caution/quorum-bundle.jsonin your application checkout.
Then verify Keymaker and save its policy for your image:
From your application checkout:
caution login
caution secret init \
--holder alice=external-pgp --holder bob=webauthn \
--threshold 2 --name "Application secrets"
--holdertakes a username or user ID. Add--pgp-key alice=FINGERPRINTif Alice has several PGP keys.- Pass a public keyring file as the first argument to add external holders.
- The default threshold is one. Set it explicitly.
- Unset
KEYMAKER_URL; it selects a self-hosted Keymaker.
On first use, the CLI offers to verify Keymaker. Platform stores the bundle in
your organization before returning it; the CLI then verifies the response and
saves .caution/quorum-bundle.json and .caution/keymaker-pcr-policy.json.
If hosted creation fails or times out, check Secrets for a matching stored
bundle before retrying; download and inspect it if present. An existing local
bundle may be from an earlier run, not the failed attempt. secret init
overwrites .caution/quorum-bundle.json, so back it up before running it again.
To generate bundles with your own Keymaker, see Self-hosted Keymaker.
2. Encrypt values¶
Put the values in a .env file and keep it out of Git:
This writes one .caution/secrets/<KEY>.asc per non-empty parsed value, encrypted to
the bundle's public key. Pass key names to encrypt only some values. Use
--env-file, --bundle and --secrets-dir for other paths. Leading and trailing
whitespace is trimmed at runtime.
Empty values are skipped: SECRET= does not delete an existing SECRET.asc.
To remove a secret, remove its encrypted artifact and update the application's
configuration and secret references before rebuilding.
Check the bundle before packaging it:
inspect verifies the Keymaker proof and shows the threshold, holders and approval
methods. Add -v for full fingerprints and the bundle ID.
3. Reference secrets in caution.hcl¶
Reference each secret with env::vault(...). Using it enables Locksmith
automatically:
enclave "main" {
network {
ingress {
cidr_ipv4 = "0.0.0.0/0"
port = 3000
}
recovery_ingress {
cidr_ipv4 = "0.0.0.0/0"
}
}
unit "default" {
command = "/app/server"
args = ["--port", "3000"]
env = {
DATABASE_URL = env::vault("DATABASE_URL")
}
}
}
env::vault("NAME") resolves to the value decrypted from .caution/secrets/NAME.asc.
This raw-TCP example declares recovery_ingress explicitly so holders can reach
Locksmith; without HTTP there are no HTTP sources to inherit. It permits any IPv4
source to connect, while holder authorization still applies. Narrow it to the
expected holder networks where practical. Do not list ports in the reserved
49500-49600 range as application ingress.
4. Package the bundle and secrets¶
Add the bundle, its Keymaker policy and the encrypted values to the final stage of
your Containerfile:
ADD .caution/quorum-bundle.json /etc/caution/bundle.json
ADD .caution/keymaker-pcr-policy.json /etc/caution/keymaker-pcr-policy.json
ADD .caution/secrets/ /etc/caution/secrets/
Use the policy file saved by the CLI. Files must be readable by the enclave
(0644 files, 0755 directories).
Commit the bundle, policy and .asc files. Never commit .env or private keyrings.
5. Deploy and verify¶
The unqualified commands below assume one running deployment. If you are adding
secrets to an app with a live deployment, the new deployment may be a candidate:
use caution verify -d <deployment> and add the same -d <deployment> to every
release command. Verify and unlock that candidate before switching it live. See
Deploy and roll back updates for lifecycle availability,
per-deployment trust paths and cutover checks.
caution verify reproduces your build, checks the selected enclave and saves
.caution/trusted_hashes.json (under .caution/deployments/<id>/ when using -d).
Commit the resulting trust file so all holders share the same baseline,
and rerun it after each deploy. Holders' CLIs refuse to release shares to an enclave
that doesn't match.
6. Release shares¶
Enough distinct holders must release their shares. The application stays locked until the threshold is reached, then starts with its secrets. Wait for the CLI to confirm the enclave received the share, not just for your approval.
PGP holders¶
Passkey holders¶
# Approve with a USB security key on this machine
caution secret send-shard --holder bob
# Approve on a phone by scanning a QR code
caution --qr secret send-shard --holder bob
--qr must run under the holder's own Caution login, in the organization that
stores the bundle. On first use, the CLI offers to verify the key service. Compare
the release details in the CLI and on the approval page before approving.
--holder takes a username or the full fingerprint from caution -v secret inspect.
Update values and restart¶
- Change a value: re-encrypt it with the existing bundle and push. Run
caution verify -d <deployment>and release shares withcaution secret send-shard -d <deployment>to the new deployment before switching it live. See Deploy and roll back updates. - Restart or redeploy: the new enclave starts locked. Holders release their shares again; no new bundle is needed.
- Change holders: create a new bundle and rotate the secret values. Editing a bundle's name or labels does not change its holders.
Self-hosted Keymaker¶
Run your own Keymaker to create PGP-only bundles without Caution's hosted services.
-
Deploy Keymaker from a separate checkout and verify it:
git clone https://git.caution.co/public/locksmith cd locksmith caution init git push caution HEAD:main caution verifyKeymaker reboots after each generation. Its
caution.hclsetsrestart { policy = "always" }so it comes back automatically. -
Create a public keyring with one certificate per holder. Each certificate needs signing, encryption and authentication subkeys.
caution secret keygen alice.asc --name "Alice" --email alice@example.com --shoot-self-in-foot caution secret keygen bob.asc --name "Bob" --email bob@example.com --shoot-self-in-foot cat alice.asc bob.asc > keyring.ascEach command also writes an unencrypted private keyring such as
alice.private.asc. Anyone who reads it can release that holder's share. Never use these keys in production.Keep holder keys on OpenPGP smartcards such as YubiKeys, for example with Keyfork. Export the public keys:
-
From your application checkout, generate the bundle:
caution secret init keyring.asc --threshold 2 --max 2 \ --keymaker-url https://YOUR_KEYMAKER \ --keymaker-pcr-policy /path/to/locksmith/.caution/trusted_hashes.json \ --name "Application secrets" --no-upload--maxmust equal the number of holders. The CLI saves the policy as.caution/keymaker-pcr-policy.json. Uploading to your organization also requires authentication, signed approval and the selected Platform operator's server-side Keymaker policy to trust your Keymaker. Local verification does not configure that server policy. Keep--no-uploadunless these prerequisites are met.If you omit
--no-uploadand upload fails after the bundle is saved locally, check your organization's bundles first. If it was not stored, retry withcaution secret upload --bundle .caution/quorum-bundle.jsonrather than generating a new quorum.
Continue at Encrypt values.
Import a V0 bundle¶
Bundles created before the current format must be imported once. Import keeps the quorum key and the existing ciphertext. It supports external PGP holders only.
Import refuses to overwrite an existing output. Keep the original and choose an
unused --output path; the default is .caution/quorum-bundle.json. This example
uses a separate imported file throughout:
caution secret import-legacy --bundle /path/original-v0.json \
--output .caution/quorum-bundle-imported.json \
--keyring /path/holder.private.asc # omit --keyring for a smartcard
caution secret inspect --bundle .caution/quorum-bundle-imported.json
One holder decrypts the bundle metadata; the quorum key is never reconstructed.
The result is shown as Legacy V0 — no Keymaker generation proof. Add --upload
to the import command to store it in your organization.
Package the imported file and existing ciphertext without a Keymaker policy:
ADD .caution/quorum-bundle-imported.json /etc/caution/bundle.json
ADD .caution/secrets/ /etc/caution/secrets/
Commit the imported file, ciphertext and updated Containerfile, then push to rebuild
and deploy. For a deployment-lifecycle app, verify the new deployment and release
shares to it, replacing 7f3a91c2 with its deployment ID:
git push caution main
caution verify -d 7f3a91c2
caution secret send-shard -d 7f3a91c2 \
--bundle .caution/quorum-bundle-imported.json --allow-legacy
Each holder repeats the release command (add --keyring for a development
keyring). Wait for the quorum and check the candidate before switching it live:
For a legacy-lifecycle app, use caution verify and the release command without
-d; there is no candidate switch. See Deploy and roll back updates
for lifecycle and cutover checks. For every
later secret encrypt or secret send-shard using this file, pass
--bundle .caution/quorum-bundle-imported.json --allow-legacy.
Public environment variables¶
Recommended: put public runtime values in unit.env in caution.hcl. Keep
application configuration alongside its startup command:
String literals are exported when the application starts and do not enable
Locksmith. Use env::vault("NAME") for secrets instead; never put plaintext secrets
in HCL or the container image. See the unit reference.
/etc/environment in the final container image remains an alternative for
image-level runtime defaults. It is loaded by the enclave startup path; it is not
necessary when you use unit.env. Prefer one place for each public value rather
than duplicating it in both:
In multi-stage builds, copy /etc/environment into the final stage:
FROM stagex/pallet-rust AS build
RUN printf '%s\n' 'APP_PORT=3000' 'LOG_LEVEL=info' > /tmp/environment
FROM stagex/core-filesystem AS run
COPY --from=build /tmp/environment /etc/environment
COPY --from=build /myapp /app/myapp
ENTRYPOINT ["/app/myapp"]
Build-time values are different¶
Neither unit.env nor writing /etc/environment supplies values to container
build steps. If a compiler or asset build needs public configuration, declare it
in the Containerfile (for example with a default ARG or an ENV used by the build
step) or a checked-in input file. Caution does not pass extra Docker build
arguments. See Container build inputs.
See also¶
-
Key Services
Understand quorums, holders and trust.
-
Verify an app
Verify apps and Caution key services.
-
caution.hcl
Configure how your application runs on Caution.