Document SSH ProxyJump configuration for accessing KVM guest VMs through grokbox hypervisor as a bastion/jump host. Documentation includes: - Architecture diagram with network topology - Jump host concept and benefits explanation - Implementation details (group_vars, hosts.yml, SSH config) - Connection flow and SSH handshake details - Usage examples (Ansible, manual SSH, SCP) - Comprehensive troubleshooting guide - Security considerations and hardening recommendations - Performance optimization (ControlMaster, connection pooling) - Monitoring and logging procedures - Alternative access patterns - Testing and validation checklist Current Configuration: - Jump Host: grokbox (grok.home.serneels.xyz) - Guest VMs: pihole, mymx, derp (192.168.122.0/24) - Method: SSH ProxyJump with ControlMaster multiplexing - Group vars configured in: group_vars/kvm_guests.yml - Per-host settings in: hosts.yml Key Features: ✅ Automatic ProxyJump for all kvm_guests group members ✅ SSH connection multiplexing for performance ✅ Keepalive configuration to prevent timeouts ✅ Security-first approach with audit logging ✅ Tested and working (pihole ping successful) Benefits: - Centralized access control through single entry point - Guest VMs remain on private network (not exposed) - Reduced attack surface - Simplified network architecture - Comprehensive audit trail Related Files: - inventories/development/group_vars/kvm_guests.yml (config) - inventories/development/hosts.yml (host definitions) - ansible.cfg (global SSH settings) This completes the network access pattern documentation required for multi-tier infrastructure access. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
544 lines
13 KiB
Markdown
544 lines
13 KiB
Markdown
# Network Access Patterns and SSH Jump Host Configuration
|
|
|
|
**Document Version**: 1.0
|
|
**Last Updated**: 2025-11-11
|
|
**Status**: Production Ready
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
This document describes the network access patterns and SSH jump host (ProxyJump/bastion) configuration for accessing infrastructure resources through intermediary hosts.
|
|
|
|
---
|
|
|
|
## Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ Control Node (Ansible) │
|
|
│ (Your Local Machine) │
|
|
└────────────────┬────────────────────────────────────────────────┘
|
|
│
|
|
│ SSH Direct Access
|
|
│
|
|
┌───────▼────────┐
|
|
│ grokbox │ ◄──── Hypervisor / Jump Host
|
|
│ (Jump Host) │ grok.home.serneels.xyz
|
|
│ KVM/libvirt │ SSH Port: 22
|
|
└───────┬────────┘
|
|
│
|
|
│ ProxyJump / Bastion
|
|
│ Internal Network: 192.168.122.0/24
|
|
│
|
|
┌───────────┼───────────┬───────────┐
|
|
│ │ │ │
|
|
┌────▼────┐ ┌───▼────┐ ┌────▼────┐ ┌───▼────┐
|
|
│ pihole │ │ mymx │ │ derp │ │ ... │
|
|
│ .12 │ │ .119 │ │ .99 │ │ │
|
|
└─────────┘ └────────┘ └─────────┘ └────────┘
|
|
Guest VMs (Private Network - virbr0)
|
|
```
|
|
|
|
---
|
|
|
|
## Jump Host Configuration
|
|
|
|
### What is a Jump Host (Bastion)?
|
|
|
|
A **jump host** (also called a **bastion host**) is an intermediary server used to access machines on a private network from an external network. In our infrastructure:
|
|
|
|
- **Jump Host**: `grokbox` (grok.home.serneels.xyz)
|
|
- **Target Hosts**: KVM guest VMs on private network (192.168.122.0/24)
|
|
- **Access Method**: SSH ProxyJump
|
|
|
|
### Benefits
|
|
|
|
1. **Security**: Guest VMs don't need public internet exposure
|
|
2. **Centralized Access Control**: All access goes through grokbox
|
|
3. **Simplified Network**: Private network behind NAT
|
|
4. **Audit Trail**: All connections logged on jump host
|
|
5. **Reduced Attack Surface**: Single hardened entry point
|
|
|
|
---
|
|
|
|
## Implementation
|
|
|
|
### 1. Group Variables Configuration
|
|
|
|
**File**: `inventories/development/group_vars/kvm_guests.yml`
|
|
|
|
```yaml
|
|
# SSH & Connectivity
|
|
ansible_user: ansible
|
|
ansible_become_password: null # Passwordless sudo configured
|
|
|
|
# Connection via ProxyJump through hypervisor
|
|
ansible_ssh_common_args: >-
|
|
-o ProxyJump=grokbox
|
|
-o StrictHostKeyChecking=accept-new
|
|
-o ServerAliveInterval=45
|
|
-o ServerAliveCountMax=3
|
|
-o ControlMaster=auto
|
|
-o ControlPersist=600s
|
|
```
|
|
|
|
**SSH Options Explained**:
|
|
- `ProxyJump=grokbox`: Route connection through grokbox
|
|
- `StrictHostKeyChecking=accept-new`: Auto-accept new host keys (development)
|
|
- `ServerAliveInterval=45`: Send keepalive every 45 seconds
|
|
- `ServerAliveCountMax=3`: Disconnect after 3 failed keepalives
|
|
- `ControlMaster=auto`: Enable SSH connection multiplexing
|
|
- `ControlPersist=600s`: Keep master connection for 10 minutes
|
|
|
|
### 2. Host Inventory Configuration
|
|
|
|
**File**: `inventories/development/hosts.yml`
|
|
|
|
```yaml
|
|
kvm_guests:
|
|
vars:
|
|
ansible_user: ansible
|
|
ansible_python_interpreter: /usr/bin/python3
|
|
ansible_ssh_common_args: '-o StrictHostKeyChecking=accept-new'
|
|
host_type: virtual_machine
|
|
hypervisor: grokbox
|
|
network: "virbr0 (192.168.122.0/24)"
|
|
|
|
children:
|
|
dns_servers:
|
|
hosts:
|
|
pihole:
|
|
ansible_host: 192.168.122.12
|
|
ansible_ssh_common_args: '-o ProxyJump=grokbox -o StrictHostKeyChecking=accept-new'
|
|
host_description: "Pi-hole DNS/DHCP Server"
|
|
```
|
|
|
|
### 3. SSH Config Integration (Optional)
|
|
|
|
For manual SSH access, add to `~/.ssh/config`:
|
|
|
|
```ssh-config
|
|
# Jump Host
|
|
Host grokbox
|
|
HostName grok.home.serneels.xyz
|
|
User grok
|
|
ForwardAgent yes
|
|
ControlMaster auto
|
|
ControlPersist 10m
|
|
ControlPath ~/.ssh/control-%C
|
|
|
|
# KVM Guest VMs via Jump Host
|
|
Host pihole mymx derp
|
|
User ansible
|
|
ProxyJump grokbox
|
|
StrictHostKeyChecking accept-new
|
|
ServerAliveInterval 45
|
|
ServerAliveCountMax 3
|
|
|
|
# Specific VM configurations
|
|
Host pihole
|
|
HostName 192.168.122.12
|
|
|
|
Host mymx
|
|
HostName 192.168.122.119
|
|
|
|
Host derp
|
|
HostName 192.168.122.99
|
|
```
|
|
|
|
---
|
|
|
|
## Usage Examples
|
|
|
|
### Ansible Ad-Hoc Commands
|
|
|
|
```bash
|
|
# Ping all KVM guest VMs through jump host
|
|
ansible kvm_guests -m ping
|
|
|
|
# Ping specific VM
|
|
ansible pihole -m ping
|
|
|
|
# Run command on all guests
|
|
ansible kvm_guests -m shell -a "uptime"
|
|
|
|
# Gather facts from pihole through jump host
|
|
ansible pihole -m setup
|
|
```
|
|
|
|
### Playbook Execution
|
|
|
|
```bash
|
|
# Run system_info playbook on KVM guests
|
|
ansible-playbook playbooks/gather_system_info.yml --limit kvm_guests
|
|
|
|
# Deploy to specific VM through jump host
|
|
ansible-playbook playbooks/deploy_vm.yml --limit pihole
|
|
|
|
# Security audit all guests
|
|
ansible-playbook playbooks/security_audit.yml --limit kvm_guests
|
|
```
|
|
|
|
### Manual SSH Access
|
|
|
|
```bash
|
|
# SSH to guest VM (using ~/.ssh/config)
|
|
ssh pihole
|
|
|
|
# SSH with explicit ProxyJump
|
|
ssh -J grokbox ansible@192.168.122.12
|
|
|
|
# SSH with verbose output for debugging
|
|
ssh -vvv -J grokbox ansible@192.168.122.12
|
|
|
|
# Copy files through jump host
|
|
scp -o ProxyJump=grokbox file.txt ansible@192.168.122.12:/tmp/
|
|
```
|
|
|
|
---
|
|
|
|
## Connection Flow
|
|
|
|
### 1. Standard Ansible Connection
|
|
```
|
|
Control Node → grokbox → Guest VM
|
|
| | |
|
|
└─ SSH ───────┴─ SSH ───┘
|
|
(public) (private)
|
|
```
|
|
|
|
### 2. Detailed SSH Handshake
|
|
```
|
|
1. Control Node connects to grokbox
|
|
ssh grok@grok.home.serneels.xyz
|
|
|
|
2. Control Node requests ProxyJump
|
|
ProxyJump=grokbox initiates second connection
|
|
|
|
3. grokbox connects to Guest VM
|
|
ssh ansible@192.168.122.12
|
|
|
|
4. Two-hop SSH tunnel established
|
|
Control Node ←→ grokbox ←→ Guest VM
|
|
|
|
5. Ansible executes tasks through tunnel
|
|
SFTP transfers, module execution, etc.
|
|
```
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Issue 1: Connection Timeout
|
|
|
|
**Symptom**: `Connection timed out` or `Operation timed out`
|
|
|
|
**Debug**:
|
|
```bash
|
|
# Test jump host connectivity
|
|
ansible grokbox -m ping
|
|
|
|
# Test with verbose SSH
|
|
ansible pihole -m ping -vvv
|
|
```
|
|
|
|
**Solution**:
|
|
- Verify jump host (grokbox) is reachable
|
|
- Check jump host firewall allows SSH
|
|
- Verify guest VM IP address is correct
|
|
- Ensure guest VM is running: `ssh grokbox "virsh list --all"`
|
|
|
|
### Issue 2: Authentication Failed
|
|
|
|
**Symptom**: `Permission denied (publickey)`
|
|
|
|
**Debug**:
|
|
```bash
|
|
# Test SSH key forwarding
|
|
ssh -A grokbox "ssh -T ansible@192.168.122.12"
|
|
|
|
# Check authorized_keys on guest
|
|
ssh grokbox "ssh ansible@192.168.122.12 'cat ~/.ssh/authorized_keys'"
|
|
```
|
|
|
|
**Solution**:
|
|
- Verify SSH public key is in guest VM's `~/.ssh/authorized_keys`
|
|
- Check file permissions: `authorized_keys` (600), `.ssh/` (700)
|
|
- Ensure ansible user exists on guest VM
|
|
- Verify SSH key format (RSA, ED25519, etc.)
|
|
|
|
### Issue 3: ProxyJump Not Working
|
|
|
|
**Symptom**: `ProxyJump not supported` or `Bad configuration option`
|
|
|
|
**Debug**:
|
|
```bash
|
|
# Check SSH version (ProxyJump requires OpenSSH 7.3+)
|
|
ssh -V
|
|
|
|
# Test with ProxyCommand instead
|
|
ssh -o ProxyCommand="ssh -W %h:%p grokbox" ansible@192.168.122.12
|
|
```
|
|
|
|
**Solution**:
|
|
- Update OpenSSH to version 7.3 or higher
|
|
- Use `-o ProxyCommand` as fallback for older SSH versions
|
|
- Check SSH config syntax
|
|
|
|
### Issue 4: Connection Multiplexing Issues
|
|
|
|
**Symptom**: `Control socket connect: Connection refused`
|
|
|
|
**Debug**:
|
|
```bash
|
|
# Clear SSH control sockets
|
|
rm -rf ~/.ssh/control-*
|
|
|
|
# Disable ControlMaster temporarily
|
|
ansible pihole -m ping -e 'ansible_ssh_common_args=""'
|
|
```
|
|
|
|
**Solution**:
|
|
- Remove stale control sockets
|
|
- Increase ControlPersist timeout
|
|
- Check control socket directory permissions
|
|
|
|
---
|
|
|
|
## Security Considerations
|
|
|
|
### Jump Host Hardening
|
|
|
|
The jump host (grokbox) must be properly secured:
|
|
|
|
1. **SSH Hardening** (applied via `deploy_linux_vm` role):
|
|
- Disable root login
|
|
- Key-based authentication only
|
|
- Fail2ban for brute-force protection
|
|
- Rate limiting on SSH port
|
|
|
|
2. **Firewall Configuration**:
|
|
```bash
|
|
# Allow SSH from specific IPs only (recommended)
|
|
ufw allow from YOUR_IP to any port 22
|
|
|
|
# Or allow SSH globally (less secure)
|
|
ufw allow 22/tcp
|
|
```
|
|
|
|
3. **Audit Logging**:
|
|
- Enable `auditd` for SSH session logging
|
|
- Monitor `/var/log/auth.log` for access attempts
|
|
- Set up alerts for failed authentication
|
|
|
|
4. **Network Segmentation**:
|
|
- Keep guest VM network (virbr0) isolated from public internet
|
|
- Use firewall rules to restrict guest-to-guest communication if needed
|
|
|
|
### SSH Key Management
|
|
|
|
1. **Use Strong Key Types**:
|
|
```bash
|
|
# Generate ED25519 key (recommended)
|
|
ssh-keygen -t ed25519 -C "ansible@infrastructure"
|
|
|
|
# Or RSA 4096-bit
|
|
ssh-keygen -t rsa -b 4096 -C "ansible@infrastructure"
|
|
```
|
|
|
|
2. **Key Rotation**:
|
|
- Rotate SSH keys every 90-180 days
|
|
- Maintain key inventory
|
|
- Remove old/unused keys from `authorized_keys`
|
|
|
|
3. **SSH Agent Forwarding** (Use with caution):
|
|
```yaml
|
|
# In group_vars if needed
|
|
ansible_ssh_extra_args: '-o ForwardAgent=yes'
|
|
```
|
|
⚠️ **Warning**: Agent forwarding can expose keys if jump host is compromised
|
|
|
|
---
|
|
|
|
## Performance Optimization
|
|
|
|
### SSH ControlMaster
|
|
|
|
ControlMaster (already configured) reuses SSH connections:
|
|
|
|
```yaml
|
|
ansible_ssh_common_args: >-
|
|
-o ControlMaster=auto
|
|
-o ControlPersist=600s
|
|
```
|
|
|
|
**Benefits**:
|
|
- Reduces connection overhead by 80-90%
|
|
- Faster playbook execution
|
|
- Lower resource usage on jump host
|
|
|
|
### Connection Pooling
|
|
|
|
ansible.cfg settings:
|
|
|
|
```ini
|
|
[ssh_connection]
|
|
pipelining = True
|
|
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
|
|
```
|
|
|
|
### Parallel Execution
|
|
|
|
```bash
|
|
# Run playbook with increased forks (default: 5)
|
|
ansible-playbook site.yml --forks 20
|
|
|
|
# Or set in ansible.cfg
|
|
[defaults]
|
|
forks = 20
|
|
```
|
|
|
|
---
|
|
|
|
## Monitoring and Logging
|
|
|
|
### Jump Host Access Logs
|
|
|
|
**View SSH connection logs**:
|
|
```bash
|
|
# On grokbox (jump host)
|
|
sudo tail -f /var/log/auth.log | grep sshd
|
|
|
|
# Filter for ansible user connections
|
|
sudo grep "ansible" /var/log/auth.log | grep "Accepted"
|
|
```
|
|
|
|
### Ansible Logging
|
|
|
|
Enable Ansible logging in `ansible.cfg`:
|
|
|
|
```ini
|
|
[defaults]
|
|
log_path = /var/log/ansible/ansible.log
|
|
```
|
|
|
|
### Connection Metrics
|
|
|
|
```bash
|
|
# Show active SSH connections on jump host
|
|
ssh grokbox "ss -tn | grep :22"
|
|
|
|
# Show Ansible control sockets
|
|
ls -lh ~/.ssh/control-*
|
|
```
|
|
|
|
---
|
|
|
|
## Alternative Access Patterns
|
|
|
|
### Pattern 1: SSH Config Only
|
|
|
|
Rely solely on `~/.ssh/config` without Ansible inventory settings:
|
|
|
|
```ssh-config
|
|
# ~/.ssh/config
|
|
Host pihole
|
|
HostName 192.168.122.12
|
|
User ansible
|
|
ProxyJump grokbox
|
|
```
|
|
|
|
Then use simple inventory:
|
|
```yaml
|
|
pihole:
|
|
ansible_host: 192.168.122.12
|
|
# No ansible_ssh_common_args needed
|
|
```
|
|
|
|
### Pattern 2: Dynamic ProxyJump
|
|
|
|
Set jump host dynamically based on group membership:
|
|
|
|
```yaml
|
|
# group_vars/kvm_guests.yml
|
|
ansible_ssh_common_args: "-o ProxyJump={{ hypervisor_host }}"
|
|
hypervisor_host: grokbox
|
|
```
|
|
|
|
### Pattern 3: Multi-Hop Jump Hosts
|
|
|
|
For infrastructure with multiple tiers:
|
|
|
|
```yaml
|
|
ansible_ssh_common_args: >-
|
|
-o ProxyJump=bastion1,bastion2,grokbox
|
|
```
|
|
|
|
Or in SSH config:
|
|
```ssh-config
|
|
Host target
|
|
ProxyJump bastion1,bastion2,grokbox
|
|
```
|
|
|
|
---
|
|
|
|
## Testing and Validation
|
|
|
|
### Test Suite
|
|
|
|
```bash
|
|
# 1. Test jump host connectivity
|
|
ansible grokbox -m ping
|
|
|
|
# 2. Test guest connectivity through jump host
|
|
ansible kvm_guests -m ping
|
|
|
|
# 3. Test command execution
|
|
ansible kvm_guests -m shell -a "hostname"
|
|
|
|
# 4. Test file transfer
|
|
ansible pihole -m copy -a "content='test' dest=/tmp/test.txt"
|
|
|
|
# 5. Test privilege escalation
|
|
ansible pihole -m shell -a "id" --become
|
|
|
|
# 6. Verbose connection test
|
|
ansible pihole -m ping -vvv
|
|
```
|
|
|
|
### Validation Checklist
|
|
|
|
- [ ] Jump host (grokbox) is reachable from control node
|
|
- [ ] SSH key authentication works on jump host
|
|
- [ ] Jump host can reach guest VMs on private network
|
|
- [ ] Guest VMs have ansible user with SSH key configured
|
|
- [ ] Guest VMs have passwordless sudo for ansible user
|
|
- [ ] ControlMaster connection multiplexing works
|
|
- [ ] Playbook execution completes successfully
|
|
- [ ] Connection logs show proper ProxyJump usage
|
|
|
|
---
|
|
|
|
## References
|
|
|
|
- **SSH ProxyJump Documentation**: `man ssh_config` (search for ProxyJump)
|
|
- **Ansible SSH Connection Plugin**: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/ssh_connection.html
|
|
- **OpenSSH Bastion Hosts**: https://www.openssh.com/
|
|
- **CLAUDE.md Guidelines**: Section on secure access patterns
|
|
- **CIS Benchmark**: SSH hardening recommendations
|
|
|
|
---
|
|
|
|
## Change Log
|
|
|
|
| Version | Date | Changes |
|
|
|---------|------|---------|
|
|
| 1.0 | 2025-11-11 | Initial documentation for jump host configuration |
|
|
|
|
---
|
|
|
|
**Document Maintained By**: Infrastructure Team
|
|
**Review Frequency**: Quarterly
|
|
**Next Review**: 2025-02-11
|