secret-distribution: verify against live-checked KB, fix diagram, add real evidence
Source Trilium note (Secret Distribution Model) was live-verified and stamped this session - Certificate Flow, App Secret Flow, Configuration Distribution Flow, and the technitium_api_token Vault-KV exception all confirmed against the real hosts, not just trusted from the KB. Replaced the Mermaid diagram, which incorrectly showed the Configuration Distribution Flow feeding into Vault directly, with a corrected version: that flow is a control-plane gate on what Vault Agent renders, not a peer data flow. Added a real Vault Agent log excerpt proving the full redeploy chain (restart -> render -> reload_command -> container recreated) actually happens, and restored the technitium_api_token exception that was in the source note but missing from this doc.
This commit is contained in:
3 files changed
+12
-11
No files matched your search
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Do not edit this file with editors other than draw.io -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="background: #ffffff; background-color: var(--ge-adaptive-bg, #ffffff); color-scheme: light dark;" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1085px" height="545px" viewBox="0 0 1085 545" id="ge-svg-lxAUNMmLjYII1QtcEPaS" content="<mxfile host="embed.diagrams.net" scale="1" border="0"> <diagram name="Secret Distribution Flows" id="secret-dist-flows-1"> <mxGraphModel dx="1414" dy="791" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1200" pageHeight="620" math="0" shadow="0"> <root> <mxCell id="0" /> <mxCell id="1" parent="0" /> <mxCell id="repo" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;fontColor=#333333;fontSize=14;fontStyle=1;shadow=1;" value="Provisioning Repo&lt;br&gt;&lt;font style=&quot;font-size:11px&quot;&gt;template inventory&lt;/font&gt;" vertex="1"> <mxGeometry height="80" width="200" x="40" y="60" as="geometry" /> </mxCell> <mxCell id="agentconfig" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;fontColor=#333333;fontSize=14;fontStyle=1;shadow=1;" value="Vault Agent Config&lt;br&gt;&lt;font style=&quot;font-size:11px&quot;&gt;on the target host&lt;/font&gt;" vertex="1"> <mxGeometry height="80" width="200" x="410" y="60" as="geometry" /> </mxCell> <mxCell id="bgfill_dataplane" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=none;" vertex="1"> <mxGeometry height="290" width="440" x="380" y="230" as="geometry" /> </mxCell> <mxCell id="dataplane" parent="1" style="swimlane;whiteSpace=wrap;html=1;startSize=30;fillColor=none;strokeColor=#9673a6;dashed=1;fontSize=12;fontStyle=1;fontColor=#333333;align=left;spacingLeft=8;verticalAlign=middle;" value="DATA PLANE — what Vault Agent actually renders" vertex="1"> <mxGeometry height="290" width="440" x="380" y="230" as="geometry" /> </mxCell> <mxCell id="vaultagent" parent="dataplane" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontColor=#333333;fontSize=13;fontStyle=1;shadow=1;" value="Vault Agent" vertex="1"> <mxGeometry height="60" width="140" x="150" y="40" as="geometry" /> </mxCell> <mxCell id="certflow" parent="dataplane" style="rounded=1;whiteSpace=wrap;html=1;fillColor=light-dark(#FFFFFF,#292929);strokeColor=#9673a6;fontColor=#333333;fontSize=12;fontStyle=1;shadow=1;" value="Certificate Flow&lt;br&gt;&lt;font style=&quot;font-size:11px&quot;&gt;PKI engine, short-lived&lt;/font&gt;" vertex="1"> <mxGeometry height="65" width="170" x="30" y="180" as="geometry" /> </mxCell> <mxCell id="secflow" parent="dataplane" style="rounded=1;whiteSpace=wrap;html=1;fillColor=light-dark(#FFFFFF,#292929);strokeColor=#9673a6;fontColor=#333333;fontSize=12;fontStyle=1;shadow=1;" value="App Secret Flow&lt;br&gt;&lt;font style=&quot;font-size:11px&quot;&gt;KV, rendered 0600&lt;/font&gt;" vertex="1"> <mxGeometry height="65" width="170" x="240" y="180" as="geometry" /> </mxCell> <mxCell id="container" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontColor=#333333;fontSize=14;fontStyle=1;shadow=1;" value="Container&lt;br&gt;&lt;font style=&quot;font-size:11px&quot;&gt;reads local disk only&lt;/font&gt;" vertex="1"> <mxGeometry height="8Line truncated
|
||||
|
After Width: | Height: | Size: 533 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 290 KiB |
@@ -3,17 +3,7 @@
|
||||
> [!NOTE]
|
||||
> [`docs/secrets-and-pki.md`](secrets-and-pki.md) covers *what* Vault is and *why* it exists. This doc covers something different and, arguably, harder to get right: the actual contract for how a secret value moves from being written into Vault to being consumed by a running container — and why that contract is deliberately split into four separate flows instead of one universal one.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Op["Operator"] -->|1: writes cert/secret request| Vault["Vault"]
|
||||
Vault -->|2: Certificate Flow| Cert["Short-lived cert<br/>rendered to disk"]
|
||||
Vault -->|3: App Secret Flow| Sec["Rendered secret file<br/>0600, gitignored"]
|
||||
Repo["Provisioning repo"] -->|4: Config Distribution Flow| Vault
|
||||
Op2["Operator"] -.5: Administrative Secret Flow<br/>separate system entirely.-> EncFile["ansible-vault encrypted file<br/>in the repo itself"]
|
||||
|
||||
Cert --> Container["Container reads<br/>local disk only"]
|
||||
Sec --> Container
|
||||
```
|
||||

|
||||
|
||||
## Why four flows instead of one
|
||||
|
||||
@@ -51,6 +41,10 @@ The obvious simpler design is one universal "secret goes in, secret comes out" p
|
||||
> [!IMPORTANT]
|
||||
> This is the flow with the sharpest failure mode in the whole model: a missing or malformed template referenced by a host's own config crashes that host's **entire** Vault Agent process — every other secret/cert that host was already rendering stops updating too, not just the one broken template. A single YAML typo in one app's template inventory can silently freeze every other app's secret rotation on that same host. This is why a config redeploy always requires confirming the underlying git change actually landed first, not just that the deploy command returned success.
|
||||
|
||||
That redeploy-and-restart sequence isn't just a claim — here's a real one, start to finish: Vault Agent restarts, authenticates, renders a fresh `.env.secrets`, and its `reload_command` fires `docker compose up -d`, which recreates and restarts the actual container:
|
||||
|
||||

|
||||
|
||||
**Shared-secret variant:** not every template is host-scoped — one credential needed by every docker host for its own registry pushes (see [`docs/registry-and-build.md`](registry-and-build.md)) is templated from a single shared path rather than a per-host one. Same mechanics, just a shared source instead of a private one — and it relies on every host's policy already granting read access to that shared path, the same "shared + private" pattern described in [`docs/secrets-and-pki.md`](secrets-and-pki.md)'s access model.
|
||||
|
||||
## 4️⃣ Administrative Secret Flow — a deliberately separate, simpler system
|
||||
@@ -64,5 +58,8 @@ The obvious simpler design is one universal "secret goes in, secret comes out" p
|
||||
> [!TIP]
|
||||
> **Why two systems, not one — the actual reasoning, not just the rule**: Vault KV is built for runtime/service/pipeline secrets a running container needs continuously and automatically. `ansible-vault` is built for secrets used by tooling that *operates on* the fleet rather than secrets *consumed by* the fleet's own running services — a Gitea admin token used only during a playbook run, for instance, doesn't need automatic rotation, per-host scoping, or a rendered file sitting on disk permanently. Building that into Vault's model would have meant either weakening Vault's per-host isolation for a use case that doesn't need it, or adding a whole parallel "admin secrets" concept inside Vault itself. Using the tool already built for exactly this case (`ansible-vault`, already part of the Ansible toolchain from [`docs/iac-pipeline.md`](iac-pipeline.md)) was less work and a better fit than extending Vault to do a second, different job.
|
||||
|
||||
> [!NOTE]
|
||||
> **One exception to this split, flagged rather than smoothed over**: Technitium's API token moved out of `ansible-vault` into live Vault KV (fetched via a `community.hashi_vault.vault_kv2_get` lookup at playbook-run time) even though it's consumed by an ansible-playbook, not a running service — exactly the case the law above says belongs in `ansible-vault` instead. The admin/runtime split is the general rule here, not an absolute one, and this is the one place it doesn't cleanly apply.
|
||||
|
||||
---
|
||||
Related: [`docs/secrets-and-pki.md`](secrets-and-pki.md), [`docs/iac-pipeline.md`](iac-pipeline.md), [`docs/registry-and-build.md`](registry-and-build.md)
|
||||
Reference in new issue
Block a user