Files
CASAN/.gitea/vault-setup-runbook.md
T
thanhnvandClaude Opus 4.8 18997caf49 docs: align all documentation with the new repo structure
Update agent protocols (.claude/agents, .github/agents), guides, runbooks and READMEs
to the post-restructure layout: .specify/{scripts,tests,security,config,templates,
governance,memory,level5-config} -> packages/casan-harness/...; docs/input +
golden-runs + traceability-map -> apps/okr/domain/...; drop AINative_OKR_CASAN5/ prefix.
Runtime-state paths (.specify/logs, .specify/agentops, .specify/level5/central-governance)
kept as-is. Historical evidence under docs/output/ left untouched (immutable run records).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 14:36:27 +09:00

226 lines
6.3 KiB
Markdown

# HashiCorp Vault Setup — CASAN H5 KMS
# Oracle Cloud VPS: 161.33.139.73
## What this achieves
Before: private key = file on disk, `verify-audit-chain` → `anchor=unsigned` in CI
After: private key = Vault Transit (never leaves Vault), CI → `anchor=signed`
---
## Step 1 — Add Vault to /opt/gitea/docker-compose.yml
Add the `vault` service and a named volume. The key sections to add:
```yaml
# ── Add to the top-level volumes section ─────────────────────────
volumes:
gitea-data:
act-runner-data:
vault-data: # ← ADD THIS
# ── Add the vault service ─────────────────────────────────────────
vault:
image: hashicorp/vault:latest
container_name: vault
restart: unless-stopped
networks:
- gitea
ports:
- "8200:8200" # expose so you can init from outside
environment:
# Dev mode: auto-init, auto-unseal, in-memory storage.
# Root token is fixed — store it as a Gitea secret after setup.
VAULT_DEV_ROOT_TOKEN_ID: "${VAULT_ROOT_TOKEN}"
VAULT_DEV_LISTEN_ADDRESS: "0.0.0.0:8200"
VAULT_LOG_LEVEL: "warn"
cap_add:
- IPC_LOCK # required by Vault
command: server -dev
```
> Dev mode stores keys in RAM — keys survive container restarts because the token
> and key name are fixed, and Vault re-creates them on each start.
> For production: switch to `server` mode with `file` storage backend.
---
## Step 2 — Add VAULT_ROOT_TOKEN to /opt/gitea/.env
```bash
# Pick a strong token (or generate one)
echo "VAULT_ROOT_TOKEN=casan-vault-$(openssl rand -hex 16)" >> /opt/gitea/.env
# Verify
grep VAULT_ROOT_TOKEN /opt/gitea/.env
```
Save this token — you'll need it for the Gitea secret in Step 5.
---
## Step 3 — Start Vault
```bash
cd /opt/gitea
docker compose pull vault
docker compose up -d vault
# Wait ~5 seconds for Vault to boot, then check:
docker compose logs vault | tail -20
# Look for: "Development mode should NOT be used in production installations!"
# and: "Root Token: <your token>"
```
Verify from outside:
```bash
curl http://161.33.139.73:8200/v1/sys/health
# Expected: {"initialized":true,"sealed":false,...}
```
---
## Step 4 — Enable Transit engine + create signing keys
Run these from anywhere that can reach port 8200.
Replace `<TOKEN>` with your `VAULT_ROOT_TOKEN` value.
```bash
export VAULT_ADDR=http://161.33.139.73:8200
export VAULT_TOKEN=<TOKEN>
# Enable Transit secrets engine
curl -sf -X POST \
-H "X-Vault-Token: $VAULT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"type":"transit"}' \
"$VAULT_ADDR/v1/sys/mounts/transit"
# Create RSA-2048 key for policy bundle signing
curl -sf -X POST \
-H "X-Vault-Token: $VAULT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"type":"rsa-2048","exportable":false}' \
"$VAULT_ADDR/v1/transit/keys/casan-policy-key"
# Create RSA-2048 key for audit chain signing
curl -sf -X POST \
-H "X-Vault-Token: $VAULT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"type":"rsa-2048","exportable":false}' \
"$VAULT_ADDR/v1/transit/keys/casan-audit-key"
echo "Vault Transit keys created."
```
Verify keys exist:
```bash
curl -sf -H "X-Vault-Token: $VAULT_TOKEN" \
"$VAULT_ADDR/v1/transit/keys/casan-policy-key" | python3 -c "
import sys,json; d=json.load(sys.stdin)
print('key type:', d['data']['type'])
print('exportable:', d['data']['exportable'])
"
# Expected: key type: rsa-2048 / exportable: False
```
---
## Step 5 — Add VAULT_TOKEN as Gitea Actions Secret
1. Open Gitea: http://161.33.139.73:3000
2. Go to your repo → Settings → Secrets → Actions
3. Add new secret:
- Name: `VAULT_TOKEN`
- Value: your `VAULT_ROOT_TOKEN` value
4. Save.
The CI workflow references `${{ secrets.VAULT_TOKEN }}` — this injects the token
into the `security-gate` job without exposing it in logs.
---
## Step 6 — Test Vault signing locally (optional)
From macOS with the project checked out:
```bash
export VAULT_ADDR=http://161.33.139.73:8200
export VAULT_TOKEN=<TOKEN>
cd Output_CASAN5_REFINED # repo root (app promoted from )
# Test sign-policy-bundle with Vault
bash packages/casan-harness/scripts/bash/sign-policy-bundle.sh sign
# Expected: POLICY_BUNDLE_SIGNED ... key_backend=vault-kms
# Test sign-audit-head
bash packages/casan-harness/scripts/bash/sign-audit-head.sh
# Expected: SIGN_AUDIT_HEAD_OK ... anchor=vault-kms
# Verify audit chain
bash packages/casan-harness/scripts/bash/verify-audit-chain.sh
# Expected: AUDIT_CHAIN_VALID anchor=signed
```
---
## Step 7 — Push code to trigger CI with Vault
```bash
git add .gitea/ packages/casan-harness/scripts/bash/vault-kms.sh \
packages/casan-harness/scripts/bash/sign-audit-head.sh \
packages/casan-harness/scripts/bash/sign-policy-bundle.sh
git commit -m "feat(H5): Vault KMS for policy + audit chain signing"
git push gitea main
```
Watch the CI run: http://161.33.139.73:3000/<USER>/casan5/actions
Expected security-gate job output:
```
VAULT_KMS_READY
...
SIGN_AUDIT_HEAD_OK head=<hash> anchor=vault-kms
POLICY_BUNDLE_SIGNED ... key_backend=vault-kms
AUDIT_CHAIN_VALID anchor=signed ← changed from "unsigned"
...
== verdict: PASS=7 FAIL=0 SKIP=1 ==
```
---
## Architecture summary
```
CI Job (act_runner container)
│
├── vault-kms.sh sign ──── POST /v1/transit/sign/casan-audit-key ──► Vault container
│ (key never leaves)
├── vault-kms.sh pubkey ──── GET /v1/transit/keys/casan-audit-key ──► export public key
│ as PEM for openssl verify
└── verify-audit-chain.sh ── openssl dgst -verify audit-public.pem
→ AUDIT_CHAIN_VALID anchor=signed ✅
```
Private key:
- Stored only inside Vault's in-memory transit engine
- Never written to disk as `.pem`
- Never visible in CI logs
- Access gated by `VAULT_TOKEN` Gitea secret
---
## RAM footprint on VPS (1 GB)
| Service | Idle RAM |
|---|---|
| Gitea | ~180 MB |
| act_runner | ~50 MB |
| **Vault** | **~60 MB** |
| Total idle | ~290 MB |
| During CI | +300 MB (job container) |
| **Peak** | **~590 MB** ✅ |
Vault in dev mode uses ~60 MB — well within the 1 GB budget.