Skip to content

Repository files navigation

SSHGateW

CI CodeQL Release Container

SSHGateW is a self-hosted SSH credential gateway. Team members authenticate to the gateway with their own SSH public keys, choose an authorized connection profile in a terminal UI, and receive a transparent interactive shell on the downstream machine. Downstream passwords and private keys stay on the gateway, encrypted at rest.

Current features

  • Public-key authentication to the gateway on port 2222 by default.
  • Optional OIDC device-flow authentication with signed ID-token validation.
  • Optional per-user TOTP second-factor authentication with terminal QR enrollment.
  • Member and administrator terminal interfaces.
  • Per-user and per-group target grants.
  • Per-protocol shell, SFTP/SCP, and TCP-forwarding capabilities.
  • Password, reusable stored-key, per-target private-key, and restricted forwarded-agent authentication.
  • Exact downstream host-key pinning; changed keys are rejected.
  • XChaCha20-Poly1305 encrypted credentials with a separate master-key file.
  • SQLite persistence, metadata auditing, session limits, and JSON logs.
  • Routed user+target logins for direct shells, SFTP, SCP, and restricted local forwarding.
  • Exact per-target TCP destination allowlists; reverse forwarding remains disabled.
  • No arbitrary remote exec, downstream agent exposure, or terminal recording.

Build

Go 1.26 or newer is required.

make build VERSION=0.1.0

Run with Docker Compose

Place the initial administrator's OpenSSH public key in admin.pub, then run:

SSHGATEW_ADMIN=admin SSHGATEW_VERSION=latest docker compose up -d
docker compose logs sshgatew

The first startup creates the database, encryption master key, gateway host key, configuration, and administrator automatically. The logs print the new gateway host-key fingerprint; verify it before connecting:

ssh -p 2222 admin@docker-host.example.com

Set SSHGATEW_PORT to publish a different host port and SSHGATEW_ADMIN_KEY_FILE if the bootstrap public key is not ./admin.pub. Configuration and encrypted data live in the sshgatew-config and sshgatew-data named volumes and survive container replacement. The bootstrap key file is used only when the configuration volume is empty. The service process runs as the unprivileged sshgatew user; the entrypoint uses its narrow startup capabilities only to repair named-volume ownership before dropping privileges.

OIDC login

SSHGateW can authenticate existing users through an OpenID Connect provider that advertises the OAuth 2.0 Device Authorization endpoint. Copy .env.example to an uncommitted .env file and configure the issuer and client. Docker Compose automatically reads .env next to compose.yaml:

SSHGATEW_OIDC_ENABLED=true
SSHGATEW_OIDC_ISSUER=https://identity.example.com/realms/team
SSHGATEW_OIDC_CLIENT_ID=sshgatew
SSHGATEW_OIDC_CLIENT_SECRET=
SSHGATEW_OIDC_USERNAME_CLAIM=preferred_username
SSHGATEW_OIDC_SCOPES=openid profile email
SSHGATEW_OIDC_LOGIN_TIMEOUT=5m

Then recreate the container and explicitly select keyboard-interactive login:

docker compose up -d
ssh -p 2222 \
  -o PubkeyAuthentication=no \
  -o PreferredAuthentications=keyboard-interactive \
  alice@gateway.example.com

The SSH session displays the provider verification URL and one-time code. Complete login in a browser and return to SSH. OIDC never creates users or changes grants. Each identity must first be linked to an existing enabled SSHGateW user. A user can authenticate with a registered gateway SSH key and press o in the TUI to link their own account. Administrators can select a user in the Users panel and map an OIDC username; the first successful login permanently binds that mapping to the provider's signed stable subject. Issuer, signature, audience, and token expiry are verified from discovery metadata and JWKS. Local TOTP remains an additional factor when enabled.

To build the image locally, add --build. To upgrade from GHCR without reinitializing:

SSHGATEW_VERSION=NEW_VERSION docker compose pull
docker compose up -d

Install from one binary

On a fresh systemd-based Linux server, download the release binary and run its interactive installer as root:

chmod +x sshgatew
sudo ./sshgatew install

The wizard asks for the administrator username, listen address, and an OpenSSH public key (which may be pasted directly). It then:

  • creates the locked sshgatew service account;
  • installs the binary at /usr/local/bin/sshgatew;
  • creates /etc/sshgatew and /var/lib/sshgatew with protected ownership;
  • generates the master key and gateway host key;
  • initializes SQLite and the administrator identity;
  • installs, enables, and starts the hardened systemd service; and
  • prints the gateway fingerprint and connection command.

