SSHadow supports three operating modes, each with different trade-offs:
┌─────────────────────────────────────────────────────────────────────────┐
│ SSHadow │
├─────────────────────┬─────────────────────┬─────────────────────────────┤
│ Proxy Mode │ Log Monitor Mode │ Hybrid Mode │
├─────────────────────┼─────────────────────┼─────────────────────────────┤
│ • Intercepts SSH │ • Reads sshd logs │ • Both simultaneously │
│ • Real-time │ • Full cert details │ • Best visibility │
│ • Password forward │ • Passive/safe │ • Shared tracker │
│ • No cert details* │ • Slight delay │ • Single metrics endpoint │
└─────────────────────┴─────────────────────┴─────────────────────────────┘
* Unless SSH agent forwarding is implemented (future enhancement)
┌─────────────────┐
│ SSH Client │
└────────┬────────┘
│ SSH (port 2222)
▼
┌─────────────────────────────────────────┐
│ SSHadow Proxy │
│ ┌─────────────────────────────────┐ │
│ │ SSH Server (handshake) │ │
│ │ • Accept connection │ │
│ │ • Capture auth metadata │ │
│ │ • Cache credentials │ │
│ └──────────────┬──────────────────┘ │
│ │ │
│ ┌──────────────▼──────────────────┐ │
│ │ SSH Client (to target) │ │
│ │ • Connect to sshd │ │
│ │ • Forward password auth │ │
│ │ • Proxy channels │ │
│ └──────────────┬──────────────────┘ │
│ │ │
│ ┌──────────────▼──────────────────┐ │
│ │ Connection Tracker │────┼──► Metrics (:9090)
│ └─────────────────────────────────┘ │
└─────────────────┬───────────────────────┘
│ SSH (port 22)
▼
┌─────────────────────────────────────────┐
│ sshd │
└─────────────────────────────────────────┘
┌─────────────────┐
│ SSH Client │
└────────┬────────┘
│ SSH (port 22)
▼
┌─────────────────────────────────────────┐
│ sshd │
│ • Handles authentication directly │
│ • Logs to auth.log / journald │
└────────┬────────────────────────────────┘
│ Log output
▼
┌─────────────────────────────────────────┐
│ SSHadow Log Monitor │
│ ┌─────────────────────────────────┐ │
│ │ Log Watcher │ │
│ │ • Tail file or journald │ │
│ └──────────────┬──────────────────┘ │
│ │ │
│ ┌──────────────▼──────────────────┐ │
│ │ Log Parser │ │
│ │ • Parse sshd log format │ │
│ │ • Extract cert details │ │
│ └──────────────┬──────────────────┘ │
│ │ │
│ ┌──────────────▼──────────────────┐ │
│ │ Connection Tracker │────┼──► Metrics (:9090)
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘
┌─────────────────┐
│ SSH Client │
└────────┬────────┘
│ SSH (port 2222)
▼
┌─────────────────────────────────────────┐
│ SSHadow Proxy │
│ • Intercepts connection │
│ • Captures basic metadata │
│ • Forwards to sshd │
└────────┬────────────────────────────────┘
│ SSH (port 22)
▼
┌─────────────────────────────────────────┐
│ sshd │
│ • Handles final authentication │
│ • Logs with full cert details │
└────────┬────────────────────────────────┘
│ Log output
▼
┌─────────────────────────────────────────┐
│ SSHadow Log Monitor │
│ • Enriches with cert details │
└────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Shared Connection Tracker │◄── Both sources feed here
│ • Unified connection state │
│ • Combined statistics │
└────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Metrics Server (:9090) │
└─────────────────────────────────────────┘
Responsibilities:
- Command-line argument parsing
- Mode selection and validation
- Component initialization based on mode
- Graceful shutdown handling
Modes:
switch mode {
case "proxy": runProxyMode(...)
case "logmon": runLogMonMode(...)
case "hybrid": // Run both concurrently
go runProxyMode(...)
runLogMonMode(...)
}Responsibilities:
- Accept incoming SSH connections
- Perform SSH handshake with clients
- Extract authentication metadata
- Forward password authentication to sshd
- Proxy SSH channels bidirectionally
Key Structures:
type Server struct {
listenAddr string
targetAddr string
config *ssh.ServerConfig
tracker *monitor.Tracker
authCache sync.Map // Temporary credential storage
}Authentication Flow:
- Client connects to SSHadow
- SSH handshake captures auth method
- Credentials cached by session ID
- Connection accepted (all auth accepted at proxy level)
- SSHadow connects to sshd as client
- Password forwarded; pubkey auth fails (known limitation)
- Channels proxied bidirectionally
Current Limitation: Public key and certificate authentication cannot be forwarded because SSHadow doesn't have access to the client's private key. Future enhancement: SSH agent forwarding support.
Responsibilities:
- Tail log files in real-time
- Connect to journald for systemd systems
- Parse and process log events
Supported Sources:
- File:
/var/log/auth.log,/var/log/secure - Journald:
journalctl -u sshd -f
Responsibilities:
- Parse sshd log formats
- Extract full authentication metadata
- Handle syslog and journald timestamp formats
Parsed Events:
| Event | Pattern |
|---|---|
| Cert Auth | Accepted publickey ... TYPE-CERT SHA256:FP ID "KEYID" serial N CA TYPE SHA256:CAFP |
| Pubkey Auth | Accepted publickey ... TYPE SHA256:FP |
| Password Auth | Accepted password for USER from IP port PORT |
| Disconnect | Disconnected from user USER IP port PORT |
| Failed Auth | Failed password/publickey for USER from IP |
| Invalid User | Invalid user USER from IP port PORT |
Data Extracted:
type LogEvent struct {
Timestamp time.Time
Username string
SourceIP string
Port int
AuthType AuthType
KeyType string
Fingerprint string
CertID string // Only from logs
CertSerial uint64 // Only from logs
CAFingerprint string // Only from logs
}Responsibilities:
- Track active connections from both sources
- Aggregate statistics per user/key
- Thread-safe state management
Dual Input Methods:
// From proxy - real-time, basic metadata
func (t *Tracker) AddConnectionFromProxy(connID, sourceIP, username string,
authType AuthType, pubKey ssh.PublicKey, fingerprint string)
// From log parser - delayed, full cert details
func (t *Tracker) AddConnectionFromLog(connID string, event *LogEvent)Tracking Keys:
- Password auth:
username:sourceIP - Public key:
username:fingerprint - Certificate:
username:certKeyID
Endpoints:
| Endpoint | Format | Purpose |
|---|---|---|
/ |
HTML | Dashboard |
/metrics |
Prometheus | Time-series |
/metrics/json |
JSON | API access |
1. Client: ssh -p 2222 user@host
2. Proxy: Accept TCP, start SSH handshake
3. Proxy: passwordCallback captures password
4. Proxy: Cache password by session ID
5. Proxy: Accept auth (always succeeds at proxy)
6. Proxy: AddConnectionFromProxy(user, ip, AuthPassword, ...)
7. Proxy: ssh.Dial to sshd with password
8. sshd: Validates password
9. Proxy: Bidirectional channel proxy begins
10. Metrics: Shows active connection
1. Client: ssh user@host (with certificate)
2. sshd: Validates certificate against CA
3. sshd: Logs "Accepted publickey ... ED25519-CERT ... ID 'key-id' ..."
4. Watcher: Reads log line
5. Parser: Extracts user, IP, cert ID, CA fingerprint
6. Tracker: AddConnectionFromLog with full cert details
7. Metrics: Shows connection with cert metadata
1. Client: ssh -p 2222 user@host (password)
2. Proxy: Captures user, IP, auth type
3. Proxy: AddConnectionFromProxy (real-time)
4. Proxy: Forwards to sshd
5. sshd: Logs authentication
6. Watcher: Reads log
7. Parser: Extracts details (enriches if cert)
8. Tracker: Already has connection, updates metadata
9. Metrics: Complete picture - real-time + details
type Tracker struct {
mu sync.RWMutex
// Protected by mu:
connections map[string]*ConnectionInfo
stats map[string]*UserStats
}- Read operations:
RLock(concurrent reads) - Write operations:
Lock(exclusive access)
main()
├─ metrics server (goroutine)
│ └─ HTTP handlers (per request)
│
├─ [proxy mode] proxy server (goroutine)
│ └─ per connection (goroutine)
│ ├─ channel handler
│ └─ data proxying (goroutines)
│
└─ [logmon mode] log watcher (goroutine)
└─ processEvent (inline)
- Base: ~10-15 MB
- Per connection: ~500 bytes
- Per stat entry: ~500 bytes
- Idle: <1%
- Active (1000 conn/sec): <5%
- Proxy mode: +1ms per connection
- Log mode: 100-500ms delay (log flush)
- Hybrid: Real-time from proxy
// In proxy/server.go
func (s *Server) handleConnection(...) {
// After SSH handshake, check for agent forwarding request
// Open agent channel back to client
// Use agent.NewClient() for signing
// Forward signed auth to sshd
}// In monitor/logparser.go
var customPattern = regexp.MustCompile(`...`)
func ParseLogLine(line string) *LogEvent {
if matches := customPattern.FindStringSubmatch(message); matches != nil {
// Handle custom pattern
}
}For log monitor functionality:
# /etc/ssh/sshd_config
LogLevel VERBOSE
This enables logging of:
- Certificate key IDs
- Certificate serial numbers
- CA fingerprints
- Full authentication details
This architecture balances flexibility, security, and observability while supporting multiple deployment scenarios.