Files
mcias/PROGRESS.md
Kyle Isom 14083b82b4 Fix linting: golangci-lint v2 config, nolint annotations
* Rewrite .golangci.yaml to v2 schema: linters-settings ->
  linters.settings, issues.exclude-rules -> issues.exclusions.rules,
  issues.exclude-dirs -> issues.exclusions.paths
* Drop deprecated revive exported/package-comments rules: personal
  project, not a public library; godoc completeness is not a CI req
* Add //nolint:gosec G101 on PassphraseEnv default in config.go:
  environment variable name is not a credential value
* Add //nolint:gosec G101 on EventPGCredUpdated in model.go:
  audit event type string, not a credential

Security: no logic changes. gosec G101 suppressions are false
positives confirmed by code inspection: neither constant holds a
credential value.
2026-03-11 12:53:25 -07:00

6.9 KiB

MCIAS Progress

Source of truth for current development state.


Current Status: Phase 6 Complete — Full Implementation

All phases complete. 117 tests pass with zero race conditions.

Completed Phases

  • Phase 0: Repository bootstrap (go.mod, .gitignore, docs)
  • Phase 1: Foundational packages (model, config, crypto, db)
  • Phase 2: Auth core (auth, token, middleware)
  • Phase 3: HTTP server (server, mciassrv binary)
  • Phase 4: Admin CLI (mciasctl binary)
  • Phase 5: E2E tests, security hardening, commit
  • Phase 6: mciasdb — direct SQLite maintenance tool

Implementation Log

2026-03-11 — Phase 6: mciasdb

cmd/mciasdb

  • Binary skeleton: config loading, master key derivation (identical to mciassrv for key compatibility), DB open + migrate on startup
  • schema verify / schema migrate — reports and applies pending migrations
  • account list/get/create/set-password/set-status/reset-totp — offline account management; set-password prompts interactively (no --password flag)
  • role list/grant/revoke — direct role management
  • token list/revoke/revoke-all + prune tokens — token maintenance
  • audit tail/query — audit log inspection with --json output flag
  • pgcreds get/set — decrypt/encrypt Postgres credentials with master key; set prompts interactively; get prints warning before sensitive output
  • All write operations emit audit log entries tagged actor:"mciasdb"

internal/db additions

  • ListTokensForAccount(accountID) — newest-first token list for an account
  • ListAuditEvents(AuditQueryParams) — filtered audit query (account, type, since, limit)
  • TailAuditEvents(n) — last n events, returned oldest-first
  • SchemaVersion(db) / LatestSchemaVersion — exported for mciasdb verify

Dependencies

  • Added golang.org/x/term v0.29.0 for interactive password prompting (no-echo terminal reads); pinned to version compatible with local module cache
  • golang.org/x/crypto pinned at v0.33.0 (compatible with term@v0.29.0)

Tests

  • internal/db/mciasdb_test.go: 4 tests covering ListTokensForAccount, ListAuditEvents filtering, TailAuditEvents ordering, combined filters
  • cmd/mciasdb/mciasdb_test.go: 20 tests covering all subcommands via in-memory SQLite and stdout capture

Total: 117 tests, all pass, zero race conditions (go test -race ./...)

2026-03-11 — Initial Full Implementation

Phase 0: Bootstrap

  • 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)

Architecture Decisions

  • 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