For unattended provisioning:

sudo ./sshgatew install \
  --admin admin \
  --authorized-key /root/admin.pub \
  --listen 0.0.0.0:2222 \
  --yes

--authorized-key also accepts an inline OpenSSH public key. Use --no-start to enable the unit without starting it. The installer refuses to overwrite an existing configuration or database. Open TCP port 2222 in the host or provider firewall when required.

Initialize a Linux server

The following manual procedure remains available for custom layouts or systems without systemd.

Create the service account and protected directories:

sudo useradd --system --home /var/lib/sshgatew --shell /usr/sbin/nologin sshgatew
sudo install -d -o sshgatew -g sshgatew -m 0750 /var/lib/sshgatew
sudo install -d -o sshgatew -g sshgatew -m 0750 /etc/sshgatew
sudo install -o root -g root -m 0755 sshgatew /usr/local/bin/sshgatew

Initialize with an existing administrator public key:

sudo -u sshgatew /usr/local/bin/sshgatew \
  --config /etc/sshgatew/config.toml \
  init --admin admin --authorized-key /path/readable/by/sshgatew/admin.pub
sudo chown root:sshgatew /etc/sshgatew/config.toml /etc/sshgatew
sudo chmod 0640 /etc/sshgatew/config.toml
sudo chmod 0750 /etc/sshgatew

Install deploy/sshgatew.service, open TCP port 2222 in the host firewall, and start the service:

sudo install -o root -g root -m 0644 deploy/sshgatew.service /etc/systemd/system/sshgatew.service
sudo systemctl daemon-reload
sudo systemctl enable --now sshgatew
sudo systemctl status sshgatew

Connect with:

ssh -p 2222 admin@gateway.example.com

Users with TOTP enabled are shown a hidden six-digit challenge after their SSH key is accepted and before the gateway menu opens. Administrators can enroll or remove TOTP through a user's Manage menu. Enrollment displays a scannable QR code and requires a current authenticator code before it takes effect.

Verify the gateway host-key fingerprint printed by sshgatew init before accepting it in the SSH client.

Add a password target

First verify the downstream server's SSH host fingerprint through a trusted channel. The initial command prints the observed fingerprint but refuses to store it:

sudo -u sshgatew sshgatew targets add \
  --name production --host 10.0.0.10 --port 22 --remote-user deploy \
  --auth password

After verification, repeat with --accept-host-key. The password is read from a hidden prompt, never an argument:

sudo -u sshgatew sshgatew targets add \
  --name production --host 10.0.0.10 --port 22 --remote-user deploy \
  --auth password --accept-host-key
sudo -u sshgatew sshgatew grants add --target production --group operators

For private-key authentication, use --auth private_key --key-file PATH. Encrypted OpenSSH private keys are supported and their passphrases are stored inside the encrypted credential payload.

Administrators can also manage reusable keys entirely in the TUI. Open the SSH KEYS tab and press a to import an existing private key or generate a new Ed25519 key. Its public key and fingerprint remain viewable for installation on downstream servers, while the private key is encrypted with the gateway master key. Choose stored_key while adding a target or replacing its credential to select one of these saved keys. A saved key cannot be deleted while a target still uses it.

For a FIDO/YubiKey or another local-agent identity, choose forwarded_agent in the TUI or use --auth forwarded_agent --key-file PUBLIC_KEY. Connect to the gateway with ssh -A. SSHGateW permits only the configured public key and closes the agent channel immediately after downstream authentication.

Direct protocol routing

Use USER+TARGET to bypass the target menu. Authorization, pinned host-key verification, encrypted credentials, session limits, TOTP, and auditing still apply:

ssh -p 2222 justin+production@gateway.example.com
sftp -P 2222 justin+production@gateway.example.com
scp -P 2222 ./release.tar justin+production:/tmp/
ssh -N -p 2222 -L 5432:127.0.0.1:5432 justin+production@gateway.example.com

Modern SCP is transported through SFTP. Legacy scp -O is supported through a strictly parsed downstream SCP command. TCP forwarding requires both the grant's TCP forwarding capability and an exact destination configured in the TUI's FORWARDS section. Dynamic forwarding works only for destinations on that allowlist. Reverse forwarding is intentionally rejected.

Run local administration commands as the sshgatew service user to avoid creating SQLite WAL files with incompatible ownership.

See the operator guide and security model for the complete command and recovery workflow.

About

Self-hosted SSH credential gateway with a terminal UI, encrypted credentials, access control, and TOTP

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages