Commit Graph

139 Commits

Author SHA1 Message Date
fuomag9
277ae6e79c Add mTLS RBAC with path-based access control, role/cert trust model, and comprehensive tests
Implements full role-based access control for mTLS client certificates:
- Database: mtls_roles, mtls_certificate_roles, mtls_access_rules tables with migration
- Models: CRUD for roles, cert-role assignments, path-based access rules
- Caddy config: HTTP-layer RBAC enforcement via CEL fingerprint matching in subroutes
- New trust model: select individual certs or entire roles instead of CAs (derives CAs automatically)
- REST API: /api/v1/mtls-roles, cert assignments, proxy-host access rules endpoints
- UI: Roles management tab (card-based), cert/role trust picker, inline RBAC rule editor
- Fix: dialog autoclose bug after creating proxy host (key-based remount)
- Tests: 85 new tests (785 total) covering models, schema, RBAC route generation, leaf override, edge cases

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 18:40:21 +02:00
fuomag9
b9a88c4330 fix: remove ACME cert scanning to eliminate caddy-data permission issue (#88)
Caddy's certmagic creates storage dirs with hardcoded 0700 permissions,
making the web container's supplementary group membership ineffective.
Rather than working around this with ACLs or chmod hacks, remove the
feature entirely — it was cosmetic (issuer/expiry display) for certs
that Caddy auto-manages anyway.

Also bump access list dropdown timeout from 5s to 10s to fix flaky E2E test.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 12:34:18 +02:00
fuomag9
49b869f0ca fix: include WAF blocks in dashboard blocked counter
The Traffic (24h) card's "Blocked" percentage only counted
geo-blocks from trafficEvents. Now also queries wafEvents to
include WAF-blocked requests in the total.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 10:15:13 +02:00
fuomag9
ebc297190d fix: route geo-blocker log entries to access.log
The caddy-blocker plugin already emits "request blocked" log entries
for geo/IP blocks, but they were going to Caddy's default log (stdout)
instead of /logs/access.log because http.handlers.blocker was not in
the access log include list. The existing log parser and dashboard were
already wired up to count these — they just never received the data.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 00:05:14 +02:00
fuomag9
e26d7a2c3f feat: improve LocationRulesFields UI and add unit tests for buildLocationReverseProxy
- Replace textarea with per-upstream rows (protocol dropdown + address input),
  matching the existing UpstreamInput component pattern
- Export buildLocationReverseProxy for testing
- Add 14 unit tests covering: dial formatting, HTTPS/TLS transport,
  host header preservation, path sanitization, IPv6, mixed upstreams

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 14:51:08 +01:00
fuomag9
23e186a22e feat: emit location rule routes in Caddy config generation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 14:51:08 +01:00
fuomag9
0f9bd04ec7 feat: add LocationRule type and model layer support
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 14:51:08 +01:00
fuomag9
b480c2cf5d chore: remove finding-ID prefixes from code comments
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 12:51:39 +01:00
fuomag9
4f2f090e2c fix: resolve lint errors from security changes
- Remove unused `/* global process */` in next.config.mjs
- Attach cause to rethrown error in secret.ts legacy key expiry

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 12:41:19 +01:00
fuomag9
c537a59f36 fix: trustHost should be true when NEXTAUTH_URL is set
The H7 fix made trustHost default to false, which caused redirect loops
in environments where NEXTAUTH_URL is set (including Docker and tests).
When NEXTAUTH_URL is explicitly configured, the operator has declared
the canonical URL, making Host header validation unnecessary.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 12:27:25 +01:00
fuomag9
debd0d98fc security: fix 17 vulnerabilities from comprehensive pentest
Fixes identified from full security audit covering auth, crypto,
injection, infrastructure, and configuration security.

Critical:
- C1: Fail-closed on unrecognized NODE_ENV (prevent DEV_SECRET in staging)
- C3: Validate API token expires_at (reject invalid dates that bypass expiry)

High:
- H1: Refresh JWT role from DB on each session (reflect demotions immediately)
- H2: Docker socket proxy for l4-port-manager (restrict API surface)
- H5: Block dangerous WAF custom directives (SecRuleEngine, SecAuditEngine)
- H7: Require explicit NEXTAUTH_TRUST_HOST instead of always trusting Host
- H8: Semantic validation of sync payload (block metadata SSRF, size limits)

Medium:
- M3: Rate limit password change current-password verification
- M5: Parameterized SQL in log/waf parsers (replace template literals)
- M6: Nonce-based CSP replacing unsafe-inline for script-src
- M9: Strip Caddy placeholders from rewrite path_prefix
- M10: Sanitize authentik outpostDomain (path traversal, placeholders)
- M14: Deny access on missing JWT role instead of defaulting to "user"

Low:
- L1: Require Origin header on mutating session-authenticated requests
- L4: Enforce password complexity on user password changes
- L5: Time-limited legacy SHA-256 key fallback (grace period until 2026-06-01)
- L6: Escape LIKE metacharacters in audit log search
- L7: Runtime-validate WAF excluded_rule_ids as positive integers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 12:14:44 +01:00
fuomag9
d9806e84e6 fix: resolve lint and typecheck errors in API routes and tests
- Remove unused imports (users, and) from api-tokens model
- Fix password_hash destructure lint error in user routes
- Fix apiErrorResponse mock pattern in all 12 test files (use instanceof)
- Remove stale eslint-disable directives from test files
- Add eslint override for tests (no-explicit-any, no-require-imports)
- Fix unused vars in settings and tokens tests
- Fix unused tokenB in integration test

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 10:33:47 +01:00
fuomag9
de28478a42 feat: add comprehensive REST API with token auth, OpenAPI docs, and full test coverage
- API token model (SHA-256 hashed, debounced lastUsedAt) with Bearer auth
- Dual auth middleware (session + API token) in src/lib/api-auth.ts
- 23 REST endpoints under /api/v1/ covering all functionality:
  tokens, proxy-hosts, l4-proxy-hosts, certificates, ca-certificates,
  client-certificates, access-lists, settings, instances, users,
  audit-log, caddy/apply
- OpenAPI 3.1 spec at /api/v1/openapi.json with fully typed schemas
- Swagger UI docs page at /api-docs in the dashboard
- API token management integrated into the Profile page
- Fix: next build now works under Node.js (bun:sqlite aliased to better-sqlite3)
- 89 new API route unit tests + 11 integration tests (592 total)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 09:45:45 +01:00
fuomag9
2d081372f0 fix: L4 UDP proxy routing and TCP disable/re-enable test reliability
- Add udp/ prefix to upstream dial addresses for UDP proxy hosts
  (Caddy L4 requires udp/ prefix on both listen and dial for UDP)
- Fix TCP "disabled host" test to check data echo instead of connection
  refusal (Docker port mapping always accepts TCP handshake)
- Add waitForTcpRoute before "both ports" test to handle re-enable timing
- Increase UDP route wait timeout to 30s for listener startup

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 08:10:02 +01:00
fuomag9
7261fa24d8 fix: add udp/ prefix to Caddy L4 UDP listen addresses and fix E2E test selectors
- Fix Caddy L4 config to use "udp/:PORT" listen syntax for UDP proxy hosts
  (previously used bare ":PORT" which Caddy treated as TCP)
- Fix TCP unused port test to check data echo instead of connection refusal
  (Docker port mapping accepts TCP handshake even without a Caddy listener)
- Fix mTLS import test to wait for sheet close and scope cert name to table
- Fix CA certificate generate test to scope name assertion to table
- Remaining L4 routing test failures are infrastructure issues with Docker
  port forwarding and Caddy L4 UDP listener startup timing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 22:37:39 +01:00
fuomag9
9c60d11c2c feat: improve UI contrast, dark mode, dialog sizing, color coherence, and add table sorting
- Fix dialog scrollability (flex layout + max-h-[90dvh]) and increase L4 dialog to lg width
- Add styled enable card to L4 dialog matching proxy host pattern
- Unify section colors across proxy host and L4 dialogs (cyan=LB, emerald=DNS, violet=upstream DNS, rose=geo, amber=mTLS)
- Improve light mode contrast: muted-foreground oklch 0.552→0.502, remove opacity modifiers on secondary text
- Improve dark mode: boost muted-foreground to 0.85, increase border opacity 10%→16%, input 15%→20%
- Add bg-card to DataTable wrapper and bg-muted/40 to table headers for surface hierarchy
- Add semantic badge variants (success, warning, info, muted) and StatusChip dark mode fix
- Add server-side sortable columns to Proxy Hosts and L4 Proxy Hosts (name, upstream, status, protocol, listen)
- Add sortKey to DataTable Column type with clickable sort headers (ArrowUp/Down indicators, URL param driven)
- Fix E2E test selectors for shadcn UI (label associations, combobox roles, dropdown menus, mobile drawer)
- Add htmlFor/id to proxy host form fields and aria-labels to select triggers for accessibility
- Add sorting E2E tests for both proxy host pages

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 22:17:56 +01:00
fuomag9
7aeaaded5e feat: scaffold tailwind + shadcn foundation, swap to next-themes
Installs Tailwind CSS v3, postcss, autoprefixer, next-themes, lucide-react,
clsx, tailwind-merge, class-variance-authority, sonner, and tailwindcss-animate.
Creates tailwind.config.ts, postcss.config.mjs, components.json, src/lib/utils.ts
(cn helper), replaces globals.css with CSS variable theme, adds
suppressHydrationWarning to html element, and replaces MUI ThemeProvider
with next-themes ThemeProvider + sonner Toaster. MUI remains installed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 11:14:09 +01:00
fuomag9
00c9bff8b4 feat: instant banner refresh on L4 mutations + master-slave L4 sync
Banner (L4PortsApplyBanner):
- Accept refreshSignal prop; re-fetch /api/l4-ports when it changes
- Signal fires immediately after create/edit/delete/toggle in L4ProxyHostsClient
  without waiting for a page reload

Master-slave replication (instance-sync):
- Add l4ProxyHosts to SyncPayload.data (optional for backward compat
  with older master instances that don't include it)
- buildSyncPayload: query and include l4ProxyHosts, sanitize ownerUserId
- applySyncPayload: clear and re-insert l4ProxyHosts in transaction;
  call applyL4Ports() if port diff requires it so the slave's sidecar
  recreates caddy with the correct ports
- Sync route: add isL4ProxyHost validator; backfill missing field from
  old masters; validate array when present

Tests (25 new tests):
- instance-sync.test.ts: buildSyncPayload includes L4 data, sanitizes ownerUserId;
  applySyncPayload replaces L4 hosts, handles missing field, writes trigger
  when ports differ, skips trigger when ports already match
- l4-ports-apply-banner.test.ts: banner refreshSignal contract + client
  increments counter on all mutation paths

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 00:22:44 +01:00
fuomag9
3a4a4d51cf feat: add L4 (TCP/UDP) proxy host support via caddy-l4
- New l4_proxy_hosts table and Drizzle migration (0015)
- Full CRUD model layer with validation, audit logging, and Caddy config
  generation (buildL4Servers integrating into buildCaddyDocument)
- Server actions, paginated list page, create/edit/delete dialogs
- L4 port manager sidecar (docker/l4-port-manager) that auto-recreates
  the caddy container when port mappings change via a trigger file
- Auto-detects Docker Compose project name from caddy container labels
- Supports both named-volume and bind-mount (COMPOSE_HOST_DIR) deployments
- getL4PortsStatus simplified: status file is sole source of truth,
  trigger files deleted after processing to prevent stuck 'Waiting' banner
- Navigation entry added (CableIcon)
- Tests: unit (entrypoint.sh invariants + validation), integration (ports
  lifecycle + caddy config), E2E (CRUD + functional routing)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 00:11:16 +01:00
fuomag9
fc680d4171 fix: use bun:sqlite in production, better-sqlite3 as test-only devDep
Production (Docker): src/lib/db.ts now uses bun:sqlite + drizzle-orm/bun-sqlite.
No native addon compilation needed — bun:sqlite is a Bun built-in. The Dockerfile
drops all native build tools (python3, make, g++) and uses --ignore-scripts.

Tests (Vitest/Node.js): bun:sqlite is unavailable under Node.js, so:
- tests/helpers/db.ts keeps better-sqlite3 + drizzle-orm/better-sqlite3 for
  integration tests that need a real in-memory SQLite
- vitest.config.ts aliases bun:sqlite → a thin better-sqlite3 shim and
  drizzle-orm/bun-sqlite → drizzle-orm/better-sqlite3 for unit tests that
  transitively import src/lib/db.ts without executing any queries
- better-sqlite3 stays as a devDependency (test-only, not built in Docker)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 11:53:33 +01:00
fuomag9
4b5323a7bf feat: add structured redirects and path prefix rewrite for proxy hosts
Adds two new UI-configurable Caddy patterns that previously required raw JSON:
- Per-path redirect rules (from/to/status) emitted as a subroute handler before
  auth so .well-known paths work without login; supports full URLs, cross-domain
  targets, and wildcard path patterns (e.g. /.well-known/*)
- Path prefix rewrite that prepends a segment to every request before proxying
  (e.g. /recipes → upstream sees /recipes/original/path)

Config is stored in the existing meta JSON column (no schema migration). Includes
integration tests for meta serialization and E2E functional tests against a real
Caddy instance covering relative/absolute destinations, all 3xx status codes, and
various wildcard combinations. Adds traefik/whoami to the test stack to verify
rewritten paths actually reach the upstream.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 17:53:33 +01:00
fuomag9
73c90894b1 Handle wildcard proxy hosts and stabilize test coverage
- accept wildcard proxy host domains like *.example.com with validation and normalization
- make exact hosts win over overlapping wildcards in generated routes and TLS policies
- add unit coverage for host-pattern priority and wildcard domain handling
- add a single test:all entry point and clean up lint/typecheck issues so the suite runs cleanly
- run mobile layout Playwright checks under both chromium and mobile-iphone
2026-03-14 01:03:34 +01:00
fuomag9
26fcf8ca90 fix WAF silently dropping WebSocket upgrade requests
When allowWebsocket=true and WAF is enabled, the WAF handler sits first
in the handler chain and processes the initial HTTP upgrade request
(GET + Upgrade: websocket). If any rule matches, Coraza can block the
handshake before SecAuditEngine captures it — producing no log entry
and an unexplained connection failure from the client's perspective.

Fix: when allowWebsocket=true, prepend a phase:1 SecLang rule that
matches Upgrade: websocket (case-insensitive) and turns the rule engine
off for that transaction via ctl:ruleEngine=off. After the 101
Switching Protocols response the connection becomes a raw WebSocket
tunnel that the WAF cannot inspect anyway, so this bypass has no impact
on normal HTTP traffic through the same host.

The rule is inserted before OWASP CRS includes so it always fires first
regardless of which ruleset is loaded.

Add 9 unit tests in caddy-waf.test.ts covering: bypass present/absent,
phase:1 placement, case-insensitive regex, nolog/noauditlog flags,
ordering before CRS, and compatibility with custom directives.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 23:14:12 +01:00
fuomag9
d6df70ab5f fix WAF merge mode ignoring host.enabled=false, extract resolveEffectiveWaf
Bug: when a proxy host had per-host WAF explicitly disabled (enabled:false)
with waf_mode:"merge" (or no waf_mode set), resolveEffectiveWaf entered the
merge branch and returned enabled:true unconditionally, applying the global
WAF to a host the user had opted out of.

Fix: add `if (host.enabled === false) return null` at the top of the merge
branch. Explicit opt-out now takes precedence over the global setting
regardless of mode. The override mode already handled this correctly.

Also extract resolveEffectiveWaf from caddy.ts into caddy-waf.ts so it
can be unit tested. Add 12 new tests covering no-config fallback,
merge opt-out regression, merge settings combination, and override mode.

What runs without OWASP CRS: only SecRuleEngine + audit directives +
any custom_directives. The @coraza.conf-recommended and CRS includes
are gated behind load_owasp_crs (fixed in previous commit).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 21:39:00 +01:00
fuomag9
f0825d6497 fix WAF crash when enabled without OWASP CRS, add regression tests
The WAF handler always prepended 'Include @coraza.conf-recommended' to the
SecLang directives regardless of load_owasp_crs. The @-prefixed paths only
resolve from the embedded coraza-coreruleset filesystem, which the Caddy
WAF plugin mounts only when load_owasp_crs=true. Without it Caddy fails:
  "failed to readfile: open @coraza.conf-recommended: no such file or directory"

Fix: gate all @-prefixed Include directives behind load_owasp_crs.

Also extract buildWafHandler from caddy.ts into caddy-waf.ts so it can be
unit tested in isolation, and add tests/unit/caddy-waf.test.ts (19 tests)
covering the regression, CRS include ordering, excluded rule IDs, and
handler structure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 01:19:10 +01:00
fuomag9
fd847e7eb5 fix mTLS cross-CA isolation bug, add instance-sync and mTLS tests
Extract pemToBase64Der and buildClientAuthentication from caddy.ts into
a new caddy-mtls.ts module, adding groupMtlsDomainsByCaSet to group mTLS
domains by their CA fingerprint before building TLS connection policies.

Previously all mTLS domains sharing a cert type (auto-managed, imported,
or managed) were grouped into a single policy, causing CA union: a client
cert from CA_B could authenticate against a host that only trusted CA_A.
The fix creates one policy per unique CA set, ensuring strict per-host
CA isolation across all three TLS policy code paths.

Also adds:
- tests/unit/caddy-mtls.test.ts (26 tests) covering pemToBase64Der,
  buildClientAuthentication, groupMtlsDomainsByCaSet, and cross-CA
  isolation regression tests
- tests/unit/instance-sync-env.test.ts (33 tests) for the five pure
  env-reading functions in instance-sync.ts
- tests/integration/instance-sync.test.ts (16 tests) for
  buildSyncPayload and applySyncPayload using an in-memory SQLite db
- Fix tests/helpers/db.ts to use a relative import for db/schema so it
  works inside vi.mock factory dynamic imports

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 18:32:52 +01:00
fuomag9
e5ba3e1ed9 refractor code to allow more tests 2026-03-07 16:53:36 +01:00
fuomag9
3572b482e8 added tests 2026-03-07 02:02:14 +01:00
fuomag9
d6658f09fd fix mTLS: fail closed when all certs revoked, fix domain split ordering
When all issued certs for a CA are revoked, buildAuth returns null.
Previously the code would merge mTLS domains back into a policy with no
client_authentication, silently dropping the requirement and allowing
unauthenticated access (open bypass).

Fix by always splitting mTLS and non-mTLS domains first, then using
drop: true when buildAuth returns null — so a fully-revoked CA causes
Caddy to drop TLS connections for those domains rather than admit them
without a client certificate.

Also removed the redundant first buildAuth(domains) call in the
auto-managed path that was used only as an existence check.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 21:46:13 +01:00
fuomag9
90fa11ae3c fix mTLS: trusted_leaf_certs requires trusted_ca_certs for chain validation
Caddy's trusted_leaf_certs is an additional check on top of CA chain
validation, not a replacement. Without trusted_ca_certs, Go's TLS
rejects the client cert before the leaf check runs, causing 'unknown ca'.

Updated buildClientAuthentication to always include the CA cert in
trusted_ca_certs for chain validation, and additionally set
trusted_leaf_certs for managed CAs to enforce revocation. When all
issued certs for a CA are revoked, the CA is excluded from
trusted_ca_certs entirely so chain validation fails for any cert from it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 21:18:33 +01:00
fuomag9
9fa57bcf28 fix mTLS: use trusted_leaf_certs for issued certs, surface CA delete errors
Two bugs fixed:

1. buildClientAuthentication was placing issued leaf cert PEMs into
   trusted_ca_certs. Caddy uses that field for CA chain validation, not
   leaf pinning — putting leaf certs there made chain verification fail
   for every presented client cert, causing the browser to be asked
   repeatedly. Fixed by using trusted_leaf_certs for managed CAs.

2. If all issued certs for a CA were revoked, the active cert map would
   be empty and the code fell back to trusting the CA cert directly,
   effectively un-revoking everything. Fixed by tracking which CAs have
   ever had issued certs (including revoked) and keeping them in
   trusted_leaf_certs mode permanently (empty list = no one trusted).

Also fix CA certificate delete action not surfacing the error message
to the user in production (Next.js strips thrown error messages in
server actions). Changed to return { success, error } and updated the
client dialog to check the result instead of using try/catch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 18:21:48 +01:00
fuomag9
9bfa86f2fc remove dead DetectionOnly coercion in buildWafHandler
WafSettings.mode is now 'Off' | 'On' so the legacy DB coercion guard
triggered a TS2367 type error. DB values are already normalised upstream.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 17:31:59 +01:00
fuomag9
b348dae4be remove DetectionOnly WAF mode
DetectionOnly was fundamentally broken in coraza-caddy (actually blocks
requests via anomaly scoring), caused massive audit log flooding, and the
threshold workaround had several issues:
- t:none is meaningless in a SecAction (no target to transform)
- SecRuleEngine directive ordering relative to SecAction is implementation-
  defined, making the override fragile
- host.mode ?? 'DetectionOnly' fallbacks silently gave any host without an
  explicit mode the broken DetectionOnly behaviour

Changes:
- Remove DetectionOnly from UI (global settings radio, per-host engine mode)
- Coerce legacy DB values of 'DetectionOnly' to 'On' in buildWafHandler
- Fix fallback defaults: host.mode ?? 'DetectionOnly' → host.mode ?? 'On'
- Fix action parsers: unknown mode defaults to 'On' (was 'DetectionOnly')
- Fix global settings defaultValue: ?? 'DetectionOnly' → ?? 'On' (or 'Off')
- Remove the fragile threshold SecAction workaround
- Update types: mode is now 'Off' | 'On' throughout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 17:27:08 +01:00
fuomag9
5cd92fe669 revert SecAuditEngine to RelevantOnly to prevent log flooding
SecAuditEngine On logs every request through the WAF regardless of whether
any rules matched, causing massive disk I/O on busy hosts (e.g. during
Docker image pushes). RelevantOnly still captures DetectionOnly hits because
OWASP CRS rules include auditlog in their SecDefaultAction, so rule-matched
transactions are marked for audit logging. Only truly clean requests (no
rule match at all) are silently skipped.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 16:28:20 +01:00
fuomag9
e06b41b604 fix WAF detection mode and payload logging
- DetectionOnly mode: add SecAction to set anomaly score thresholds to
  9999999 so rule 949110/980130 never fires; works around coraza-caddy
  bug where is_interrupted=true still causes a 403 in detection mode
- Switch SecAuditEngine back to On (from RelevantOnly) so DetectionOnly
  hits are captured, now safe because body parts are excluded
- SecAuditLogParts: ABIJDEFHZ → ABFHZ, dropping request body (I),
  multipart files (J), intermediate response headers (D), and response
  body (E) — prevents multi-MB payloads being written to audit log
- Parser: store both blocked and detected events; filter on rule matched
  OR is_interrupted instead of is_interrupted only
- Add blocked column to waf_events (migration 0014); existing rows
  default to blocked=true
- WAF Events UI: Blocked/Detected chip in table and drawer header
- Fix misleading help text that said to use Detection Only to observe
  traffic before blocking

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 15:32:56 +01:00
fuomag9
044f012dd0 Added issued-client-cert tracking and revocation for mTLS 2026-03-06 14:53:17 +01:00
fuomag9
333f7d4733 Revert "fix authentik not being removed when toggle is disabled"
This reverts commit 641ab9ef34.
2026-03-06 09:56:59 +01:00
fuomag9
641ab9ef34 fix authentik not being removed when toggle is disabled 2026-03-06 09:34:26 +01:00
fuomag9
c76004f8ac better pki 2026-03-06 00:22:30 +01:00
fuomag9
94eba03595 fix: use inline PEM for imported TLS certs and CA certs in Caddy config
Replace file-based cert loading with inline content to fix cross-container
filesystem issues (web and caddy containers don't share the data volume):

- Imported server certs: switch from tls.certificates.load_files to
  tls.certificates.load_pem (inline PEM content in JSON config)
- Client CA certs: use trusted_ca_certs (base64 DER) instead of
  trusted_ca_certs_pem_files
- Fix pre-existing bug where certificates[] was placed inside
  tls_connection_policies (invalid Caddy JSON field)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 22:40:37 +01:00
fuomag9
f3358c20cd feat: add mTLS support for proxy hosts
- New `ca_certificates` table for reusable CA certs (migration 0011)
- CA cert CRUD model, server actions, and UI dialogs
- Proxy host create/edit dialogs include mTLS toggle + CA cert selection
- Caddy config generates `client_authentication` TLS policy blocks with
  `require_and_verify` mode for hosts with mTLS enabled
- CA certs sync to slave instances via instance-sync payload
- Certificates page shows CA Certificates section

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 20:29:55 +01:00
fuomag9
bdd3019214 security: add same-origin CSRF check to state-changing user API routes
Adds checkSameOrigin() helper in auth.ts that validates the Origin header
against the Host header. If Origin is present and mismatched, returns 403.
Applied to all 5 custom POST routes flagged in CPM-003 (NEXT-CSRF-001):
  - change-password, link-oauth-start, unlink-oauth, update-avatar, logout

SameSite=Lax (NextAuth default) already blocks standard cross-site CSRF;
this adds defense-in-depth against subdomain and misconfiguration scenarios.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 01:04:18 +01:00
fuomag9
e44e932e45 fix: sync global WAF and GeoBlock settings to slave instances
Both "waf" and "geoblock" settings were missing from the sync payload,
meaning slaves used their own (potentially unconfigured) values.
Per-host WAF was already synced via the proxyHosts table.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 00:30:44 +01:00
fuomag9
c20ba54b4c feat: analytics WAF improvements — bar chart, host chips, country column
- Add getTopWafRulesWithHosts() and getWafEventCountries() model queries
- WAF stats API now returns topRules with per-host breakdown and byCountry
- Analytics: replace WAF rules table with bar chart + host chip details
- Analytics: add WAF column (amber) to Top Countries table

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 22:42:10 +01:00
fuomag9
7ceeb84fc2 improve UX 2026-03-04 22:26:11 +01:00
fuomag9
7341070c0d Fix rule parsing for single reverse proxies 2026-03-04 21:16:11 +01:00
fuomag9
77d3e35c63 feat: clickable WAF event rows with detail drawer
- WafEvent model: expose rawData field from DB
- DataTable: add optional onRowClick prop with hover cursor
- WafEventsClient: clicking a row opens a right-side drawer showing
  all event fields plus the raw Coraza audit JSON (pretty-printed)

Safety: rawData is rendered via JSON.stringify into a <pre> element,
never via dangerouslySetInnerHTML, so attack payloads are displayed
as inert text.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 18:21:08 +01:00
fuomag9
54a2a9ea0d feat: parse WAF rule logs for rule ID/message/severity
Coraza does not write matched rules to the audit log (known upstream
bug). Rule details are logged by Caddy's http.handlers.waf logger.

Two changes:

1. caddy.ts: Always configure a dedicated Caddy log sink that writes
   http.handlers.waf logger output to /logs/waf-rules.log as JSON.

2. waf-log-parser.ts: Before parsing the audit log, read the new
   waf-rules.log to build a Map<unique_id, RuleInfo>. Each audit log
   entry joins against this map via transaction.id to populate
   ruleId, ruleMessage, and severity fields. Skips anomaly evaluation
   rules (949110/980130) to show the actual detection rule instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 08:21:49 +01:00
fuomag9
5e7ceedfe3 fix: filter WAF events by is_interrupted instead of messages array
Coraza does not populate the messages array in the audit log JSON
(known bug — matched rules appear in Caddy's error log but not in
the audit log's messages field). The transaction.is_interrupted flag
IS correctly set to true for blocked/detected requests.

Changes:
- Filter on tx.is_interrupted instead of entry.messages.length
- Check both top-level and tx.messages for rule info (future compat)
- Fall back to tx.highest_severity when messages missing
- Document the Coraza messages bug in interface comments

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 08:16:02 +01:00
fuomag9
ab7fb70ee4 fix: revert SecAuditEngine to On — RelevantOnly suppresses WAF-blocked logs
Coraza's RelevantOnly mode does not write audit log entries for requests
blocked by the WAF itself (403 responses), so the waf-log-parser had
nothing to parse. Reverting to On so all transactions are logged, and
relying on the parser-side messages[] filter to skip clean requests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 02:29:06 +01:00