README
¶
sshbox
A small, programmable SSH server that can:
- act as a regular SSH server and run a command (or shell) per session
- act as a flexible “SSH proxy/jump” that executes a command template to another SSH endpoint
- sandbox sessions by running a containerized environment (e.g. uLinux
boxor Docker)
It focuses on composable authentication methods and a powerful command templating model so you can control exactly what runs for an incoming SSH connection.
Based on the excellent examples from gliderlabs/ssh
Features
- Public key authentication via multiple pluggable methods (OR semantics; first match wins):
- none: accept any key (for testing only)
- file:: parse authorized keys from a local file
- github[:user1,user2,...]: fetch keys from https://github.com/.keys (optional allow-list)
- url:[|user1,user2,...]: fetch keys from an arbitrary URL format (e.g. your Gitea), with optional allow-list
- lookup:<users.yml>[|user1,user2,...]: in-repo YAML user DB with keys and attributes
- Agent forwarding passthrough for child processes you run (enable ForwardAgent in the client if you intend to hop to an upstream SSH server)
- Command templating with session/user variables and helper functions
- Optional pre-hook and post-hook commands per session (with templating)
- MOTD display (toggle)
- Host key management: generates a host key if not present
- Graceful shutdown on signals
- Cron-like hook runner for periodic tasks
- Sandbox helpers using Docker or uLinux
box(scripts included)
Install
With a Go toolchain:
- Go 1.20+ recommended
Install latest:
go install git.mills.io/prologic/sshbox@latest
Build from source:
git clone https://git.mills.io/prologic/sshbox.git
cd sshbox
make
Prebuilt binaries are published on the Releases page.
Quick start
Run an SSH server on :2222, authenticating against an authorized_keys file, and starting a shell:
sshbox -a file:$HOME/.ssh/authorized_keys /bin/sh
Authorize via GitHub for an explicit allow-list of users and start a shell:
sshbox -a github:alice,bob /bin/sh
Authorize via an arbitrary URL format (e.g. your Gitea or other service). The format must include a single %s placeholder for the SSH username:
# Example: url:<format>|<allow-list>
sshbox -a 'url:https://git.mills.io/%s.keys|alice,bob' /bin/sh
YAML lookup (bundled DB with attributes):
sshbox -a lookup:./users.yml|alice,bob /bin/sh
Using sshbox as an SSH proxy/jump
sshbox does not embed an SSH client to upstream, but it executes the command you provide for each session. You can therefore run the system ssh client in a controlled way and pass through the original command and environment. Agent forwarding is supported when the connecting client requests it.
Important points:
- The command you pass to sshbox is tokenized and executed. The user’s requested command (if any) is appended to the args.
- You can template variables into your command to route by username, set environment, or enforce policy.
- If you want the upstream SSH to authenticate using the caller’s keys, ensure the caller enables agent forwarding to sshbox (ForwardAgent yes), so child ssh processes see SSH_AUTH_SOCK.
Example: forward all sessions to an upstream Gitea instance as the git user, passing the original command:
sshbox \
-a github:alice,bob \
-a 'url:https://git.mills.io/%s.keys|alice,bob' \
'ssh -o BatchMode=yes -o StrictHostKeyChecking=no [email protected] ${Args}'
Notes:
- ${Args} expands to the client’s requested command (e.g. git-receive-pack 'repo.git').
- If the connecting client forwarded their agent, the spawned ssh process will be able to use it to authenticate to the upstream.
- If you want to force a specific upstream port or user, set it in the template. The template is evaluated before arg splitting.
Sandboxing sessions
You can use the included scripts to start/attach to a Docker-based sandbox per user. The YAML user DB can provide resource attributes that become environment variables.
Example using the provided scripts:
# Prepare the per-user home volume if needed
sshbox \
-a lookup:./users.yml \
-p './setup-home.sh ${User}' \
'./sandbox.sh ${User}'
- setup-home.sh ensures a Docker volume and data container exist for ${User} and sets ownership
- sandbox.sh starts or reuses a per-user container with constraints sourced from user_attrs_* env
See users.yml for an example of per-user attributes (e.g. mem, cpus, pids, image). These appear as env vars named user_attrs_ and can be used in hooks/templates.
CLI options
- -b, --bind string (default ":2222"): interface:port to bind
- -v, --version: print version
- -d, --debug: enable debug logging
- -k, --host-key string (default "ssh_host_key"): path to host private key; auto-generated if missing
- -a, --auth method [...]: one or more auth methods (first to pass wins)
- --disable-motd: suppress /etc/motd output on PTY sessions
- -p, --pre-hook string: command to run before starting the session
- -P, --post-hook string: command to run after the session ends
- -e, --every-hook 'spec:cmd': run cmd on a cron-like schedule (spec is robfig/cron v3 format)
Auth method syntax:
- none
- file:
- github[:user1,user2,...]
- url:[|user1,user2,...]
- lookup:<users.yml>[|user1,user2,...]
When an allow-list is provided (comma separated), only those SSH usernames may authenticate with that method.
Templating and environment
The command, pre-hook and post-hook strings are processed with a template engine prior to execution. Available variables:
- User: SSH username (ctx.User)
- Host: remote address
- Time: Unix seconds at session start
- Args: client’s requested command joined as a single string
- Env: full environment passed by the client
- user_name, user_email: from lookup: users.yml
- user_attrs_: for each attribute in users.yml under attrs
Helper functions available in templates:
- getEnv "VAR": read a server-side environment variable
- getUserID "username": resolve a local system username to UID
- getGroupID "group": resolve a local system group to GID
Example:
sshbox \
-a lookup:./users.yml \
-p 'logger pre user=${User} host=${Host} ts=${Time}' \
-P 'logger post user=${User} ts=${Time}' \
'box run ${user_attrs_image:-alpine} /bin/sh'
Security considerations
- Auth methods are OR’d with “first match wins.” Order matters. Place stricter/fast methods first if desired.
- github and url methods fetch keys over HTTPS for the SSH username. If you do not supply an allow-list, you effectively trust “any valid user at the provider” for the username presented. Use allow-lists wherever possible.
- If you run sshbox as a proxy, ensure you always pass a fixed command that does only what you intend (e.g., spawn a specific ssh command). If you omit the command, sshbox will default to /bin/sh, which is almost certainly not what you want for a proxy.
- Agent forwarding: the upstream hop will only work with the caller’s keys if they enable agent forwarding to sshbox. Document this requirement for your users.
- Hooks and templates: avoid interpolating untrusted values directly into shell commands without quoting. The template engine substitutes literal strings into your command; use cautious shell practices if you expand variables.
- Host key: sshbox auto-generates a host key if missing. Back up or manage this key if you require host key pinning for clients.
Example: Gitea proxy layout
Goal: accept SSH connections to sshbox and forward to [email protected], while authenticating callers using a combination of your Gitea keys API and GitHub keys.
- Require an allow-list of users for both methods to avoid authorizing unexpected accounts.
- Require clients to enable agent forwarding for sshbox so the child ssh can authenticate to git.mills.io using their own keys.
- Force the forwarding command.
sshbox \
-b :2222 \
-d \
-k /etc/ssh/sshbox_host_key \
-a 'url:https://git.mills.io/%s.keys|alice,bob' \
-a 'github:alice,bob' \
'ssh -o BatchMode=yes -o StrictHostKeyChecking=no [email protected] ${Args}'
Client-side (example):
# ~/.ssh/config
Host sshbox
HostName sshbox.example.org
Port 2222
User alice
ForwardAgent yes
Users then run:
git clone ssh://sshbox/repo.git
# or
ssh sshbox info
The upstream [email protected] will still enforce repository-level writes based on keys it trusts. If a user is not present at Gitea (or the repo denies them), the write will fail even if sshbox authentication succeeded.
License
MIT