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:
@@ -0,0 +1,71 @@
|
||||
// Exercises the hand-written DeepCopy methods in zz_generated.deepcopy.go
|
||||
// -- see that file's doc comment for why these aren't controller-gen
|
||||
// output here. A DeepCopy that accidentally shares a slice/map with the
|
||||
// original is a real, easy-to-introduce bug (client-go relies on
|
||||
// DeepCopyObject returning something safe to mutate independently), so
|
||||
// these tests mutate the copy and assert the original is unaffected.
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestTenantDeepCopyIsIndependent(t *testing.T) {
|
||||
orig := &Tenant{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "acme", Labels: map[string]string{"a": "1"}},
|
||||
Spec: TenantSpec{DisplayName: "Acme", Suspended: false},
|
||||
Status: TenantStatus{
|
||||
Phase: PhaseActive,
|
||||
Conditions: []metav1.Condition{
|
||||
{Type: ConditionReady, Status: metav1.ConditionTrue, Reason: "x"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cp := orig.DeepCopy()
|
||||
cp.Spec.DisplayName = "Changed"
|
||||
cp.Status.Conditions[0].Reason = "changed"
|
||||
cp.Labels["a"] = "changed"
|
||||
|
||||
if orig.Spec.DisplayName != "Acme" {
|
||||
t.Fatalf("mutating the copy's Spec affected the original: %q", orig.Spec.DisplayName)
|
||||
}
|
||||
if orig.Status.Conditions[0].Reason != "x" {
|
||||
t.Fatalf("mutating the copy's Conditions affected the original: %q", orig.Status.Conditions[0].Reason)
|
||||
}
|
||||
// Labels comes from metav1.ObjectMeta.DeepCopyInto, which this
|
||||
// package doesn't implement itself -- this assertion is really
|
||||
// checking that Tenant.DeepCopyInto actually calls
|
||||
// ObjectMeta.DeepCopyInto rather than doing a shallow `out.ObjectMeta
|
||||
// = in.ObjectMeta`.
|
||||
if orig.Labels["a"] != "1" {
|
||||
t.Fatalf("mutating the copy's Labels affected the original: %q", orig.Labels["a"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestTenantDeepCopyObjectPreservesData(t *testing.T) {
|
||||
orig := &Tenant{ObjectMeta: metav1.ObjectMeta{Name: "acme"}, Spec: TenantSpec{DisplayName: "Acme"}}
|
||||
obj := orig.DeepCopyObject()
|
||||
cp, ok := obj.(*Tenant)
|
||||
if !ok {
|
||||
t.Fatalf("DeepCopyObject returned %T, want *Tenant", obj)
|
||||
}
|
||||
if cp.Name != "acme" || cp.Spec.DisplayName != "Acme" {
|
||||
t.Fatalf("unexpected copy: %+v", cp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTenantListDeepCopyIsIndependent(t *testing.T) {
|
||||
orig := &TenantList{Items: []Tenant{
|
||||
{ObjectMeta: metav1.ObjectMeta{Name: "acme"}},
|
||||
{ObjectMeta: metav1.ObjectMeta{Name: "globex"}},
|
||||
}}
|
||||
cp := orig.DeepCopy()
|
||||
cp.Items[0].Name = "changed"
|
||||
|
||||
if orig.Items[0].Name != "acme" {
|
||||
t.Fatalf("mutating the copy's Items affected the original: %q", orig.Items[0].Name)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user