Skip to content

Repository files navigation

 ┌─────────────────────────────────┐
│ ╔═══╗ │
│ ║ ⬡ ║ SSH GUARD │
│ ╚═══╝ │
│ ┌───┐ ┌───┐ ┌───┐ │
│ │ U ├──→ P ─┤ T │ │ T │ │
│ └───┘ ↕ └───┘ └───┘ │
│ AUDIT │
└─────────────────────────────────┘

🛡️ SSH Guard Proxy

A high-performance SSH proxy gateway built in Rust for security auditing, access control, and session recording.

RustLicense: MITtokio

Every keystroke recorded. Every connection authorized. Every session auditable.


🎯 Why SSH Guard Proxy?

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.

The Problem

  • 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

The Solution

Developer → SSH Guard Proxy → Target Server
↓
Audit Log (every keystroke)

✨ Features

FeatureDescription
🔐 Unified AuthenticationPassword (Argon2id) and public key auth at the gateway
🎛️ Access Control (ACL)Per-user host access policies — who can access what
📝 Full Audit LoggingEvery input/output recorded in JSON Lines format
🎬 Session RecordingAsciicast v2 format — replay any session with asciinema
🚫 Command FilteringBlacklist/whitelist mode — block dangerous commands in real-time
📂 SCP/SFTP AuditingFull file transfer logging with filenames, sizes, and direction
👁️ Session SharingMultiple admins can watch a live session in real-time (read-only)
High PerformanceBuilt on Rust + Tokio async runtime — minimal overhead
🏗️ Zero Target ChangesNo agent or modification needed on target servers
🔑 Host Key Auto-generationEd25519 host keys generated on first run
🚦 Connection LimitingMax session control and auth failure lockout

🏗️ Architecture

┌─────────────────────────────────────────────────────────────────┐
│ SSH Guard Proxy │
├─────────────────────────────────────────────────────────────────┤
│ │
│ User ──SSH──→ [SSH Server] ──→ [Auth & ACL] ──→ [Session Mgr] │
│ │ │
│ ▼ │
│ [Audit Logger] ◀────────────── [SSH Client] ──→ Target
│ │ │
│ ▼ │
│ logs/audit.jsonl │
│ │
└─────────────────────────────────────────────────────────────────┘

Data Flow

  1. User connects: ssh admin@proxy -p 2222
  2. Proxy authenticates the user (password or public key)
  3. User selects a target host from the authorized list
  4. Proxy establishes SSH connection to target
  5. All data is bidirectionally forwarded and logged
  6. Session ends → audit record finalized

🚀 Quick Start

Installation

Option 1: Download Pre-built Binary (Recommended)

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_password

The release package includes:

  • ssh_proxy — Main proxy binary
  • hash_password — Password hash generator tool
  • config/ — Configuration templates

Option 2: Build from Source

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

Configure

1. Proxy Settings (config/proxy.toml)

[server]
listen_address = "0.0.0.0"listen_port = 2222host_key_path = "config/host_key"
[session]
idle_timeout_secs = 1800max_sessions = 100
[audit]
log_dir = "logs"record_session = true
[security]
max_auth_attempts = 3lockout_duration_secs = 300

2. Add Users (config/users.toml)

Generate 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

3. Add Target Hosts (config/hosts.toml)

[[hosts]]
name = "web-01"address = "192.168.1.10"port = 22username = "deploy"auth_method = "key"private_key_path = "config/keys/web-01"
[[hosts]]
name = "db-01"address = "10.0.0.50"port = 22username = "dbadmin"auth_method = "password"password = "encrypted:your_password_here"

Run

# 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 proxykill$(cat ssh_proxy.pid)

Note: In production, use systemd (see Production Deployment below) for auto-restart, log management, and proper signal handling.

Connect

ssh admin@your-proxy-host -p 2222

You'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.

File Transfer (SCP/SFTP)

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 a File

# 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 a File

# 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 ./

Legacy SCP Mode

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/

Target Host Selection

MethodExampleDescription
Defaultadmin@proxyUses the first allowed host from ACL
Explicitadmin%db-server-01@proxySpecifies exact target host by name

SCP Audit Log Events

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"}

Session Sharing (Live Watch)

Admins with watch permission can observe another user's active session in real-time (read-only).

Enable Watch Permission

# config/users.toml
[[users]]
name = "admin"can_watch_sessions = truewatch_allowed_users = ["*"] # "*" = all users, or specific names

Usage

  1. Connect to the proxy: ssh admin@proxy-host -p 2222
  2. 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
    
  3. 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
    
  4. You now see the session output in real-time. Press Ctrl+C to stop watching.

Notes

  • 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
image
  • then execute ls command
image
  • Duplicate the terminal and log in to the ssh-proxy server. We will notice a ls command is being executed
image

📊 Audit Logs

All audit data is stored in logs/audit.jsonl in append-only JSON Lines format.

Log Events

EventDescription
auth_successSuccessful authentication
auth_failureFailed authentication attempt
session_startUser connected to a target host
session_endSession terminated
data (input)User keystrokes / commands
data (output)Server responses
command_blockedCommand rejected by filter
scp_session_startSCP/SFTP transfer session initiated
scp_file_transferFile transferred (name, size, direction)
session_watch_startAdmin started watching a session
session_watch_endAdmin stopped watching (with duration)

Example Log Entries

{"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"}

Decoding Commands from Logs

# View all input commands from a session
grep '"direction":"input"' logs/audit.jsonl | \
jq -r '.data_base64'| \
whileread line;doecho"$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 -l

Session Replay

Sessions are recorded in asciicast v2 format:

# Replay a recorded session
asciinema play logs/sessions/<session_id>.cast

🏢 Production Deployment

Systemd Service

Create /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 hardeningNoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/opt/ssh_proxy/logs
[Install]WantedBy=multi-user.target
sudo systemctl enable ssh-guard-proxy
sudo systemctl start ssh-guard-proxy

File Permissions

chmod 600 config/host_key
chmod 600 config/keys/*
chmod 644 config/*.toml
chmod 700 logs/

Log Rotation

Add to /etc/logrotate.d/ssh-guard-proxy:

/opt/ssh_proxy/logs/audit.jsonl {
daily
rotate 90
compress
delaycompress
missingok
notifempty
copytruncate
}

Firewall

# 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 22

📁 Project Structure

ssh_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

🔧 Technology Stack

ComponentChoiceRationale
LanguageRustMemory safety, zero-cost abstractions, fearless concurrency
SSH ProtocolrusshNative async SSH implementation (server + client)
Async RuntimeTokioIndustry-standard, battle-tested async runtime
Password HashingArgon2idWinner of Password Hashing Competition
LoggingtracingStructured, async-aware instrumentation
ConfigTOML + serdeHuman-readable, type-safe configuration

🗺️ Roadmap

  • 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)

🤝 Contributing

Contributions are welcome! Please open an issue first to discuss what you'd like to change.


📄 License

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.

About

🛡️ A high-performance SSH proxy gateway built in Rust — unified authentication, per-user access control (ACL), full session audit logging, and terminal replay. Secure your infrastructure without modifying target servers.

Topics

Resources

Stars

42 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages