The zone page and jarl zone show list the addresses only other views' records use (ptr_other_views_list), with the name each would get and the view it comes from, and say plainly that they aren't missing here. The page's button reads 'Add PTR records for them in view X'. zone show lists the missing PTRs too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
122 lines
6.8 KiB
Go
122 lines
6.8 KiB
Go
package server
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// The DNSSEC page says where each zone's DS record goes.
|
|
func TestDNSSECWhereDSGoes(t *testing.T) {
|
|
e := newAPIEnv(t)
|
|
e.do("POST", "/views", e.token, map[string]any{"name": "internal", "match_clients": []string{"10.0.0.0/8"}}, 201)
|
|
e.do("POST", "/views", e.token, map[string]any{"name": "external"}, 201)
|
|
zone := func(name, view string) {
|
|
e.do("POST", "/zones", e.token, map[string]any{"name": name, "view": view, "nameservers": []string{"ns1.example.net."}}, 201)
|
|
}
|
|
zone("adbraun.lol", "external")
|
|
zone("lab.adbraun.lol", "external")
|
|
zone("dev.adbraun.lol", "internal") // its parent only in external
|
|
zone("sub.example.com", "external")
|
|
zone("10.0.10.0/24", "internal")
|
|
zone("home.lan", "internal")
|
|
for _, c := range []struct{ zone, at, place, parent string }{
|
|
{"adbraun.lol?view=external", "registrar", "at your registrar", ""},
|
|
{"lab.adbraun.lol?view=external", "parent_zone", "in the parent zone adbraun.lol", "adbraun.lol."},
|
|
{"dev.adbraun.lol?view=internal", "parent_zone", "in the parent zone adbraun.lol (view external)", "adbraun.lol."},
|
|
{"sub.example.com?view=external", "parent", "in its parent zone (most likely example.com)", ""},
|
|
{"10.0.10.in-addr.arpa?view=internal", "reverse", "your ISP", ""},
|
|
{"home.lan?view=internal", "none", "", ""},
|
|
} {
|
|
r := e.do("GET", "/zones/"+strings.Replace(c.zone, "?", "/dnssec?", 1), e.token, nil, 200)
|
|
place, _ := r["ds_place"].(string)
|
|
parent, _ := r["parent_zone"].(string)
|
|
if r["ds_at"] != c.at || !strings.Contains(place, c.place) || (c.place == "" && place != "") || parent != c.parent {
|
|
t.Errorf("%s: ds_at %v, ds_place %q, parent_zone %q", c.zone, r["ds_at"], place, parent)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A zone lists the zones of its name in the other views.
|
|
func TestZoneInViews(t *testing.T) {
|
|
e := newAPIEnv(t)
|
|
e.do("POST", "/views", e.token, map[string]any{"name": "internal", "match_clients": []string{"10.0.0.0/8"}}, 201)
|
|
e.do("POST", "/views", e.token, map[string]any{"name": "external"}, 201)
|
|
e.do("POST", "/zones", e.token, map[string]any{"name": "adbraun.lol", "view": "internal", "nameservers": []string{"ns1.example.net."}}, 201)
|
|
e.do("POST", "/zones", e.token, map[string]any{"name": "adbraun.lol", "view": "external", "nameservers": []string{"ns1.example.net."}}, 201)
|
|
z := e.do("GET", "/zones/adbraun.lol?view=internal", e.token, nil, 200)
|
|
in, _ := z["in_views"].([]any)
|
|
if len(in) != 1 || in[0].(map[string]any)["view"] != "external" || in[0].(map[string]any)["type"] != "primary" {
|
|
t.Errorf("in_views = %v", z["in_views"])
|
|
}
|
|
}
|
|
|
|
// A token for records in one view of a zone that is in two: the zone is
|
|
// found without naming the view, and view list names the views it can
|
|
// see zones in, without their settings.
|
|
func TestOneViewToken(t *testing.T) {
|
|
e := newAPIEnv(t)
|
|
e.do("POST", "/views", e.token, map[string]any{"name": "internal", "match_clients": []string{"10.0.0.0/8"}}, 201)
|
|
e.do("POST", "/views", e.token, map[string]any{"name": "external"}, 201)
|
|
for _, v := range []string{"internal", "external"} {
|
|
e.do("POST", "/zones", e.token, map[string]any{"name": "adbraun.lol", "view": v, "nameservers": []string{"ns1.example.net."}}, 201)
|
|
}
|
|
e.do("POST", "/users", e.token, map[string]any{"username": "certbot", "password": "", "grant_strings": []string{"records:write zones=adbraun.lol views=external names=_acme-challenge.* types=TXT"}}, 201)
|
|
var uid int64
|
|
for _, u := range e.list("/users", e.token) {
|
|
if u["username"] == "certbot" {
|
|
uid = int64(u["id"].(float64))
|
|
}
|
|
}
|
|
tok := e.do("POST", "/users/"+itoa(uid)+"/tokens", e.token, map[string]any{"name": "certbot"}, 201)["token"].(string)
|
|
|
|
if z := e.do("GET", "/zones/adbraun.lol", tok, nil, 200); z["view"] != "external" {
|
|
t.Errorf("zone without a view: %v", z["view"])
|
|
}
|
|
e.do("GET", "/zones/adbraun.lol/records", tok, nil, 200)
|
|
views := e.list("/views", tok)
|
|
if len(views) != 1 || views[0]["name"] != "external" || views[0]["limited"] != true || views[0]["match_clients"] != nil {
|
|
t.Errorf("views = %v", views)
|
|
}
|
|
// The admin still has to say which.
|
|
e.do("GET", "/zones/adbraun.lol", e.token, nil, 400)
|
|
}
|
|
|
|
// A reverse zone lists the PTR records it lacks, not just how many.
|
|
func TestPTRMissingList(t *testing.T) {
|
|
e := newAPIEnv(t)
|
|
e.do("POST", "/zones", e.token, map[string]any{"name": "adbraun.lol", "nameservers": []string{"ns1.example.net."},
|
|
"records": []map[string]any{{"name": "nas", "type": "A", "data": "10.60.60.5"}}}, 201)
|
|
e.do("POST", "/zones", e.token, map[string]any{"name": "10.60.60.0/24", "nameservers": []string{"ns1.example.net."}}, 201)
|
|
z := e.do("GET", "/zones/60.60.10.in-addr.arpa", e.token, nil, 200)
|
|
list, _ := z["ptr_missing_list"].([]any)
|
|
if z["ptr_missing"] != float64(1) || len(list) != 1 || list[0].(map[string]any)["address"] != "10.60.60.5" || list[0].(map[string]any)["target"] != "nas.adbraun.lol." {
|
|
t.Errorf("ptr_missing %v, list %v", z["ptr_missing"], z["ptr_missing_list"])
|
|
}
|
|
}
|
|
|
|
// An address only another view's records use isn't missing from a
|
|
// reverse zone: it is counted apart, and filled only when asked.
|
|
func TestPTROtherViews(t *testing.T) {
|
|
e := newAPIEnv(t)
|
|
e.do("POST", "/views", e.token, map[string]any{"name": "internal", "match_clients": []string{"10.0.0.0/8"}}, 201)
|
|
e.do("POST", "/views", e.token, map[string]any{"name": "vps"}, 201)
|
|
e.do("POST", "/zones", e.token, map[string]any{"name": "adbraun.lol", "view": "internal", "nameservers": []string{"ns1.example.net."},
|
|
"records": []map[string]any{{"name": "claude", "type": "A", "data": "10.0.10.101"}}}, 201)
|
|
for _, v := range []string{"internal", "vps"} {
|
|
e.do("POST", "/zones", e.token, map[string]any{"name": "10.0.10.0/24", "view": v, "nameservers": []string{"ns1.example.net."}}, 201)
|
|
}
|
|
if z := e.do("GET", "/zones/10.0.10.in-addr.arpa?view=internal", e.token, nil, 200); z["ptr_missing"] != float64(1) || z["ptr_other_views"] != nil {
|
|
t.Errorf("internal: ptr_missing %v, ptr_other_views %v", z["ptr_missing"], z["ptr_other_views"])
|
|
}
|
|
if z := e.do("GET", "/zones/10.0.10.in-addr.arpa?view=vps", e.token, nil, 200); z["ptr_missing"] != nil || z["ptr_other_views"] != float64(1) {
|
|
t.Errorf("vps: ptr_missing %v, ptr_other_views %v", z["ptr_missing"], z["ptr_other_views"])
|
|
} else if l, _ := z["ptr_other_views_list"].([]any); len(l) != 1 || l[0].(map[string]any)["address"] != "10.0.10.101" || l[0].(map[string]any)["target"] != "claude.adbraun.lol." || l[0].(map[string]any)["from"] != "internal" {
|
|
t.Errorf("vps: ptr_other_views_list %v", z["ptr_other_views_list"])
|
|
}
|
|
e.do("POST", "/zones/10.0.10.in-addr.arpa/fill-ptr?view=vps", e.token, nil, 400)
|
|
e.do("POST", "/zones/10.0.10.in-addr.arpa/fill-ptr?view=vps&other_views=1", e.token, nil, 201)
|
|
if z := e.do("GET", "/zones/10.0.10.in-addr.arpa?view=vps", e.token, nil, 200); z["ptr_other_views"] != nil {
|
|
t.Errorf("vps after filling: ptr_other_views %v", z["ptr_other_views"])
|
|
}
|
|
}
|