mirror of
https://github.com/hanzoai/dns.git
synced 2026-10-30 18:40:55 +00:00
Red found the authoritative store had no tenant isolation: only the CF-token read was org-scoped, so on the shared multi-tenant instance any authenticated org could list/read/mutate/delete another org's zones and records (DNS hijack / outage). Fixes: HIGH — org-scope the store structurally. Zone carries an Org (the validated `owner` claim, stamped server-side); every Store method takes org and funnels through zoneForOrg(org,name), so a caller can never reach another org's zone even if a handler forgets the check. List filters to the caller's org; get/mutate/delete of a foreign or absent zone is 'not found' (404, no existence confirmation); zone names are globally unique on the shared nameservers so a name taken by another org is a conflict. /v1/dns/sync derives the owner from the ctx principal (never the body — the dead SyncZoneRequest.OrgID is removed) and refuses to overwrite another org's zone. MED — auth fails closed. authChain denies (503) when neither OIDC nor a static key is configured; the anonymous path is reachable only behind an explicit HANZO_DNS_DEV_INSECURE=1 opt-in (authMiddleware no longer allow-alls on an empty key). OIDC audience is a comma-separated allowlist (string-or-array aud), and a token with no exp is rejected. LOW — Cloudflare updates now PATCH only the caller's changed fields (a stale local mirror can't revert an out-of-band upstream change); a create/update mirror failure is logged, not swallowed. Tests: TestCrossTenantIsolation (org B gets 404 on list/get/create/update/ delete of org A's zone+records, org A's data intact), TestSyncIsOrgScoped, TestAuthChainFailsClosedByDefault, TestDevInsecureAllows. go test -tags metrics -race green; go build ./... green.
164 lines
4.1 KiB
Go
164 lines
4.1 KiB
Go
package hanzodns
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func TestCreateAndListZones(t *testing.T) {
|
|
s := NewStore()
|
|
|
|
z, err := s.CreateZone(testOrg, "example.com", ProviderAuthoritative)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if z.Name != "example.com." {
|
|
t.Fatalf("expected example.com., got %s", z.Name)
|
|
}
|
|
if z.Status != "active" {
|
|
t.Fatalf("expected active, got %s", z.Status)
|
|
}
|
|
|
|
zones := s.ListZones(testOrg)
|
|
if len(zones) != 1 {
|
|
t.Fatalf("expected 1 zone, got %d", len(zones))
|
|
}
|
|
}
|
|
|
|
func TestDuplicateZone(t *testing.T) {
|
|
s := NewStore()
|
|
if _, err := s.CreateZone(testOrg, "example.com", ProviderAuthoritative); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := s.CreateZone(testOrg, "example.com.", ProviderAuthoritative); err == nil {
|
|
t.Fatal("expected error for duplicate zone")
|
|
}
|
|
}
|
|
|
|
func TestDeleteZone(t *testing.T) {
|
|
s := NewStore()
|
|
if _, err := s.CreateZone(testOrg, "example.com", ProviderAuthoritative); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.DeleteZone(testOrg, "example.com"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.DeleteZone(testOrg, "example.com"); err == nil {
|
|
t.Fatal("expected error deleting nonexistent zone")
|
|
}
|
|
}
|
|
|
|
func TestCRUDRecords(t *testing.T) {
|
|
s := NewStore()
|
|
if _, err := s.CreateZone(testOrg, "example.com", ProviderAuthoritative); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create
|
|
rec, err := s.CreateRecord(testOrg, "example.com", "www", TypeA, 300, "1.2.3.4", 0, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if rec.Name != "www" || rec.Content != "1.2.3.4" {
|
|
t.Fatalf("unexpected record: %+v", rec)
|
|
}
|
|
|
|
// List
|
|
records, err := s.ListRecords(testOrg, "example.com")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(records) != 1 {
|
|
t.Fatalf("expected 1 record, got %d", len(records))
|
|
}
|
|
|
|
// Get
|
|
got, err := s.GetRecord(testOrg, "example.com", rec.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got.ID != rec.ID {
|
|
t.Fatalf("expected ID %s, got %s", rec.ID, got.ID)
|
|
}
|
|
|
|
// Update
|
|
newContent := "5.6.7.8"
|
|
updated, err := s.UpdateRecord(testOrg, "example.com", rec.ID, RecordPatch{Content: &newContent})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if updated.Content != "5.6.7.8" {
|
|
t.Fatalf("expected 5.6.7.8, got %s", updated.Content)
|
|
}
|
|
|
|
// Delete
|
|
if err := s.DeleteRecord(testOrg, "example.com", rec.ID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
records, _ = s.ListRecords(testOrg, "example.com")
|
|
if len(records) != 0 {
|
|
t.Fatalf("expected 0 records after delete, got %d", len(records))
|
|
}
|
|
}
|
|
|
|
func TestCreateRecordInvalidType(t *testing.T) {
|
|
s := NewStore()
|
|
if _, err := s.CreateZone(testOrg, "example.com", ProviderAuthoritative); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := s.CreateRecord(testOrg, "example.com", "www", RecordType("BOGUS"), 300, "1.2.3.4", 0, false); err == nil {
|
|
t.Fatal("expected error for invalid record type")
|
|
}
|
|
}
|
|
|
|
func TestCreateRecordNoZone(t *testing.T) {
|
|
s := NewStore()
|
|
if _, err := s.CreateRecord(testOrg, "nope.com", "www", TypeA, 300, "1.2.3.4", 0, false); err == nil {
|
|
t.Fatal("expected error for nonexistent zone")
|
|
}
|
|
}
|
|
|
|
func TestLookup(t *testing.T) {
|
|
s := NewStore()
|
|
if _, err := s.CreateZone(testOrg, "example.com", ProviderAuthoritative); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := s.CreateRecord(testOrg, "example.com", "www", TypeA, 300, "1.2.3.4", 0, false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := s.CreateRecord(testOrg, "example.com", "@", TypeA, 300, "10.0.0.1", 0, false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Lookup www.example.com.
|
|
recs := s.Lookup("www.example.com.", "A")
|
|
if len(recs) != 1 {
|
|
t.Fatalf("expected 1 record for www, got %d", len(recs))
|
|
}
|
|
if recs[0].Content != "1.2.3.4" {
|
|
t.Fatalf("expected 1.2.3.4, got %s", recs[0].Content)
|
|
}
|
|
|
|
// Lookup apex.
|
|
recs = s.Lookup("example.com.", "A")
|
|
if len(recs) != 1 {
|
|
t.Fatalf("expected 1 record for apex, got %d", len(recs))
|
|
}
|
|
|
|
// Lookup miss.
|
|
recs = s.Lookup("nope.example.com.", "A")
|
|
if len(recs) != 0 {
|
|
t.Fatalf("expected 0 records, got %d", len(recs))
|
|
}
|
|
}
|
|
|
|
func TestZoneNames(t *testing.T) {
|
|
s := NewStore()
|
|
s.CreateZone(testOrg, "a.com", ProviderAuthoritative)
|
|
s.CreateZone(testOrg, "b.com", ProviderAuthoritative)
|
|
|
|
names := s.ZoneNames()
|
|
if len(names) != 2 {
|
|
t.Fatalf("expected 2 zone names, got %d", len(names))
|
|
}
|
|
}
|