Phase 4: SSO scaffolding, RBAC enforcement, tenant-scoped dashboards, audit logging, K8s deployment

RBAC (api/internal/authz) is live on /query and /dashboards, backed by a
new enterprise/ module (session issuance, audit logging, RBAC storage,
OIDC/SAML protocol wiring) that core never imports -- only calls over
HTTP. Found and fixed a real cross-tenant vulnerability in dashboards
(no tenant_id filtering at all) while writing the threat model doc.

Two things are explicitly NOT done, documented rather than hidden:
tenant isolation for log data itself (/query still shares one ClickHouse
connection and Tantivy index across every tenant -- RBAC controls who
can query, not what a query can see), and human SSO login (protocol
wiring exists, no HTTP handler calls it yet). See
docs/security/threat-model.md and docs/phase-4-runbook.md.

Also adds deploy/ (Go Operator + Helm chart, validated offline only --
no cluster was reachable in this environment).
This commit is contained in:
2026-08-13 22:16:59 -07:00
parent 9435115ab7
commit 3eb0f4c589
116 changed files with 8589 additions and 126 deletions
+31
View File
@@ -5,6 +5,12 @@
export const apiBase = import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:8080';
export const alertingBase = import.meta.env.VITE_ALERTING_API_BASE_URL ?? 'http://localhost:8081';
// Optional third backend (Phase 4, commercial-license) -- undefined in a
// deployment that hasn't built/deployed enterprise-auth, same "runtime
// capability check" shape /docs/phase-4-rbac-design.md's Web UI boundary
// section describes. getAuthFeatures below treats a missing base URL the
// same as a failed fetch: everything reports disabled, no broken links.
export const enterpriseAuthBase = import.meta.env.VITE_ENTERPRISE_AUTH_BASE_URL as string | undefined;
export type Language = '' | 'sql' | 'spl';
@@ -118,6 +124,31 @@ export function deletePanel(dashboardId: string, panelId: string): Promise<void>
return request(`/dashboards/${dashboardId}/panels/${panelId}`, { method: 'DELETE' });
}
export type AuthFeatures = { sso_configured: boolean; oidc_enabled: boolean; saml_enabled: boolean };
// getAuthFeatures never throws -- an absent/unreachable enterprise-auth
// is a normal, expected deployment shape (Phase 0-3-style single-tenant,
// no enterprise/ deployed), not an error condition the settings page
// should surface. Deliberately no `credentials: 'include'` here: this
// endpoint needs no session, and CORS_ALLOWED_ORIGIN's dev default of
// "*" can't be combined with credentialed fetches anyway (the browser
// rejects that combination) -- the same reason api.ts's other requests
// don't send credentials yet. Wiring session cookies through is part of
// the deferred login-flow work, alongside tightening CORS_ALLOWED_ORIGIN
// to a real origin, done together as one change so neither breaks the
// other.
export async function getAuthFeatures(): Promise<AuthFeatures> {
const disabled = { sso_configured: false, oidc_enabled: false, saml_enabled: false };
if (!enterpriseAuthBase) return disabled;
try {
const res = await fetch(`${enterpriseAuthBase}/auth/features`);
if (!res.ok) return disabled;
return await res.json();
} catch {
return disabled;
}
}
export function exportDashboard(id: string): Promise<Dashboard> {
return request(`/dashboards/${id}/export`);
}