Files
MontaukOS/docs/ssh.md
T
2026-08-11 12:53:36 +02:00

101 lines
4.2 KiB
Markdown

to-do: rewrite & convert to html for docs pages
# SSH
MontaukOS ships an SSH-2 server, `sshd`, that gives a remote client a shell on
the machine. It speaks enough of the protocol to interoperate with stock
OpenSSH clients:
```
ssh dan@montauk-box interactive shell
ssh dan@montauk-box 'ls 0:/' run one command
```
Remote access is **off by default**. Turn it on from Settings > SSH Server,
which flips `services.ssh.enabled` in `0:/config/init.toml`; `init` starts the
daemon on the next boot.
Two gates stand in front of a login: the account must exist in the MontaukOS
user database with a valid password, and it must be listed in the `[allow]`
table of `0:/config/ssh.toml`. A user absent from that table is refused even
with the right password. The Settings applet maintains the table; `sshd` never
writes it.
## Layout
```
programs/src/sshd/
main.cpp transport framing, key exchange, auth, session/channel loop
crypto.hpp host key storage, DH group 14 arithmetic, cipher/MAC wrappers
programs/src/sshserver/
main.cpp Settings applet: enable the service, edit the allow table
programs/include/montauk/ssh.h
shared policy helpers used by both
```
The crypto primitives (SHA-256, HMAC, AES-CTR, RSA signing, the DRBG) come from
BearSSL. The modular exponentiation for Diffie-Hellman is in `crypto.hpp`
because BearSSL exposes no public bignum API.
## What it implements
| | |
|---|---|
| Key exchange | `diffie-hellman-group14-sha256` |
| Host key | `rsa-sha2-256`, 2048-bit, generated on first start |
| Cipher | `aes128-ctr` |
| MAC | `hmac-sha2-256` |
| Compression | none |
| Auth | password only |
Client-initiated rekeying works, so long sessions survive OpenSSH's 1 GiB /
one-hour rekey threshold. Channel windows are tracked in both directions, so
sessions that move more than a window's worth of data keep flowing.
## What it does not
No public key authentication, no port or X11 forwarding, no SFTP or SCP. One
session channel per connection, and connections are served one at a time --
there is no `fork`, so a second client waits for the first to finish. A
pre-authentication timeout keeps an idle peer from holding the daemon shut.
`exec` requests are run by typing the command into an interactive shell rather
than executing it directly, so the prompt and the echoed command come back
mixed into the output. This is why `scp` and friends will not work as-is.
## The host key
Generated on first start and written to `0:/config/ssh_hostkey.toml`, separate
from the policy file so the unprivileged Settings applet -- which rewrites
`ssh.toml` whenever the allow table changes -- never round-trips private key
material.
That separation is hygiene, not protection: **the filesystem has no permission
model**, so any local process can read the host key. Closing that hole needs
FS-level access control, at which point the key file should be restricted to
the account `sshd` runs as. A key found in an older `ssh.toml` is migrated to
the new file on first start so the host key does not change under existing
clients.
## Debugging
`sshd` calls `montauk::print`, which reaches the boot console only. **It has no
usable diagnostics once the desktop is up**, pending the unified syslog.
Worth understanding, because it catches out anything written as a daemon:
`montauk::print` is `SYS_PRINT`, which writes the kernel *terminal*, not the
kernel *log*. Only in-kernel `KernelLogStream` writes raise `g_kernelLogDepth`,
and only those append to the ring buffer `SYS_KLOG` reads -- so daemon output
never shows up in `klog`. On top of that, `Sys_Print` returns early once
`g_suppressKernelLog` is set, which the desktop does at startup, so the output is
discarded outright from then on. `init` spawns services with `spawn` rather than
`spawn_redir`, so there is no stream to capture either.
Until there is somewhere for it to go, debugging a handshake means watching the
boot console, or temporarily pointing the print calls at a file.
Failures print a numeric code -- `key exchange failed (N)`, `authentication
failed (N)` -- where N identifies the step; see the `kex_error` assignments in
`main.cpp`. Running the client with `ssh -vvv` and lining its trace up against
those codes is usually the fastest way to find where a handshake diverged.