Leads with the hardest actual decision (4 trust boundaries, each calibrated to a different risk level, enforced by 3 separate routing authorities) given full narrative depth instead of flat template treatment. The other 6 traffic laws move to a compact reference table linking to their own dedicated docs, avoiding redundancy with content that already lives elsewhere in more depth.
97 lines
8.1 KiB
Markdown
97 lines
8.1 KiB
Markdown
# 📐 Architecture Overview
|
|
|
|
> [!NOTE]
|
|
> Most homelabs have one trust boundary: "inside the LAN = trusted." This one has four, each calibrated to a different actual risk level rather than applying maximum security everywhere or none anywhere — that calibration is the hardest and most interesting decision in the whole architecture, and the rest of this doc builds out from it.
|
|
|
|
## Dependency graph
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
Internet(["Internet"]) --> Edge["Edge Firewall"]
|
|
Edge --> LAN["LAN"]
|
|
|
|
LAN --> DNS["Internal DNS"]
|
|
LAN --> Vault["Vault"]
|
|
DNS --> Vault
|
|
|
|
Vault --> Apps["App Host<br/>~40 stacks"]
|
|
Vault --> Automation["Automation Host"]
|
|
LAN --> FileServer["File Server"]
|
|
DNS --> FileServer
|
|
LAN --> Automation
|
|
|
|
Apps --> InternalProxy["Internal Proxy Zone"]
|
|
Apps --> SSO["SSO"]
|
|
Automation --> GitHost["Git Hosting"]
|
|
|
|
Vault --> EdgeProxy["User-Facing Proxy<br/>mTLS"]
|
|
DNS --> EdgeProxy
|
|
Apps -.proxied.-> EdgeProxy
|
|
SSO -.auth.-> EdgeProxy
|
|
|
|
Apps -.-> Monitoring["Monitoring"]
|
|
Automation -.-> Monitoring
|
|
FileServer -.-> Monitoring
|
|
Vault -.-> Monitoring
|
|
DNS -.-> Monitoring
|
|
EdgeProxy -.-> Monitoring
|
|
|
|
Apps -.-> SecMon["Security Monitoring"]
|
|
Automation -.-> SecMon
|
|
FileServer -.-> SecMon
|
|
Vault -.-> SecMon
|
|
DNS -.-> SecMon
|
|
EdgeProxy -.-> SecMon
|
|
```
|
|
|
|
> [!TIP]
|
|
> Solid arrows are real dependencies — the target has to be reachable and healthy for the source to function. Dotted arrows are asynchronous reporting, not a blocking dependency.
|
|
|
|
## The hardest decision: how many trust boundaries is enough?
|
|
|
|
The easy answers are both wrong. **One boundary** ("you're on the LAN, you're trusted") is what most self-hosted setups actually run, and it means a single compromised container can reach everything else with no further resistance. **Maximum boundaries everywhere** (mTLS on every single connection, no exceptions) sounds safer but isn't free — every hop gets slower, every service needs its own client certificate lifecycle, and the operational burden of *that* is exactly the kind of complexity that gets quietly disabled under deadline pressure, which is worse than never having built it.
|
|
|
|
The actual answer here is **four boundaries, each doing a different job**:
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
WAN["WAN"] -->|public| EdgeFW["Edge Firewall<br/>public proxy"]
|
|
Browser["LAN browsers"] -->|mTLS + SSO| UserProxy["User-facing proxy"]
|
|
Service["App-to-app"] -->|allowlist + SSO| InternalProxy["Internal proxy"]
|
|
Infra["LAN infra-to-infra"] -->|per-flow rule| HostFW["Host firewall"]
|
|
```
|
|
|
|
| Boundary | Who's on the other end | Control | Why this control, not another |
|
|
|---|---|---|---|
|
|
| WAN → edge | Anyone on the internet | Public reverse proxy, firewall-filtered | The only boundary facing genuinely untrusted traffic — gets the strictest, most isolated treatment |
|
|
| Browsers → user-facing proxy | A human, on a device that could be compromised, phished, or simply lost | Mutual TLS (client cert required) + SSO | The highest-value target for credential theft — a stolen password alone isn't enough without the client cert too |
|
|
| App-to-app | A container that's already inside the network, already provisioned by the same pipeline as everything else | IP-allowlist + SSO fallback, no mTLS | Full mTLS here would mean managing certificate lifecycle for every internal service-to-service call — real overhead for traffic that's already constrained to known, provisioned sources. The risk profile doesn't justify the same control as human-operated browsers. |
|
|
| LAN infra-to-infra | Non-HTTP traffic — metrics, logs, NTP, agent reporting | Explicit per-flow firewall rule, no proxy at all | Can't go through a reverse proxy — it isn't HTTP. The firewall itself is the only layer that *can* enforce this one. |
|
|
|
|
**Three separate routing authorities enforce these** — the edge firewall, the user-facing proxy, and the internal proxy zone — rather than one central router handling everything. That's a deliberate rejection of the simpler single-router design: one router is one thing to hardening-focus on and one thing that takes everything down if it's compromised or misconfigured. Three means a mistake in one router's config, or a full outage of one of them, has a blast radius bounded to the traffic it alone handles — the failure-behavior tables throughout this doc all trace back to this one decision.
|
|
|
|
> [!IMPORTANT]
|
|
> **This wasn't the starting design — it was built up over two real, deliberate changes.** The public-facing reverse proxy used to run as its own dedicated VM; it was consolidated directly onto the edge firewall as a package instead, removing an entire host from the public-ingress path without losing any functionality. And the app-to-app boundary used to not exist at all — internal service traffic ran over a flat, unauthenticated mesh network with no trust boundary. It was replaced by the two-zone model now described in [`docs/dns-naming.md`](dns-naming.md). Both changes added real operational complexity in exchange for a smaller blast radius — accepted deliberately, not stumbled into, and the kind of decision that only looks obvious in hindsight.
|
|
|
|
## The other six traffic laws
|
|
|
|
Each of these gets its own full mechanism doc elsewhere in this repo — this table is the map, not the territory:
|
|
|
|
| # | Law | Failure behavior |
|
|
|---|---|---|
|
|
| 3. Secrets | Applications never talk to Vault directly — one hop through a local agent only. See [`docs/secrets-and-pki.md`](secrets-and-pki.md). | Vault down → secret renewal stops, services run until their current token expires. No immediate disruption. |
|
|
| 4. DNS | Which internal zone a hostname resolves in determines which trust boundary (above) a request enters. See [`docs/dns-naming.md`](dns-naming.md). | DNS down → app-to-app resolution fails; direct-IP fallback exists but silently bypasses the SSO gate. |
|
|
| 5. Observability | Logs and metrics push asynchronously — never inline with application traffic, full stop. See [`docs/observability.md`](observability.md) and [`docs/security-monitoring.md`](security-monitoring.md). | Monitoring down → collection stops; every application service keeps running, completely unaffected. |
|
|
| 6. Notifications | Two independent branches (message-queue and HTTP webhook) converge on one formatter; neither is part of any service's runtime dependency chain. See [`docs/notifications.md`](notifications.md). | A dropped notification is lost, not retried into a backlog that could pressure the sender. |
|
|
| 7. CI/CD | Build/deploy traffic never sits inline with runtime; schedule-triggered, not push-triggered. See [`docs/registry-and-build.md`](registry-and-build.md). | A failed deploy doesn't affect already-running services — enforced by the redeploy step's own rollback logic. |
|
|
| 8. Management | Administrative access is its own separate plane — one dedicated identity, non-standard ports, explicit per-host firewall rules from one source only. | Management access down → administration unavailable; every running service is completely unaffected. |
|
|
|
|
The pattern repeats across all eight laws, not just the trust-boundary story above: **control-plane failure never takes down the data plane.** Every "failure behavior" row above ends the same way — the thing that broke stops working, and everything else keeps running regardless. That's not a coincidence; it's the same design principle applied consistently everywhere, not just to network trust.
|
|
|
|
## What this map deliberately excludes
|
|
|
|
Not a firewall rule listing, not a port inventory, not a service configuration reference — those live in [`docs/firewall-and-network.md`](firewall-and-network.md) and the docs linked in the table above. This is strictly the stable, directional shape of how trust and traffic actually move — meant to still be true even after the specific tools implementing it change again, the way it already has twice.
|
|
|
|
---
|
|
Related: [`docs/firewall-and-network.md`](firewall-and-network.md), [`docs/dns-naming.md`](dns-naming.md), [`docs/secrets-and-pki.md`](secrets-and-pki.md), [`docs/notifications.md`](notifications.md), [`docs/registry-and-build.md`](registry-and-build.md)
|