feat(k8s-portal): update onboarding + architecture with SOPS state docs
Onboarding (namespace-owner): - Add steps for sops/terragrunt install, state decrypt, apply workflow - Add flow diagram showing auth → decrypt → apply → encrypt → push - Add architecture overview with security model table - Add access control callout explaining per-stack Transit keys Architecture: - Add secrets & state encryption section with ASCII diagrams - Add request flow diagram (Cloudflare → Traefik → pods) - Add CI/CD pipeline diagram (GHA → Woodpecker → K8s) [ci skip]
This commit is contained in:
parent
ccbcebb670
commit
0fff155f17
2 changed files with 226 additions and 18 deletions
|
|
@ -59,6 +59,74 @@ Proxmox (Dell R730)
|
|||
└── ... (70+ more)</pre>
|
||||
<p>Changes go through git: branch → PR → review → merge → CI applies automatically.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Secrets & State Encryption</h2>
|
||||
<p>Terraform state is committed to git as SOPS-encrypted JSON. Secrets live in HashiCorp Vault.</p>
|
||||
<pre class="output">
|
||||
Authentication & Authorization
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
User → Authentik SSO (OIDC) → Vault Token
|
||||
│
|
||||
┌──────────────┼──────────────┐
|
||||
▼ ▼ ▼
|
||||
KV Secrets Transit Keys K8s Creds
|
||||
(per-stack) (per-stack) (deployer)
|
||||
|
||||
State Encryption Flow
|
||||
━━━━━━━━━━━━━━━━━━━━
|
||||
.tfstate ──SOPS──▶ .tfstate.enc ──git──▶ repo
|
||||
│
|
||||
encrypted with:
|
||||
├── Vault Transit key (per-stack)
|
||||
└── age keys (admin DR fallback)
|
||||
|
||||
Access Control
|
||||
━━━━━━━━━━━━━━
|
||||
Admin: vault-admin policy → all transit keys
|
||||
User: sops-user-* policy → own stack keys only
|
||||
Fallback: age key on disk → admin only (no users)</pre>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Request Flow</h2>
|
||||
<pre class="output">
|
||||
Internet → Cloudflare (CDN + WAF)
|
||||
│
|
||||
▼
|
||||
Cloudflared tunnel
|
||||
│
|
||||
▼
|
||||
Traefik (3 replicas)
|
||||
├── CrowdSec bouncer (rate limit, bot block)
|
||||
├── Authentik forward-auth (SSO for protected apps)
|
||||
└── TLS termination
|
||||
│
|
||||
▼
|
||||
K8s Service → Pod(s)
|
||||
│
|
||||
├── NFS volume (app data)
|
||||
└── iSCSI volume (databases)</pre>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>CI/CD Pipeline</h2>
|
||||
<pre class="output">
|
||||
git push
|
||||
│
|
||||
├──▶ GitHub Actions (build Docker image, push to DockerHub)
|
||||
│ │
|
||||
│ ▼
|
||||
│ POST Woodpecker API (trigger deploy)
|
||||
│ │
|
||||
│ ▼
|
||||
│ Woodpecker (kubectl set image, Slack notify)
|
||||
│
|
||||
└──▶ Woodpecker (terragrunt apply — infra changes)
|
||||
│
|
||||
▼
|
||||
Kubernetes API → rolling update</pre>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
|
|
|
|||
|
|
@ -64,11 +64,75 @@
|
|||
<h2>Step 5 — Clone the infra repo</h2>
|
||||
<pre>git clone https://github.com/ViktorBarzin/infra.git
|
||||
cd infra</pre>
|
||||
<p>This is where all the infrastructure configuration lives.</p>
|
||||
<p>This is where all the infrastructure configuration lives. Terraform state is committed as encrypted files.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Step 6 — Create your first app stack</h2>
|
||||
<h2>Step 6 — Install tools</h2>
|
||||
<p>You need <code>sops</code> and <code>terragrunt</code> to work with infrastructure state:</p>
|
||||
<h3>macOS</h3>
|
||||
<pre>brew install sops terragrunt</pre>
|
||||
<h3>Linux</h3>
|
||||
<pre># sops
|
||||
curl -LO https://github.com/getsops/sops/releases/latest/download/sops-v3.9.4.linux.amd64
|
||||
sudo mv sops-*.linux.amd64 /usr/local/bin/sops && sudo chmod +x /usr/local/bin/sops
|
||||
|
||||
# terragrunt
|
||||
curl -LO https://github.com/gruntwork-io/terragrunt/releases/latest/download/terragrunt_linux_amd64
|
||||
sudo mv terragrunt_linux_amd64 /usr/local/bin/terragrunt && sudo chmod +x /usr/local/bin/terragrunt</pre>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Step 7 — Decrypt your state</h2>
|
||||
<p>Terraform state is encrypted with SOPS. Your Vault login gives you access to <strong>only your stacks</strong>.</p>
|
||||
<pre># Make sure you're logged into Vault
|
||||
vault login -method=oidc
|
||||
|
||||
# Decrypt your stack's state
|
||||
scripts/state-sync decrypt YOUR_NAMESPACE
|
||||
|
||||
# Plan changes (auto-decrypts before, auto-encrypts after)
|
||||
cd stacks/YOUR_NAMESPACE
|
||||
../../scripts/tg plan</pre>
|
||||
|
||||
<div class="diagram">
|
||||
<h3>How state encryption works</h3>
|
||||
<div class="flow-diagram">
|
||||
<div class="flow-row">
|
||||
<div class="flow-box accent">vault login -method=oidc</div>
|
||||
<div class="flow-arrow">→</div>
|
||||
<div class="flow-box">Authentik SSO</div>
|
||||
<div class="flow-arrow">→</div>
|
||||
<div class="flow-box accent">~/.vault-token</div>
|
||||
</div>
|
||||
<div class="flow-separator">↓</div>
|
||||
<div class="flow-row">
|
||||
<div class="flow-box accent">scripts/tg plan</div>
|
||||
<div class="flow-arrow">→</div>
|
||||
<div class="flow-box">state-sync decrypt</div>
|
||||
<div class="flow-arrow">→</div>
|
||||
<div class="flow-box">Vault Transit<br/><small>sops-state-YOUR_NS</small></div>
|
||||
</div>
|
||||
<div class="flow-separator">↓</div>
|
||||
<div class="flow-row">
|
||||
<div class="flow-box">terragrunt plan/apply</div>
|
||||
<div class="flow-arrow">→</div>
|
||||
<div class="flow-box">state-sync encrypt</div>
|
||||
<div class="flow-arrow">→</div>
|
||||
<div class="flow-box accent">git commit + push</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="callout info">
|
||||
<strong>Access control:</strong> You can only decrypt state for your own namespaces.
|
||||
Each namespace has its own Vault Transit encryption key. Your Vault policy
|
||||
(<code>sops-user-YOUR_USERNAME</code>) only grants access to your keys.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Step 8 — Create your first app stack</h2>
|
||||
<ol>
|
||||
<li>Copy the template: <pre>cp -r stacks/_template stacks/myapp
|
||||
mv stacks/myapp/main.tf.example stacks/myapp/main.tf</pre></li>
|
||||
|
|
@ -76,17 +140,68 @@ mv stacks/myapp/main.tf.example stacks/myapp/main.tf</pre></li>
|
|||
<li>Store secrets in Vault:
|
||||
<pre>vault kv put secret/YOUR_USERNAME/myapp DB_PASSWORD=secret123</pre>
|
||||
</li>
|
||||
<li>Add your app domain to <code>domains</code> list in Vault KV <code>k8s_users</code></li>
|
||||
<li>Submit a PR:
|
||||
<pre>git checkout -b feat/myapp
|
||||
git add stacks/myapp/
|
||||
git commit -m "add myapp stack"
|
||||
git push -u origin feat/myapp</pre>
|
||||
<li>Apply your stack:
|
||||
<pre>cd stacks/myapp && ../../scripts/tg apply</pre>
|
||||
</li>
|
||||
<li>Commit encrypted state:
|
||||
<pre>cd ../..
|
||||
git add stacks/myapp/ state/stacks/myapp/terraform.tfstate.enc
|
||||
git commit -m "add myapp stack"
|
||||
git push</pre>
|
||||
</li>
|
||||
<li>Viktor reviews and merges</li>
|
||||
<li>After merge: <code>cd stacks/myapp && terragrunt apply</code></li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Architecture Overview</h2>
|
||||
<p>Here's how your changes flow through the system:</p>
|
||||
|
||||
<div class="diagram">
|
||||
<h3>Apply workflow</h3>
|
||||
<div class="arch-grid">
|
||||
<div class="arch-col">
|
||||
<div class="arch-header">Your Machine</div>
|
||||
<div class="arch-box">git pull</div>
|
||||
<div class="arch-arrow">↓</div>
|
||||
<div class="arch-box">scripts/tg plan</div>
|
||||
<div class="arch-arrow">↓ <small>auto-decrypt</small></div>
|
||||
<div class="arch-box">scripts/tg apply</div>
|
||||
<div class="arch-arrow">↓ <small>auto-encrypt</small></div>
|
||||
<div class="arch-box">git push</div>
|
||||
</div>
|
||||
<div class="arch-col">
|
||||
<div class="arch-header">Vault</div>
|
||||
<div class="arch-box small">OIDC auth<br/><small>Authentik SSO</small></div>
|
||||
<div class="arch-arrow">↓</div>
|
||||
<div class="arch-box small">Transit decrypt<br/><small>sops-state-*</small></div>
|
||||
<div class="arch-arrow">↓</div>
|
||||
<div class="arch-box small">Transit encrypt<br/><small>per-stack key</small></div>
|
||||
</div>
|
||||
<div class="arch-col">
|
||||
<div class="arch-header">Cluster</div>
|
||||
<div class="arch-box small">K8s API</div>
|
||||
<div class="arch-arrow">↓</div>
|
||||
<div class="arch-box small">Your namespace<br/><small>pods, services</small></div>
|
||||
<div class="arch-arrow">↓</div>
|
||||
<div class="arch-box small">Traefik ingress<br/><small>*.viktorbarzin.me</small></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="diagram">
|
||||
<h3>Security model</h3>
|
||||
<table>
|
||||
<thead><tr><th>Layer</th><th>What</th><th>How</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Authentication</td><td>Who are you?</td><td>Authentik SSO (OIDC) → Vault token</td></tr>
|
||||
<tr><td>Authorization</td><td>What can you access?</td><td>Vault policy (<code>sops-user-*</code>) scoped to your namespaces</td></tr>
|
||||
<tr><td>Encryption at rest</td><td>State in git</td><td>SOPS + Vault Transit (per-stack key)</td></tr>
|
||||
<tr><td>Encryption fallback</td><td>Bootstrap / DR</td><td>age keys (admin only)</td></tr>
|
||||
<tr><td>Network</td><td>Cluster access</td><td>Headscale VPN (private 10.0.20.0/24)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
{:else}
|
||||
<section>
|
||||
<h2>Step 3 — Verify access</h2>
|
||||
|
|
@ -109,14 +224,7 @@ cd infra</pre>
|
|||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Step 5 — Install your AI assistant (optional)</h2>
|
||||
<p>Install <a href="https://github.com/openai/codex" target="_blank">Codex CLI</a> for AI-assisted cluster management:</p>
|
||||
<pre>npm install -g @openai/codex</pre>
|
||||
<p>Codex reads the <code>AGENTS.md</code> file in the repo and knows how to work with the cluster.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Step 6 — Your first change</h2>
|
||||
<h2>Step 5 — Your first change</h2>
|
||||
<ol>
|
||||
<li>Create a branch: <pre>git checkout -b my-first-change</pre></li>
|
||||
<li>Edit a service file (e.g., change an image tag in <code>stacks/echo/main.tf</code>)</li>
|
||||
|
|
@ -143,4 +251,36 @@ cd infra</pre>
|
|||
.role-tabs { display: flex; gap: 0; margin: 1.5rem 0; border-bottom: 2px solid #e0e0e0; }
|
||||
.role-tabs a { padding: 0.5rem 1.5rem; text-decoration: none; color: #666; border-bottom: 2px solid transparent; margin-bottom: -2px; }
|
||||
.role-tabs a.active { color: #333; border-bottom-color: #333; font-weight: 600; }
|
||||
table { border-collapse: collapse; width: 100%; margin: 0.5rem 0; }
|
||||
th, td { border: 1px solid #ddd; padding: 0.5rem; text-align: left; }
|
||||
th { background: #f5f5f5; }
|
||||
|
||||
.callout { padding: 1rem; border-radius: 6px; margin: 1rem 0; }
|
||||
.callout.info { background: #e8f4fd; border-left: 4px solid #2196f3; }
|
||||
.callout.warning { background: #fff3cd; border-left: 4px solid #ffc107; }
|
||||
|
||||
.diagram { background: #fafafa; border: 1px solid #e0e0e0; border-radius: 8px; padding: 1.5rem; margin: 1.5rem 0; }
|
||||
.diagram h3 { margin: 0 0 1rem 0; color: #333; font-size: 0.95rem; text-transform: uppercase; letter-spacing: 0.5px; }
|
||||
|
||||
.flow-diagram { display: flex; flex-direction: column; align-items: center; gap: 0.25rem; }
|
||||
.flow-row { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; justify-content: center; }
|
||||
.flow-box { background: white; border: 2px solid #ddd; border-radius: 6px; padding: 0.5rem 1rem; font-family: monospace; font-size: 0.85rem; text-align: center; }
|
||||
.flow-box.accent { border-color: #333; font-weight: 600; }
|
||||
.flow-box small { display: block; color: #888; font-weight: normal; }
|
||||
.flow-arrow { color: #999; font-size: 1.2rem; }
|
||||
.flow-separator { color: #999; font-size: 1.2rem; }
|
||||
|
||||
.arch-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 1.5rem; }
|
||||
.arch-col { display: flex; flex-direction: column; align-items: center; gap: 0.25rem; }
|
||||
.arch-header { font-weight: 700; font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.5px; color: #555; margin-bottom: 0.5rem; padding: 0.25rem 0.75rem; background: #e8e8e8; border-radius: 4px; }
|
||||
.arch-box { background: white; border: 2px solid #ddd; border-radius: 6px; padding: 0.5rem 0.75rem; font-family: monospace; font-size: 0.8rem; text-align: center; width: 100%; box-sizing: border-box; }
|
||||
.arch-box.small { font-size: 0.75rem; }
|
||||
.arch-box small { display: block; color: #888; font-family: monospace; }
|
||||
.arch-arrow { color: #999; font-size: 1rem; }
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.arch-grid { grid-template-columns: 1fr; }
|
||||
.flow-row { flex-direction: column; }
|
||||
.flow-arrow { transform: rotate(90deg); }
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue