Repository navigation
Security
Hardening guide for HomePilot Agent infrastructure — Zabbix, Proxmox, Docker, and network security.
┌──────────────────────────────────────────┐
│ VPN / Firewall │ ← Network perimeter
├──────────────────────────────────────────┤
│ TLS (Caddy reverse proxy) │ ← Transport encryption
├──────────────────────────────────────────┤
│ Docker network isolation + secrets │ ← Container security
├──────────────────────────────────────────┤
│ Least-privilege PVE tokens + PSK TLS │ ← Monitoring auth
├──────────────────────────────────────────┤
│ Zabbix role separation + audit log │ ← Access control
├──────────────────────────────────────────┤
│ Artifact approval lifecycle │ ← Change control
└──────────────────────────────────────────┘
The default Zabbix PostgreSQL password is insecure. Rotate immediately:
# Generate strong password
NEW_PW=$(openssl rand -base64 24 | tr -d '/+=' | head -c 28)
# Update secret file
echo "$NEW_PW" > monitoring/zabbix/secrets/zabbix_pg_password.txt
# Update .env
sed -i "s/^ZABBIX_DB_PASSWORD=.*/ZABBIX_DB_PASSWORD=$NEW_PW/" .env
# Update PostgreSQL (in running container)
docker exec zabbix-postgres psql -U zabbix -c \
"ALTER USER zabbix WITH PASSWORD '$NEW_PW';"
# Restart Zabbix to pick up new secret
docker compose -f docker-compose.yml \
-f docker-compose.monitoring.yml restart zabbix-server zabbix-webAfter first login, immediately change the default Admin / zabbix credentials:
- Log in to Zabbix Web UI
- User settings → Change password
- Use a strong, unique password
All Zabbix agent connections should use TLS with Pre-Shared Key (PSK):
# Generate PSK on each monitored host
openssl rand -hex 32 > /etc/zabbix/psk/<hostname>.psk
chmod 600 /etc/zabbix/psk/<hostname>.pskConfigure in Zabbix UI for each host:
- Encryption → Connections from host: PSK
- PSK identity: descriptive name (e.g.,
PSK-pve) - PSK: paste from the generated
.pskfile
Zabbix services should only be accessible through the Caddy reverse proxy:
# In docker-compose.monitoring.yml
zabbix-web:
networks:
- monitoring-net
- caddy-net # Only if using Caddy
# Do NOT publish ports directlyCaddy handles TLS termination and restricts access:
homepilot.example.com {
reverse_proxy zabbix-web:8084
}
Create separate Zabbix users for different roles:
| Role | Access | Use Case |
|---|---|---|
| Super Admin | Full access | Initial setup only |
| Admin | Read/write monitoring | Day-to-day management |
| User | Read-only dashboards | NOC / status viewing |
Create a dedicated monitoring user and token with minimal permissions:
-
Create user:
monitor@pamin PVE web UI -
Set permissions:
/→PVEAuditor(read-only) -
Generate API token:
monitor@pam!monitoring -
Copy the secret — store in n8n credential store, never in
.envor git
The token grants:
- Read node status, VM/LXC configs, storage info
- List APT updates and changelogs
- NO write operations
If you need proxmox_refresh_apt_updates or similar write operations:
- Create
monitor-ops@pamwithPVEPowerUseron/nodesonly - Generate token:
monitor-ops@pam!ops - Scope permissions to the minimum required path
- Never use
root@pamtokens in n8n workflows
All sensitive values use Docker secrets (file-based, mounted at /run/secrets/), not environment variables:
# docker-compose.monitoring.yml
zabbix-server:
secrets:
- zabbix_pg_password
- zabbix_agent_pskDocker secret files are stored in monitoring/zabbix/secrets/ (gitignored) and mounted at runtime.
Each service group uses its own Docker network:
| Network | Services | Purpose |
|---|---|---|
default |
n8n, searxng, radicale, llm | Core application |
monitoring-net |
zabbix-server, postgres, web, agent | Monitoring stack |
caddy-net |
caddy | TLS termination |
Containers on different networks cannot communicate unless explicitly connected.
# Example: read-only filesystem, no new privileges
services:
zabbix-server:
read_only: true
tmpfs:
- /tmp
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE# Allow only necessary inbound
# SSH, HTTPS (Caddy), monitoring
iptables -A INPUT -p tcp --dport 22 -j ACCEPT # SSH
iptables -A INPUT -p tcp --dport 443 -j ACCEPT # HTTPS (Caddy)
iptables -A INPUT -p tcp --dport 10051 -j ACCEPT # Zabbix trapper (internal only)
# Default deny
iptables -A INPUT -j DROPDocker handles inter-container networking. Only publish ports that need external access:
- 443 — Caddy TLS (Zabbix, n8n web UIs)
- 5678 — n8n (consider restricting to VPN only)
- Everything else stays on internal Docker networks
- WireGuard or Tailscale for remote access to management interfaces
- Route all admin traffic (n8n UI, Zabbix UI, PVE UI) through VPN only
- Do not expose management ports to the public internet
- Use VPN for SSH access to all servers
Example Tailscale network policy:
# Only allow VPN clients to reach management ports
homepilot-agent: 5678, 8084 # n8n, Zabbix
pve: 8006 # PVE web UI
zabbix: 10051 # Zabbix trapper (internal)
- All tokens stored in n8n credential store (encrypted with
N8N_ENCRYPTION_KEY) - Never commit credentials to git
- Use MCP read-only token by default; read-write only for explicit mutations
- Rotate MCP tokens periodically via HomePilot admin UI
The Artifact Notification workflow validates:
-
X-HP-Webhook-Secretheader matchesHP Webhook Secretcredential - Payload structure matches expected schema (
event,artifact.id,artifact.intent, etc.) - Invalid requests return 401 and are logged
- All inference runs locally on your hardware — no data sent to external APIs
- LLM has no direct database access — it goes through MCP (scoped by token)
- Read-write mutations require
propose_artifact(human approval required)
Enable audit logging in Zabbix:
- Administration → General → Housekeeping → Enable audit logging
- Set retention period (e.g., 365 days)
- Monitor
Audit logreport for unauthorized access attempts
- Revoke compromised tokens immediately in HomePilot and PVE
- Rotate passwords — Zabbix admin, PostgreSQL, Docker secrets
- Review audit logs — Zabbix, PVE, Docker
- Check agent health — verify no unexpected Zabbix host registrations
- Isolate if needed — disconnect from VPN, disable webhooks
- Document — record timeline, affected systems, root cause
- Zabbix admin password changed from default
- PostgreSQL password rotated from
zabbix_pw - All Zabbix agent connections use TLS-PSK
- PVE monitoring uses read-only
monitor@pamtoken (notroot@pam) - No credentials in
.env— all secrets in n8n credential store or Docker secrets - Caddy TLS reverse proxy active for all web UIs
- Docker networks isolated by service group
- Firewall blocks all non-essential inbound traffic
- VPN configured for remote admin access
- Zabbix audit logging enabled with retention ≥ 90 days
- Webhook secrets validated on every incoming request