Enroll a bare host
Bind a keypair to your VPS when it has no signed metadata. Every login advances a counter you and we both hold; a copied key locks both out.
Hetzner, DigitalOcean, Linode, Vultr and OVH serve unsigned metadata over plain HTTP and expose no secure element. Your host there holds an Ed25519 key instead, and we hold the public half. Both sides keep a generation number that every successful login advances by one.
This detects a copy after the fact. A stolen key still authenticates once; that login advances our number, so your real host arrives stale on its next login and we lock both out.
Enroll the host
Issue the enrollment secret
Open Machine Identities then All Identities, open the identity, and enroll a bound keypair.
This needs machine:issue and step-up. We show a one-time pce_ secret once.
The dialog also prints a shell script from an older setup. Ignore it.
Enroll
penv machine enroll pce_exampleenrolled 3b1f...c204
the key stays in this host's keychain and is never printedpenv machine enroll generates the key, sends the public half, and
prints the credential id. The private key lives in the operating system keychain, never in a file
and never in output.
| Refusal | Cause | Fix |
|---|---|---|
no_keychain | Your host has no keychain, so the key would not survive the process | Set PENV_TOKEN, or run where the OS keychain opens |
no_secret | The enrollment secret was empty | Copy the pce_ secret from the console |
The pce_ prefix keeps an enrollment secret from being sent as a bearer credential, and we spend
it in the statement that reads it. Codes are listed in
error codes.
Login sequence
- penv requests a nonce. We issue it with a 120-second lifetime.
- penv signs four lines: a fixed prefix, the credential id, the nonce, the current number.
- We burn the nonce on our replay guard before touching the database.
- We check the signature, then the expiry, then the number.
- On success we advance the number and return a fifteen-minute credential.
- penv writes the new number to the keychain before using the credential.
| Design choice | Effect |
|---|---|
| Nonce issued by us | Someone with brief access to your host cannot stash pre-signed challenges |
| Nonce burned first | An immediate replay costs one hash |
| Signature checked before expiry and number | A caller with no key cannot lock out a host or enumerate credential ids |
Refusals
| Answer | Meaning | Revoked |
|---|---|---|
409 cloned | The presented number differs from ours | The keypair and every credential it minted |
401 replayed | The same request arrived twice; not a copy | Nothing |
A clone stops the host:
{
"error": "cloned",
"message": "this machine's identity was used from a second machine, so the server locked it.",
"fix": "Create a new enrolment secret in the console, then run penv machine enroll <secret>."
}A replay prints unauthorized at the command line. We revoke nothing, and the next login with a
fresh nonce works.
On a lockout we write a credential.cloned audit entry with both numbers and email the members chosen for security alerts (alerts).
Recover from a lockout
You cannot restore a revoked keypair. Find where the key was copied first: a fresh key on a host someone else still reaches buys one more lockout.
- Filter the audit log for
credential.clonedand read both numbers. - Find how the key was copied.
- Revoke the identity and enroll a new key on a clean host.
See penv machine, machine identity, and
a credential leaked.
Authenticate an AWS workload
Your AWS workload signs sts:GetCallerIdentity with its role; we replay it to AWS to learn the caller. Console setup, exchange rules, and the beta.2 gap.
Rotate a value
Write your new value as the next version with penv set or Set New Version in the console, then retire the old one at its issuer.