systemd Encrypted Credentials (systemd-creds) Design Prompt
Replace plaintext secrets in environment variables and EnvironmentFile= with TPM-backed or host-bound encrypted credentials delivered to units via systemd-creds and LoadCredentialEncrypted=.
- Target user
- Linux admins hardening how services receive secrets on modern systemd hosts
- Difficulty
- Advanced
- Tools
- Claude, ChatGPT
The prompt
You are a senior Linux engineer who has migrated services off plaintext env-var secrets onto systemd's encrypted credentials, including TPM2-bound credentials, and understands the trade-offs of host binding.
I will provide:
- The unit file(s) currently passing secrets (paste any `Environment=`, `EnvironmentFile=`, or secret-in-ExecStart usage)
- systemd version (`systemctl --version`) and whether a TPM2 is present (`systemd-creds has-tpm2`)
- The service: what reads the secret, and whether it can read from a file path or only an env var
- Whether the host is a snowflake or part of an image/fleet (this decides host-binding vs portability)
Your job:
1. **Assess the current exposure** — explain exactly who can read the secret today (env vars are visible in `/proc/<pid>/environ` to root and leak into core dumps and child processes; `EnvironmentFile=` sits in plaintext on disk).
2. **Choose the encryption mode** — explain `systemd-creds encrypt` options for THIS case: `--with-key=host` (bound to /var/lib/systemd/credential.secret), `--with-key=tpm2` (bound to the TPM, optionally PCR-sealed), or `host+tpm2`. State plainly what each binding means for restoring on a new host or after a TPM clear.
3. **Wire up the unit** — show the exact `LoadCredentialEncrypted=NAME:/path/to/cred` directive and how the service reads it at runtime via `$CREDENTIALS_DIRECTORY/NAME`. If the app only takes an env var, show the `${CREDENTIALS_DIRECTORY}` indirection or a minimal wrapper.
4. **Plan rotation & recovery** — how to re-encrypt on rotation, where the encrypted blob lives, and the recovery story if the host is reimaged or the TPM PCRs change after a firmware update (PCR-sealing can lock you out).
5. **Verify the migration** — confirm the plaintext secret is gone from the unit, env, and disk; confirm the service still starts and the credential is delivered (check `$CREDENTIALS_DIRECTORY` is set and the file is `0400` root-only inside the unit's namespace).
Output as: (a) the encryption mode recommendation with its restore/portability trade-off stated explicitly, (b) the exact `systemd-creds encrypt` command, (c) the revised unit snippet, (d) the rotation + disaster-recovery runbook, (e) a verification checklist.
Verify before acting: test the encrypted credential delivery on a non-production unit first, and confirm you have a recovery path BEFORE choosing TPM PCR-sealing — a BIOS/firmware update can change PCRs and make the secret undecryptable.
Why this prompt works
Passing secrets to services via Environment= or EnvironmentFile= is the default-but-wrong pattern on most Linux hosts. Environment variables are readable from /proc/<pid>/environ, get inherited by every child process, and leak into core dumps and crash reports; an EnvironmentFile= is just a plaintext file on disk waiting for a backup or a misconfigured permission to expose it. This prompt starts by making the AI articulate that exposure concretely, because half the value is convincing yourself (and your reviewer) that the migration is worth doing.
The hard part of systemd-creds isn’t the syntax — it’s the binding trade-off, and that’s where this prompt earns its keep. --with-key=host, --with-key=tpm2, and PCR-sealing each have very different disaster-recovery stories, and the failure mode of TPM PCR-sealing is brutal: a routine firmware update changes the PCR measurements and your credential becomes undecryptable. By forcing the model to state the restore and portability implications of each mode explicitly, and to design a recovery/escrow path before committing to PCR-sealing, you avoid the classic self-inflicted lockout.
The prompt also closes the loop on verification, which matters because a half-done migration is worse than none — you think the secret is protected while the old plaintext still sits in the unit or env. The AI is told to confirm the plaintext is gone from unit, env, and disk, that the service still starts, and that the credential lands as a root-only file under $CREDENTIALS_DIRECTORY. The model drafts the commands, the unit edits, and the runbook; you test it on a throwaway unit and confirm the recovery path before any production secret depends on it.