$ 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, ephemeral SSH certificates on 65 hosts

Secrets layer

Centralized secrets vault (Infisical), fronted by an Agent Vault proxy that injects credentials in transit — agents never hold a plaintext secret

Detection layer

Wazuh SIEM (38 agents), Grafana Alloy → centralized Loki, 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.

CAstep-ca (CT 102) — Smallstep, private root authority
ProvisionerACME — Traefik renews its certs automatically via TLS challenge
Duration90 days (2160h) — compromise between security and maintenance
TrustRoot cert deployed on all CTs + workstation (Fedora: update-ca-trust)
MonitoringA homegrown, open-source TLS-expiration monitor (cert-check) for every service behind the reverse proxy
Result47+ 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.

Secrets — Infisical vault

Credentials do not belong in plaintext config files. Every token, API key and CA secret lives in a centralized, self-hosted vault — Infisical on CT 116 — with an encrypted offline copy as backup.

VaultInfisical (CT 116) — self-hosted, one folder per scope (per agent, per service)
ReachSecrets pulled at runtime, or injected on the wire by an Agent Vault proxy (CT 118) — agents call APIs through placeholders and never hold the real token. Read the story →
BackupEvery secret mirrored to a git-crypt AES-256 encrypted store — losing the vault never means losing a secret
DisciplineOne source of truth, not secrets scattered across hosts — rotation propagates from a single place

Hardened SSH + YubiKey FIDO2

Every host on the homelab — 65 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 fleet-wide.

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.

Ephemeral SSH certificates

The static automation key is gone fleet-wide — replaced by 15-minute certificates from an Infisical SSH CA. Hosts trust the CA (TrustedUserCAKeys); the client mints a short-lived cert per connection, so revocation is just expiration. Read the story →

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 38 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 planeHeadscale v0.28.0 (CT 106) — self-hosted, WireGuard encryption
Subnet routerTailscale on CT 106 — routes 192.168.1.0/24 to the full LAN
Exit nodeAll internet traffic can route through the home router — toggle in the client app
TLSCaddy + Let's Encrypt on headscale.pixelium.win — the only publicly exposed service
TelemetryDisabled — -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.

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

The numbers

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

65hardened SSH hosts
6SSO services
7defensive layers
0SSH 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.