Skip to content

Commit 76fcc94

Browse files
feat: enhance WAF dashboard with timeline chart, event detail view, and sidebar nav
Add time-series stacked bar chart to WAF index page with auto-selected bucket sizes based on time range. Create WAF event detail show page with full event info, request headers, and metadata. Add sidebar navigation link for WAF Events. Wire up clickable event rows for drill-down. Update V2 roadmap to reflect all completed stretch goals.
1 parent fda0d6f commit 76fcc94

8 files changed

Lines changed: 578 additions & 57 deletions

File tree

.claude/FEATURE_ROADMAP_V2.md

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,13 @@
3939
> Make Sentinel CP capable of configuring all core proxy features through the UI.
4040
> Every competitor has these — they are table stakes.
4141
42-
### 8.1 TLS / Certificate Management — DONE (ACME/CA stretch goals remain)
42+
### 8.1 TLS / Certificate Management — DONE
4343
- [x] Certificate CRUD (upload PEM/DER certs and private keys) — `certificate.ex` schema + `certificate_crypto.ex` AES-256-GCM encryption
4444
- [x] Certificate-to-service binding (assign certs to virtual hosts / services) — `Service.certificate_id` FK
4545
- [x] Certificate expiry tracking with dashboard warnings — `certificate_expiry_worker.ex` Oban worker, statuses: active/expiring_soon/expired/revoked
46-
- [ ] Auto-renewal integration (Let's Encrypt / ACME) — `acme_config` field exists, no ACME client
47-
- [ ] Internal CA for inter-node mTLS (inspired by nevisAdmin4 auto-PKI)
48-
- [ ] Trust store management (CA bundles for upstream verification)
46+
- [x] Auto-renewal integration (Let's Encrypt / ACME) — `acme/client.ex`, `acme/renewal.ex`, `acme/crypto.ex`, `certificate_renewal_worker.ex`
47+
- [x] Internal CA for inter-node mTLS (inspired by nevisAdmin4 auto-PKI)`internal_ca.ex`, `internal_ca_live/`
48+
- [x] Trust store management (CA bundles for upstream verification)`trust_store.ex`, `trust_stores_live/`
4949
- [x] KDL generation for TLS blocks — `kdl_generator.ex` `build_tls_certificates` + `build_tls_ref`
5050

5151
### 8.2 Upstream Groups / Load Balancing — DONE
@@ -91,11 +91,11 @@
9191

9292
> Match enterprise expectations for proxy security and reliability features.
9393
94-
### 9.1 Circuit Breakers — DONE (health view status is a stretch goal)
94+
### 9.1 Circuit Breakers — DONE
9595
- [x] Per-upstream circuit breaker configuration — `UpstreamGroup.circuit_breaker` map field
9696
- [x] Configurable thresholds (failure_threshold, success_threshold, timeout, half_open_max_requests)
9797
- [x] Half-open state with configurable probe interval
98-
- [ ] Circuit breaker status in node/service health views — no dedicated health view
98+
- [x] Circuit breaker status in node/service health views — `circuit_breaker_status.ex`, upstream group show page
9999
- [x] KDL generation for circuit breaker blocks — `build_nested_map_block` via upstream group
100100

101101
### 9.2 Proxy-Level Authentication — DONE
@@ -108,13 +108,13 @@
108108
- [x] Auth configuration UI + API — `auth_policies_live/`, `auth_policy_controller.ex`
109109
- [x] KDL generation for auth blocks — `build_auth_block`
110110

111-
### 9.3 WAF / Request Security — DONE (anomaly detection / WAF dashboard are stretch goals)
111+
### 9.3 WAF / Request Security — DONE
112112
- [x] Request size limits (max body, max headers, max URI length) — `security.max_body_size`
113113
- [x] Content-type enforcement (whitelist allowed content types)
114114
- [x] Common attack pattern detection (SQLi, XSS, path traversal, RFI) — `security.block_sqli`, `security.block_xss`
115115
- [x] Custom WAF rules (regex-based block/allow) — via middleware system with custom type
116-
- [ ] Request rate anomaly detection
117-
- [ ] WAF event logging and dashboard
116+
- [x] Request rate anomaly detection`waf_baseline.ex`, `waf_baseline_worker.ex`, `waf_anomaly.ex`, anomalies live view
117+
- [x] WAF event logging and dashboard`waf_event.ex`, `waf_live/index.ex`, `waf_live/show.ex`, time-series chart
118118
- [x] Per-service WAF policy (enable/disable, sensitivity level) — `Service.security` map
119119
- [x] KDL generation for security blocks — `build_security_block`, `build_global_security`
120120

@@ -178,9 +178,9 @@
178178
- [x] Link spec to service for documentation — `Service.openapi_spec_id` FK + `Service.openapi_path`
179179
- [x] Preview generated services before applying — dedicated preview endpoint
180180

181-
### 11.2 Service Discovery Integration — DONE (DNS/SRV; Consul/K8s are stretch goals)
182-
- [ ] Consul service discovery (watch for backend changes, auto-update upstream groups)
183-
- [ ] Kubernetes service discovery (watch Services/Endpoints)
181+
### 11.2 Service Discovery Integration — DONE
182+
- [x] Consul service discovery (watch for backend changes, auto-update upstream groups)`consul_resolver.ex`
183+
- [x] Kubernetes service discovery (watch Services/Endpoints)`k8s_resolver.ex`
184184
- [x] DNS-based discovery (SRV records) — `discovery_source.ex`, `dns_resolver.ex`, `dns_resolver/inet.ex`
185185
- [x] Manual refresh + auto-sync toggle — `discovery_sync_worker.ex` background worker
186186
- [x] Discovery source status in UI — `last_synced_at`, `last_sync_status`, `last_sync_error`, `last_sync_targets_count`
@@ -208,31 +208,31 @@
208208
- [x] Customizable portal branding — `portal_title`, `portal_custom_css`, `portal_logo_url`
209209
- [x] Usage analytics per API consumer
210210

211-
### 11.6 Secrets Management — DONE (Vault integration is a stretch goal)
211+
### 11.6 Secrets Management — DONE
212212
- [x] Secrets store (encrypted at rest, scoped to project or environment) — `secrets/secret.ex`, `secret_crypto.ex` AES-GCM
213213
- [x] Secret references in service config (e.g., `${secrets.NAME}`) — `secrets.ex` reference pattern
214214
- [x] Secret injection into KDL at compile time (never stored in bundles in plaintext) — `kdl_generator.ex` `maybe_resolve_secrets`
215-
- [ ] Vault integration (HashiCorp Vault as external secrets backend)
215+
- [x] Vault integration (HashiCorp Vault as external secrets backend)`vault_client.ex`, `vault_config.ex`
216216
- [x] Secret rotation workflows — `secrets.ex` `rotate_secret`, `last_rotated_at` tracking
217217
- [x] Audit logging for secret access — audit logs on create/update/rotate
218218

219219
---
220220

221-
## Remaining Stretch Goals
222-
223-
Items that are not yet implemented but would add value:
224-
225-
| Feature | Phase | Effort | Impact |
226-
|---------|-------|--------|--------|
227-
| ACME / Let's Encrypt auto-renewal | 8.1 | Medium | High — eliminates manual cert rotation |
228-
| Internal CA for mTLS | 8.1 | Large | Medium — enterprise mTLS use cases |
229-
| Trust store management | 8.1 | Small | Medium — upstream TLS verification |
230-
| Circuit breaker health view status | 9.1 | Small | Low — observability improvement |
231-
| WAF anomaly detection | 9.3 | Large | Medium — advanced threat detection |
232-
| WAF event logging dashboard | 9.3 | Medium | Medium — security visibility |
233-
| Consul service discovery | 11.2 | Medium | Medium — HashiCorp ecosystem |
234-
| Kubernetes service discovery | 11.2 | Medium | High — K8s is dominant deployment target |
235-
| Vault integration for secrets | 11.6 | Medium | Medium — enterprise secrets management |
221+
## Completed Stretch Goals
222+
223+
All originally-identified stretch goals have been implemented:
224+
225+
| Feature | Phase | Status |
226+
|---------|-------|--------|
227+
| ACME / Let's Encrypt auto-renewal | 8.1 | Done — `acme/client.ex`, `acme/renewal.ex`, `certificate_renewal_worker.ex` |
228+
| Internal CA for mTLS | 8.1 | Done — `internal_ca.ex`, `internal_ca_live/` |
229+
| Trust store management | 8.1 | Done — `trust_store.ex`, `trust_stores_live/` |
230+
| Circuit breaker health view status | 9.1 | Done — `circuit_breaker_status.ex`, upstream group show page |
231+
| WAF anomaly detection | 9.3 | Done — `waf_baseline.ex`, `waf_baseline_worker.ex`, `waf_anomaly.ex` |
232+
| WAF event logging dashboard | 9.3 | Done — `waf_event.ex`, `waf_live/index.ex`, `waf_live/show.ex` |
233+
| Consul service discovery | 11.2 | Done — `consul_resolver.ex` |
234+
| Kubernetes service discovery | 11.2 | Done — `k8s_resolver.ex` |
235+
| Vault integration for secrets | 11.6 | Done — `vault_client.ex`, `vault_config.ex` |
236236

237237
---
238238

lib/sentinel_cp/analytics.ex

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,11 @@ defmodule SentinelCp.Analytics do
200200

201201
## WAF Events
202202

203+
@doc """
204+
Gets a single WAF event by ID.
205+
"""
206+
def get_waf_event(id), do: Repo.get(WafEvent, id)
207+
203208
@doc """
204209
Bulk inserts WAF event records from a node push.
205210
"""
@@ -243,9 +248,7 @@ defmodule SentinelCp.Analytics do
243248

244249
if blocked_count > 0 do
245250
for pid <- project_ids do
246-
SentinelCp.Events.emit("security.waf_blocked", %{count: blocked_count},
247-
project_id: pid
248-
)
251+
SentinelCp.Events.emit("security.waf_blocked", %{count: blocked_count}, project_id: pid)
249252
end
250253
end
251254

@@ -364,11 +367,20 @@ defmodule SentinelCp.Analytics do
364367
e.rule_type
365368
],
366369
select: %{
367-
bucket: fragment("datetime((strftime('%s', ?) / ? * ?), 'unixepoch')", e.timestamp, ^bucket_seconds, ^bucket_seconds),
370+
bucket:
371+
fragment(
372+
"datetime((strftime('%s', ?) / ? * ?), 'unixepoch')",
373+
e.timestamp,
374+
^bucket_seconds,
375+
^bucket_seconds
376+
),
368377
rule_type: e.rule_type,
369378
count: count(e.id)
370379
},
371-
order_by: [asc: fragment("(strftime('%s', ?) / ? * ?)", e.timestamp, ^bucket_seconds, ^bucket_seconds)]
380+
order_by: [
381+
asc:
382+
fragment("(strftime('%s', ?) / ? * ?)", e.timestamp, ^bucket_seconds, ^bucket_seconds)
383+
]
372384
)
373385
|> Repo.all()
374386
end
@@ -553,9 +565,21 @@ defmodule SentinelCp.Analytics do
553565

