diff --git a/README.md b/README.md index f5593ac..888866c 100644 --- a/README.md +++ b/README.md @@ -67,87 +67,66 @@ flowchart LR Solid arrows are real dependencies; dotted arrows are asynchronous telemetry reporting, not a blocking dependency. Full breakdown of all 8 traffic laws (ingress, east-west, secrets, DNS, observability, notifications, CI/CD, management) with the actual trust-boundary reasoning: [docs/architecture-overview.md](docs/architecture-overview.md). -## Every system, at a glance - -| System | The core decision | Doc | -|---|---|---| -| 🔐 Secrets & PKI | One Vault cluster as CA + secret store, zero plaintext secrets in git | [→](docs/secrets-and-pki.md) | -| 🔑 Secret Distribution | Four distinct flows, each with its own failure contract | [→](docs/secret-distribution.md) | -| 🪪 SSO & Identity | Fleet-wide SSO, every OIDC login crosses zones mid-handshake | [→](docs/sso-and-identity.md) | -| 🧱 Firewall & Network | Zero blanket-allow rules — every flow explicit, everything else dropped | [→](docs/firewall-and-network.md) | -| 🌐 DNS | Two zones decide which trust boundary a request enters | [→](docs/dns-naming.md) | -| 📈 Observability | Push-based metrics, chosen specifically to fit the firewall model | [→](docs/observability.md) | -| 🛡️ Security Monitoring | Curated alert runbook + justified compliance exceptions | [→](docs/security-monitoring.md) | -| 🔁 Orchestration | 13 flows, check → pause → human approval → apply | [→](docs/orchestration.md) | -| 🔔 Notifications | Two fan-in branches, one shared formatter, zero runtime coupling | [→](docs/notifications.md) | -| 📦 Registry & Build | Self-hosted registry, digest-checked before any rebuild | [→](docs/registry-and-build.md) | -| 🖥️ Dashy SOC Board | 14 data sources aggregated into one glanceable board | [→](docs/dashy-soc-board.md) | -| ⏱️ Cron Scheduler | Fleet-wide job inventory, weighted load heatmap | [→](docs/cron-scheduler.md) | -| 📝 Content Pipeline | Content tracker → automation → blog; social fan-out still incomplete | [→](docs/content-pipeline.md) | -| 🏗️ Infrastructure as Code | Packer → Terraform → Ansible, one tag threading all three | [→](docs/iac-pipeline.md) | -| 🧯 Disaster Recovery | 3 independent recovery layers, each covering a different kind of loss | [→](docs/disaster-recovery.md) | -| 📐 Architecture Overview | 4 trust boundaries, 3 routing authorities, calibrated not maxed | [→](docs/architecture-overview.md) | - ## Systems ### 🔐 Secrets & PKI -Every secret in this fleet used to sit in plaintext inside git-tracked `.env` files — readable by anyone with repo access, permanent in git history even after being "fixed." Today the real value of any secret lives in exactly one place, rendered locally to disk by an agent that authenticates itself, and nothing else ever calls the vault directly. Even the platform's own biggest risk — a bootstrap token that never expires — is ranked #1 of 3 known gaps, not left unranked. +A self-hosted Vault cluster is both the certificate authority and the secret store for every host in the fleet — every TLS certificate, every database password, every API key traces back to this one system. Nothing reaches Vault directly; a local agent on each host authenticates itself and renders what that host needs to disk. Replaced an earlier setup where secrets sat in plaintext inside git. → [docs/secrets-and-pki.md](docs/secrets-and-pki.md) ### 🔑 Secret Distribution Model -A certificate expiring is a slow problem measured in days. A missing application secret is often an outage measured in seconds. Treating those two the same was the mistake avoided here: four separate delivery contracts instead of one universal pipeline, each built around how badly its own specific failure actually hurts. +Four separate delivery paths move material out of Vault, purpose-built for what's actually being delivered — a short-lived certificate, an application secret, a full config file, or an admin-only credential — each with its own contract for what the rest of the fleet does if that specific path fails. → [docs/secret-distribution.md](docs/secret-distribution.md) ### 🪪 SSO & Identity -Authentik fronts SSO fleet-wide, registered per app per zone actually needed. Every OIDC login crosses zones mid-handshake — the app lives on one zone, the real auth exchange happens on the other. Git automation follows the same least-privilege rule: a scoped bot account, never the admin identity. +Authentik, self-hosted, fronts single sign-on fleet-wide — native OIDC where an app supports it, proxy-based forward-auth in front of everything else, so apps that were never built with SSO in mind still get it. Every OIDC login crosses reverse-proxy zones mid-handshake, and Git automation runs on a scoped bot account rather than an admin identity. → [docs/sso-and-identity.md](docs/sso-and-identity.md) ### 🧱 Firewall & Network Segmentation -Zero blanket internal trust — every real service-to-service flow earns its own explicit rule, everything else gets logged and dropped. Even that model has a named limit worth knowing: container traffic slips past the host firewall entirely, so the real protection on any given host is narrower than the ruleset alone would suggest. +No blanket internal trust anywhere on the network — every real service-to-service flow gets its own explicit rule, and everything not explicitly allowed is logged and dropped by default. Enforced at two layers: a firewall at the network edge, and firewalld locally on every host, each opening only what that specific host actually needs. → [docs/firewall-and-network.md](docs/firewall-and-network.md) ### 🌐 DNS -Two internal zones exist for one reason: which zone a hostname resolves in decides whether a request gets treated as a human on a browser or a machine calling another machine. Get that resolution wrong and the wrong trust boundary applies to that request — not a naming detail, the actual security-relevant part of the whole model. +Two internal DNS zones split browser-facing traffic from service-to-service traffic — which zone a hostname resolves in is what decides which trust boundary a request enters, not just where it's found. Technitium runs both zones, self-hosted, with direct DNS bypass blocked at the firewall so the split is actually enforced, not just a naming convention. → [docs/dns-naming.md](docs/dns-naming.md) ### 📈 Observability -Metrics get pushed outward, never pulled — a call made specifically so the monitoring stack never needs inbound access punched through the firewall model above to every single host it watches. The collection agent runs natively on every host, not in a container, installed by a role that keeps re-checking itself rather than trusting it stayed correct after day one. +Every fleet host runs a native metrics-and-logs agent that pushes outward to a central Loki/Prometheus stack, rather than waiting to be scraped — chosen specifically so monitoring never needs inbound access opened through the firewall model to every host it watches. Logs, container output, and DNS query history all land in one searchable place. → [docs/observability.md](docs/observability.md) ### 🛡️ Security Monitoring -Routine alerts get root-caused exactly once and never re-investigated from scratch again — including one case where the alert itself turned out to be the bug, not the system it was checking. Compliance hardening applies fleet-wide with named, reasoned exceptions instead of blanket enforcement, and the benchmarking tool runs as a native binary after its own official container image hit a real compatibility wall. +A Wazuh agent runs on every host in the fleet, continuously watching file integrity, rootkit behavior, and compliance drift against a CIS-based benchmark. A curated runbook root-causes routine alerts once so they're never re-triaged from scratch, and hardening controls apply fleet-wide with specific, reasoned exceptions rather than uniform enforcement regardless of fit. → [docs/security-monitoring.md](docs/security-monitoring.md) ### 🔁 Orchestration -Nothing changes on this fleet automatically. Every update starts as a dry run, reports exactly what it would do, and then sits paused — sometimes for days — until a human actually approves it. Thirteen flows run this way, and one genuinely hard edge case (the automation engine updating the very node it runs on) gets handled with a fire-and-forget path built specifically for that one scenario. +Kestra runs thirteen scheduled flows that handle fleet maintenance end to end — OS patching, container rebuilds, app updates, disk cleanup — every one of them built around a check → pause → human approval → apply pattern, so nothing changes on the fleet without someone actually saying go. → [docs/orchestration.md](docs/orchestration.md) ### 🔔 Notifications -Every alert from every source — apps, hosts, security events — comes out formatted identically, because all of it funnels through one shared step before reaching Slack. That includes the debt: one decommissioned app's routing code is still sitting in the flow unswept, and a credential that should live in exactly one place is actually kept in sync by hand across three. +Every alert in the fleet — from apps, from hosts, from security events — funnels through one Node-RED instance and one shared formatter before reaching Slack, so a notification reads the same regardless of which of a dozen-plus sources triggered it. A dedicated role puts every host onto the same pipeline for OS-level health, independent of any single application. → [docs/notifications.md](docs/notifications.md) ### 📦 Self-Hosted Registry & Build Pipeline -Runs on the Git server's own built-in registry — no separate service to operate, no runtime dependency on Docker Hub. A cheap digest check runs before every possible rebuild, so a week where nothing upstream actually changed costs almost nothing to verify. +Docker images get built and hosted entirely in-fleet on the Git server's own built-in registry — no runtime dependency on Docker Hub or ghcr for the stacks that have opted in. A weekly scheduled pass checks upstream image digests before committing to a full rebuild, so nothing gets rebuilt unless something upstream actually changed. → [docs/registry-and-build.md](docs/registry-and-build.md) ### 🖥️ Dashy SOC Board -Fourteen different systems — alerts, logs, metrics, CI health, secrets state, DNS, backups — used to mean fourteen different tabs to check. One custom-built dashboard joins all of it into a single board, with acknowledgement state that lives entirely in the browser: nothing to host, nothing to secure, nothing to back up. +One custom-built dashboard aggregates fourteen different data sources — security alerts, logs, metrics, CI health, secrets state, DNS, backups, firewall stats — into a single glanceable board, replacing what used to be fourteen separate tabs to check. Acknowledgement state lives entirely in the browser, no backend to host or secure. → [docs/dashy-soc-board.md](docs/dashy-soc-board.md) ### ⏱️ Cron Scheduler -Every scheduled job across the fleet lives in one inventory, rendered as a load heatmap weighted by actual resource impact — a backup job counts for more than a lightweight health check, because an overlapping backup is a worse collision than two quiet jobs sharing an hour. +Every scheduled job across the fleet — orchestration flows, system timers, app-internal schedules — lives in one inventory, visualized as a load heatmap weighted by actual resource impact, so a new job's time slot can be picked without colliding with existing load. → [docs/cron-scheduler.md](docs/cron-scheduler.md) ### 📝 Content Pipeline -The one system in this repo that isn't finished — included anyway, because a portfolio of only-finished systems doesn't show how anything actually gets built. A content tracker, an automation glue layer, and a blog already work end to end; the social-publishing leg is still being wired up. +A structured content tracker, an automation layer, and a self-hosted blog already work together end to end to take an idea from draft to published post. The one piece still being built is automatic fan-out to social platforms — included here in progress, not hidden until finished. → [docs/content-pipeline.md](docs/content-pipeline.md) ### 🏗️ Infrastructure as Code -Image builds, provisioning, and configuration stay as three separate tools with three separate rhythms — an image gets rebuilt rarely, a VM gets provisioned once, config gets re-applied continuously — connected by one deliberate handoff instead of one tool trying to do all three jobs at once. +Packer builds a hardened base image, Terraform provisions each VM from it, and Ansible configures and continuously re-hardens every host after that — three tools, three separate responsibilities, connected by one tag that threads through all three stages instead of one tool trying to do every job. → [docs/iac-pipeline.md](docs/iac-pipeline.md) ### 🧯 Disaster Recovery & Upgrades -"A lost host" and "a database's lost data" are different kinds of loss needing different answers, so three independent recovery layers each cover one — with one real gap between them: a storage pool covered by neither. OS and application upgrades stay deliberately decoupled too: batching them together would let one host's expected no-op silently abort every other host's run. +Three independent recovery layers cover three different kinds of loss — a lost host rebuilds from code, VM-level backups cover full-host snapshots, and a separate volume-level backup covers application data specifically. OS-level and application-level upgrades run on entirely separate, deliberately decoupled schedules and safety checks. → [docs/disaster-recovery.md](docs/disaster-recovery.md) ## What's running