Repository navigation
Dashboard phase 2: read-only web UI, sign-in and API additions - #581
Merged
Merged
Conversation
Password sign-in with a signed 12h session cookie (Secure in shared mode, sign-in refused over plain http), server-side list filters, namespace list, severity counts, timeline kinds, fix steps, scan health and channel names. Embedded UI with hashed assets and a strict CSP. Chart: dashboard.mode, password in the dashboard Secret, Ingress for shared mode. Kind dashboard check in the operational e2e job.
dashboard.expose: local, service, ingress (nginx, ALB, GKE, AGIC, Tailscale, ngrok helpers), gateway, istio, route, loadBalancer and nodePort (kwatch serves TLS itself, renewals reloaded), external. Shared ways are https only; X-Forwarded-Proto is trusted only behind a proxy. allowedSourceRanges where the platform enforces it. The render fails with a plain message when a required value is missing.
The chart keeps the ClusterIP Service, the password and token Secret, dashboard.mode, trustProxy and tls.secretName. Ingress, Gateway, Istio, Route, LoadBalancer and NodePort objects are created by the kwatch.sh Dashboard menu or by the operator.
Password from crypto random bytes; the Secret is kept on uninstall and GitOps users are pointed at existingSecret. Failures count against the connecting peer as well as the forwarded client. The session key gains a per-start random part, the revoked set evicts the oldest entry, and the listener opens with the lifecycle context.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 2 of the dashboard (spec
packages/brand/specs/dashboard-phase2.md, sections 5, 7, 7a, 7b; approved mockup).API (internal/dashboard)
POST /login{"password"}→ 204 + cookiekwatch_session(HttpOnly, SameSite=Strict, Path=/, 12h, no sliding; Secure only in shared mode). 401 wrong, 429 + Retry-After (shared failure limiter), 400 non-JSON, 403 cross-origin.POST /logoutrevokes.GET /api/session204/401./loginand/api/sessionanswer 403https_requiredunless kwatch served TLS itself (tls.secretName) or, withtrustProxy,X-Forwarded-Proto: httpsfrom a non-loopback peer. Failures are counted per last X-Forwarded-For hop behind the Ingress./api/*accepts the cookie or the bearer token. A request with no credential no longer counts as a failure.q,severity(repeatable),state(repeatable;active|resolvedpicks the list,settling|open|recovering|flappingnarrow it),since(duration or RFC 3339),countsby severity; Active = Urgent first, silent hidden (D4); Resolved by resolvedAt (D5).GET /api/namespaces?state=.workloadKind,fixSteps/fixCommands(stored from the message steps, persisted additively), timelinekind+ resolvedreason.lastScanis now the engine's last loop (it was the request time),lastScanLate,scanEverySeconds,kwatchNamespace,kwatchDeployment(D6); channeltypeandname.ackedAt(GAP 12): the final screens don't use them (no ack in UI per 7a).Bugs fixed from phase 1
suggestedFixreturnedincident.Fix(how it ended, e.g. "rollback"), not advice. Now the message steps.dashboard.enablednever turned the server on (only config.yaml did). The chart now passesKWATCH_DASHBOARD_ENABLED/PORT/MODE.Web UI (internal/dashboard/web)
Plain HTML templates + vanilla JS (5 small files) + CSS, no build step: the repo has no web toolchain and the UI is small, so a framework would add a build and supply chain for no gain. go:embed, content-hashed names under
/assets/(1-year immutable), HTML/APIno-store. CSPdefault-src 'none'; script-src 'self'; style-src 'self'; ...on every response; no inline script/style, API text via textContent only; brand woff2 fonts embedded (OFL texts shipped). Screens and states per mockup: sign in, active, detail, resolved, status; filters with More filters, hidden under 5 issues; 20 s live refresh paused when hidden; New highlight; aria-live for new Urgent; back link to the list you came from; phone order; one-line commands with Copy; tab title and favicon dot; light/dark from the system;[hidden]wins.D8
StatusLow/MarkerLow).internal/redactreplaces known patterns with[redacted]. UI now says "Known secret patterns are removed."stopped_trackingshown as "kwatch stopped tracking it: …";recoveredshown as "Resolved: It stayed healthy." (needs designer review).For kwatch-web (installer)
The chart never exposes the dashboard; the installer creates Ingress / HTTPRoute / VirtualService / Route / LoadBalancer or NodePort Service itself, pointing at Service
<release>-dashboard(ClusterIP, port namedashboard, port 8061).Values:
dashboard.enabled(false),dashboard.port(8061)dashboard.mode:local(port-forward) |shared(https only, Secure cookie; render fails unless one of the next two is set)dashboard.trustProxy: true when an Ingress/route/proxy terminates TLS and sets X-Forwarded-Protodashboard.tls.secretName: kubernetes.io/tls Secret kwatch serves https from itself (LB/NodePort with nothing in front); renewals reloadeddashboard.existingSecret,dashboard.networkPolicyFrom(extra NetworkPolicy peers for the port; namespace + healthIngressFrom always allowed)Secret
<release>-dashboard(kwatch-dashboard): keyspassword(XXXX-XXXX-XXXX-XXXX, alphabetABCDEFGHJKLMNPQRSTUVWXYZ23456789) andtoken; generated from crypto random bytes, kept across upgrades, annotatedhelm.sh/resource-policy: keep(survives uninstall/turn-off; GitOps should useexistingSecret). Don't pre-create it unowned (Helm adoption fails) unless viaexistingSecret. Reset: patchpassword,kubectl rollout restart deploy/kwatch. Local:kubectl -n $NS port-forward svc/kwatch-dashboard <local>:8061.Tests
Handler tests for sessions, expiry, reset, logout, shared https, origin, rate limit, filters, counts, namespaces, new fields, event kinds, scan health; static tests (no external URLs, no inline code, hashed assets, CSP). Chart template tests + lifecycle check that password/token survive
helm upgrade. Newscripts/test-kind-dashboard.shin the operational e2e job: installs with the chart, signs in with the Secret's password, loads/api/incidentsand/api/statuswith the cookie, signs out, then shared mode refuses plain http.Local: dashboard/config/incident/app tests pass,
test_helm.shpasses, shellcheck/actionlint clean. Localmake cigot through verify (tests, coverage, negative regressions) but was stopped at the low-disk point before licensetest; CI is the gate. UI checked in a browser against fake data (sign in, list, detail, resolved, status).