Update docs for recent changes
- ARCHITECTURE.md: add gRPC listener, mciasgrpcctl, new roles, granular role endpoints, profile page, audit events, policy actions, trusted_proxy config, validate package, schema force command - PROGRESS.md: document role expansion and UI privilege escalation fix - PROJECT_PLAN.md: align mciasctl subcommands with implementation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
110
ARCHITECTURE.md
110
ARCHITECTURE.md
@@ -15,36 +15,46 @@ parties that delegate authentication decisions to it.
|
|||||||
### Components
|
### Components
|
||||||
|
|
||||||
```
|
```
|
||||||
┌────────────────────────────────────────────────────┐
|
┌──────────────────────────────────────────────────────────┐
|
||||||
│ MCIAS Server (mciassrv) │
|
│ MCIAS Server (mciassrv) │
|
||||||
│ ┌──────────┐ ┌──────────┐ ┌───────────────────┐ │
|
│ ┌──────────┐ ┌──────────┐ ┌───────────────────┐ │
|
||||||
│ │ Auth │ │ Token │ │ Account / Role │ │
|
│ │ Auth │ │ Token │ │ Account / Role │ │
|
||||||
│ │ Handler │ │ Manager │ │ Manager │ │
|
│ │ Handler │ │ Manager │ │ Manager │ │
|
||||||
│ └────┬─────┘ └────┬─────┘ └─────────┬─────────┘ │
|
│ └────┬─────┘ └────┬─────┘ └─────────┬─────────┘ │
|
||||||
│ └─────────────┴─────────────────┘ │
|
│ └─────────────┴─────────────────┘ │
|
||||||
│ │ │
|
│ │ │
|
||||||
│ ┌─────────▼──────────┐ │
|
│ ┌─────────▼──────────┐ │
|
||||||
│ │ SQLite Database │ │
|
│ │ SQLite Database │ │
|
||||||
│ └────────────────────┘ │
|
│ └────────────────────┘ │
|
||||||
└────────────────────────────────────────────────────┘
|
│ │
|
||||||
▲ ▲ ▲
|
│ ┌──────────────────┐ ┌──────────────────────┐ │
|
||||||
│ HTTPS/REST │ HTTPS/REST │ direct file I/O
|
│ │ REST listener │ │ gRPC listener │ │
|
||||||
│ │ │
|
│ │ (net/http) │ │ (google.golang.org/ │ │
|
||||||
┌──────┴──────┐ ┌────┴─────┐ ┌──────┴──────┐
|
│ │ :8443 │ │ grpc) :9443 │ │
|
||||||
│ Personal │ │ mciasctl │ │ mciasdb │
|
│ └──────────────────┘ └──────────────────────┘ │
|
||||||
│ Apps │ │ (admin │ │ (DB tool) │
|
└──────────────────────────────────────────────────────────┘
|
||||||
└─────────────┘ │ CLI) │ └─────────────┘
|
▲ ▲ ▲ ▲
|
||||||
└──────────┘
|
│ HTTPS/REST │ HTTPS/REST │ gRPC/TLS │ direct file I/O
|
||||||
|
│ │ │ │
|
||||||
|
┌────┴──────┐ ┌────┴─────┐ ┌─────┴────────┐ ┌───┴────────┐
|
||||||
|
│ Personal │ │ mciasctl │ │ mciasgrpcctl │ │ mciasdb │
|
||||||
|
│ Apps │ │ (admin │ │ (gRPC admin │ │ (DB tool) │
|
||||||
|
└───────────┘ │ CLI) │ │ CLI) │ └────────────┘
|
||||||
|
└──────────┘ └──────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
**mciassrv** — The authentication server. Exposes a REST API over HTTPS/TLS.
|
**mciassrv** — The authentication server. Exposes a REST API and gRPC API over
|
||||||
Handles login, token issuance, token validation, token renewal, and token
|
HTTPS/TLS (dual-stack; see §17). Handles login, token issuance, token
|
||||||
revocation.
|
validation, token renewal, and token revocation.
|
||||||
|
|
||||||
**mciasctl** — The administrator CLI. Communicates with mciassrv's REST API
|
**mciasctl** — The administrator CLI. Communicates with mciassrv's REST API
|
||||||
using an admin JWT. Creates/manages human accounts, system accounts, roles,
|
using an admin JWT. Creates/manages human accounts, system accounts, roles,
|
||||||
and Postgres credential records.
|
and Postgres credential records.
|
||||||
|
|
||||||
|
**mciasgrpcctl** — The gRPC administrator CLI. Mirrors mciasctl's subcommands
|
||||||
|
but communicates over gRPC/TLS instead of REST. Both CLIs can coexist; neither
|
||||||
|
depends on the other.
|
||||||
|
|
||||||
**mciasdb** — The database maintenance tool. Operates directly on the SQLite
|
**mciasdb** — The database maintenance tool. Operates directly on the SQLite
|
||||||
file, bypassing the server API. Intended for break-glass recovery, offline
|
file, bypassing the server API. Intended for break-glass recovery, offline
|
||||||
inspection, schema verification, and maintenance tasks that cannot be
|
inspection, schema verification, and maintenance tasks that cannot be
|
||||||
@@ -127,13 +137,21 @@ mciassrv (passphrase or keyfile) to decrypt secrets at rest.
|
|||||||
|
|
||||||
### Roles
|
### Roles
|
||||||
|
|
||||||
Roles are simple string labels stored in the `account_roles` table.
|
Roles are simple string labels stored in the `account_roles` table. Only
|
||||||
|
compile-time allowlisted role names are accepted; attempting to grant an
|
||||||
|
unknown role returns an error (prevents typos like "admim" from silently
|
||||||
|
creating a useless role).
|
||||||
|
|
||||||
Reserved roles:
|
Compile-time allowlisted roles:
|
||||||
- `admin` — superuser; can manage all accounts, tokens, and credentials
|
- `admin` — superuser; can manage all accounts, tokens, and credentials
|
||||||
|
- `user` — standard user role
|
||||||
|
- `guest` — limited read-only access
|
||||||
|
- `viewer` — read-only access
|
||||||
|
- `editor` — create/modify access
|
||||||
|
- `commenter` — comment/annotate access
|
||||||
- Any role named identically to a system account — grants that human account
|
- Any role named identically to a system account — grants that human account
|
||||||
the ability to issue/revoke tokens and retrieve Postgres credentials for that
|
the ability to issue/revoke tokens and retrieve Postgres credentials for that
|
||||||
system account
|
system account (via policy rules, not the allowlist)
|
||||||
|
|
||||||
Role assignment requires admin privileges.
|
Role assignment requires admin privileges.
|
||||||
|
|
||||||
@@ -340,7 +358,6 @@ All endpoints use JSON request/response bodies. All responses include a
|
|||||||
| POST | `/v1/auth/login` | none | Username/password (+TOTP) login → JWT |
|
| POST | `/v1/auth/login` | none | Username/password (+TOTP) login → JWT |
|
||||||
| POST | `/v1/auth/logout` | bearer JWT | Revoke current token |
|
| POST | `/v1/auth/logout` | bearer JWT | Revoke current token |
|
||||||
| POST | `/v1/auth/renew` | bearer JWT | Exchange token for new token |
|
| POST | `/v1/auth/renew` | bearer JWT | Exchange token for new token |
|
||||||
| PUT | `/v1/auth/password` | bearer JWT | Self-service password change (requires current password) |
|
|
||||||
|
|
||||||
### Token Endpoints
|
### Token Endpoints
|
||||||
|
|
||||||
@@ -372,7 +389,9 @@ All endpoints use JSON request/response bodies. All responses include a
|
|||||||
| Method | Path | Auth required | Description |
|
| Method | Path | Auth required | Description |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| GET | `/v1/accounts/{id}/roles` | admin JWT | List roles for account |
|
| GET | `/v1/accounts/{id}/roles` | admin JWT | List roles for account |
|
||||||
| PUT | `/v1/accounts/{id}/roles` | admin JWT | Replace role set |
|
| PUT | `/v1/accounts/{id}/roles` | admin JWT | Replace role set (atomic) |
|
||||||
|
| POST | `/v1/accounts/{id}/roles` | admin JWT | Grant a single role |
|
||||||
|
| DELETE | `/v1/accounts/{id}/roles/{role}` | admin JWT | Revoke a single role |
|
||||||
|
|
||||||
### TOTP Endpoints
|
### TOTP Endpoints
|
||||||
|
|
||||||
@@ -446,6 +465,7 @@ cookie pattern (`mcias_csrf`).
|
|||||||
| `/pgcreds` | Postgres credentials list (owned + granted) with create form |
|
| `/pgcreds` | Postgres credentials list (owned + granted) with create form |
|
||||||
| `/policies` | Policy rules management — create, enable/disable, delete |
|
| `/policies` | Policy rules management — create, enable/disable, delete |
|
||||||
| `/audit` | Audit log viewer |
|
| `/audit` | Audit log viewer |
|
||||||
|
| `/profile` | User profile — self-service password change (any authenticated user) |
|
||||||
|
|
||||||
**HTMX fragments:** Mutating operations (role updates, tag edits, credential
|
**HTMX fragments:** Mutating operations (role updates, tag edits, credential
|
||||||
saves, policy toggles, access grants) use HTMX partial-page updates for a
|
saves, policy toggles, access grants) use HTMX partial-page updates for a
|
||||||
@@ -490,6 +510,9 @@ CREATE TABLE accounts (
|
|||||||
-- AES-256-GCM encrypted TOTP secret; NULL if not enrolled
|
-- AES-256-GCM encrypted TOTP secret; NULL if not enrolled
|
||||||
totp_secret_enc BLOB,
|
totp_secret_enc BLOB,
|
||||||
totp_secret_nonce BLOB,
|
totp_secret_nonce BLOB,
|
||||||
|
-- Last accepted TOTP counter value; prevents replay attacks within the
|
||||||
|
-- ±1 time-step window (RFC 6238 §5.2). NULL = no code accepted yet.
|
||||||
|
last_totp_counter INTEGER DEFAULT NULL,
|
||||||
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')),
|
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')),
|
||||||
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')),
|
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')),
|
||||||
deleted_at TEXT
|
deleted_at TEXT
|
||||||
@@ -661,10 +684,13 @@ or a keyfile path — never inline in the config file.
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
[server]
|
[server]
|
||||||
listen_addr = "0.0.0.0:8443"
|
listen_addr = "0.0.0.0:8443"
|
||||||
grpc_addr = "0.0.0.0:9443" # optional; omit to disable gRPC
|
grpc_addr = "0.0.0.0:9443" # optional; omit to disable gRPC
|
||||||
tls_cert = "/etc/mcias/server.crt"
|
tls_cert = "/etc/mcias/server.crt"
|
||||||
tls_key = "/etc/mcias/server.key"
|
tls_key = "/etc/mcias/server.key"
|
||||||
|
# trusted_proxy = "127.0.0.1" # optional; IP of reverse proxy — when set,
|
||||||
|
# X-Forwarded-For is trusted only from this IP
|
||||||
|
# for rate limiting and audit log IP extraction
|
||||||
|
|
||||||
[database]
|
[database]
|
||||||
path = "/var/lib/mcias/mcias.db"
|
path = "/var/lib/mcias/mcias.db"
|
||||||
@@ -711,7 +737,8 @@ mcias/
|
|||||||
│ ├── policy/ # in-process authorization policy engine (§20)
|
│ ├── policy/ # in-process authorization policy engine (§20)
|
||||||
│ ├── server/ # HTTP handlers, router setup
|
│ ├── server/ # HTTP handlers, router setup
|
||||||
│ ├── token/ # JWT issuance, validation, revocation
|
│ ├── token/ # JWT issuance, validation, revocation
|
||||||
│ └── ui/ # web UI context, CSRF, session, template handlers
|
│ ├── ui/ # web UI context, CSRF, session, template handlers
|
||||||
|
│ └── validate/ # input validation helpers (username, password strength)
|
||||||
├── web/
|
├── web/
|
||||||
│ ├── static/ # CSS and static assets
|
│ ├── static/ # CSS and static assets
|
||||||
│ └── templates/ # HTML templates (base layout, pages, HTMX fragments)
|
│ └── templates/ # HTML templates (base layout, pages, HTMX fragments)
|
||||||
@@ -761,6 +788,9 @@ The `cmd/` packages are thin wrappers that wire dependencies and call into
|
|||||||
| `totp_removed` | TOTP removed from account |
|
| `totp_removed` | TOTP removed from account |
|
||||||
| `pgcred_accessed` | Postgres credentials retrieved |
|
| `pgcred_accessed` | Postgres credentials retrieved |
|
||||||
| `pgcred_updated` | Postgres credentials stored/updated |
|
| `pgcred_updated` | Postgres credentials stored/updated |
|
||||||
|
| `pgcred_access_granted` | Read access to PG credentials granted to another account |
|
||||||
|
| `pgcred_access_revoked` | Read access to PG credentials revoked from an account |
|
||||||
|
| `password_changed` | Account password changed (self-service or admin reset) |
|
||||||
| `tag_added` | Tag added to account |
|
| `tag_added` | Tag added to account |
|
||||||
| `tag_removed` | Tag removed from account |
|
| `tag_removed` | Tag removed from account |
|
||||||
| `policy_rule_created` | Policy rule created |
|
| `policy_rule_created` | Policy rule created |
|
||||||
@@ -838,6 +868,7 @@ mciasdb --config PATH <subcommand> [flags]
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `mciasdb schema verify` | Open DB, run migrations in dry-run mode, report version |
|
| `mciasdb schema verify` | Open DB, run migrations in dry-run mode, report version |
|
||||||
| `mciasdb schema migrate` | Apply any pending migrations and exit |
|
| `mciasdb schema migrate` | Apply any pending migrations and exit |
|
||||||
|
| `mciasdb schema force --version N` | Force schema version (clears dirty state); break-glass recovery |
|
||||||
| `mciasdb prune tokens` | Delete expired rows from `token_revocation` and `system_tokens` |
|
| `mciasdb prune tokens` | Delete expired rows from `token_revocation` and `system_tokens` |
|
||||||
|
|
||||||
**Account management (offline):**
|
**Account management (offline):**
|
||||||
@@ -943,7 +974,7 @@ in `proto/generate.go` using `protoc-gen-go` and `protoc-gen-go-grpc`.
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `AuthService` | `Login`, `Logout`, `RenewToken`, `EnrollTOTP`, `ConfirmTOTP`, `RemoveTOTP` |
|
| `AuthService` | `Login`, `Logout`, `RenewToken`, `EnrollTOTP`, `ConfirmTOTP`, `RemoveTOTP` |
|
||||||
| `TokenService` | `ValidateToken`, `IssueServiceToken`, `RevokeToken` |
|
| `TokenService` | `ValidateToken`, `IssueServiceToken`, `RevokeToken` |
|
||||||
| `AccountService` | `ListAccounts`, `CreateAccount`, `GetAccount`, `UpdateAccount`, `DeleteAccount`, `GetRoles`, `SetRoles` |
|
| `AccountService` | `ListAccounts`, `CreateAccount`, `GetAccount`, `UpdateAccount`, `DeleteAccount`, `GetRoles`, `SetRoles`, `GrantRole`, `RevokeRole` |
|
||||||
| `CredentialService` | `GetPGCreds`, `SetPGCreds` |
|
| `CredentialService` | `GetPGCreds`, `SetPGCreds` |
|
||||||
| `AdminService` | `Health`, `GetPublicKey` |
|
| `AdminService` | `Health`, `GetPublicKey` |
|
||||||
|
|
||||||
@@ -1374,9 +1405,10 @@ const (
|
|||||||
ActionReadAudit Action = "audit:read"
|
ActionReadAudit Action = "audit:read"
|
||||||
ActionEnrollTOTP Action = "totp:enroll" // self-service
|
ActionEnrollTOTP Action = "totp:enroll" // self-service
|
||||||
ActionRemoveTOTP Action = "totp:remove" // admin
|
ActionRemoveTOTP Action = "totp:remove" // admin
|
||||||
ActionLogin Action = "auth:login" // public
|
ActionLogin Action = "auth:login" // public
|
||||||
ActionLogout Action = "auth:logout" // self-service
|
ActionLogout Action = "auth:logout" // self-service
|
||||||
ActionListRules Action = "policy:list"
|
ActionChangePassword Action = "auth:change_password" // self-service
|
||||||
|
ActionListRules Action = "policy:list"
|
||||||
ActionManageRules Action = "policy:manage"
|
ActionManageRules Action = "policy:manage"
|
||||||
|
|
||||||
// Resource types
|
// Resource types
|
||||||
@@ -1476,8 +1508,10 @@ at the same priority level.
|
|||||||
|
|
||||||
```
|
```
|
||||||
Priority 0, Allow: roles=[admin], actions=<all> — admin wildcard
|
Priority 0, Allow: roles=[admin], actions=<all> — admin wildcard
|
||||||
Priority 0, Allow: actions=[tokens:renew, auth:logout] — self-service logout/renew
|
Priority 0, Allow: actions=[auth:logout, tokens:renew] — self-service logout/renew
|
||||||
Priority 0, Allow: actions=[totp:enroll] — self-service TOTP enrollment
|
Priority 0, Allow: actions=[totp:enroll] — self-service TOTP enrollment
|
||||||
|
Priority 0, Allow: accountTypes=[human], actions=[auth:change_password]
|
||||||
|
— self-service password change
|
||||||
Priority 0, Allow: accountTypes=[system], actions=[pgcreds:read],
|
Priority 0, Allow: accountTypes=[system], actions=[pgcreds:read],
|
||||||
resourceType=pgcreds, ownerMatchesSubject=true
|
resourceType=pgcreds, ownerMatchesSubject=true
|
||||||
— system account reads own creds
|
— system account reads own creds
|
||||||
|
|||||||
33
PROGRESS.md
33
PROGRESS.md
@@ -4,6 +4,39 @@ Source of truth for current development state.
|
|||||||
---
|
---
|
||||||
All phases complete. **v1.0.0 tagged.** All packages pass `go test ./...`; `golangci-lint run ./...` clean.
|
All phases complete. **v1.0.0 tagged.** All packages pass `go test ./...`; `golangci-lint run ./...` clean.
|
||||||
|
|
||||||
|
### 2026-03-12 — Update web UI and model for all compile-time roles
|
||||||
|
|
||||||
|
- `internal/model/model.go`: added `RoleGuest`, `RoleViewer`, `RoleEditor`, and
|
||||||
|
`RoleCommenter` constants; updated `allowedRoles` map and `ValidateRole` error
|
||||||
|
message to include the full set of recognised roles.
|
||||||
|
- `internal/ui/`: updated `knownRoles` to include guest, viewer, editor, and
|
||||||
|
commenter; replaced hardcoded role strings with model constants; removed
|
||||||
|
obsolete "service" role from UI dropdowns.
|
||||||
|
- All tests pass; build verified.
|
||||||
|
|
||||||
|
### 2026-03-12 — Fix UI privilege escalation vulnerability
|
||||||
|
|
||||||
|
**internal/ui/ui.go**
|
||||||
|
- Added `requireAdminRole` middleware that checks `claims.HasRole("admin")`
|
||||||
|
and returns 403 if absent
|
||||||
|
- Updated `admin` and `adminGet` middleware wrappers to include
|
||||||
|
`requireAdminRole` in the chain — previously only `requireCookieAuth`
|
||||||
|
was applied, allowing any authenticated user to access admin endpoints
|
||||||
|
- Profile routes correctly use only `requireCookieAuth` (not admin-gated)
|
||||||
|
|
||||||
|
**internal/ui/handlers_accounts.go**
|
||||||
|
- Removed redundant inline admin check from `handleAdminResetPassword`
|
||||||
|
(now handled by route-level middleware)
|
||||||
|
|
||||||
|
**Full audit performed across all three API surfaces:**
|
||||||
|
- REST (`internal/server/server.go`): all admin routes use
|
||||||
|
`requireAuth → RequireRole("admin")` — correct
|
||||||
|
- gRPC (all service files): every admin RPC calls `requireAdmin(ctx)` as
|
||||||
|
first statement — correct
|
||||||
|
- UI: was vulnerable, now fixed with `requireAdminRole` middleware
|
||||||
|
|
||||||
|
All tests pass; `go vet ./...` clean.
|
||||||
|
|
||||||
### 2026-03-12 — Checkpoint: password change UI enforcement + migration recovery
|
### 2026-03-12 — Checkpoint: password change UI enforcement + migration recovery
|
||||||
|
|
||||||
**internal/ui/handlers_accounts.go**
|
**internal/ui/handlers_accounts.go**
|
||||||
|
|||||||
@@ -165,18 +165,27 @@ See ARCHITECTURE.md for design rationale.
|
|||||||
### Step 4.1: `cmd/mciasctl` — admin CLI
|
### Step 4.1: `cmd/mciasctl` — admin CLI
|
||||||
**Acceptance criteria:**
|
**Acceptance criteria:**
|
||||||
- Subcommands:
|
- Subcommands:
|
||||||
- `mciasctl account create --username NAME --type human|system`
|
- `mciasctl account create -username NAME -type human|system`
|
||||||
- `mciasctl account list`
|
- `mciasctl account list`
|
||||||
- `mciasctl account suspend --id UUID`
|
- `mciasctl account update -id UUID -status active|inactive`
|
||||||
- `mciasctl account delete --id UUID`
|
- `mciasctl account delete -id UUID`
|
||||||
- `mciasctl role grant --account UUID --role ROLE`
|
- `mciasctl account get -id UUID`
|
||||||
- `mciasctl role revoke --account UUID --role ROLE`
|
- `mciasctl account set-password -id UUID`
|
||||||
- `mciasctl token issue --account UUID` (system accounts)
|
- `mciasctl role list -id UUID`
|
||||||
- `mciasctl token revoke --jti JTI`
|
- `mciasctl role set -id UUID -roles role1,role2`
|
||||||
- `mciasctl pgcreds set --account UUID --host H --port P --db D --user U --password P`
|
- `mciasctl role grant -id UUID -role ROLE`
|
||||||
- `mciasctl pgcreds get --account UUID`
|
- `mciasctl role revoke -id UUID -role ROLE`
|
||||||
- CLI reads admin JWT from `MCIAS_ADMIN_TOKEN` env var or `--token` flag
|
- `mciasctl token issue -id UUID` (system accounts)
|
||||||
- All commands make HTTPS requests to mciassrv (base URL from `--server` flag
|
- `mciasctl token revoke -jti JTI`
|
||||||
|
- `mciasctl pgcreds set -id UUID -host H -port P -db D -user U`
|
||||||
|
- `mciasctl pgcreds get -id UUID`
|
||||||
|
- `mciasctl auth login`
|
||||||
|
- `mciasctl auth change-password`
|
||||||
|
- `mciasctl tag list -id UUID`
|
||||||
|
- `mciasctl tag set -id UUID -tags tag1,tag2`
|
||||||
|
- `mciasctl policy list|create|get|update|delete`
|
||||||
|
- CLI reads admin JWT from `MCIAS_TOKEN` env var or `-token` flag
|
||||||
|
- All commands make HTTPS requests to mciassrv (base URL from `-server` flag
|
||||||
or `MCIAS_SERVER` env var)
|
or `MCIAS_SERVER` env var)
|
||||||
- Tests: flag parsing; missing required flags → error; help text complete
|
- Tests: flag parsing; missing required flags → error; help text complete
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user