A production-ready SSH/SFTP reverse proxy written in Go that provides dynamic backend routing with IP-based access control.
- Dynamic Routing: Route SSH/SFTP connections to different backend servers based on credentials
- Multiple Backend Support:
- HTTP API with flexible authentication (Bearer, Basic, None)
- MySQL database queries
- IP Access Control: Per-target whitelist/blacklist support with CIDR notation
- Per-Target Host Keys: Persistent host keys prevent "host changed" warnings
- Transparent Proxying: Full SSH and SFTP protocol support
- Production Ready: Health checks, graceful shutdown, structured logging
- Docker Support: Complete containerization with docker-compose
- Clone the repository:
git clone https://github.com/klent/gatessh.git
cd gatessh- Create environment file:
cp .env.example .env
# Edit .env with your configuration- Start the proxy:
docker-compose -f deployments/docker-compose.yml up -d- Test the connection:
ssh -p 2222 user@localhost- Install dependencies:
make install-deps- Build:
make build- Configure environment and run:
export GATESSH_BACKEND_TYPE=api
export GATESSH_API_URL=https://your-api.com/route
export GATESSH_API_AUTH_TYPE=bearer
export GATESSH_API_AUTH_TOKEN=your_token
./gatesshAll configuration is done via environment variables with the GATESSH_ prefix.
| Variable | Default | Description |
|---|---|---|
GATESSH_LISTEN_ADDR |
:2222 |
SSH proxy listen address |
GATESSH_LOG_LEVEL |
info |
Log level (debug, info, warn, error) |
GATESSH_LOG_FORMAT |
json |
Log format (json, text) |
| Variable | Required | Description |
|---|---|---|
GATESSH_BACKEND_TYPE |
Yes | Backend type (api or mysql) |
| Variable | Default | Description |
|---|---|---|
GATESSH_API_URL |
- | API endpoint URL |
GATESSH_API_METHOD |
POST |
HTTP method |
GATESSH_API_AUTH_TYPE |
none |
Auth type (none, bearer, basic) |
GATESSH_API_AUTH_TOKEN |
- | Bearer token (if auth_type=bearer) |
GATESSH_API_AUTH_USERNAME |
- | Basic auth username |
GATESSH_API_AUTH_PASSWORD |
- | Basic auth password |
GATESSH_API_TIMEOUT |
10s |
Request timeout |
GATESSH_API_RESPONSE_HOST_FIELD |
host |
JSON field for target host |
GATESSH_API_RESPONSE_PORT_FIELD |
port |
JSON field for target port |
GATESSH_API_RESPONSE_WHITELIST_FIELD |
ip_whitelist |
JSON field for IP whitelist |
GATESSH_API_RESPONSE_BLACKLIST_FIELD |
ip_blacklist |
JSON field for IP blacklist |
API Request Format:
POST /route
{
"username": "user@example.com",
"password": "secret123",
"client_ip": "192.168.1.100"
}Expected API Response:
{
"host": "ssh-server-1.internal",
"port": 22,
"ip_whitelist": ["192.168.1.0/24", "10.0.0.0/8"],
"ip_blacklist": ["192.168.1.50"]
}| Variable | Default | Description |
|---|---|---|
GATESSH_MYSQL_DSN |
- | MySQL connection string |
GATESSH_MYSQL_QUERY |
- | SQL query with placeholders |
GATESSH_MYSQL_MAX_OPEN_CONNS |
10 |
Max open connections |
GATESSH_MYSQL_MAX_IDLE_CONNS |
5 |
Max idle connections |
GATESSH_MYSQL_CONN_MAX_LIFETIME |
5m |
Connection max lifetime |
MySQL Schema Example:
CREATE TABLE routes (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL,
target_host VARCHAR(255) NOT NULL,
target_port INT NOT NULL DEFAULT 22,
ip_whitelist TEXT COMMENT 'Comma-separated CIDRs',
ip_blacklist TEXT COMMENT 'Comma-separated CIDRs',
INDEX idx_username (username)
);
-- Example data
INSERT INTO routes (username, password, target_host, target_port, ip_whitelist, ip_blacklist)
VALUES ('alice', 'secret123', 'prod-server.internal', 22, '203.0.113.0/24', '');Query Example:
GATESSH_MYSQL_QUERY="SELECT target_host, target_port, ip_whitelist, ip_blacklist FROM routes WHERE username = ? AND password = ?"| Variable | Default | Description |
|---|---|---|
GATESSH_HOSTKEY_DIR |
/var/lib/gatessh/hostkeys |
Host key storage directory |
GATESSH_HOSTKEY_ALGORITHM |
ed25519 |
Key algorithm (ed25519, rsa) |
GATESSH_HOSTKEY_RSA_BITS |
2048 |
RSA key size (if algorithm=rsa) |
| Variable | Default | Description |
|---|---|---|
GATESSH_UPSTREAM_TIMEOUT |
30s |
Connection timeout |
GATESSH_UPSTREAM_KEEPALIVE |
15s |
SSH keepalive interval |
| Variable | Default | Description |
|---|---|---|
GATESSH_HEALTH_ENABLED |
true |
Enable health endpoint |
GATESSH_HEALTH_ADDR |
:8080 |
Health check HTTP address |
GATESSH_HEALTH_PATH |
/health |
Health check path |
GateSSH supports per-target IP whitelist and blacklist with CIDR notation.
- Whitelist only: Client IP must match at least one whitelist CIDR
- Blacklist only: Client IP must not match any blacklist CIDR
- Both: Whitelist is checked first (must pass), then blacklist (must not match)
- Neither: All IPs allowed (no filtering)
Example 1: Whitelist Only (Office Network)
{
"host": "prod-server.internal",
"port": 22,
"ip_whitelist": ["203.0.113.0/24"],
"ip_blacklist": []
}- ✅
203.0.113.50→ Allowed (in whitelist) - ❌
198.51.100.10→ Blocked (not in whitelist)
Example 2: Blacklist Only
{
"host": "staging-server.internal",
"port": 22,
"ip_whitelist": [],
"ip_blacklist": ["192.0.2.50"]
}- ❌
192.0.2.50→ Blocked (in blacklist) - ✅
10.0.0.100→ Allowed (not in blacklist)
Example 3: Combined
{
"host": "sensitive-server.internal",
"port": 22,
"ip_whitelist": ["10.0.0.0/8"],
"ip_blacklist": ["10.0.50.0/24"]
}- ✅
10.0.1.100→ Allowed (in whitelist, not in blacklist) - ❌
10.0.50.10→ Blocked (in blacklist) - ❌
192.168.1.1→ Blocked (not in whitelist)
ssh -p 2222 user@proxy.company.comsftp -P 2222 user@proxy.company.comscp -P 2222 file.txt user@proxy.company.com:/remote/path/# Build image
make docker-build
# Or manually
docker build -f deployments/docker/Dockerfile -t gatessh:latest .# Start
docker-compose -f deployments/docker-compose.yml up -d
# View logs
docker-compose -f deployments/docker-compose.yml logs -f gatessh
# Stop
docker-compose -f deployments/docker-compose.yml downcurl http://localhost:8080/healthResponse:
{
"status": "healthy",
"timestamp": "2026-01-10T14:30:00Z",
"version": "1.0.0"
}- Go 1.23 or later
- Make
- Docker and Docker Compose
make help # Show all available commands
make build # Build binary
make test # Run tests
make fmt # Format code
make clean # Clean artifactsClient → GateSSH → Backend → Upstream
(Port 2222) (Route) (Target SSH)
GateSSH generates unique host keys for each target, preventing "host changed" warnings.
# Docker
docker logs gatessh -f
# Parse JSON logs
docker logs gatessh 2>&1 | jq .Connection Refused
- Check if service is running
- Verify port 2222 is accessible
- Check firewall rules
Authentication Failures
- Verify backend configuration
- Check backend logs
- Test backend connectivity
IP Blocked
- Review whitelist/blacklist rules
- Check logs for client IP
- Verify CIDR notation
MIT License
Created by klent