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"]) } 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"]) } }