package engine import ( "slices" "strings" "testing" "jarl/internal/model" ) // ptrs returns a reverse zone's PTR records in a view as "owner target", // with " disabled" when so. func (env *testEnv) ptrs(view, zone string) []string { env.t.Helper() s := env.state() z := s.FindZone(zone, s.ViewByName(view).ID) if z == nil { env.t.Fatalf("no zone %s in %s", zone, view) } var out []string for _, r := range s.ZoneRecords(z.ID) { if r.Type == "PTR" { d := r.Name + " " + r.Data if r.Disabled { d += " disabled" } out = append(out, d) } } return out } func TestReverseFillAndSuggest(t *testing.T) { env := linkedEnv(t) env.submit(env.admin, recIn("internal", "@", "A", "192.0.2.25"), recIn("internal", "nas", "A", "10.0.10.20"), recIn("internal", "files", "A", "10.0.10.20"), recIn("internal", "nas", "AAAA", "fdab:abab:abab:10::20"), recIn("internal", "*.dev", "A", "10.0.10.99"), recIn("internal", "lo", "A", "127.0.0.1")) sugg := ReverseSuggestions(env.state(), env.e.Exposures(env.state())) var got []string for _, s := range sugg { got = append(got, s.View+" "+s.Prefix+" "+s.Zone) } want(t, "suggestions", got, "internal 10.0.10.0/24 10.0.10.in-addr.arpa", "internal 192.0.2.10/32 10.2.0.192.in-addr.arpa", "internal 192.0.2.25/32 25.2.0.192.in-addr.arpa", "internal fdab:abab:abab::/48 b.a.b.a.b.a.b.a.b.a.d.f.ip6.arpa") if sugg[0].Fill != 1 || sugg[0].Samples[0] != "10.0.10.20 files.example.com" { t.Errorf("10.0.10.0/24: %+v", sugg[0]) } // No view given: the private view whose records use the addresses. res := env.submit(env.admin, NewRequest(OpZoneCreate, 0, ZoneInput{Name: model.Ptr("10.0.10.0/24"), Fill: true})) want(t, "10.0.10/24", env.ptrs("internal", "10.0.10.in-addr.arpa."), "20.10.0.10.in-addr.arpa. files.example.com.") if !slices.ContainsFunc(res.Warnings, func(w string) bool { return strings.Contains(w, "10.0.10.20 has several names") }) { t.Errorf("warnings %q", res.Warnings) } // The mail server's name beats the domain's own. env.submit(env.admin, NewRequest(OpZoneCreate, 0, ZoneInput{Name: model.Ptr("192.0.2.25/32"), Fill: true})) want(t, "192.0.2.25", env.ptrs("internal", "25.2.0.192.in-addr.arpa."), "25.2.0.192.in-addr.arpa. mail.example.com.") // Filling an existing zone; then there is nothing left to add. env.submit(env.admin, NewRequest(OpZoneCreate, 0, ZoneInput{Name: model.Ptr("fdab:abab:abab::/48")})) st := env.state() z6 := st.FindZone("b.a.b.a.b.a.b.a.b.a.d.f.ip6.arpa.", st.ViewByName("internal").ID) env.submit(env.admin, NewRequest(OpZoneFillPTR, z6.ID, LinkInput{})) if p := env.ptrs("internal", z6.Name); len(p) != 1 || !strings.HasSuffix(p[0], " nas.example.com.") { t.Errorf("ip6 fill: %q", p) } if e := env.submitErr(env.admin, NewRequest(OpZoneFillPTR, z6.ID, LinkInput{})); !strings.Contains(e.Message, "nothing to add") { t.Errorf("second fill: %v", e) } got = got[:0] for _, s := range ReverseSuggestions(env.state(), env.e.Exposures(env.state())) { got = append(got, s.Prefix) } want(t, "suggestions left", got, "192.0.2.10/32") // Addresses that two private views use: the view must be given. env.submit(env.admin, NewRequest(OpViewCreate, 0, ViewInput{Name: model.Ptr("lab"), MatchClients: &[]string{"172.16.0.0/12"}})) env.submit(env.admin, NewRequest(OpZoneCreate, 0, ZoneInput{Name: model.Ptr("lab.test"), View: model.Ptr("lab")}), NewRequest(OpRecordCreate, 0, RecordInput{Zone: "lab.test", View: "lab", Name: model.Ptr("x"), Type: model.Ptr("A"), Data: model.Ptr("192.0.2.10")})) if e := env.submitErr(env.admin, NewRequest(OpZoneCreate, 0, ZoneInput{Name: model.Ptr("192.0.2.10/32")})); !strings.Contains(e.Message, "views internal and lab all have addresses") { t.Errorf("two views: %v", e) } } func TestAutoPTR(t *testing.T) { env := linkedEnv(t) env.submit(env.admin, NewRequest(OpZoneCreate, 0, ZoneInput{Name: model.Ptr("10.0.10.0/24"), View: model.Ptr("internal")})) st := env.state() zid := st.FindZone("example.com.", st.ViewByName("internal").ID).ID env.submit(env.admin, NewRequest(OpZoneUpdate, zid, ZoneInput{AutoPTR: model.Ptr(true)})) rz := "10.0.10.in-addr.arpa." res := env.submit(env.admin, recIn("internal", "host", "A", "10.0.10.30"), recIn("internal", "pub", "A", "203.0.113.5")) want(t, "added", env.ptrs("internal", rz), "30.10.0.10.in-addr.arpa. host.example.com.") if len(res.Warnings) > 0 { t.Errorf("no reverse zone for 203.0.113.5 isn't worth a warning: %q", res.Warnings) } // A second name for the address: its PTR stays. res = env.submit(env.admin, recIn("internal", "alias", "A", "10.0.10.30")) want(t, "second name", env.ptrs("internal", rz), "30.10.0.10.in-addr.arpa. host.example.com.") if len(res.Warnings) > 0 { t.Errorf("warnings %q", res.Warnings) } env.submit(env.admin, NewRequest(OpRecordDelete, env.recordID("internal", "alias", "A"), nil)) // Moved, disabled, enabled, deleted. env.submit(env.admin, NewRequest(OpRecordUpdate, env.recordID("internal", "host", "A"), RecordInput{Data: model.Ptr("10.0.10.31")})) want(t, "moved", env.ptrs("internal", rz), "31.10.0.10.in-addr.arpa. host.example.com.") env.submit(env.admin, NewRequest(OpRecordUpdate, env.recordID("internal", "host", "A"), RecordInput{Disabled: model.Ptr(true)})) want(t, "disabled", env.ptrs("internal", rz), "31.10.0.10.in-addr.arpa. host.example.com. disabled") env.submit(env.admin, NewRequest(OpRecordUpdate, env.recordID("internal", "host", "A"), RecordInput{Disabled: model.Ptr(false)})) want(t, "enabled", env.ptrs("internal", rz), "31.10.0.10.in-addr.arpa. host.example.com.") env.submit(env.admin, NewRequest(OpRecordDelete, env.recordID("internal", "host", "A"), nil)) want(t, "deleted", env.ptrs("internal", rz)) // A whole set replaced. env.submit(env.admin, NewRequest(OpRRSetReplace, 0, RRSetInput{Zone: "example.com", View: "internal", Name: "multi", Type: "A", Data: []string{"10.0.10.40", "10.0.10.41"}})) want(t, "set", env.ptrs("internal", rz), "40.10.0.10.in-addr.arpa. multi.example.com.", "41.10.0.10.in-addr.arpa. multi.example.com.") env.submit(env.admin, NewRequest(OpRRSetReplace, 0, RRSetInput{Zone: "example.com", View: "internal", Name: "multi", Type: "A", Data: []string{"10.0.10.41"}})) want(t, "set shrunk", env.ptrs("internal", rz), "41.10.0.10.in-addr.arpa. multi.example.com.") // Deleting by name and type. env.submit(env.admin, NewRequest(OpRecordDelete, 0, RecordInput{Zone: "example.com", View: "internal", Name: model.Ptr("multi"), Type: model.Ptr("A")})) want(t, "deleted by match", env.ptrs("internal", rz)) // Off again: records come and go alone. env.submit(env.admin, NewRequest(OpZoneUpdate, zid, ZoneInput{AutoPTR: model.Ptr(false)})) env.submit(env.admin, recIn("internal", "quiet", "A", "10.0.10.50")) want(t, "off", env.ptrs("internal", rz)) }