Linked zones: PTR records follow each view, deleting an override stays deleted
Auto-PTR (and --ptr) changed PTR records in every linked view, whatever view the address record was in. ptrSync now compares each touched A/AAAA set before and after, per view, and changes only that view's reverse zone. PTR records in linked reverse zones are local (fill too); a shared PTR set is made local in each view before one view changes it. Deleting a linked zone's local records no longer brings the shared ones back into the view: the name and type are gone from that view and the other views keep theirs, local to each. Share still restores. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e38390df7d
commit
65086e314a
9 files changed
+453
-238
No files matched your search
@@ -303,3 +303,102 @@ func TestReverseFromOtherView(t *testing.T) {
|
||||
t.Errorf("warnings %q", res.Warnings)
|
||||
}
|
||||
}
|
||||
|
||||
// ptrsMarked is ptrs with S (shared) or L (local) after each record.
|
||||
func (env *testEnv) ptrsMarked(view, zone string) []string {
|
||||
env.t.Helper()
|
||||
s := env.state()
|
||||
z := s.FindZone(zone, s.ViewByName(view).ID)
|
||||
var out []string
|
||||
for _, r := range s.ZoneRecords(z.ID) {
|
||||
if r.Type == "PTR" {
|
||||
mark := " L"
|
||||
if r.SharedID != 0 {
|
||||
mark = " S"
|
||||
}
|
||||
out = append(out, strings.TrimSuffix(r.Name, ".10.0.10.in-addr.arpa.")+" "+strings.TrimSuffix(r.Data, ".example.com.")+mark)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Auto-PTR with linked forward and reverse zones: PTR records follow each
|
||||
// view's own address records, and are local to their view.
|
||||
func TestAutoPTRLinked(t *testing.T) {
|
||||
env := linkedEnv(t)
|
||||
env.submit(env.admin, NewRequest(OpZoneCreate, 0, ZoneInput{Name: model.Ptr("example.com"), View: model.Ptr("external"), LinkWith: "internal"}))
|
||||
env.submit(env.admin, NewRequest(OpZoneCreate, 0, ZoneInput{Name: model.Ptr("10.0.10.0/24"), View: model.Ptr("internal")}))
|
||||
env.submit(env.admin, NewRequest(OpZoneCreate, 0, ZoneInput{Name: model.Ptr("10.0.10.0/24"), View: model.Ptr("external"), LinkWith: "internal"}))
|
||||
st := env.state()
|
||||
for _, v := range []string{"internal", "external"} {
|
||||
env.submit(env.admin, NewRequest(OpZoneUpdate, st.FindZone("example.com.", st.ViewByName(v).ID).ID, ZoneInput{AutoPTR: model.Ptr(true)}))
|
||||
}
|
||||
rz := "10.0.10.in-addr.arpa."
|
||||
local := func(view, name, data string) Request {
|
||||
r := recIn(view, name, "A", data)
|
||||
var in RecordInput
|
||||
_ = decode(r.Data, &in)
|
||||
in.Local = model.Ptr(true)
|
||||
return NewRequest(OpRecordCreate, 0, in)
|
||||
}
|
||||
|
||||
// A local record: a local PTR record in its own view only.
|
||||
env.submit(env.admin, local("internal", "a", "10.0.10.1"))
|
||||
want(t, "local a internal", env.ptrsMarked("internal", rz), "1 a L")
|
||||
want(t, "local a external", env.ptrsMarked("external", rz))
|
||||
|
||||
// The other view adds the same name (shared, as it has no local
|
||||
// one): its own PTR record; deleting it in one view leaves the other.
|
||||
env.submit(env.admin, recIn("external", "a", "A", "10.0.10.1"))
|
||||
want(t, "both internal", env.ptrsMarked("internal", rz), "1 a L")
|
||||
want(t, "both external", env.ptrsMarked("external", rz), "1 a L")
|
||||
env.submit(env.admin, NewRequest(OpRecordDelete, env.recordID("external", "a", "A"), nil))
|
||||
want(t, "deleted in external: internal", env.ptrsMarked("internal", rz), "1 a L")
|
||||
want(t, "deleted in external: external", env.ptrsMarked("external", rz))
|
||||
|
||||
// Both local; one changes its address, the other keeps its PTR.
|
||||
env.submit(env.admin, local("external", "a", "10.0.10.1"))
|
||||
env.submit(env.admin, NewRequest(OpRecordUpdate, env.recordID("internal", "a", "A"), RecordInput{Data: model.Ptr("10.0.10.2")}))
|
||||
want(t, "moved internal", env.ptrsMarked("internal", rz), "2 a L")
|
||||
want(t, "moved external", env.ptrsMarked("external", rz), "1 a L")
|
||||
// Disabling in one view disables there only.
|
||||
env.submit(env.admin, NewRequest(OpRecordUpdate, env.recordID("external", "a", "A"), RecordInput{Disabled: model.Ptr(true)}))
|
||||
want(t, "disabled external", env.ptrs("external", rz), "1.10.0.10.in-addr.arpa. a.example.com. disabled")
|
||||
want(t, "disabled: internal", env.ptrsMarked("internal", rz), "2 a L")
|
||||
env.submit(env.admin, NewRequest(OpRecordDelete, env.recordID("external", "a", "A"), nil))
|
||||
env.submit(env.admin, NewRequest(OpRecordDelete, env.recordID("internal", "a", "A"), nil))
|
||||
want(t, "gone internal", env.ptrsMarked("internal", rz))
|
||||
want(t, "gone external", env.ptrsMarked("external", rz))
|
||||
|
||||
// A shared record: a local PTR in each view; deleting it removes both.
|
||||
env.submit(env.admin, recIn("internal", "b", "A", "10.0.10.3"))
|
||||
want(t, "shared internal", env.ptrsMarked("internal", rz), "3 b L")
|
||||
want(t, "shared external", env.ptrsMarked("external", rz), "3 b L")
|
||||
// Overridden in one view with another address: that view's PTR moves.
|
||||
env.submit(env.admin, NewRequest(OpRecordUpdate, env.recordID("external", "b", "A"), RecordInput{Local: model.Ptr(true), Data: model.Ptr("10.0.10.4")}))
|
||||
want(t, "override internal", env.ptrsMarked("internal", rz), "3 b L")
|
||||
want(t, "override external", env.ptrsMarked("external", rz), "4 b L")
|
||||
// Sharing internal's version: external gets it back, and its PTR.
|
||||
env.submit(env.admin, NewRequest(OpRecordUpdate, env.recordID("internal", "b", "A"), RecordInput{Local: model.Ptr(false)}))
|
||||
want(t, "shared again internal", env.ptrsMarked("internal", rz), "3 b L")
|
||||
want(t, "shared again external", env.ptrsMarked("external", rz), "3 b L")
|
||||
// The override deleted: external goes without b (and its PTR);
|
||||
// internal keeps it, local now.
|
||||
env.submit(env.admin, NewRequest(OpRecordUpdate, env.recordID("external", "b", "A"), RecordInput{Local: model.Ptr(true), Data: model.Ptr("10.0.10.4")}))
|
||||
env.submit(env.admin, NewRequest(OpRecordDelete, env.recordID("external", "b", "A"), nil))
|
||||
want(t, "override gone internal", env.ptrsMarked("internal", rz), "3 b L")
|
||||
want(t, "override gone external", env.ptrsMarked("external", rz))
|
||||
env.submit(env.admin, NewRequest(OpRecordDelete, env.recordID("internal", "b", "A"), nil))
|
||||
want(t, "b gone internal", env.ptrsMarked("internal", rz))
|
||||
|
||||
// A shared PTR record from before (shared by hand) becomes local in
|
||||
// each view when one view's address goes.
|
||||
env.submit(env.admin, recIn("internal", "c", "A", "10.0.10.5"))
|
||||
env.submit(env.admin, NewRequest(OpRecordDelete, 0, RecordInput{Zone: rz, View: "internal", Name: model.Ptr("5"), Type: model.Ptr("PTR")}))
|
||||
env.submit(env.admin, NewRequest(OpRecordDelete, 0, RecordInput{Zone: rz, View: "external", Name: model.Ptr("5"), Type: model.Ptr("PTR")}))
|
||||
env.submit(env.admin, NewRequest(OpRecordCreate, 0, RecordInput{Zone: rz, View: "internal", Name: model.Ptr("5"), Type: model.Ptr("PTR"), Data: model.Ptr("c.example.com."), Local: model.Ptr(false)}))
|
||||
want(t, "shared ptr external", env.ptrsMarked("external", rz), "5 c S")
|
||||
env.submit(env.admin, NewRequest(OpRecordUpdate, env.recordID("external", "c", "A"), RecordInput{Local: model.Ptr(true), Data: model.Ptr("10.0.10.6")}))
|
||||
want(t, "unshared internal", env.ptrsMarked("internal", rz), "5 c L")
|
||||
want(t, "unshared external", env.ptrsMarked("external", rz), "6 c L")
|
||||
}
|
||||
Reference in new issue
Block a user