βββββββββββββββββββββββββββββββββββ
β βββββ β
β β ⬑ β SSH GUARD β
β βββββ β
β βββββ βββββ βββββ β
β β U ββββ P ββ€ T β β T β β
β βββββ β βββββ βββββ β
β AUDIT β
βββββββββββββββββββββββββββββββββββ
A high-performance SSH proxy gateway built in Rust for security auditing, access control, and session recording.
Every keystroke recorded. Every connection authorized. Every session auditable.
In modern infrastructure, direct SSH access to production servers is a security risk. SSH Guard Proxy solves this by acting as a single point of entry β a bastion host that enforces authentication, authorization, and full audit logging for every SSH session.
- Developers SSH directly into production servers with no oversight
- No centralized record of who did what and when
- Shared credentials make accountability impossible
- Revoking access requires touching every server
Developer β SSH Guard Proxy β Target Server
β
Audit Log (every keystroke)
| Feature | Description |
|---|---|
| π Unified Authentication | Password (Argon2id) and public key auth at the gateway |
| ποΈ Access Control (ACL) | Per-user host access policies β who can access what |
| π Full Audit Logging | Every input/output recorded in JSON Lines format |
| π¬ Session Recording | Asciicast v2 format β replay any session with asciinema |
| π« Command Filtering | Blacklist/whitelist mode β block dangerous commands in real-time |
| π SCP/SFTP Auditing | Full file transfer logging with filenames, sizes, and direction |
| ποΈ Session Sharing | Multiple admins can watch a live session in real-time (read-only) |
| β‘ High Performance | Built on Rust + Tokio async runtime β minimal overhead |
| ποΈ Zero Target Changes | No agent or modification needed on target servers |
| π Host Key Auto-generation | Ed25519 host keys generated on first run |
| π¦ Connection Limiting | Max session control and auth failure lockout |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β SSH Guard Proxy β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β User ββSSHβββ [SSH Server] βββ [Auth & ACL] βββ [Session Mgr] β
β β β
β βΌ β
β [Audit Logger] βββββββββββββββ [SSH Client] βββ Target
β β β
β βΌ β
β logs/audit.jsonl β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- User connects:
ssh admin@proxy -p 2222 - Proxy authenticates the user (password or public key)
- User selects a target host from the authorized list
- Proxy establishes SSH connection to target
- All data is bidirectionally forwarded and logged
- Session ends β audit record finalized
Download the latest release from the Releases page:
# Download and extract (example for macOS arm64)
curl -L https://github.com/mac119/ssh_proxy/releases/download/v0.1.0/ssh-guard-proxy-v0.1.0-darwin-arm64.tar.gz | tar xz
# Or manually download, extract, and set permissions
chmod +x ssh_proxy hash_passwordThe release package includes:
ssh_proxyβ Main proxy binaryhash_passwordβ Password hash generator toolconfig/β Configuration templates
Requires Rust 1.70+ (install via rustup):
git clone https://github.com/mac119/ssh_proxy.git
cd ssh_proxy
cargo build --release
# Binaries at: target/release/ssh_proxy, target/release/hash_password[server]
listen_address = "0.0.0.0"
listen_port = 2222
host_key_path = "config/host_key"
[session]
idle_timeout_secs = 1800
max_sessions = 100
[audit]
log_dir = "logs"
record_session = true
[security]
max_auth_attempts = 3
lockout_duration_secs = 300Generate a password hash first:
./hash_password 'YourSecurePassword'
# Output: $argon2id$v=19$m=19456,t=2,p=1$...Then add to config:
[[users]]
name = "admin"
password_hash = "$argon2id$v=19$m=19456,t=2,p=1$..."
public_keys = []
allowed_hosts = ["*"] # Access to all hosts
[[users]]
name = "developer"
password_hash = "$argon2id$v=19$m=19456,t=2,p=1$..."
public_keys = ["ssh-ed25519 AAAAC3Nza..."]
allowed_hosts = ["web-01", "web-02"] # Restricted access[[hosts]]
name = "web-01"
address = "192.168.1.10"
port = 22
username = "deploy"
auth_method = "key"
private_key_path = "config/keys/web-01"
[[hosts]]
name = "db-01"
address = "10.0.0.50"
port = 22
username = "dbadmin"
auth_method = "password"
password = "encrypted:your_password_here"# Foreground (for testing)
./ssh_proxy
# Background with nohup
nohup ./ssh_proxy > /var/log/ssh_proxy.log 2>&1 &
# Background with output to file (recommended for debugging)
./ssh_proxy >> logs/proxy.log 2>&1 &
echo $! > ssh_proxy.pid # Save PID for later stop
# Stop the proxy
kill $(cat ssh_proxy.pid)Note: In production, use systemd (see Production Deployment below) for auto-restart, log management, and proper signal handling.
ssh admin@your-proxy-host -p 2222You'll see:
Welcome, admin! Available hosts:
βββββββββββββββββββββββββββββββββββββ
[1] web-01 (192.168.1.10:22)
[2] web-02 (192.168.1.11:22)
[3] db-01 (10.0.0.50:22)
βββββββββββββββββββββββββββββββββββββ
Select host number:
Select a host and you're in β fully transparent, fully audited.
SSH Guard Proxy supports SCP and SFTP file transfers with full audit logging. All file transfers are recorded β including filenames, sizes, direction, and timestamps.
# Upload to the default target host (first allowed host)
scp -P 2222 myfile.txt admin@proxy-host:/tmp/
# Upload to a specific target host (use user%host format)
scp -P 2222 myfile.txt admin%db-server-01@proxy-host:/tmp/
# Recursive directory upload
scp -r -P 2222 ./my-folder admin%web-server-01@proxy-host:/opt/# Download from the default target host
scp -P 2222 admin@proxy-host:/etc/hosts ./
# Download from a specific target host
scp -P 2222 admin%db-server-01@proxy-host:/var/log/app.log ./Modern OpenSSH (9.0+) uses SFTP by default for scp commands. Both modes are fully supported:
# Default (SFTP mode) β works out of the box
scp -P 2222 file.txt admin@proxy-host:/tmp/
# Force legacy SCP protocol (if needed)
scp -O -P 2222 file.txt admin@proxy-host:/tmp/| Method | Example | Description |
|---|---|---|
| Default | admin@proxy |
Uses the first allowed host from ACL |
| Explicit | admin%db-server-01@proxy |
Specifies exact target host by name |
All file transfers generate audit entries:
{"event":"scp_session_start","session_id":"...","user":"admin","direction":"upload","target_host":"db-server-01","remote_path":"/tmp/"}
{"event":"scp_file_transfer","session_id":"...","user":"admin","direction":"upload","filename":"myfile.txt","size":10240,"mode":"0644"}Admins with watch permission can observe another user's active session in real-time (read-only).
# config/users.toml
[[users]]
name = "admin"
can_watch_sessions = true
watch_allowed_users = ["*"] # "*" = all users, or specific names- Connect to the proxy:
ssh admin@proxy-host -p 2222 - At the host selection menu, enter
w:Welcome, admin! Available hosts: βββββββββββββββββββββββββββββββββββββ [1] web-server-01 (192.168.1.10:22) βββββββββββββββββββββββββββββββββββββ [w] Watch active session βββββββββββββββββββββββββββββββββββββ Select host number: w - Select a session to watch:
Active sessions: βββββββββββββββββββββββββββββββββββββ [1] user=developer target=web-server-01 (5m ago, 0 watchers) βββββββββββββββββββββββββββββββββββββ Select session number (q to cancel): 1 - You now see the session output in real-time. Press
Ctrl+Cto stop watching.
- Watchers are read-only β no input is sent to the watched session
- Multiple admins can watch the same session simultaneously
- All watch events are audit-logged (
session_watch_start,session_watch_end)
Demo
- log in to the remote server
- then execute
lscommand
- Duplicate the terminal and log in to the ssh-proxy server. We will notice a
lscommand is being executed
All audit data is stored in logs/audit.jsonl in append-only JSON Lines format.
| Event | Description |
|---|---|
auth_success |
Successful authentication |
auth_failure |
Failed authentication attempt |
session_start |
User connected to a target host |
session_end |
Session terminated |
data (input) |
User keystrokes / commands |
data (output) |
Server responses |
command_blocked |
Command rejected by filter |
scp_session_start |
SCP/SFTP transfer session initiated |
scp_file_transfer |
File transferred (name, size, direction) |
session_watch_start |
Admin started watching a session |
session_watch_end |
Admin stopped watching (with duration) |
{"event":"auth_success","timestamp":"2026-05-05T07:10:00Z","user":"admin","peer_addr":"10.0.1.5:54321","method":"password"}
{"event":"session_start","timestamp":"2026-05-05T07:10:05Z","session_id":"a1b2c3d4","user":"admin","peer_addr":"10.0.1.5:54321","target_host":"web-01","target_addr":"192.168.1.10"}
{"event":"data","timestamp":"2026-05-05T07:10:10Z","session_id":"a1b2c3d4","direction":"input","data_base64":"bHMgLWxhCg==","data_len":7}
{"event":"data","timestamp":"2026-05-05T07:10:10Z","session_id":"a1b2c3d4","direction":"output","data_base64":"dG90YWwgNDgK...","data_len":256}
{"event":"session_end","timestamp":"2026-05-05T07:45:00Z","session_id":"a1b2c3d4"}# View all input commands from a session
grep '"direction":"input"' logs/audit.jsonl | \
jq -r '.data_base64' | \
while read line; do echo "$line" | base64 -d; done
# Find who connected today
grep '"event":"session_start"' logs/audit.jsonl | \
grep "$(date +%Y-%m-%d)" | \
jq '{user, target_host, timestamp}'
# Count failed logins
grep '"event":"auth_failure"' logs/audit.jsonl | wc -lSessions are recorded in asciicast v2 format:
# Replay a recorded session
asciinema play logs/sessions/<session_id>.castCreate /etc/systemd/system/ssh-guard-proxy.service:
[Unit]
Description=SSH Guard Proxy
After=network.target
[Service]
Type=simple
User=sshproxy
Group=sshproxy
WorkingDirectory=/opt/ssh_proxy
ExecStart=/opt/ssh_proxy/ssh_proxy
Restart=always
RestartSec=5
# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/opt/ssh_proxy/logs
[Install]
WantedBy=multi-user.targetsudo systemctl enable ssh-guard-proxy
sudo systemctl start ssh-guard-proxychmod 600 config/host_key
chmod 600 config/keys/*
chmod 644 config/*.toml
chmod 700 logs/Add to /etc/logrotate.d/ssh-guard-proxy:
/opt/ssh_proxy/logs/audit.jsonl {
daily
rotate 90
compress
delaycompress
missingok
notifempty
copytruncate
}
# Only expose the proxy port
ufw allow 2222/tcp
# Block direct SSH to target hosts from outside
ufw deny from any to 192.168.1.0/24 port 22ssh_proxy/
βββ Cargo.toml # Dependencies & build config
βββ config/
β βββ proxy.toml # Main proxy configuration
β βββ users.toml # User accounts & ACL
β βββ hosts.toml # Target host definitions
β βββ host_key # Auto-generated Ed25519 host key
βββ src/
β βββ main.rs # Entry point
β βββ config.rs # Configuration loading
β βββ server/
β β βββ mod.rs # TCP listener & session spawning
β β βββ handler.rs # SSH protocol handler (auth, data relay)
β βββ client/
β β βββ mod.rs # SSH client (connects to targets)
β βββ auth/
β β βββ mod.rs # Authentication (Argon2id, pubkey)
β β βββ acl.rs # Access control logic
β βββ session/
β β βββ mod.rs # Session lifecycle management
β βββ audit/
β βββ mod.rs # Audit event logger
β βββ recorder.rs # Asciicast session recorder
βββ src/bin/
β βββ hash_password.rs # CLI tool to generate password hashes
βββ logs/ # Audit output directory
| Component | Choice | Rationale |
|---|---|---|
| Language | Rust | Memory safety, zero-cost abstractions, fearless concurrency |
| SSH Protocol | russh | Native async SSH implementation (server + client) |
| Async Runtime | Tokio | Industry-standard, battle-tested async runtime |
| Password Hashing | Argon2id | Winner of Password Hashing Competition |
| Logging | tracing | Structured, async-aware instrumentation |
| Config | TOML + serde | Human-readable, type-safe configuration |
- Web management UI (live sessions, replay, user management)
- Database backend (PostgreSQL/SQLite for config & logs)
- Multi-factor authentication (TOTP/WebAuthn)
- Command blacklist/whitelist filtering
- SCP/SFTP file transfer auditing
- Cluster mode with load balancing
- Real-time alerting (Slack/webhook on suspicious activity)
- Session sharing (multiple admins watching one session)
Contributions are welcome! Please open an issue first to discuss what you'd like to change.
This project is licensed under the MIT License β see the LICENSE file for details.
Built with π¦ Rust for maximum performance and safety.
SSH Guard Proxy β Because security shouldn't be an afterthought.