SSH Key Onboarding¶
The ssh enrolment engine generates Ed25519 SSH key pairs in OpenSSH format. The private key can optionally be encrypted with a passphrase. The generated keys are stored in Vault, then synced to local files by the sync engine.
Configuration¶
Minimal¶
enrolments:
ssh:
engine: ssh
rules:
- name: ssh-private-key
vault_key: "ssh"
target:
path: "~/.ssh/id_ed25519"
format: text
template: "{{ .private_key }}"
- name: ssh-public-key
vault_key: "ssh"
target:
path: "~/.ssh/id_ed25519.pub"
format: text
template: "{{ .public_key }}"
With custom settings¶
enrolments:
ssh:
engine: ssh
settings:
passphrase: recommended # prompt for passphrase but allow skipping
Settings reference¶
| Setting | Default | Description |
|---|---|---|
passphrase |
required |
Passphrase mode: required, recommended, or unsafe |
Passphrase modes¶
| Mode | Behaviour |
|---|---|
required |
User must provide a passphrase; enrolment fails if empty |
recommended |
User is prompted but can press Enter to skip |
unsafe |
No passphrase prompt; private key is stored unencrypted |
Choosing a mode: it depends on how the key is consumed¶
The passphrase protects the private key at rest on disk. So the right mode depends entirely on whether the key ever lands on a filesystem:
-
File-sync mode (a sync rule writes the private key to
~/.ssh/…viaformat: text): the key sits on disk, where it can be read by a stolen laptop, a backup, or any process running as you. Encourage a passphrase (required) — it is the only thing protecting the key at rest, exactly as it would for a hand-generatedssh-keygenkey. -
Agent mode (the SSH agent reads the key from Vault on demand and signs in-process — the key is never written to a filesystem): the at-rest protection is Vault, not the passphrase. The daemon is headless and cannot prompt to decrypt, so a passphrase-encrypted key cannot be used by the agent at all. Use
unsafe. Despite the name it is not unsafe here: the secret is encrypted at rest in Vault and gated by your token policy. The one assumption is that you never exfiltrate the secret to disk (e.g. don't also add a file-sync rule for the same key, and don'tvault kv get … > id_ed25519) — doing so silently reintroduces the at-rest exposure that the passphrase would have covered.
In short: a passphrase on a key that also lives unencrypted in Vault and gets used by the agent adds friction with no security gain; a passphrase on a key written to disk is essential.
Minimum safe example — file-sync mode (passphrase required)¶
enrolments:
ssh:
engine: ssh
settings:
passphrase: required # key lands on disk — protect it at rest
rules:
- name: ssh-private-key
vault_key: "ssh"
target:
path: "~/.ssh/id_ed25519"
format: text
template: "{{ .private_key }}"
- name: ssh-public-key
vault_key: "ssh"
target:
path: "~/.ssh/id_ed25519.pub"
format: text
template: "{{ .public_key }}"
Minimum safe example — agent mode (unsafe, key never on disk)¶
enrolments:
ssh:
engine: ssh
settings:
passphrase: unsafe # Vault is the at-rest protection; agent can't prompt
agent:
enabled: true
keys:
- source: kv
path_prefix: "ssh/" # kv/data/users/<you>/ssh/*
# Note: deliberately NO sync rule writing the private key to disk.
How key generation works¶
- dotvault generates an Ed25519 key pair using Go's
crypto/ed25519 - The user is prompted for a passphrase (unless mode is
unsafe) - If a passphrase is provided, the user must confirm it by entering it a second time
- The private key is marshalled to OpenSSH PEM format (encrypted if a passphrase was given)
- The public key is marshalled to
authorized_keysformat with a{username}@dotvaultcomment - Both keys are written to Vault
Terminal output¶
Credentials stored in Vault¶
The engine writes these fields to the Vault KV secret:
| Field | Description |
|---|---|
public_key |
SSH public key in authorized_keys format (ssh-ed25519 ... user@dotvault) |
private_key |
OpenSSH PEM-formatted private key (optionally passphrase-encrypted) |
Combining enrolment with sync¶
A typical setup pairs the enrolment with sync rules so the workflow is:
- User starts dotvault for the first time
- dotvault checks Vault for
users/{username}/ssh— it's empty - The enrolment wizard generates an Ed25519 key pair
- Credentials are written to Vault
- Sync rules write the private and public keys to
~/.ssh/ - The user can add the public key to services like GitHub, GitLab, or remote hosts
On subsequent starts, the enrolment check finds the keys already present and skips the flow.
The text format handler uses full replacement (no merge), which is the correct behaviour for key files — each key file should contain exactly one key.