HTTP key server
For hosts that can't run the command locally, authkeys serve exposes the same
resolution over HTTP or HTTPS:
GET /keys?username=alice&apikey=<key>
GET /keys?username=alice (with the header Authorization: Bearer <key>)
The options come from the [serve] table, from AUTHKEYS_SERVE_<OPTION>
variables, or from the command line (--listen, --certificate, --key,
--path, --max-usernames, --request-timeout, --max-connections); the
command line wins, then the variable, then the file. The API key comes only from
the file:
[serve]
listen = "127.0.0.1:8090"
path = "/keys"
api_key = "${env.AUTHKEYS_APIKEY}"
certificate = "/etc/authkeys/tls.pem" # with key: serve HTTPS
key = "/etc/authkeys/tls.key"
request_timeout = 10 # seconds a connection may stay idle
max_connections = 64 # requests served at once; more get 503
authkeys -c /etc/authkeys.cfg serve --listen "[::]:8443,0.0.0.0:8443"
Where it listens
listen is a netimps listen
specification: host:port, [v6]:port, :port (every IPv4 address), an
interface name (eth1:8090), or several of them separated by commas — one socket
each. A missing port is 8090. A specification that names no address, a port out
of range or a port already in use stops serve with one line on stderr (status
3).
TLS
With certificate and key (PEM files; the certificate file may hold the chain)
serve speaks HTTPS. Without TLS the keys and the API key cross the network in
clear text, and whoever sits in the middle decides who logs in on the hosts that
fetch keys: serve logs a warning when it listens on a non-loopback address
without TLS. Either configure a certificate or keep serve on loopback behind a
TLS proxy.
Security
- The API key is compared in constant time. Pull it from the environment with
api_key = "${env.AUTHKEYS_APIKEY}"to keep it off disk. - Fail closed. If the
api_keyline is present but resolves empty (for example an unset environment variable),authkeys serverefuses to start rather than silently running unauthenticated. Remove the line entirely to intentionally run without authentication — and then listen only on a trusted interface. - The server resolves concurrently; the cache read/write is locked but the upstream fetch is not, so one slow/hung source can't stall other requests.
max_usernames(default 16) caps how many?username=params a single request may carry, bounding the work an unauthenticated request can force.- Requests are logged as the method, the path without its query string and the
status, so the
apikeyparameter never reaches the log. Prefer theAuthorization: Bearerheader anyway: proxies log URLs. - A connection idle for
request_timeoutseconds (default 10) is closed, and at mostmax_connections(default 64) requests run at once; the rest get503.
Delegation
authkeys serve honors each user's ~/.ssh/authkeys.cfg delegation, exactly as
authkeys resolve does — a request for alice returns the keys of everyone
alice has authorized.
Running under systemd
examples/authkeys-serve.service
is a template unit for running authkeys serve as a systemd service. It sets
DynamicUser= for an unprivileged, unique identity and a broad set of other
hardening directives (NoNewPrivileges=, ProtectSystem=strict,
PrivateTmp=, a minimal RestrictAddressFamilies=, dropped capabilities,
...), and shows both ways to supply AUTHKEYS_APIKEY without putting it in
authkeys.cfg (Environment=/EnvironmentFile=).
One directive is intentionally not at its strictest: ProtectHome=read-only
rather than fully hiding home directories. authkeys serve needs to read
each delegating user's ~/.ssh (for file-backed sources and
~/.ssh/authkeys.cfg delegation), so home directories must stay readable —
this is a deliberate trade-off, documented in the unit file itself, not an
oversight.
Copy the unit to /etc/systemd/system/authkeys-serve.service, adjust the
config path and identity for your install, then:
systemctl daemon-reload
systemctl enable --now authkeys-serve