Fix policy form roles; add JSON edit mode
- Replace stale "service" role option with correct set: admin, user, guest, viewer, editor, commenter (matches model.go) - Add Form/JSON tab toggle to policy create form - JSON tab accepts raw RuleBody JSON with description/priority - Handler detects rule_json field and parses/validates it directly, falling back to field-by-field form mode otherwise
This commit is contained in:
183
PROJECT_PLAN.md
183
PROJECT_PLAN.md
@@ -5,7 +5,19 @@ See ARCHITECTURE.md for design rationale.
|
||||
|
||||
---
|
||||
|
||||
## Phase 0 — Repository Bootstrap
|
||||
## Status
|
||||
|
||||
**v1.0.0 tagged (2026-03-15). All phases complete.**
|
||||
|
||||
All packages pass `go test ./...`; `golangci-lint run ./...` clean.
|
||||
See PROGRESS.md for the detailed development log.
|
||||
|
||||
Phases 0–9 match the original plan. Phases 10–13 document significant
|
||||
features implemented beyond the original plan scope.
|
||||
|
||||
---
|
||||
|
||||
## Phase 0 — Repository Bootstrap **[COMPLETE]**
|
||||
|
||||
### Step 0.1: Go module and dependency setup
|
||||
**Acceptance criteria:**
|
||||
@@ -23,7 +35,7 @@ See ARCHITECTURE.md for design rationale.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — Foundational Packages
|
||||
## Phase 1 — Foundational Packages **[COMPLETE]**
|
||||
|
||||
### Step 1.1: `internal/model` — shared data types
|
||||
**Acceptance criteria:**
|
||||
@@ -69,7 +81,7 @@ See ARCHITECTURE.md for design rationale.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — Authentication Core
|
||||
## Phase 2 — Authentication Core **[COMPLETE]**
|
||||
|
||||
### Step 2.1: `internal/token` — JWT issuance and validation
|
||||
**Acceptance criteria:**
|
||||
@@ -107,7 +119,7 @@ See ARCHITECTURE.md for design rationale.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — HTTP Server
|
||||
## Phase 3 — HTTP Server **[COMPLETE]**
|
||||
|
||||
### Step 3.1: `internal/middleware` — HTTP middleware
|
||||
**Acceptance criteria:**
|
||||
@@ -143,6 +155,7 @@ See ARCHITECTURE.md for design rationale.
|
||||
- `POST /v1/auth/totp/confirm` — confirms TOTP enrollment
|
||||
- `DELETE /v1/auth/totp` — admin; removes TOTP from account
|
||||
- `GET|PUT /v1/accounts/{id}/pgcreds` — get/set Postgres credentials
|
||||
- `GET /v1/pgcreds` — list all accessible credentials (owned + granted)
|
||||
- Credential fields (password hash, TOTP secret, Postgres password) are
|
||||
**never** included in any API response
|
||||
- Tests: each endpoint happy path; auth middleware applied correctly; invalid
|
||||
@@ -160,7 +173,7 @@ See ARCHITECTURE.md for design rationale.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 — Admin CLI
|
||||
## Phase 4 — Admin CLI **[COMPLETE]**
|
||||
|
||||
### Step 4.1: `cmd/mciasctl` — admin CLI
|
||||
**Acceptance criteria:**
|
||||
@@ -177,6 +190,7 @@ See ARCHITECTURE.md for design rationale.
|
||||
- `mciasctl role revoke -id UUID -role ROLE`
|
||||
- `mciasctl token issue -id UUID` (system accounts)
|
||||
- `mciasctl token revoke -jti JTI`
|
||||
- `mciasctl pgcreds list`
|
||||
- `mciasctl pgcreds set -id UUID -host H -port P -db D -user U`
|
||||
- `mciasctl pgcreds get -id UUID`
|
||||
- `mciasctl auth login`
|
||||
@@ -191,7 +205,7 @@ See ARCHITECTURE.md for design rationale.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5 — End-to-End Tests and Hardening
|
||||
## Phase 5 — End-to-End Tests and Hardening **[COMPLETE]**
|
||||
|
||||
### Step 5.1: End-to-end test suite
|
||||
**Acceptance criteria:**
|
||||
@@ -228,7 +242,7 @@ See ARCHITECTURE.md for design rationale.
|
||||
|
||||
---
|
||||
|
||||
## Phase 6 — mciasdb: Database Maintenance Tool
|
||||
## Phase 6 — mciasdb: Database Maintenance Tool **[COMPLETE]**
|
||||
|
||||
See ARCHITECTURE.md §16 for full design rationale, trust model, and command
|
||||
surface.
|
||||
@@ -314,9 +328,7 @@ surface.
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## Phase 7 — gRPC Interface
|
||||
## Phase 7 — gRPC Interface **[COMPLETE]**
|
||||
|
||||
See ARCHITECTURE.md §17 for full design rationale, proto definitions, and
|
||||
transport security requirements.
|
||||
@@ -324,7 +336,8 @@ transport security requirements.
|
||||
### Step 7.1: Protobuf definitions and generated code
|
||||
**Acceptance criteria:**
|
||||
- `proto/mcias/v1/` directory contains `.proto` files for all service groups:
|
||||
`auth.proto`, `token.proto`, `account.proto`, `admin.proto`
|
||||
`auth.proto`, `token.proto`, `account.proto`, `policy.proto`, `admin.proto`,
|
||||
`common.proto`
|
||||
- All RPC methods mirror the REST API surface (see ARCHITECTURE.md §8 and §17)
|
||||
- `proto/generate.go` contains a `//go:generate protoc ...` directive that
|
||||
produces Go stubs under `gen/mcias/v1/` using `protoc-gen-go` and
|
||||
@@ -357,10 +370,11 @@ transport security requirements.
|
||||
- gRPC server uses the same TLS certificate and key as the REST server (loaded
|
||||
from config); minimum TLS 1.2 enforced via `tls.Config`
|
||||
- Unary server interceptor chain:
|
||||
1. Request logger (method name, peer IP, status, duration)
|
||||
2. Auth interceptor (extracts Bearer token, validates, injects claims into
|
||||
1. Sealed interceptor (blocks all RPCs when vault sealed, except Health)
|
||||
2. Request logger (method name, peer IP, status, duration)
|
||||
3. Auth interceptor (extracts Bearer token, validates, injects claims into
|
||||
`context.Context`)
|
||||
3. Rate-limit interceptor (per-IP token bucket, same parameters as REST)
|
||||
4. Rate-limit interceptor (per-IP token bucket, same parameters as REST)
|
||||
- No credential material logged by any interceptor
|
||||
- Tests: interceptor chain applied correctly; rate-limit triggers after burst
|
||||
|
||||
@@ -396,7 +410,7 @@ transport security requirements.
|
||||
|
||||
---
|
||||
|
||||
## Phase 8 — Operational Artifacts
|
||||
## Phase 8 — Operational Artifacts **[COMPLETE]**
|
||||
|
||||
See ARCHITECTURE.md §18 for full design rationale and artifact inventory.
|
||||
|
||||
@@ -461,7 +475,10 @@ See ARCHITECTURE.md §18 for full design rationale and artifact inventory.
|
||||
- `generate` — `go generate ./...` (proto stubs from Phase 7)
|
||||
- `man` — build compressed man pages
|
||||
- `install` — run `dist/install.sh`
|
||||
- `clean` — remove `bin/` and generated artifacts
|
||||
- `docker` — `docker build -t mcias:$(VERSION) -t mcias:latest .`
|
||||
- `docker-clean` — remove local `mcias:$(VERSION)` and `mcias:latest` images;
|
||||
prune dangling images with the mcias label
|
||||
- `clean` — remove `bin/`, compressed man pages, and local Docker images
|
||||
- `dist` — build release tarballs for linux/amd64 and linux/arm64 (using
|
||||
`GOOS`/`GOARCH` cross-compilation)
|
||||
- `make build` works from a clean checkout after `go mod download`
|
||||
@@ -483,13 +500,10 @@ See ARCHITECTURE.md §18 for full design rationale and artifact inventory.
|
||||
- `dist/mcias.conf.docker.example` — config template suitable for container
|
||||
deployment: `listen_addr = "0.0.0.0:8443"`, `grpc_addr = "0.0.0.0:9443"`,
|
||||
`db_path = "/data/mcias.db"`, TLS cert/key paths under `/etc/mcias/`
|
||||
- `Makefile` gains a `docker` target: `docker build -t mcias:$(VERSION) .`
|
||||
where `VERSION` defaults to the output of `git describe --tags --always`
|
||||
- Tests:
|
||||
- `docker build .` completes without error (run in CI if Docker available;
|
||||
skip gracefully if not)
|
||||
- `docker run --rm mcias:latest mciassrv --help` exits 0
|
||||
- Image size documented in PROGRESS.md (target: under 50 MB)
|
||||
|
||||
### Step 8.7: Documentation
|
||||
**Acceptance criteria:**
|
||||
@@ -501,7 +515,7 @@ See ARCHITECTURE.md §18 for full design rationale and artifact inventory.
|
||||
|
||||
---
|
||||
|
||||
## Phase 9 — Client Libraries
|
||||
## Phase 9 — Client Libraries **[COMPLETE]**
|
||||
|
||||
See ARCHITECTURE.md §19 for full design rationale, API surface, and per-language
|
||||
implementation notes.
|
||||
@@ -606,6 +620,130 @@ implementation notes.
|
||||
|
||||
---
|
||||
|
||||
## Phase 10 — Web UI (HTMX) **[COMPLETE]**
|
||||
|
||||
Not in the original plan. Implemented alongside and after Phase 3.
|
||||
|
||||
See ARCHITECTURE.md §8 (Web Management UI) for design details.
|
||||
|
||||
### Step 10.1: `internal/ui` — HTMX web interface
|
||||
**Acceptance criteria:**
|
||||
- Go `html/template` pages embedded at compile time via `web/embed.go`
|
||||
- CSRF protection: HMAC-signed double-submit cookie (`mcias_csrf`)
|
||||
- Session: JWT stored as `HttpOnly; Secure; SameSite=Strict` cookie
|
||||
- Security headers: `Content-Security-Policy: default-src 'self'`,
|
||||
`X-Frame-Options: DENY`, `Referrer-Policy: strict-origin`
|
||||
- Pages: login, dashboard, account list/detail, role editor, tag editor,
|
||||
pgcreds, audit log viewer, policy rules, user profile, service-accounts
|
||||
- HTMX partial-page updates for mutations (role updates, tag edits, policy
|
||||
toggles, access grants)
|
||||
- Empty-state handling on all list pages (zero records case tested)
|
||||
|
||||
### Step 10.2: Swagger UI at `/docs`
|
||||
**Acceptance criteria:**
|
||||
- `GET /docs` serves Swagger UI for `openapi.yaml`
|
||||
- swagger-ui-bundle.js and swagger-ui.css bundled locally in `web/static/`
|
||||
(CDN blocked by CSP `default-src 'self'`)
|
||||
- `GET /docs/openapi.yaml` serves the OpenAPI spec
|
||||
- `openapi.yaml` kept in sync with REST API surface
|
||||
|
||||
---
|
||||
|
||||
## Phase 11 — Authorization Policy Engine **[COMPLETE]**
|
||||
|
||||
Not in the original plan (CLI subcommands for policy were planned in Phase 4,
|
||||
but the engine itself was not a discrete plan phase).
|
||||
|
||||
See ARCHITECTURE.md §20 for full design, evaluation algorithm, and built-in
|
||||
default rules.
|
||||
|
||||
### Step 11.1: `internal/policy` — in-process ABAC engine
|
||||
**Acceptance criteria:**
|
||||
- Pure evaluation: `Evaluate(input PolicyInput, rules []Rule) (Effect, *Rule)`
|
||||
- Deny-wins: any explicit deny overrides all allows
|
||||
- Default-deny: no matching rule → deny
|
||||
- Built-in default rules (IDs -1 … -7) compiled in; reproduce previous
|
||||
binary admin/non-admin behavior exactly; cannot be disabled via API
|
||||
- Match fields: roles, account types, subject UUID, actions, resource type,
|
||||
owner-matches-subject, service names, required tags (all ANDed; zero value
|
||||
= wildcard)
|
||||
- Temporal constraints on DB-backed rules: `not_before`, `expires_at`
|
||||
- `Engine` wrapper: caches rule set in memory; reloads on policy mutations
|
||||
- Tests: all built-in rules; deny-wins over allow; default-deny fallback;
|
||||
temporal filtering; concurrent access
|
||||
|
||||
### Step 11.2: Middleware and REST integration
|
||||
**Acceptance criteria:**
|
||||
- `RequirePolicy(engine, action, resourceType)` middleware replaces
|
||||
`RequireRole("admin")` where policy-gated
|
||||
- Every explicit deny produces a `policy_deny` audit event
|
||||
- REST endpoints: `GET|POST /v1/policy/rules`, `GET|PATCH|DELETE /v1/policy/rules/{id}`
|
||||
- DB schema: `policy_rules` and `account_tags` tables (migrations 000004,
|
||||
000006)
|
||||
- `PATCH /v1/policy/rules/{id}` supports updating `priority`, `enabled`,
|
||||
`not_before`, `expires_at`
|
||||
|
||||
---
|
||||
|
||||
## Phase 12 — Vault Seal/Unseal Lifecycle **[COMPLETE]**
|
||||
|
||||
Not in the original plan.
|
||||
|
||||
See ARCHITECTURE.md §8 (Vault Endpoints) for the API surface.
|
||||
|
||||
### Step 12.1: `internal/vault` — master key lifecycle
|
||||
**Acceptance criteria:**
|
||||
- Thread-safe `Vault` struct with `sync.RWMutex`-protected state
|
||||
- Methods: `IsSealed()`, `Unseal(passphrase)`, `Seal()`, `MasterKey()`,
|
||||
`PrivKey()`, `PubKey()`
|
||||
- `Seal()` zeroes all key material before nilling (memguard-style cleanup)
|
||||
- `DeriveFromPassphrase()` and `DecryptSigningKey()` extracted to `derive.go`
|
||||
for reuse by unseal handlers
|
||||
- Tests: state transitions; key zeroing verified; concurrent read/write safety
|
||||
|
||||
### Step 12.2: REST and UI integration
|
||||
**Acceptance criteria:**
|
||||
- `POST /v1/vault/unseal` — rate-limited (3/s burst 5); derives key, unseals
|
||||
- `GET /v1/vault/status` — always accessible; returns `{"sealed": bool}`
|
||||
- `POST /v1/vault/seal` — admin only; zeroes key material
|
||||
- `GET /v1/health` returns `{"status":"sealed"}` when sealed
|
||||
- All other `/v1/*` endpoints return 503 `vault_sealed` when sealed
|
||||
- UI redirects all paths to `/unseal` when sealed (except `/static/`)
|
||||
- gRPC: `sealedInterceptor` first in chain; blocks all RPCs except Health
|
||||
- Startup: server may start in sealed state if passphrase env var is absent
|
||||
- Audit events: `vault_sealed`, `vault_unsealed`
|
||||
|
||||
---
|
||||
|
||||
## Phase 13 — Token Delegation and pgcred Access Grants **[COMPLETE]**
|
||||
|
||||
Not in the original plan.
|
||||
|
||||
See ARCHITECTURE.md §21 (Token Issuance Delegation) for design details.
|
||||
|
||||
### Step 13.1: Service account token delegation
|
||||
**Acceptance criteria:**
|
||||
- DB migration 000008: `service_account_delegates` table
|
||||
- `POST /accounts/{id}/token/delegates` — admin grants delegation
|
||||
- `DELETE /accounts/{id}/token/delegates/{grantee}` — admin revokes delegation
|
||||
- `POST /accounts/{id}/token` — accepts admin or delegate (not admin-only)
|
||||
- One-time token download: nonce stored in `sync.Map` with 5-minute TTL;
|
||||
`GET /token/download/{nonce}` serves token as attachment, deletes nonce
|
||||
- `/service-accounts` page for non-admin delegates
|
||||
- Audit events: `token_delegate_granted`, `token_delegate_revoked`
|
||||
|
||||
### Step 13.2: pgcred fine-grained access grants
|
||||
**Acceptance criteria:**
|
||||
- DB migration 000005: `pgcred_access_grants` table
|
||||
- `POST /accounts/{id}/pgcreds/access` — owner grants read access to grantee
|
||||
- `DELETE /accounts/{id}/pgcreds/access/{grantee}` — owner revokes access
|
||||
- `GET /v1/pgcreds` — lists all credentials accessible to caller (owned +
|
||||
granted); includes credential ID for reference
|
||||
- Grantees may view connection metadata; password is never decrypted for them
|
||||
- Audit events: `pgcred_access_granted`, `pgcred_access_revoked`
|
||||
|
||||
---
|
||||
|
||||
## Implementation Order
|
||||
|
||||
```
|
||||
@@ -618,6 +756,11 @@ Phase 0 → Phase 1 (1.1, 1.2, 1.3, 1.4 in parallel or sequence)
|
||||
→ Phase 7 (7.1 → 7.2 → 7.3 → 7.4 → 7.5 → 7.6)
|
||||
→ Phase 8 (8.1 → 8.2 → 8.3 → 8.4 → 8.5 → 8.6)
|
||||
→ Phase 9 (9.1 → 9.2 → 9.3 → 9.4 → 9.5 → 9.6)
|
||||
→ Phase 10 (interleaved with Phase 3 and later phases)
|
||||
→ Phase 11 (interleaved with Phase 3–4)
|
||||
→ Phase 12 (post Phase 3)
|
||||
→ Phase 13 (post Phase 3 and 11)
|
||||
```
|
||||
|
||||
Each step must have passing tests before the next step begins.
|
||||
All phases complete as of v1.0.0 (2026-03-15).
|
||||
|
||||
Reference in New Issue
Block a user