Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71d9a06e43 | ||
|
|
2fa9828bcf |
No files matched your search
+3
-1
@@ -52,7 +52,9 @@ All notable changes to Jarl are documented here. The format follows
|
||||
(`fill_other_views`, `?other_views=1`, a checkbox in the New reverse
|
||||
zone dialog), so one view's host names don't leak into another. When
|
||||
there is nothing else to fill, Jarl says how many there are and how to
|
||||
add them.
|
||||
add them. The page and `jarl zone show` list those addresses with the
|
||||
name each would get, and the view it comes from
|
||||
(`ptr_other_views_list`); `jarl zone show` lists the missing ones too.
|
||||
|
||||
- Records, zones and validation findings sort numeric labels by value, so
|
||||
`2.10.0.10.in-addr.arpa` comes before `101.10.0.10.in-addr.arpa` in the
|
||||
|
||||
@@ -203,7 +203,7 @@ review step.
|
||||
## Status
|
||||
|
||||
Version 0.6.0 is the current release, and 0.6.1 is in testing
|
||||
(0.6.1-rc.2): it gets certificates onto your other hosts (`jarl cert
|
||||
(0.6.1-rc.3): it gets certificates onto your other hosts (`jarl cert
|
||||
get`: Jarl obtains and renews them, a host downloads them and runs a
|
||||
hook, a daily timer keeps them current), exports certificates, fixes
|
||||
PTR records and overrides in linked zones, and makes one-view tokens
|
||||
|
||||
+42
-1
@@ -8,6 +8,7 @@ import (
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"os"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
@@ -100,9 +101,16 @@ func zoneCmd() *cobra.Command {
|
||||
fmt.Fprintf(w, "Network:\t%s\n", z.Prefix)
|
||||
if z.PTRMissing > 0 {
|
||||
fmt.Fprintf(w, "Missing PTRs:\t%d (addresses the view's records use here; add them: jarl zone fill-ptr %s)\n", z.PTRMissing, args[0])
|
||||
printPTRFills(w, z.PTRMissingList, z.PTRMissing)
|
||||
}
|
||||
if z.PTROtherViews > 0 {
|
||||
fmt.Fprintf(w, "Other views:\t%d addresses here only other views' records use, without a PTR record (to name them from there: jarl zone fill-ptr %s --other-views)\n", z.PTROtherViews, args[0])
|
||||
what := "addresses"
|
||||
if z.PTROtherViews == 1 {
|
||||
what = "address"
|
||||
}
|
||||
fmt.Fprintf(w, "Other views:\t%d %s with A or AAAA records only in %s, so not missing here (add PTRs for them in this view too: jarl zone fill-ptr %s --other-views)\n",
|
||||
z.PTROtherViews, what, fromViews(z.PTROtherViewsList), args[0])
|
||||
printPTRFills(w, z.PTROtherViewsList, z.PTROtherViews)
|
||||
}
|
||||
}
|
||||
if len(z.LinkedWith) > 0 {
|
||||
@@ -1174,6 +1182,39 @@ func terminalWidth() int {
|
||||
return width
|
||||
}
|
||||
|
||||
// printPTRFills lists PTR records a reverse zone lacks under zone show's
|
||||
// line about them: address -> name, and the view the name is from.
|
||||
func printPTRFills(w io.Writer, fills []api.PTRFill, total int) {
|
||||
for _, f := range fills {
|
||||
from := ""
|
||||
if f.From != "" {
|
||||
from = " (view " + f.From + ")"
|
||||
}
|
||||
fmt.Fprintf(w, "\t %s -> %s%s\n", f.Address, dnsname.Display(f.Target), from)
|
||||
}
|
||||
if total > len(fills) {
|
||||
fmt.Fprintf(w, "\t and %d more\n", total-len(fills))
|
||||
}
|
||||
}
|
||||
|
||||
// fromViews names the views PTR fills take their names from: "view
|
||||
// internal", "views internal and lab".
|
||||
func fromViews(fills []api.PTRFill) string {
|
||||
var vs []string
|
||||
for _, f := range fills {
|
||||
if f.From != "" && !slices.Contains(vs, f.From) {
|
||||
vs = append(vs, f.From)
|
||||
}
|
||||
}
|
||||
switch len(vs) {
|
||||
case 0:
|
||||
return "other views"
|
||||
case 1:
|
||||
return "view " + vs[0]
|
||||
}
|
||||
return "views " + strings.Join(vs[:len(vs)-1], ", ") + " and " + vs[len(vs)-1]
|
||||
}
|
||||
|
||||
func zoneFillPTRCmd() *cobra.Command {
|
||||
var view string
|
||||
var otherViews bool
|
||||
|
||||
+4
-1
@@ -215,7 +215,10 @@ A reverse zone created without a view goes to the private view whose
|
||||
records use its addresses. Zones come back with `prefix` (reverse zones:
|
||||
the network), `ptr_missing` (addresses in it that the view's A and AAAA
|
||||
records use, without a PTR record) and `ptr_other_views` (addresses in it
|
||||
without a PTR record that only other views' records use; not missing).
|
||||
without a PTR record that only other views' records use; not missing),
|
||||
with the first 25 of each in `ptr_missing_list` and
|
||||
`ptr_other_views_list` (`address`, `name`, `target`, and `from`, the view
|
||||
the name comes from).
|
||||
|
||||
`/serials` and `/delegation` send DNS queries from the server, and only
|
||||
when called ([operations.md](operations.md#checking-name-servers-and-the-delegation)).
|
||||
|
||||
+13
-3
@@ -147,9 +147,19 @@ zone show` shows how many as `Missing PTRs`; the API lists them in
|
||||
`ptr_missing_list`. Only the zone's own view's records count: an address
|
||||
that only another view's records use isn't missing (say internal's
|
||||
`claude.adbraun.lol` and the vps view's copy of `10.0.10.in-addr.arpa`).
|
||||
Those are shown apart, as `Other views` in `jarl zone show`
|
||||
(`ptr_other_views` in the API) and a quiet note with a **Name them from
|
||||
there** button on the page; filling never adds them unasked.
|
||||
Those are listed apart, with the name each would get: `Other views` in
|
||||
`jarl zone show`, `ptr_other_views` and `ptr_other_views_list` in the
|
||||
API, and a quiet note on the page with an **Add PTR records for them in
|
||||
view vps** button. Filling never adds them unasked. `jarl zone show`
|
||||
lists the missing ones too:
|
||||
|
||||
```
|
||||
Missing PTRs: 1 (addresses the view's records use here; add them: jarl zone fill-ptr 10.0.10.0/24)
|
||||
10.0.10.7 -> printer.adbraun.lol
|
||||
Other views: 2 addresses with A or AAAA records only in view internal, so not missing here (add PTRs for them in this view too: jarl zone fill-ptr 10.0.10.0/24 --other-views)
|
||||
10.0.10.26 -> gomft.adbraun.lol (view internal)
|
||||
10.0.10.101 -> claude.adbraun.lol (view internal)
|
||||
```
|
||||
|
||||
## Keeping PTR records in step
|
||||
|
||||
|
||||
@@ -50,8 +50,10 @@ type Zone struct {
|
||||
PTRMissingList []PTRFill `json:"ptr_missing_list,omitempty"`
|
||||
// PTROtherViews counts the addresses in it, without a PTR record,
|
||||
// that only other views' records use: not missing, but fill-ptr with
|
||||
// other_views=1 can name them from there.
|
||||
PTROtherViews int `json:"ptr_other_views,omitempty"`
|
||||
// other_views=1 adds them, naming them from there. PTROtherViewsList
|
||||
// are the first 25 (From says which view).
|
||||
PTROtherViews int `json:"ptr_other_views,omitempty"`
|
||||
PTROtherViewsList []PTRFill `json:"ptr_other_views_list,omitempty"`
|
||||
// Pending is set when listing with a changeset: add, change or delete.
|
||||
Pending string `json:"pending,omitempty"`
|
||||
}
|
||||
|
||||
@@ -489,7 +489,7 @@ func (x *expander) zoneCreate(req Request) ([]model.Op, error) {
|
||||
x.warnings = append(x.warnings, fmt.Sprintf("no A or AAAA record of %s has an address in %s, so there was nothing to fill it with", viewOrServer(x.st, vid), dnsname.Display(name)))
|
||||
}
|
||||
if !fillOther {
|
||||
if h := otherViewsHint(x.st, vid, name, moved, len(fills), "to name them from there, fill it with other views (jarl zone fill-ptr ZONE --other-views)"); h != "" {
|
||||
if h := otherViewsHint(x.st, vid, name, moved, len(fills), "to add PTR records for them in this view too: jarl zone fill-ptr ZONE --other-views"); h != "" {
|
||||
x.warnings = append(x.warnings, h)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,11 +207,11 @@ func otherViewsHint(st *model.State, viewID int64, zoneName string, existing []*
|
||||
if n <= 0 {
|
||||
return ""
|
||||
}
|
||||
what := "addresses"
|
||||
what, has, they := "addresses", "have", "they are"
|
||||
if n == 1 {
|
||||
what = "address"
|
||||
what, has, they = "address", "has", "it is"
|
||||
}
|
||||
return fmt.Sprintf("%d %s in %s only other views' records use; %s", n, what, dnsname.Display(zoneName), how)
|
||||
return fmt.Sprintf("%d %s in %s %s A or AAAA records only in other views, so %s not missing here; %s", n, what, dnsname.Display(zoneName), has, they, how)
|
||||
}
|
||||
|
||||
// zoneFillPTR expands zone.fill_ptr: the PTR records a reverse zone lacks.
|
||||
@@ -238,7 +238,7 @@ func (x *expander) zoneFillPTR(req Request) ([]model.Op, error) {
|
||||
if len(fills) == 0 {
|
||||
msg := fmt.Sprintf("nothing to add: every address of %s's A and AAAA records in %s has a PTR record", viewOrServer(x.st, z.ViewID), zoneDesc(x.st, z))
|
||||
if !in.OtherViews {
|
||||
if h := otherViewsHint(x.st, z.ViewID, z.Name, x.st.ZoneRecords(z.ID), 0, "to name them from there, fill with other views (--other-views)"); h != "" {
|
||||
if h := otherViewsHint(x.st, z.ViewID, z.Name, x.st.ZoneRecords(z.ID), 0, "to add PTR records for them in this view too: --other-views"); h != "" {
|
||||
msg += "; " + h
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +110,8 @@ func TestPTROtherViews(t *testing.T) {
|
||||
}
|
||||
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)
|
||||
|
||||
@@ -43,12 +43,19 @@ func (s *Server) presentZone(st *model.State, z *model.Zone, counts map[int64]re
|
||||
if z.Type == model.ZonePrimary {
|
||||
fills := engine.PTRFills(st, z.ViewID, z.Name, st.ZoneRecords(z.ID), false)
|
||||
out.PTRMissing = len(fills)
|
||||
if z.ViewID != 0 {
|
||||
out.PTROtherViews = len(engine.PTRFills(st, z.ViewID, z.Name, st.ZoneRecords(z.ID), true)) - len(fills)
|
||||
}
|
||||
for _, f := range fills[:min(len(fills), 25)] {
|
||||
out.PTRMissingList = append(out.PTRMissingList, api.PTRFill(f))
|
||||
}
|
||||
if z.ViewID != 0 {
|
||||
for _, f := range engine.PTRFills(st, z.ViewID, z.Name, st.ZoneRecords(z.ID), true) {
|
||||
if f.From == "" {
|
||||
continue
|
||||
}
|
||||
if out.PTROtherViews++; out.PTROtherViews <= 25 {
|
||||
out.PTROtherViewsList = append(out.PTROtherViewsList, api.PTRFill(f))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, o := range st.LinkedWith(z) {
|
||||
|
||||
@@ -73,9 +73,20 @@ export async function render(el, params, query) {
|
||||
f.from ? h("span", { class: "muted" }, " (from view " + f.from + ")") : null,
|
||||
f.others && f.others.length ? h("span", { class: "muted" }, " (also " + f.others.map(display).join(", ") + ")") : null)),
|
||||
zone.ptr_missing > (zone.ptr_missing_list || []).length ? h("li", { class: "muted" }, "and " + (zone.ptr_missing - zone.ptr_missing_list.length) + " more") : null))
|
||||
: zone.ptr_other_views && can("zones", true) ? h("div", { class: "muted small" },
|
||||
plural(zone.ptr_other_views, "address", "addresses") + " in " + zone.prefix + " only other views' records use, without a PTR record here. ",
|
||||
h("button", { class: "small", title: "Adds PTR records naming them from the other views' records, publishing those names in view " + zone.view, onclick: () => fillPTR(zone, true) }, "Name them from there")) : null;
|
||||
: null;
|
||||
// Addresses only other views have records for: not missing here, but
|
||||
// they can be added (publishing those views' names in this one).
|
||||
const others = zone.ptr_other_views_list || [];
|
||||
const fromViews = [...new Set(others.map((f) => f.from))];
|
||||
const otherNote = !zone.disabled && zone.ptr_other_views && can("zones", true) ? h("div", { class: "muted small" },
|
||||
plural(zone.ptr_other_views, "address", "addresses") + " in " + zone.prefix + " " + (zone.ptr_other_views === 1 ? "has" : "have") + " A or AAAA records only in view " + fromViews.join(", ") +
|
||||
", not in view " + zone.view + ", so " + (zone.ptr_other_views === 1 ? "it isn't" : "they aren't") + " counted as missing here. ",
|
||||
h("button", { class: "small", title: "Clients of view " + zone.view + " will then get these names, which come from view " + fromViews.join(", "), onclick: () => fillPTR(zone, true) },
|
||||
"Add PTR records for " + (zone.ptr_other_views === 1 ? "it" : "them") + " in view " + zone.view),
|
||||
h("ul", { class: "small ptr-missing" }, others.map((f) => h("li", null,
|
||||
h("span", { class: "mono" }, f.address), " → ", h("span", { class: "mono" }, display(f.target)),
|
||||
fromViews.length > 1 ? h("span", { class: "muted" }, " (view " + f.from + ")") : null)),
|
||||
zone.ptr_other_views > others.length ? h("li", { class: "muted" }, "and " + (zone.ptr_other_views - others.length) + " more") : null)) : null;
|
||||
const tabBar = h("div", { class: "tabs" }, tabs.map(([k, label]) => h("a", { href: "#/zones/" + id + "/" + k, class: k === tab ? "active" : "" }, label)));
|
||||
// The header and tabs show at once. Below them, switching tabs keeps the
|
||||
// previous tab's content (faded) until the new one is loaded, rather
|
||||
@@ -86,7 +97,7 @@ export async function render(el, params, query) {
|
||||
holder.classList.add("stale");
|
||||
body.className = "zone-body";
|
||||
body.dataset.zone = id;
|
||||
mount(el, head, banner, tabBar, holder);
|
||||
mount(el, head, banner, otherNote, tabBar, holder);
|
||||
if (tab === "records") await recordsTab(body, zone, query);
|
||||
else if (tab === "settings") await settingsTab(body, zone);
|
||||
else if (tab === "options") await optionsEditor(body, { scope: "zone", scope_id: zone.id, catalogScope: "zone:" + zone.type, title: "Zone options" });
|
||||
|
||||
+3
-2
@@ -706,10 +706,11 @@ contains "nothing to add"
|
||||
# The same network in another view: internal's hosts aren't missing there,
|
||||
# and are named from internal only when asked.
|
||||
run "the network in external too, filled" 0 $J zone create 10.99.1.0/24 --view external --fill
|
||||
contains "only other views' records use"
|
||||
contains "A or AAAA records only in other views"
|
||||
run "nothing missing there" 0 $J zone show 1.99.10.in-addr.arpa --view external
|
||||
not_contains "Missing PTRs"
|
||||
contains "^Other views: *2 addresses"
|
||||
contains "^Other views: *2 addresses with A or AAAA records only in view internal"
|
||||
contains "10.99.1.11 -> pi.revtest.test (view internal)"
|
||||
run "fill-ptr there: nothing of its own (exit 6)" 6 $J zone fill-ptr 10.99.1.0/24 --view external
|
||||
contains "--other-views"
|
||||
run "fill-ptr --other-views" 0 $J zone fill-ptr 10.99.1.0/24 --view external --other-views
|
||||
|
||||
Reference in new issue
Block a user