6.5 KiB
title, description
| title | description |
|---|---|
| Security Architecture | Firewall configuration, Docker isolation, and security hardening details |
Security Architecture
Overview
This playbook implements a multi-layer defense strategy to secure OpenClaw installations.
Security Layers
Layer 1: UFW Firewall
# Default policies
Incoming: DENY
Outgoing: ALLOW
Routed: DENY
# Allowed
SSH (22/tcp): ALLOW
Tailscale (41641/udp): ALLOW
Layer 2: Fail2ban (SSH Protection)
Automatic protection against SSH brute-force attacks:
# Configuration
Max retries: 5 attempts
Ban time: 1 hour (3600 seconds)
Find time: 10 minutes (600 seconds)
# Check status
sudo fail2ban-client status sshd
# Unban an IP
sudo fail2ban-client set sshd unbanip IP_ADDRESS
Layer 3: DOCKER-USER Chain
Custom iptables chain that prevents Docker from bypassing UFW:
*filter
:DOCKER-USER - [0:0]
-A DOCKER-USER -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A DOCKER-USER -i lo -j ACCEPT
-A DOCKER-USER -i <default_interface> -j DROP
COMMIT
Result: Even docker run -p 80:80 nginx won't expose port 80 externally.
Layer 4: Localhost-Only Binding
All container ports bind to 127.0.0.1:
ports:
- "127.0.0.1:3000:3000"
Layer 5: Non-Root Container
Container processes run as unprivileged openclaw user.
Layer 6: Systemd Hardening
The openclaw service runs with security restrictions:
NoNewPrivileges=true- Prevents privilege escalationPrivateTmp=true- Isolated /tmp directoryProtectSystem=strict- Read-only system directoriesProtectHome=read-only- Limited home directory accessReadWritePaths- Only ~/.openclaw is writable
Layer 7: Scoped Sudo Access
The openclaw user has limited sudo permissions (not full root):
# Allowed commands only:
- systemctl start/stop/restart/status openclaw
- systemctl daemon-reload
- tailscale commands
- journalctl for openclaw logs
Layer 8: Automatic Security Updates
Unattended-upgrades is configured for automatic security patches:
# Check status
sudo unattended-upgrade --dry-run
# View logs
sudo cat /var/log/unattended-upgrades/unattended-upgrades.log
Note: Automatic reboots are disabled. Monitor for pending reboots:
cat /var/run/reboot-required 2>/dev/null || echo "No reboot required"
Verification
Run these checks after installation and onboarding. Interface names, IP addresses, packet counters, and process IDs vary by host; compare the stated healthy result rather than expecting byte-for-byte output.
Firewall
sudo ufw status verbose
Expected: Status: active, with incoming and routed traffic denied by default. The default rules allow 22/tcp for SSH and, when Tailscale is enabled, 41641/udp; IPv6-enabled hosts may show matching (v6) entries.
SSH Protection
sudo fail2ban-client status
sudo fail2ban-client status sshd
Expected: the first command lists sshd under Jail list; the second reports the jail as active and shows its failure and ban counters. Zero failures or bans is healthy.
Local Listeners
sudo ss -tlnp
Expected: SSH listens on the configured public address or 0.0.0.0; OpenClaw and its supporting services listen on 127.0.0.1. No OpenClaw or Docker service should listen on 0.0.0.0.
Docker Isolation
sudo iptables -L DOCKER-USER -n -v
Expected: the chain includes accepts for established traffic and loopback traffic, followed by a drop rule for traffic arriving on the server's default external interface. Packet counters may be zero before traffic reaches the chain.
From another machine, scan the server:
nmap -p- YOUR_SERVER_IP
Expected: only the configured SSH TCP port is open in the default configuration. Tailscale uses UDP port 41641, so it does not appear in this TCP scan.
Then publish a temporary container port:
sudo docker run -d -p 80:80 --name test-nginx nginx
curl --connect-timeout 5 http://YOUR_SERVER_IP:80
curl --fail http://localhost:80
sudo docker rm -f test-nginx
Expected: the external request fails or times out, while the localhost request returns the nginx welcome page. Remove the test container even if either request produces an unexpected result.
Tailscale
sudo tailscale status
When Tailscale is enabled, expected: the server has a 100.x.x.x Tailscale address and appears in the peer table. Logged out or Stopped means sudo tailscale up still needs to be completed. Skip this check when tailscale_enabled is false.
Automatic Security Updates
sudo systemctl status unattended-upgrades
Expected: the unit is loaded and active. Use sudo unattended-upgrade --dry-run --debug if the service is inactive or reports errors.
Tailscale Access
OpenClaw's web interface (port 3000) is bound to localhost. Access it via:
-
SSH tunnel:
ssh -L 3000:localhost:3000 user@server # Then browse to http://localhost:3000 -
Tailscale (recommended):
# On server: already done by playbook sudo tailscale up # From your machine: # Browse to http://TAILSCALE_IP:3000
Network Flow
Internet → UFW (SSH only) → fail2ban → DOCKER-USER Chain → DROP
Container → NAT → Internet (outbound allowed)
Known Limitations
macOS Support
- macOS firewall configuration is basic (Application Firewall only)
- No fail2ban equivalent on macOS
- Consider using Little Snitch or similar for enhanced macOS security
IPv6
- Docker IPv6 is disabled by default (
ip6tables: falsein daemon.json) - If your network uses IPv6, review and test firewall rules accordingly
Installation Script
- The
curl | bashinstallation pattern has inherent risks - For high-security environments, clone the repository and audit before running
- Consider using
--checkmode first:ansible-playbook playbook.yml --check
Security Checklist
After installation, verify:
sudo ufw statusshows only SSH and Tailscale allowedsudo fail2ban-client status sshdshows jail activesudo iptables -L DOCKER-USER -nshows DROP rulenmap -p- YOUR_IPfrom external shows only port 22docker run -p 80:80 nginx+curl YOUR_IP:80times out- Tailscale access works for web UI
Reporting Security Issues
If you discover a security vulnerability, please report it privately:
- OpenClaw: https://github.com/openclaw/openclaw/security
- This installer: https://github.com/openclaw/openclaw-ansible/security