Commit Graph

10 Commits

Author SHA1 Message Date
7e5fc9f111 Fix flaky gRPC renewal test timing
Increase token lifetime from 2s to 4s in TestRenewToken to prevent
the token from expiring before the gRPC call completes through bufconn.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 01:08:44 -07:00
fe780bf873 Merge SEC-03: require token proximity for renewal
# Conflicts:
#	internal/server/server_test.go
2026-03-13 01:07:34 -07:00
6191c5e00a Merge SEC-02: normalize lockout response
# Conflicts:
#	internal/grpcserver/grpcserver_test.go
#	internal/server/server_test.go
2026-03-13 01:05:56 -07:00
eef7d1bc1a Fix SEC-03: require token proximity for renewal
- Add 50% lifetime elapsed check to REST handleRenew and gRPC RenewToken
- Reject renewal attempts before 50% of token lifetime has elapsed
- Update existing renewal tests to use short-lived tokens with sleep
- Add TestRenewTokenTooEarly tests for both REST and gRPC

Security: Tokens can only be renewed after 50% of their lifetime has
elapsed, preventing indefinite renewal of stolen tokens.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 00:45:35 -07:00
4d3d438253 Fix SEC-02: normalize lockout response
- REST login: change locked account response from HTTP 429
  "account_locked" to HTTP 401 "invalid credentials"
- gRPC login: change from ResourceExhausted to Unauthenticated
  with "invalid credentials" message
- UI login: change from "account temporarily locked" to
  "invalid credentials"
- REST password-change endpoint: same normalization
- Audit logs still record "account_locked" internally
- Added tests in all three layers verifying locked-account
  responses are indistinguishable from wrong-password responses

Security: lockout responses now return identical status codes and
messages as wrong-password failures across REST, gRPC, and UI,
preventing user-enumeration via lockout differentiation. Internal
audit logging of lockout events is preserved for operational use.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 00:43:57 -07:00
d3b63b1f87 Fix SEC-06: proxy-aware gRPC rate limiting
- Add grpcClientIP() helper that mirrors middleware.ClientIP
  for proxy-aware IP extraction from gRPC metadata
- Update rateLimitInterceptor to use grpcClientIP with the
  TrustedProxy config setting
- Only trust x-forwarded-for/x-real-ip metadata when the
  peer address matches the configured trusted proxy
- Add 7 unit tests covering: no proxy, xff, x-real-ip
  preference, untrusted peer ignoring headers, no headers
  fallback, invalid header fallback, and no peer

Security: gRPC rate limiter now extracts real client IPs
behind a reverse proxy using the same trust model as the
REST middleware (DEF-03). Headers from untrusted peers are
ignored, preventing IP-spoofing for rate-limit bypass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 00:43:09 -07:00
98ed858c67 trusted proxy, TOTP replay protection, new tests
- Trusted proxy config option for proxy-aware IP extraction
  used by rate limiting and audit logs; validates proxy IP
  before trusting X-Forwarded-For / X-Real-IP headers
- TOTP replay protection via counter-based validation to
  reject reused codes within the same time step (±30s)
- RateLimit middleware updated to extract client IP from
  proxy headers without IP spoofing risk
- New tests for ClientIP proxy logic (spoofed headers,
  fallback) and extended rate-limit proxy coverage
- HTMX error banner script integrated into web UI base
- .gitignore updated for mciasdb build artifact

Security: resolves CRIT-01 (TOTP replay attack) and
DEF-03 (proxy-unaware rate limiting); gRPC TOTP
enrollment aligned with REST via StorePendingTOTP

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 17:44:01 -07:00
b495a90a9d Fix F-08, F-13: Adjust lockout expiration logic and enforce password length in tests
- Corrected lockout logic (`IsLockedOut`) to properly evaluate failed login thresholds within the rolling window, ensuring stale attempts outside the window do not trigger lockout.
- Updated test passwords in `grpcserver_test.go` to comply with 12-character minimum requirement.
- Reformatted import blocks with `goimports` to address lint warnings.
- Verified all tests pass and linter is clean.
2026-03-11 21:36:04 -07:00
f34e9a69a0 Fix all golangci-lint warnings
- errorlint: use errors.Is for db.ErrNotFound comparisons
  in accountservice.go, credentialservice.go, tokenservice.go
- gofmt/goimports: move mciasv1 alias into internal import group
  in auth.go, credentialservice.go, grpcserver.go, grpcserver_test.go
- gosec G115: add nolint annotation on int32 port conversions
  in mciasgrpcctl/main.go and credentialservice.go (port validated
  as [1,65535] on input; overflow not reachable)
- govet fieldalignment: reorder Server, grpcRateLimiter,
  grpcRateLimitEntry, testEnv structs to reduce GC bitmap size
  (96 -> 80 pointer bytes each)
- ineffassign: remove intermediate grpcSrv = GRPCServer() call
  in cmd/mciassrv/main.go (immediately overwritten by TLS build)
- staticcheck SA9003: replace empty if-body with _ = Serve(lis)
  in grpcserver_test.go
0 golangci-lint issues; 137 tests pass (go test -race ./...)
2026-03-11 15:24:07 -07:00
59d51a1d38 Implement Phase 7: gRPC dual-stack interface
- proto/mcias/v1/: AdminService, AuthService, TokenService,
  AccountService, CredentialService; generated Go stubs in gen/
- internal/grpcserver: full handler implementations sharing all
  business logic (auth, token, db, crypto) with REST server;
  interceptor chain: logging -> auth (JWT alg-first + revocation) ->
  rate-limit (token bucket, 10 req/s, burst 10, per-IP)
- internal/config: optional grpc_addr field in [server] section
- cmd/mciassrv: dual-stack startup; gRPC/TLS listener on grpc_addr
  when configured; graceful shutdown of both servers in 15s window
- cmd/mciasgrpcctl: companion gRPC CLI mirroring mciasctl commands
  (health, pubkey, account, role, token, pgcreds) using TLS with
  optional custom CA cert
- internal/grpcserver/grpcserver_test.go: 20 tests via bufconn covering
  public RPCs, auth interceptor (no token, invalid, revoked -> 401),
  non-admin -> 403, Login/Logout/RenewToken/ValidateToken flows,
  AccountService CRUD, SetPGCreds/GetPGCreds AES-GCM round-trip,
  credential fields absent from all responses
Security:
  JWT validation path identical to REST: alg header checked before
  signature, alg:none rejected, revocation table checked after sig.
  Authorization metadata value never logged by any interceptor.
  Credential fields (PasswordHash, TOTPSecret*, PGPassword) absent from
  all proto response messages — enforced by proto design and confirmed
  by test TestCredentialFieldsAbsentFromAccountResponse.
  Login dummy-Argon2 timing guard preserves timing uniformity for
  unknown users (same as REST handleLogin).
  TLS required at listener level; cmd/mciassrv uses
  credentials.NewServerTLSFromFile; no h2c offered.
137 tests pass, zero race conditions (go test -race ./...)
2026-03-11 14:38:47 -07:00