https://github.com/dolph/ussher
Remotely source SSH authorized_keys
https://github.com/dolph/ussher
ssh sshd sshd-config
Last synced: about 2 months ago
JSON representation
Remotely source SSH authorized_keys
- Host: GitHub
- URL: https://github.com/dolph/ussher
- Owner: dolph
- License: apache-2.0
- Created: 2021-07-08T23:53:07.000Z (almost 5 years ago)
- Default Branch: main
- Last Pushed: 2023-04-30T18:00:20.000Z (about 3 years ago)
- Last Synced: 2025-04-04T15:52:54.208Z (about 1 year ago)
- Topics: ssh, sshd, sshd-config
- Language: Go
- Homepage:
- Size: 60.5 KB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Security: security.go
Awesome Lists containing this project
README
# `ussher`
`ussher` aims to provide a backend for `sshd`'s [`AuthorizedKeysCommand` option](https://man.openbsd.org/sshd_config.5#AuthorizedKeysCommand), by remotely sourcing SSH `authorized_keys`. In short:
> `AuthorizedKeysCommand`: Specifies a program to be used to look up the user's public keys. The program must be owned by root, not writable by group or others and specified by an absolute path.
>
> The program should produce on standard output zero or more lines of authorized_keys output. `AuthorizedKeysCommand` is tried after the usual `AuthorizedKeysFile` files and will not be executed if a matching key is found there. By default, no `AuthorizedKeysCommand` is run.
When `~/.ssh/authorized_keys` does not contain the keys required to authenticate a user, `sshd` invokes `ussher` to provide additional, remotely-sourced keys, such as from Github or another identity and access management provider.
## How it works
`ussher` provides a fallback mechanism for statically-defined `authorized_keys` files, such as when `authorized_keys` needs to be frequently updated, composed from a large number of sources, or simply defined at the moment of authorization.
1. When you `ssh $USER@$HOSTNAME`, `sshd` first reads something like `/home/$USER/.ssh/authorized_keys` to authenticate incoming SSH connections. If `authorized_keys` contains a public key that matches the incoming connection, then the connection attempt proceeds normally.
1. If that file does not exist or does not contain a public key for the incoming connection, `sshd` will invoke `AuthorizedKeysCommand` as `AuthorizedKeysCommandUser`. The `AuthorizedKeysCommand` (`ussher`, in this case), is responsible for returning a list of authorized public keys, which `sshd` then uses to validate the incoming connection.
1. When invoked, `ussher` sources authorized keys from any number of remote sources, such as a static text file or more commonly something like `https://github.com/{username}.keys`. `ussher` then returns a single set of authorized keys to `sshd` which are used to validate the incoming connection.
## Recommended installation & usage
1. Download the latest release of `ussher` from [github.com/dolph/ussher](https://github.com/dolph/ussher/releases/latest), along with its `sha256` checksum, and verify the binary before installing:
```bash
curl -fLO https://github.com/dolph/ussher/releases/latest/download/ussher
curl -fLO https://github.com/dolph/ussher/releases/latest/download/ussher.sha256
sha256sum -c ussher.sha256
```
`sha256sum -c` exits non-zero if the binary doesn't match the published checksum. Don't proceed past this step on a mismatch.
2. Create a dedicated user and group to run `ussher`, named `ussher`:
```bash
sudo adduser --system --user-group ussher
```
3. Install the `ussher` binary: to `/usr/local/bin`.
```bash
sudo install -o root -g ussher -m 0750 ussher /usr/local/bin/ussher
```
4. Create a configuration directory.
```bash
sudo mkdir --parents /etc/ussher
```
5. Create a directory for caching remotely-sourced data.
```bash
sudo mkdir --parents /var/cache
sudo mkdir --parents --mode=0700 /var/cache/ussher
sudo chown ussher:ussher /var/cache/ussher
```
6. Create a directory for logging.
```bash
sudo mkdir --parents --mode=0700 /var/log/ussher
sudo chown ussher:ussher /var/log/ussher
```
7. Configure `sshd` to invoke `ussher`. Add the following lines to
`/etc/ssh/sshd_config`:
```
AuthorizedKeysCommand /usr/local/bin/ussher
AuthorizedKeysCommandUser ussher
```
You can script this with:
```bash
sudo sed -i -E "s~^#?AuthorizedKeysCommand .*~AuthorizedKeysCommand /usr/local/bin/ussher~" /etc/ssh/sshd_config
sudo sed -i -E "s~^#?AuthorizedKeysCommandUser .*~AuthorizedKeysCommandUser ussher~" /etc/ssh/sshd_config
```
Validate sshd's new configuration and restart `sshd`, for example:
```bash
sudo sshd -t
sudo systemctl restart sshd
```
## Configuration
`/etc/ussher` contains configuration files for each user it supports. For example, to allow @dolph to SSH to your host as root (but, you know, _don't_), you would configure `/etc/ussher/root.yml` using:
```yaml
sources:
- url: https://github.com/dolph.keys
```
### `cache_ttl`
Responses from each source URL are cached on disk under `/var/cache/ussher` so that frequent SSH attempts don't hammer the upstream. Cached entries expire after `cache_ttl`; once expired, the next login refetches the source. After `cache_ttl` elapses, key revocations on the upstream propagate to this host. Smaller values mean revocations take effect sooner and add upstream load; larger values reduce upstream load and lengthen the window during which a revoked key could still authenticate.
`cache_ttl` accepts any duration string understood by Go's [`time.ParseDuration`](https://pkg.go.dev/time#ParseDuration) — for example `30s`, `5m`, `1h`. The default when unset is `5m`.
```yaml
cache_ttl: 5m
sources:
- url: https://github.com/dolph.keys
```
### `http_timeout`
`http_timeout` caps how long a single upstream fetch (connect + headers + body read) can take before `ussher` gives up on it. A hung or stalled source — DNS that resolves but never returns, a TLS handshake that hangs, an HTTP server that accepts the connection but never responds — would otherwise block `sshd`'s authentication path until the OS-default timeout fires (often 2+ minutes per attempt). With `http_timeout`, `ussher` abandons the source quickly, treats the failure exactly like any other (logs and contributes zero keys from that source), and the rest of the configured sources continue to be served.
`http_timeout` accepts any duration string understood by [`time.ParseDuration`](https://pkg.go.dev/time#ParseDuration) — for example `500ms`, `10s`, `30s`. The default when unset is `10s`. Tighter values trade a higher rate of "slow but healthy upstream gave up" denials for tighter login latency under partial outages; looser values do the opposite.
```yaml
http_timeout: 10s
sources:
- url: https://github.com/dolph.keys
```
## Troubleshooting
### `Refusing to run unnecessarily writable binary`
Per the `sshd` man page:
> The program must be owned by root, not writable by group or others and specified by an absolute path.
`ussher` checks to ensure it's own binary is not unnecessarily writable at startup. If it is, a malicious user could remove this check or return any set of keys to sshd, which may be difficult to detect.
Ensure the file mode is similar to:
```
$ ls -l /usr/local/bin/ussher
-rwxr-x---. 1 root ussher 7823184 Apr 27 11:10 /usr/local/bin/ussher*
```
For example:
```bash
sudo chmod g-w /usr/local/bin/ussher
sudo chmod o-w /usr/local/bin/ussher
```
... but the binary may have been tainted. Verify it's checksum.
### `Refusing to run as root`
`ussher` checks to ensure it's not unnecessarily _running_ as root.
Ensure `sshd_config` specifies a non-root user:
```
AuthorizedKeysCommandUser ussher
```
### `Failed to write to /var/log/ussher`
`ussher` tries to ensure it can produce file-based logging output for auditing purposes. It's first preference is for `/var/log/ussher` which requires:
```bash
sudo mkdir --parents --mode=0700 /var/log/ussher
sudo chown ussher:ussher /var/log/ussher
```
### `Refusing to run without being able to log to /var/log/ussher/ or current working directory`
`ussher` tries to ensure it can produce file-based logging output for auditing purposes, and will fallback to the current working directory if `/var/log/ussher` is not writable. The best solution is to ensure `/var/log/ussher` is writable (see previous troubleshooting issue).
### `Refusing to run due to permissions issue on the ussher executable`
`ussher` tries to ensure that its own binary is not susceptible to manipulation by unauthorized users. This error may be accompanied by a more specific error in stdout.
Possible solutions include:
```bash
sudo chmod g-w /usr/local/bin/ussher
sudo chmod o-w /usr/local/bin/ussher
```
### `usage: ussher `
Per the `sshd_config` man page:
> Arguments to AuthorizedKeysCommand accept the tokens described in the TOKENS section. If no arguments are specified then the username of the target user is used.
`ussher` only expects the default configuration from sshd (the username of the target user).
Ensure `sshd_config` only specifies the absolute path to `ussher`, without any additional arguments:
```
AuthorizedKeysCommand /usr/local/bin/ussher
```
### `User not found`
The user specified to `ussher` is either not a valid Linux username or not an existing user on the host. Double check the username specified to `ussher` as well as the `AuthorizedKeysCommand` value in `/etc/ssh/sshd_config`.
## Changelog
See [`CHANGELOG.md`](./CHANGELOG.md) for the per-release history. The project follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and adheres to [Semantic Versioning](https://semver.org/).
## License
Apache 2.0