554566
defp normalize_aggregation(result) when is_map(result) do
555567
Map.new(result, fn
556-
{k, nil} when k in [:total_requests, :total_errors, :total_bandwidth_in, :total_bandwidth_out,
557-
:total_2xx, :total_3xx, :total_4xx, :total_5xx,
558-
:status_2xx, :status_3xx, :status_4xx, :status_5xx] ->
568+
{k, nil}
569+
when k in [
570+
:total_requests,
571+
:total_errors,
572+
:total_bandwidth_in,
573+
:total_bandwidth_out,
574+
:total_2xx,
575+
:total_3xx,
576+
:total_4xx,
577+
:total_5xx,
578+
:status_2xx,
579+
:status_3xx,
580+
:status_4xx,
581+
:status_5xx
582+
] ->
559583
{k, 0}
560584

561585
{k, %Decimal{} = v} ->

lib/sentinel_cp_web/components/layouts.ex

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,13 @@ defmodule SentinelCpWeb.Layouts do
169169
current={@path}
170170
match="/topology"
171171
/>
172+
<.sidebar_link
173+
path={~p"/orgs/#{@org_slug}/projects/#{@project_slug}/waf"}
174+
icon="hero-shield-check"
175+
label="WAF Events"
176+
current={@path}
177+
match="/waf"
178+
/>
172179
173180
<div class="sidebar-section-title mt-4">Settings</div>
174181
<.sidebar_link

0 commit comments

Comments
 (0)