- Go 89%
- Shell 11%
|
|
||
|---|---|---|
| internal | ||
| .gitignore | ||
| CLAUDE.md | ||
| deploy.md | ||
| deploy.sh | ||
| go.mod | ||
| go.sum | ||
| keys.openrc | ||
| log.md | ||
| main.go | ||
| README.md | ||
keys
Managing SSH keys across multiple servers is tedious. You either paste the same key into every authorized_keys file by hand, or set up something complex like LDAP. keys is the simple middle ground: one place to manage your SSH public keys, served over HTTP so any server can consume them with a single config line.
It also solves the new-device problem: when you get a new laptop and SSH in with an unknown key, instead of getting "Permission denied", the server records the key as pending and notifies you next time you log in from an approved device. One keypress to approve it.
How it works
- HTTP server on
:8080— serves your keys as anauthorized_keysfile; pointAuthorizedKeysURLat it on any sshd - SSH server on
:22— an interactive TUI for managing keys, itself protected by the managed keys - Auto-bootstrap — first ever connection is automatically approved, so setup requires no CLI work
- Pending keys — unknown SSH keys are saved as pending; the next authorized login can approve or reject them
- Keys are stored in
~/.local/share/keys/keys.json(XDG-aware)
Quick start
go install code.bas.es/arne/keys@latest
keys serve
The first time you SSH in, your key is automatically approved:
ssh <host> # bootstraps your key — opens the TUI
On any other server, add one line to /etc/ssh/sshd_config:
AuthorizedKeysURL http://keys-host:8080/
TUI
Connect from any approved device to manage your keys:
ssh keys-host
| Key | Action |
|---|---|
a |
Add a new key manually |
d |
Delete selected key |
r |
Rename selected key |
p |
Review pending keys (shown when there are pending keys) |
q / esc |
Quit / go back |
Approving a new device
When a new key attempts to connect, they see:
Key submitted for approval.
The next time you log in from an approved device, the help bar shows [p] pending (1). Press p, select the key, press a, give it a label — done. The new device can now connect.
CLI
keys serve start HTTP + SSH servers
| Flag | Env var | Default |
|---|---|---|
-http-addr |
KEYS_HTTP_ADDR |
:8080 |
-ssh-addr |
KEYS_SSH_ADDR |
:22 |
-host-key |
KEYS_HOST_KEY |
./host_key |
-store |
KEYS_STORE |
~/.local/share/keys/keys.json |
Integration
sshd AuthorizedKeysURL
# /etc/ssh/sshd_config
AuthorizedKeysURL http://keys:8080/
Any HTTP request to the keys server returns the full authorized_keys content.
Fetch on login
# ~/.profile or equivalent
curl -sf http://keys:8080/ >> ~/.ssh/authorized_keys
Deployment
See deploy.md for the full setup guide: Incus container on Alpine Linux with its own Tailscale node, Caddy reverse proxy for HTTPS.
Internet
└── keys.example.com (Caddy, TLS termination)
└── Tailscale → keys container :8080
Tailscale network
└── ssh keys → TUI