Complete implementation: e2e tests, gofmt, hardening
- Add test/e2e: 11 end-to-end tests covering full login/logout, token renewal, admin account management, credential-never-in-response, unauthorised access, JWT alg confusion and alg:none attacks, revoked token rejection, system account token issuance, wrong-password vs unknown-user indistinguishability - Apply gofmt to all source files (formatting only, no logic changes) - Update .golangci.yaml for golangci-lint v2 (version field required, gosimple merged into staticcheck, formatters section separated) - Update PROGRESS.md to reflect Phase 5 completion Security: All 97 tests pass with go test -race ./... (zero race conditions). Adversarial JWT tests (alg confusion, alg:none) confirm the ValidateToken alg-first check is effective against both attack classes. Credential fields (PasswordHash, TOTPSecret*, PGPassword) confirmed absent from all API responses via both unit and e2e tests. go vet ./... clean. golangci-lint v2.6.2 incompatible with go1.26 runtime; go vet used as linter until toolchain is updated.
This commit is contained in:
174
PROGRESS.md
174
PROGRESS.md
@@ -4,61 +4,147 @@ Source of truth for current development state.
|
||||
|
||||
---
|
||||
|
||||
## Current Status: Phase 0 — Repository Bootstrap
|
||||
## Current Status: Phase 5 Complete — Full Implementation
|
||||
|
||||
### Completed
|
||||
All phases are complete. The system is ready for deployment.
|
||||
|
||||
- [x] CLAUDE.md — project conventions and constraints
|
||||
- [x] .golangci.yaml — linter configuration
|
||||
- [x] PROJECT.md — project specifications
|
||||
- [x] ARCHITECTURE.md — technical design document (token lifecycle, session
|
||||
management, multi-app trust boundaries, database schema)
|
||||
- [x] PROJECT_PLAN.md — discrete implementation steps with acceptance criteria
|
||||
- [x] PROGRESS.md — this file
|
||||
### Completed Phases
|
||||
|
||||
### In Progress
|
||||
|
||||
- [ ] Step 0.1: Go module and dependency setup (`go.mod`, `go get`)
|
||||
- [ ] Step 0.2: `.gitignore`
|
||||
|
||||
### Up Next
|
||||
|
||||
- Phase 1: Foundational packages (`internal/model`, `internal/config`,
|
||||
`internal/crypto`, `internal/db`)
|
||||
- [x] Phase 0: Repository bootstrap (go.mod, .gitignore, docs)
|
||||
- [x] Phase 1: Foundational packages (model, config, crypto, db)
|
||||
- [x] Phase 2: Auth core (auth, token, middleware)
|
||||
- [x] Phase 3: HTTP server (server, mciassrv binary)
|
||||
- [x] Phase 4: Admin CLI (mciasctl binary)
|
||||
- [x] Phase 5: E2E tests, security hardening, commit
|
||||
|
||||
---
|
||||
|
||||
## Implementation Log
|
||||
|
||||
### 2026-03-11
|
||||
### 2026-03-11 — Initial Full Implementation
|
||||
|
||||
- Wrote ARCHITECTURE.md covering:
|
||||
- Security model and threat model
|
||||
- Cryptographic primitive choices with rationale
|
||||
- Account model (human + system accounts, roles, lifecycle)
|
||||
- Token lifecycle (issuance, validation, renewal, revocation flows)
|
||||
- Session management approach (stateless JWT + revocation table)
|
||||
- Multi-app trust boundaries
|
||||
- REST API design (all endpoints)
|
||||
- Database schema (SQLite, all tables with indexes)
|
||||
- TLS configuration
|
||||
- TOML configuration format
|
||||
- Package/directory structure
|
||||
- Error handling and logging conventions
|
||||
- Audit event catalog
|
||||
- Operational considerations
|
||||
#### Phase 0: Bootstrap
|
||||
|
||||
- Wrote PROJECT_PLAN.md with 5 phases, 12 steps, each with specific
|
||||
acceptance criteria.
|
||||
- Wrote ARCHITECTURE.md (security model, crypto choices, DB schema, API design)
|
||||
- Wrote PROJECT_PLAN.md (5 phases, 12 steps with acceptance criteria)
|
||||
- Created go.mod with dependencies (golang-jwt/jwt/v5, uuid, go-toml/v2,
|
||||
golang.org/x/crypto, modernc.org/sqlite)
|
||||
- Created .gitignore
|
||||
|
||||
#### Phase 1: Foundational Packages
|
||||
|
||||
**internal/model**
|
||||
- Account (human/system), Role, TokenRecord, SystemToken, PGCredential,
|
||||
AuditEvent structs
|
||||
- All credential fields tagged `json:"-"` — never serialised to responses
|
||||
- Audit event type constants
|
||||
|
||||
**internal/config**
|
||||
- TOML config parsing with validation
|
||||
- Enforces OWASP 2023 Argon2id minimums (time≥2, memory≥64MiB)
|
||||
- Requires exactly one of passphrase_env or keyfile for master key
|
||||
- NewTestConfig() for test use
|
||||
|
||||
**internal/crypto**
|
||||
- Ed25519 key generation, PEM marshal/parse
|
||||
- AES-256-GCM seal/open with random nonces
|
||||
- Argon2id KDF (DeriveKey) with OWASP-exceeding parameters
|
||||
- NewSalt(), RandomBytes()
|
||||
|
||||
**internal/db**
|
||||
- SQLite with WAL mode, FK enforcement, busy timeout
|
||||
- Idempotent migrations (schema_version table)
|
||||
- Migration 1: full schema (server_config, accounts, account_roles,
|
||||
token_revocation, system_tokens, pg_credentials, audit_log)
|
||||
- Migration 2: master_key_salt column in server_config
|
||||
- Full CRUD: accounts, roles, tokens, PG credentials, audit log
|
||||
|
||||
#### Phase 2: Auth Core
|
||||
|
||||
**internal/auth**
|
||||
- Argon2id password hashing in PHC format
|
||||
- Constant-time password verification (crypto/subtle)
|
||||
- TOTP generation and validation (RFC 6238 ±1 window, constant-time)
|
||||
- HOTP per RFC 4226
|
||||
|
||||
**internal/token**
|
||||
- Ed25519/EdDSA JWT issuance with UUID JTI
|
||||
- alg header validated BEFORE signature verification (alg confusion defence)
|
||||
- alg:none explicitly rejected
|
||||
- ErrWrongAlgorithm, ErrExpiredToken, ErrInvalidSignature, ErrMissingClaim
|
||||
|
||||
**internal/middleware**
|
||||
- RequestLogger — never logs Authorization header
|
||||
- RequireAuth — validates JWT, checks revocation table
|
||||
- RequireRole — checks claims for required role
|
||||
- RateLimit — per-IP token bucket
|
||||
|
||||
#### Phase 3: HTTP Server
|
||||
|
||||
**internal/server**
|
||||
- Full REST API wired to middleware
|
||||
- Handlers: health, public-key, login (dummy Argon2 on unknown user for
|
||||
timing uniformity), logout, renew, token validate/issue/revoke,
|
||||
account CRUD, roles, TOTP enrol/confirm/remove, PG credentials
|
||||
- Strict JSON decoding (DisallowUnknownFields)
|
||||
- Credential fields never appear in any response
|
||||
|
||||
**cmd/mciassrv**
|
||||
- Config loading, master key derivation (passphrase via Argon2id KDF or
|
||||
key file), signing key load/generate (AES-256-GCM encrypted in DB),
|
||||
HTTPS listener with graceful shutdown
|
||||
- TLS 1.2+ minimum, X25519+P256 curves
|
||||
- 30s read/write timeouts, 5s header timeout
|
||||
|
||||
#### Phase 4: Admin CLI
|
||||
|
||||
**cmd/mciasctl**
|
||||
- Subcommands: account (list/create/get/update/delete), role (list/set),
|
||||
token (issue/revoke), pgcreds (get/set)
|
||||
- Auth via -token flag or MCIAS_TOKEN env var
|
||||
- Custom CA cert support for self-signed TLS
|
||||
|
||||
#### Phase 5: Tests and Hardening
|
||||
|
||||
**Test coverage:**
|
||||
- internal/model: 5 tests
|
||||
- internal/config: 8 tests
|
||||
- internal/crypto: 12 tests
|
||||
- internal/db: 13 tests
|
||||
- internal/auth: 13 tests
|
||||
- internal/token: 9 tests (including alg confusion and alg:none attacks)
|
||||
- internal/middleware: 12 tests
|
||||
- internal/server: 14 tests
|
||||
- test/e2e: 11 tests
|
||||
|
||||
Total: 97 tests — all pass, zero race conditions (go test -race ./...)
|
||||
|
||||
**Security tests (adversarial):**
|
||||
- JWT alg:HS256 confusion attack → 401
|
||||
- JWT alg:none attack → 401
|
||||
- Revoked token reuse → 401
|
||||
- Non-admin calling admin endpoint → 403
|
||||
- Wrong password → 401 (same response as unknown user)
|
||||
- Credential material absent from all API responses
|
||||
|
||||
**Security hardening:**
|
||||
- go vet ./... — zero issues
|
||||
- gofmt applied to all files
|
||||
- golangci-lint v2 config updated (note: v2.6.2 built with go1.25.3
|
||||
cannot analyse go1.26 source; go vet used as primary linter for now)
|
||||
|
||||
---
|
||||
|
||||
## Notes / Decisions
|
||||
## Architecture Decisions
|
||||
|
||||
- SQLite driver: using `modernc.org/sqlite` (pure Go, no CGo dependency).
|
||||
This simplifies cross-compilation and removes the need for a C toolchain.
|
||||
- JWT library: `github.com/golang-jwt/jwt/v5`. The `alg` header validation
|
||||
is implemented manually before delegating to the library to ensure the
|
||||
library's own algorithm dispatch cannot be bypassed.
|
||||
- No ORM. All database access via the standard `database/sql` interface with
|
||||
prepared statements.
|
||||
- **SQLite driver**: `modernc.org/sqlite` (pure Go, no CGo)
|
||||
- **JWT**: `github.com/golang-jwt/jwt/v5`; alg validated manually before
|
||||
library dispatch to defeat algorithm confusion
|
||||
- **No ORM**: `database/sql` with parameterized statements only
|
||||
- **Master key salt**: stored in server_config table for stable KDF across
|
||||
restarts; generated on first run
|
||||
- **Signing key**: stored AES-256-GCM encrypted in server_config; generated
|
||||
on first run, decrypted each startup using master key
|
||||
- **Timing uniformity**: unknown user login runs dummy Argon2 to match
|
||||
timing of wrong-password path; all credential comparisons use
|
||||
`crypto/subtle.ConstantTimeCompare`
|
||||
|
||||
Reference in New Issue
Block a user