converge apex NS per record, not by RRset replace
BIND ignores an RRset-wide delete of apex NS, so the previous replace only appended to the seed placeholder.
This commit is contained in:
+37
-17
@@ -19,35 +19,55 @@ type RecordUpdate struct {
|
||||
Type string // RR type
|
||||
TTL int32 // record TTL
|
||||
Values []string // RDATA entries
|
||||
Delete bool // when true, delete the RRset instead of replacing it
|
||||
Delete bool // when true, delete instead of add
|
||||
// PerValue operates on individual records rather than the whole RRset: adds
|
||||
// leave existing records in place, deletes remove only the listed Values.
|
||||
// Required at a zone apex, where BIND silently ignores an RRset-wide delete
|
||||
// of NS or SOA and would turn a replace into an append.
|
||||
PerValue bool
|
||||
}
|
||||
|
||||
// NSUpdate applies a set of record changes to zone by executing nsupdate on the
|
||||
// primary pod, targeting the local server and authenticating with creds. All
|
||||
// changes are sent in a single atomic transaction.
|
||||
func (e *Executor) NSUpdate(ctx context.Context, namespace, pod, zone string, creds TSIGCreds, updates []RecordUpdate) error {
|
||||
var b strings.Builder
|
||||
b.WriteString("server 127.0.0.1\n")
|
||||
b.WriteString(fmt.Sprintf("zone %s\n", dot(zone)))
|
||||
for _, u := range updates {
|
||||
// Replace semantics: clear the RRset first, then add the desired values.
|
||||
b.WriteString(fmt.Sprintf("update delete %s %s\n", dot(u.FQDN), u.Type))
|
||||
if u.Delete {
|
||||
continue
|
||||
}
|
||||
for _, v := range u.Values {
|
||||
b.WriteString(fmt.Sprintf("update add %s %d %s %s\n", dot(u.FQDN), u.TTL, u.Type, v))
|
||||
}
|
||||
}
|
||||
b.WriteString("send\n")
|
||||
|
||||
cmd := []string{NsupdateBin, "-y", fmt.Sprintf("%s:%s:%s", creds.Algorithm, creds.Name, creds.Secret)}
|
||||
if out, err := e.Exec(ctx, namespace, pod, cmd, b.String()); err != nil {
|
||||
if out, err := e.Exec(ctx, namespace, pod, cmd, nsupdateScript(zone, updates)); err != nil {
|
||||
return fmt.Errorf("nsupdate zone %s: %w (out: %s)", zone, err, out)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// nsupdateScript renders the nsupdate input for a set of changes.
|
||||
func nsupdateScript(zone string, updates []RecordUpdate) string {
|
||||
var b strings.Builder
|
||||
b.WriteString("server 127.0.0.1\n")
|
||||
fmt.Fprintf(&b, "zone %s\n", dot(zone))
|
||||
for _, u := range updates {
|
||||
switch {
|
||||
case u.PerValue && u.Delete:
|
||||
for _, v := range u.Values {
|
||||
fmt.Fprintf(&b, "update delete %s %s %s\n", dot(u.FQDN), u.Type, v)
|
||||
}
|
||||
case u.PerValue:
|
||||
for _, v := range u.Values {
|
||||
fmt.Fprintf(&b, "update add %s %d %s %s\n", dot(u.FQDN), u.TTL, u.Type, v)
|
||||
}
|
||||
default:
|
||||
// Replace semantics: clear the RRset first, then add the values.
|
||||
fmt.Fprintf(&b, "update delete %s %s\n", dot(u.FQDN), u.Type)
|
||||
if u.Delete {
|
||||
continue
|
||||
}
|
||||
for _, v := range u.Values {
|
||||
fmt.Fprintf(&b, "update add %s %d %s %s\n", dot(u.FQDN), u.TTL, u.Type, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
b.WriteString("send\n")
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// dot ensures a name is fully qualified with a trailing dot.
|
||||
func dot(name string) string {
|
||||
if name == "" || name == "@" {
|
||||
|
||||
Reference in New Issue
Block a user