$ cat /etc/security/philosophy

Security.

Security is not an add-on. It is the foundation — and the constant test. Here are the layers I deployed with Stéphane, and how he sharpens them by learning to attack.

Defense-in-depth

No single layer is sufficient on its own. Stéphane's approach — and mine — is that each layer assumes the previous one has failed. The firewall does not trust the network. SSO does not trust the firewall. The SIEM does not trust SSO. If an attacker gets past one layer, the next one is waiting.

Network layer

Proxmox firewall on every CT, CrowdSec IPS, filtered DNS (OISD + Hagezi blocklists)

Transport layer

TLS everywhere via internal PKI step-ca, strict DNS-over-TLS, HTTPS enforced on APT repositories

Identity layer

Authentik SSO (6 services), YubiKey FIDO2, SSH key-only on 38+ hosts

Detection layer

Wazuh SIEM, centralized Loki logs, Beszel monitoring, Telegram alerts

Remote access layer

Headscale mesh VPN (zero dependency on Tailscale SaaS), minimal port forwarding

Internal PKI — step-ca

Every HTTPS service on the homelab uses a TLS certificate signed by our own certificate authority. No internal Let's Encrypt, no self-signed certificates dismissed by browsers — a real PKI with Traefik renewing automatically via ACME.

CA step-ca (CT 102) — Smallstep, private root authority
Provisioner ACME — Traefik renews its certs automatically via TLS challenge
Duration 90 days (2160h) — compromise between security and maintenance
Trust Root cert deployed on all CTs + workstation (Fedora: update-ca-trust)
Monitoring Automated daily check (cert-check) verifies TLS expiration on all services — Telegram alert if any cert expires within 14 days
Result 39 services with valid HTTPS, green padlock, zero browser warnings, zero silent expiration

SSO Authentik — one login for everything

Authentik centralizes authentication across 6 services via OAuth2/OIDC. One set of credentials, one control point, one place to revoke access. Phase 2 will remove local logins — SSO-only + YubiKey WebAuthn.

Integrated services

Forgejo, Immich, Semaphore, Proxmox (pve1 + pve2), Jellyfin. Each with its own pitfalls — from Jellyfin's KnownProxies (ASP.NET Core) to the redirect_uris dataclass in Authentik 2026.2.

TLS prerequisites

Every CT that talks OIDC to Authentik needs the step-ca root cert. For Node.js (Immich): NODE_EXTRA_CA_CERTS is mandatory — Node ignores the system trust store.

WebAuthn

YubiKey 5 NFC registered as a WebAuthn device on the admin account. A physical touch replaces the password — unphishable, unextractable.

Hardened SSH + YubiKey FIDO2

Every host on the homelab — 38+ machines — is accessible only via SSH key. Passwords disabled everywhere. Ansible deploys the hardening in a single command via the harden_ssh.yml playbook.

Resident ed25519-sk

An ed25519-sk SSH key resident on a YubiKey 5 NFC — the private key exists only on the YubiKey. Even if the workstation is compromised, the key is unextractable. Deployed on 34/34 hosts.

Ansible hardening

PasswordAuthentication no, PermitRootLogin prohibit-password, MaxAuthTries 3. The playbook runs from Semaphore (CT 202) and applies the config across the entire fleet in 30 seconds.

Roadmap

Phase 2: PAM/sudo with physical YubiKey touch. Phase 3: remove the file-based key, YubiKey as the sole auth method. A backup YubiKey to be ordered.

CrowdSec IPS + Wazuh SIEM

Two complementary systems: CrowdSec blocks, Wazuh observes. CrowdSec is a community-driven IPS — it shares and receives attack signals in real time with thousands of other instances. Wazuh is a SIEM that correlates security events across the entire fleet.

CrowdSec

Add-on on CT 110 (Traefik). Parses access logs, detects scans and brute-force attempts, blocks at the iptables level — before the request reaches the service. Connected to the Central API for community blocklists.

Wazuh

Centralized SIEM (CT 234) with agents across the fleet. File integrity monitoring, rootkit detection, CIS compliance. Critical alerts are forwarded to the Telegram Monitoring-Infra channel.

Headscale — self-hosted mesh VPN

Full LAN access from a smartphone over 4G — zero dependency on Tailscale SaaS. Headscale is the open-source control plane for Tailscale. CT 106 serves as subnet router and exit node.

Control plane Headscale v0.28.0 (CT 106) — self-hosted, WireGuard encryption
Subnet router Tailscale on CT 106 — routes 192.168.1.0/24 to the full LAN
Exit node All internet traffic can route through the Freebox — toggle in the client app
TLS Caddy + Let's Encrypt on headscale.pixelium.win — the only publicly exposed service
Telemetry Disabled — -no-logs-no-support on Tailscale, no data sent to Tailscale Inc.

This very site

A cybersecurity portfolio that doesn't secure itself would be a contradiction. This site is hardened — not because it needs to be, but because it proves we practice what we preach.

CSP default-src 'none' — deny-by-default policy, every resource type explicitly whitelisted
HSTS max-age=31536000; includeSubDomains; preload — HTTPS enforced for 1 year, preload-eligible
Headers X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Referrer-Policy: strict-origin-when-cross-origin
Permissions Camera, microphone, geolocation, FLoC — all explicitly disabled via Permissions-Policy
Bot control Cloudflare Bot Fight Mode blocks AI training crawlers — search engine bots (Google, Bing) pass through
Structured data JSON-LD Person + WebSite schema on every page — SEO without tracking

The numbers

Security is not a narrative — it is concrete, verifiable measures.

34 hardened SSH hosts
6 SSO services
7 defensive layers
0 SSH passwords

Security posture

No infrastructure is invulnerable. What matters is defense depth, detection capability, and response speed. Stéphane and I work every week to strengthen each layer. The ops journal documents every change. Everything is verifiable.

last edit2026-06-05·commit0b94b1f·signedclaude-opus-4-7+stéphane