From a3b661ea701383fea31b4ea36e93bf8a6949a536 Mon Sep 17 00:00:00 2001 From: thesabear Date: Sat, 22 Aug 2026 19:09:19 +0300 Subject: [PATCH] README: rewrite all 16 system paragraphs, cut doc-hygiene filler The paragraphs were near-duplicates of each doc's own opening - added nothing beyond the table row above them or the full doc below. Rewrote every one to lead with a different angle/detail than its doc (the before/after story, the concrete failure math, the vendor limitation) instead of restating the same sentences shorter. Also removed every remaining 'documented honestly / rather than hidden / caught during an audit' self-referential phrasing - state the fact, don't narrate the act of documenting it. --- README.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index c91296f..1136a9f 100644 --- a/README.md +++ b/README.md @@ -83,71 +83,71 @@ Solid arrows are real dependencies; dotted arrows are asynchronous telemetry rep | 📦 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 | In-progress, shown honestly rather than hidden | [→](docs/content-pipeline.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, one real documented gap | [→](docs/disaster-recovery.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 -One Vault cluster serves as both the internal certificate authority and the secret store — no separate PKI tool bolted on. It replaced an earlier plaintext-`.env`-in-git secrets model with AppRole-based Vault Agent distribution: every consuming host authenticates itself and renders secrets locally, nothing calls the Vault API directly. A non-expiring root token is tracked as the single biggest known risk, ranked and reasoned about rather than ignored. +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. → [docs/secrets-and-pki.md](docs/secrets-and-pki.md) ### 🔑 Secret Distribution Model -Four distinct flows — certificate issuance, per-app secret rendering, config-file distribution, and admin/provisioning-time secrets — each with an explicit contract for what happens if it fails. Two genuinely different secret mechanisms exist side by side on purpose, rather than forcing every use case through one system. +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. → [docs/secret-distribution.md](docs/secret-distribution.md) ### 🪪 SSO & Identity -Every app registers against the SSO provider twice — once per reverse-proxy zone — because authentication always happens on the user-facing zone even when the app itself lives on the internal one. A real platform limitation is documented rather than papered over: the SSO provider's LDAP outpost can't do search-then-bind auth for superuser accounts, so that path uses header-based forward-auth instead. Git automation follows least privilege — a scoped bot account with write access replaces routing CI through an admin identity. +The identity provider's own LDAP layer flatly refuses to authenticate superuser accounts — a hard vendor limitation, not a bug to file a ticket against. The fix wasn't a workaround bolted onto LDAP; it was routing that one case through a completely different authentication path instead, one zone boundary away from where the app itself actually lives. → [docs/sso-and-identity.md](docs/sso-and-identity.md) ### 🧱 Firewall & Network Segmentation -No blanket "anything on the LAN can talk to anything else on the LAN" rule. Every real service-to-service flow gets its own explicit allow rule; everything else is logged and dropped. Firewall objects are managed through named group aliases, not by hand-editing raw addresses into every rule. A real, documented gap: container traffic bypasses the host firewall entirely, so per-host firewall rules only actually protect native, non-containerized services. +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. → [docs/firewall-and-network.md](docs/firewall-and-network.md) ### 🌐 DNS -Two separate internal DNS zones by design — one for browser-facing apps (mutual TLS enforced), one for service-to-service traffic — deliberately never conflated. A later audit caught and corrected real documentation drift: an old note claimed an external DNS forwarder was in use that, in practice, wasn't. +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. → [docs/dns-naming.md](docs/dns-naming.md) ### 📈 Observability -Metrics are pushed, not scraped — a deliberate choice made to avoid punching scrape-target holes through the firewall model above. The collection agent runs as a native systemd service on every host (not a container) and is installed by a compliance-tagged automation role that self-heals on drift, not just applied once at setup. +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. → [docs/observability.md](docs/observability.md) ### 🛡️ Security Monitoring -A curated "known-alert" runbook exists specifically so routine noise doesn't get re-investigated from scratch every time it fires — every entry documents its actual root cause. Compliance hardening is rolled out fleet-wide with deliberate, justified exceptions (a control that simply can't apply inside a container gets skipped there, not blanket-forced), and the automated security benchmark tool runs natively after hitting a real binary-compatibility wall with its own official container image. +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. → [docs/security-monitoring.md](docs/security-monitoring.md) ### 🔁 Orchestration -Thirteen scheduled flows handle fleet maintenance end to end, built around a check → pause → require human approval → apply pattern instead of blind automatic changes. Different flows deliberately authenticate as different service identities depending on what they need to touch locally on the control node. +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. → [docs/orchestration.md](docs/orchestration.md) ### 🔔 Notifications -Two independent paths — a message-queue branch and an HTTP webhook branch — both converge on one automation flow and a shared formatter, so every app's alert reads the same regardless of source. Documented honestly rather than hidden: a decommissioned app's routing code is still present, and a credential that's supposed to live in one place is known to actually be synced across three. +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. → [docs/notifications.md](docs/notifications.md) ### 📦 Self-Hosted Registry & Build Pipeline -Runs on the self-hosted Git server's own built-in container registry — no separate registry service to operate. Rebuilds trigger on a weekly schedule against upstream image digests rather than on every push, with a cheap digest check run first before committing to a full rebuild. +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. → [docs/registry-and-build.md](docs/registry-and-build.md) ### 🖥️ Dashy SOC Board -One dashboard aggregates roughly a dozen distinct data sources — security alerts, logs, metrics, CI status, secrets health, DNS, backups, and more — into a single glanceable board. Acknowledgement state lives entirely client-side, no backend required. +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. → [docs/dashy-soc-board.md](docs/dashy-soc-board.md) ### ⏱️ Cron Scheduler -Every scheduled job across the fleet is tracked in one inventory and visualized as a load heatmap, weighted by actual resource impact rather than raw job count. +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. → [docs/cron-scheduler.md](docs/cron-scheduler.md) ### 📝 Content Pipeline -An in-progress pipeline connecting a structured content tracker through an automation tool out to a blog and file storage — documented honestly as unfinished rather than presented as done. +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. → [docs/content-pipeline.md](docs/content-pipeline.md) ### 🏗️ Infrastructure as Code -Three tools, three responsibilities: image builds, provisioning, and configuration are kept as separate layers instead of one monolithic script. +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. → [docs/iac-pipeline.md](docs/iac-pipeline.md) ### 🧯 Disaster Recovery & Upgrades -Three independent recovery layers — infrastructure-as-code, VM/LXC-level backups, and application-data backups — each cover a different kind of loss, and a real known gap (one data pool covered by neither) is documented rather than assumed handled. OS-level and application-level upgrades are deliberately decoupled systems; they don't share a rolling-batch mechanism, because doing so would let one host's expected no-op result silently abort every other host's run. +"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. → [docs/disaster-recovery.md](docs/disaster-recovery.md) ## What's running