Multi-server CLI, more reverse zones, and a fix for new view directories
- CLI: name several servers and groups in the config file; select them with -s/--server NAME|GROUP|all|URL or JARL_SERVER. A command aimed at several servers is dry-run on all of them first and only runs if every one accepts it. Named servers always use their own token. - Reverse zones: /32 and /128 get single-address zones; networks between octet or nibble boundaries (e.g. a /22) are covered by several zones created together, from the CLI and the web UI. - Fix: under jarl.service's RestrictSUIDSGID, creating a view directory failed on chmod with the setgid bit; fall back to the permission bits. - Web UI: the review/live switch is a small header button, and the pending-changes bar only shows when there are pending changes. - TSIG key wording; the keys page points to restricted API tokens. - Docs: several servers (cli.md), a secondary server (operations.md), tokens vs TSIG keys (permissions.md); CHANGELOG. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a7d389be27
commit
785b0006f9
22 files changed
+1375
-185
No files matched your search
+62
-17
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
@@ -330,31 +331,75 @@ func ReverseName(addr netip.Addr) string {
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// ReverseZone returns the name of the reverse zone covering prefix. IPv4
|
||||
// prefixes must be /8, /16 or /24 and IPv6 prefixes a multiple of /4; other
|
||||
// lengths need RFC 2317 style delegation, which must be set up by hand.
|
||||
func ReverseZone(p netip.Prefix) (string, error) {
|
||||
// MaxReverseZones is the most zones ReverseZones splits a network into.
|
||||
const MaxReverseZones = 16
|
||||
|
||||
// ReverseZones returns the reverse zones that together cover prefix.
|
||||
// Reverse names follow octet (IPv4) or nibble (IPv6) boundaries, so a
|
||||
// prefix in between is covered by several zones: a /22 by four /24 zones.
|
||||
// A single address (/32, /128) gets a zone of its own, which answers for
|
||||
// just that address. IPv4 networks smaller than a /24 (/25 to /31) are
|
||||
// refused: their names live in the enclosing /24 zone.
|
||||
func ReverseZones(p netip.Prefix) ([]string, error) {
|
||||
p = p.Masked()
|
||||
addr := p.Addr()
|
||||
bits := p.Bits()
|
||||
step := 4
|
||||
if addr.Is4() {
|
||||
if bits != 8 && bits != 16 && bits != 24 {
|
||||
return "", fmt.Errorf("IPv4 reverse zones must be /8, /16 or /24 (got /%d); use the enclosing /24 zone instead", bits)
|
||||
step = 8
|
||||
switch {
|
||||
case bits < 8:
|
||||
return nil, fmt.Errorf("/%d is too large for a reverse zone; the largest IPv4 reverse zone is a /8", bits)
|
||||
case bits > 24 && bits < 32:
|
||||
enclosing, _ := addr.Prefix(24)
|
||||
return nil, fmt.Errorf("a /%d is part of %s, and its reverse names live in that /24's zone; create %s, or a /32 zone per address to answer for single addresses only", bits, enclosing, enclosing)
|
||||
}
|
||||
b := addr.As4()
|
||||
parts := []string{}
|
||||
for i := bits/8 - 1; i >= 0; i-- {
|
||||
parts = append(parts, fmt.Sprint(b[i]))
|
||||
} else if bits < 4 {
|
||||
return nil, fmt.Errorf("/%d is too large for a reverse zone", bits)
|
||||
}
|
||||
zoneBits := (bits + step - 1) / step * step
|
||||
n := 1 << (zoneBits - bits)
|
||||
if n > MaxReverseZones {
|
||||
return nil, fmt.Errorf("a /%d spans %d /%d reverse zones; use a /%d or create the zones you need", bits, n, zoneBits, zoneBits-step)
|
||||
}
|
||||
full := ReverseName(addr)
|
||||
labels := strings.Split(strings.TrimSuffix(strings.TrimSuffix(full, ".in-addr.arpa."), ".ip6.arpa."), ".")
|
||||
suffix := ".ip6.arpa."
|
||||
if addr.Is4() {
|
||||
suffix = ".in-addr.arpa."
|
||||
}
|
||||
// labels are least significant first; the zone keeps zoneBits/step of
|
||||
// the most significant ones.
|
||||
keep := zoneBits / step
|
||||
zones := make([]string, 0, n)
|
||||
for i := 0; i < n; i++ {
|
||||
zl := append([]string(nil), labels[len(labels)-keep:]...)
|
||||
if n > 1 {
|
||||
// The last zone label counts through the network's range.
|
||||
if addr.Is4() {
|
||||
v, _ := strconv.Atoi(zl[0])
|
||||
zl[0] = strconv.Itoa(v + i)
|
||||
} else {
|
||||
v := strings.IndexByte("0123456789abcdef", zl[0][0])
|
||||
zl[0] = string("0123456789abcdef"[v+i])
|
||||
}
|
||||
}
|
||||
return strings.Join(parts, ".") + ".in-addr.arpa.", nil
|
||||
zones = append(zones, strings.Join(zl, ".")+suffix)
|
||||
}
|
||||
if bits%4 != 0 || bits == 0 {
|
||||
return "", fmt.Errorf("IPv6 reverse zones must be a multiple of /4 (got /%d)", bits)
|
||||
return zones, nil
|
||||
}
|
||||
|
||||
// ReverseZone returns the name of the reverse zone covering prefix, or an
|
||||
// error when the prefix needs several zones (see ReverseZones).
|
||||
func ReverseZone(p netip.Prefix) (string, error) {
|
||||
zones, err := ReverseZones(p)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
full := ReverseName(addr) // 32 nibbles
|
||||
nibbles := strings.Split(strings.TrimSuffix(full, ".ip6.arpa."), ".")
|
||||
keep := nibbles[len(nibbles)-bits/4:]
|
||||
return strings.Join(keep, ".") + ".ip6.arpa.", nil
|
||||
if len(zones) > 1 {
|
||||
return "", fmt.Errorf("%s spans %d reverse zones (%s ... %s); create each of them, or use `jarl zone create %s`, which does", p.Masked(), len(zones), Display(zones[0]), Display(zones[len(zones)-1]), p.Masked())
|
||||
}
|
||||
return zones[0], nil
|
||||
}
|
||||
|
||||
// AddrFromReverse parses a full reverse-lookup owner name back into an
|
||||
|
||||
Reference in new issue
Block a user