Comprehensive security architecture implementing defense-in-depth strategies for the homelab infrastructure, including network segmentation, authentication systems, encryption, and continuous monitoring.
Internet/WAN
↓
🔥 Perimeter Security (Firewall/Router)
↓
🌐 VPN Access Layer (Tailscale)
↓
🔐 Authentication Gateway (Authentik SSO)
↓
🏠 Internal Network (Segmented VLANs)
↓
🔒 Service-Level Security (TLS/mTLS)
↓
💾 Data Layer Security (Encryption at Rest)
## Core authentication settings
authentik:
secret_key: ${AUTHENTIK_SECRET_KEY} # 50+ character random key
bootstrap_email: admin@speicher.family
bootstrap_password: ${AUTHENTIK_BOOTSTRAP_PASSWORD}
## Database configuration
postgresql:
host: authentik-db
name: authentik
user: authentik
password: ${AUTHENTIK_DB_PASSWORD}
## Redis cache
redis:
host: authentik-redis
password: ${AUTHENTIK_REDIS_PASSWORD}
## Email configuration
email:
host: smtp.fastmail.com
port: 587
username: ${FASTMAIL_USERNAME}
password: ${FASTMAIL_APP_PASSWORD}
use_tls: true
from: homelab@speicher.family
{
"ACLs": [
{
"Action": "accept",
"Users": ["family@speicher.family"],
"Ports": ["*:22", "*:80", "*:443"]
},
{
"Action": "accept",
"Users": ["admin@speicher.family"],
"Ports": ["*:*"]
},
{
"Action": "accept",
"Users": ["guest@speicher.family"],
"Ports": ["nas02:8096", "homeassistant:8123"]
}
],
"Groups": {
"group:family": ["kandace@speicher.family", "violet@speicher.family"],
"group:admin": ["matt@speicher.family"],
"group:services": ["homeassistant", "n8n-automation"]
}
}
## UFW firewall configuration
## Default policies
ufw default deny incoming
ufw default allow outgoing
ufw default deny forward
## SSH access (key-based only)
ufw allow 22/tcp
## HTTP/HTTPS (Traefik reverse proxy)
ufw allow 80/tcp
ufw allow 443/tcp
## Tailscale
ufw allow 41641/udp
## Home Assistant (local network only)
ufw allow from 192.168.1.0/24 to any port 8123
## Block suspicious activity
ufw deny from 192.168.1.100 # Example: compromised device
## Traefik TLS settings
api:
dashboard: true
insecure: false
entryPoints:
web:
address: ":80"
http:
redirections:
entrypoint:
to: websecure
scheme: https
websecure:
address: ":443"
http:
tls:
options: modern
tls:
options:
modern:
minVersion: "VersionTLS13"
cipherSuites:
- "TLS_AES_256_GCM_SHA384"
- "TLS_CHACHA20_POLY1305_SHA256"
- "TLS_AES_128_GCM_SHA256"
certificatesResolvers:
letsencrypt:
acme:
email: matt@speicher.family
storage: /letsencrypt/acme.json
httpChallenge:
entryPoint: web
## .sops.yaml
creation_rules:
- path_regex: \.env$
age: >-
age1abc123...xyz789 # Primary age key
- path_regex: secrets/.*\.yaml$
age: >-
age1abc123...xyz789,
age1def456...uvw012 # Backup age key
- path_regex: .*\.secrets\.yaml$
age: >-
age1abc123...xyz789
## SSH configuration with 1Password
## ~/.ssh/config
Host *
IdentityAgent "~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock"
AddKeysToAgent yes
UseKeychain yes
Host lucille4
HostName lucille4.speicher.family
User matt
Port 22
IdentitiesOnly yes
Host nas02
HostName nas02.speicher.family
User admin
Port 22
IdentitiesOnly yes
-- Failed authentication attempts
@Message like '%authentication%' and @Message like '%failed%'
and @Timestamp > Now() - 1h
group by @Fields.source_ip, @Fields.username
having count(*) > 5
-- Suspicious network activity
@Message like '%connection%' and (@Message like '%refused%' or @Message like '%timeout%')
and @Timestamp > Now() - 15m
group by @Fields.source_ip
having count(*) > 10
-- Privilege escalation attempts
@Message like '%sudo%' and (@Message like '%FAILED%' or @Message like '%incorrect%')
and @Timestamp > Now() - 1h
-- Certificate expiration warnings
@Message like '%certificate%' and @Message like '%expir%'
and @Level = 'Warning'
-- Unauthorized API access
@Fields.http_status_code in [401, 403] and @Timestamp > Now() - 1h
group by @Fields.source_ip, @Fields.path
having count(*) > 20
| Service | Family | Admin | Guest | Service Account |
|---|---|---|---|---|
| Jellyfin | ✅ | ✅ | ✅ | ❌ |
| Home Assistant | ✅ | ✅ | ❌ | ✅ |
| Paperless | ✅ | ✅ | ❌ | ❌ |
| Grafana | ❌ | ✅ | ❌ | ✅ |
| Seq | ❌ | ✅ | ❌ | ✅ |
| Authentik Admin | ❌ | ✅ | ❌ | ❌ |
| SSH Access | ❌ | ✅ | ❌ | ✅ |
| API Keys | ❌ | ✅ | ❌ | ✅ |
## Tailscale ACL policies
{
"ACLs": [
// Family members - basic services
{
"Action": "accept",
"Users": ["group:family"],
"Ports": [
"jellyfin:8096",
"homeassistant:8123",
"paperless:8000",
"wiki:3000"
]
},
// Admin - full infrastructure access
{
"Action": "accept",
"Users": ["group:admin"],
"Ports": ["*:*"]
},
// Guests - limited media access
{
"Action": "accept",
"Users": ["group:guests"],
"Ports": ["jellyfin:8096"]
},
// Service accounts - API access only
{
"Action": "accept",
"Users": ["group:services"],
"Ports": [
"homeassistant:8123",
"seq:5341",
"grafana:3000"
]
}
]
}
## Security automation script
import requests
import subprocess
from datetime import datetime
class SecurityAutomation:
def __init__(self):
self.seq_api = "https://seq.speicher.family"
self.discord_webhook = os.getenv('DISCORD_SECURITY_WEBHOOK')
def detect_brute_force(self, threshold=5, window_minutes=15):
"""Detect brute force authentication attempts"""
query = f"""
@Message like '%authentication%' and @Message like '%failed%'
and @Timestamp > Now() - {window_minutes}m
group by @Fields.source_ip
having count(*) > {threshold}
"""
response = requests.get(f"{self.seq_api}/api/events/signal",
params={'filter': query})
return response.json()
def block_suspicious_ip(self, ip_address):
"""Block IP address using UFW"""
try:
subprocess.run(['ufw', 'deny', 'from', ip_address],
check=True, capture_output=True)
self.send_alert(f"Blocked suspicious IP: {ip_address}")
return True
except subprocess.CalledProcessError as e:
self.send_alert(f"Failed to block IP {ip_address}: {e}")
return False
def send_alert(self, message):
"""Send security alert to Discord"""
payload = {
"embeds": [{
"title": "🚨 Security Alert",
"description": message,
"color": 15158332,
"timestamp": datetime.utcnow().isoformat()
}]
}
requests.post(self.discord_webhook, json=payload)
def check_certificate_expiry(self, days_threshold=30):
"""Check for expiring certificates"""
## Implementation for certificate monitoring
pass
def monitor_security_events(self):
"""Main monitoring loop"""
## Check for brute force attacks
brute_force_events = self.detect_brute_force()
for event in brute_force_events:
if event['count'] > 10:
self.block_suspicious_ip(event['source_ip'])
## Check certificate expiry
self.check_certificate_expiry()
## Additional security checks...
## Grafana security dashboard
dashboard:
title: "Security Overview"
panels:
- title: "Authentication Failures"
type: "graph"
targets:
- expr: 'rate(authentik_events_total{event="login_failed"}[5m])'
- title: "Active VPN Connections"
type: "stat"
targets:
- expr: 'tailscale_connected_devices'
- title: "Certificate Expiry"
type: "table"
targets:
- expr: 'probe_ssl_earliest_cert_expiry'
- title: "Firewall Blocks"
type: "graph"
targets:
- expr: 'rate(node_ufw_blocked_total[5m])'
## Security monitoring in Home Assistant
sensor:
- platform: rest
resource: https://seq.speicher.family/api/events/signal
name: Security Events
value_template: '{{ value_json.count }}'
headers:
X-Seq-ApiKey: !secret seq_api_key
- platform: command_line
name: Failed SSH Attempts
command: "journalctl -u ssh -S today | grep 'Failed password' | wc -l"
- platform: template
sensors:
security_status:
friendly_name: "Overall Security Status"
value_template: >
{% if states('sensor.security_events')|int > 10 %}
Alert
{% elif states('sensor.failed_ssh_attempts')|int > 5 %}
Warning
{% else %}
Normal
{% endif %}
automation:
- alias: "Security Alert"
trigger:
platform: state
entity_id: sensor.security_status
to: 'Alert'
action:
- service: notify.discord
data:
message: "🚨 Security alert detected! Check monitoring systems."
- service: light.turn_on
entity_id: light.security_status
data:
color_name: red