wijjit_ssh.keys
SSH host keys: loading, first-run generation, and fingerprints.
A host key is the server’s identity. Clients pin it on first connect and refuse
to talk to you if it changes, so the operational rules are: generate one, keep
it private, keep it stable across restarts, and rotate deliberately (serve the
old and new key together for a transition window - see load_host_keys(),
which takes several).
The DX goal is that a first run should just work without a README detour through
ssh-keygen, while a production run should never silently invent a new
identity that breaks every client’s known_hosts. ensure_host_key()
does the former and logs loudly about the latter.
Examples
Development, or a container with a mounted volume - generate on first run, reuse it forever after:
>>> from wijjit_ssh.keys import ensure_host_key
>>> WijjitSSH(make_app, host_keys=[ensure_host_key("ssh_host_key")], ...)
Production - manage the key out of band and fail if it is missing:
$ ssh-keygen -t ed25519 -f ssh_host_key -N ''
>>> from wijjit_ssh.keys import load_host_keys
>>> WijjitSSH(make_app, host_keys=load_host_keys(["ssh_host_key"]), ...)
- wijjit_ssh.keys.DEFAULT_HOST_KEY_ALGORITHM = 'ssh-ed25519'
small, fast, no parameter choices to get wrong, and supported by every client we target.
- Type:
Algorithm used by
ensure_host_key(). ed25519
- wijjit_ssh.keys.HostKeySource
a path to a private key file, or an already-loaded key. Paths cover the config-file case, live keys cover tests and callers who mint keys themselves.
- Type:
Anything accepted as a host key
- wijjit_ssh.keys.ensure_host_key(path, *, algorithm=DEFAULT_HOST_KEY_ALGORITHM)[source]
Load the host key at
path, generating and persisting it if absent.The convenient default for development and for containers with a mounted volume: the first run creates a key, every later run reuses it, so clients’
known_hostsentries stay valid.In production, prefer
load_host_keys()and manage the key out of band. The difference matters: if a deployment’s volume is not actually persistent,ensure_host_keywill cheerfully mint a new identity on every restart and every client will report a host key mismatch. That is why generation logs at WARNING rather than INFO - on a healthy server it should happen exactly once.- Parameters:
path (str or PathLike) – Private key file. Parent directories are created if needed.
algorithm (str, optional) – Key algorithm to generate, in asyncssh’s naming (default
DEFAULT_HOST_KEY_ALGORITHM,"ssh-ed25519"). Ignored when the file already exists.
- Returns:
The loaded or newly generated key.
- Return type:
- Raises:
ValueError – If the file exists but is not a readable private key, or if
algorithmis not a valid key algorithm.OSError – If the key cannot be written.
Examples
>>> key = ensure_host_key("ssh_host_key") >>> key = ensure_host_key("/var/lib/myapp/host_key")
- wijjit_ssh.keys.fingerprint(key)[source]
Return a human-readable
"<algorithm> <SHA256:...>"fingerprint.The string clients compare against
known_hosts, so it is what you want in a startup log line and in a “did my key change?” investigation.- Parameters:
key (asyncssh.SSHKey) – The key to describe.
- Returns:
e.g.
"ssh-ed25519 SHA256:8xKNBvF6TPMK/LxQ+zKOmM0GzGIQSfEfp4pF/ZqWlE4".- Return type:
- wijjit_ssh.keys.load_host_keys(paths)[source]
Load host keys from private key files.
Pass more than one to rotate: asyncssh offers every key it is given, so a server can serve a new key alongside the old one until clients have seen it.
- Parameters:
paths (iterable of str or PathLike) – Private key files, in OpenSSH or PEM format.
- Returns:
The loaded keys, in the order given.
- Return type:
- Raises:
FileNotFoundError – If a path does not exist. Never silently skipped: a typo in a key path would otherwise start a server with a different identity than intended, which every client would report as a possible attack.
ValueError – If a file exists but is not a readable private key.
Examples
>>> keys = load_host_keys(["ssh_host_key"]) >>> keys = load_host_keys(["host_key_new", "host_key_old"]) # rotation
- wijjit_ssh.keys.resolve_host_keys(sources)[source]
Normalize mixed host key sources into loaded keys.
Accepts paths and already-loaded
SSHKeyobjects in one list, so callers can mixensure_host_key(...)with a path from config without thinking about it.Resolving eagerly (rather than handing paths to asyncssh at listen time) is the point: a bad key path becomes a clear error where the server was configured, with a fingerprint logged for the key that was actually loaded, instead of a late failure inside
create_server.- Parameters:
sources (iterable of str, PathLike, or asyncssh.SSHKey) – Mixed host key sources.
- Returns:
Loaded keys, in the order given.
- Return type:
- Raises:
FileNotFoundError – If a path does not exist.
ValueError – If a path is not a readable private key.
Examples
>>> resolve_host_keys([ensure_host_key("host_key"), "backup_key"])