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

alias of str | os.PathLike[str] | SSHKey

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_hosts entries 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_key will 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:

asyncssh.SSHKey

Raises:
  • ValueError – If the file exists but is not a readable private key, or if algorithm is 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:

str

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:

list[asyncssh.SSHKey]

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 SSHKey objects in one list, so callers can mix ensure_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:

list[asyncssh.SSHKey]

Raises:

Examples

>>> resolve_host_keys([ensure_host_key("host_key"), "backup_key"])