refactor(structure): promote app to repo root + remove redundant workspace cruft
Standard production layout: the OKR app (was nested under AINative_OKR_CASAN5/) is now
the repository root. No more wrapper directory.
- Promote AINative_OKR_CASAN5/* -> repo root (backend/ frontend/ packages/ apps/
.specify/ docs/ infra/ nginx/ scripts/ + configs). Merge tool dirs: .gitea (kept the
active deploy ci.yml, added harness-ci.yml + runbooks), .claude (agents/commands +
launch.json), .github moved up.
- Remove redundant: 00_SUBMISSION_PACKAGE, scattered root notes (FPT_CASAN_Full.md,
tu-tuong-casan.md, casan-tu-sinh..., casan_harness_assessment.md, source-review...,
README_CASAN5_REFINED.md), casan-next-plans/ and optimize-docs/ (competition/planning
artifacts — roadmap + design history preserved in git log / commit messages).
- Update all references to the old layout:
- .gitea/workflows/{ci,harness-ci}.yml, .github/workflows/{ci,deploy}.yml:
working-directory .; drop AINative_OKR_CASAN5/ prefix; .specify/{tests,scripts}
-> packages/casan-harness/... (.specify/logs state kept)
- .claude/launch.json, .gitea/*-runbook.md: path prefixes
- CLAUDE.md, README.md: docs/input -> apps/okr/domain/input
- policy-bundle.yaml: 8 policy paths -> packages/casan-harness/...; manifest re-signed
- secrets-scan.sh: fixture excludes -> new package/domain paths.
Full gate from the new root: PASS=64 FAIL=0 SKIP=3.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
7101af9fd4
commit
36a4812ef3
@@ -0,0 +1,225 @@
|
||||
# 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 AINative_OKR_CASAN5/)
|
||||
|
||||
# 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.
|
||||
Reference in New Issue
Block a user