Render empty forwarders for forward zones
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/build Pipeline was successful

A forward zone with no forwarders now renders "forwarders { };", which
BIND accepts and treats as disabling forwarding for the zone.
This commit is contained in:
2026-10-16 23:32:09 +11:00
parent f149b79cce
commit 53cb7fa059
7 changed files with 51 additions and 10 deletions
+1 -1
View File
@@ -103,7 +103,7 @@ type BindZoneSpec struct {
// +optional
DNSSECPolicyRef string `json:"dnssecPolicyRef,omitempty"`
// Forwarders lists upstreams for a forward-type zone.
// Forwarders lists upstreams for a forward-type zone; an empty list disables forwarding for the zone.
// +optional
Forwarders []string `json:"forwarders,omitempty"`
@@ -90,7 +90,8 @@ spec:
When true, UpdateKeyRef must reference a BindTSIGKey.
type: boolean
forwarders:
description: Forwarders lists upstreams for a forward-type zone.
description: Forwarders lists upstreams for a forward-type zone; an
empty list disables forwarding for the zone.
items:
type: string
type: array
+2 -1
View File
@@ -2756,7 +2756,8 @@ spec:
When true, UpdateKeyRef must reference a BindTSIGKey.
type: boolean
forwarders:
description: Forwarders lists upstreams for a forward-type zone.
description: Forwarders lists upstreams for a forward-type zone; an
empty list disables forwarding for the zone.
items:
type: string
type: array
+9 -3
View File
@@ -154,9 +154,7 @@ func renderForwardZone(z bindv1alpha1.BindZone, indent string) string {
b.WriteString(fmt.Sprintf("%szone \"%s\" {\n", indent, z.Spec.ZoneName))
b.WriteString(indent + " type forward;\n")
b.WriteString(indent + " forward only;\n")
if len(z.Spec.Forwarders) > 0 {
b.WriteString(fmt.Sprintf("%s forwarders { %s };\n", indent, terminate(z.Spec.Forwarders)))
}
b.WriteString(indent + " " + ForwardersClause(z.Spec.Forwarders) + ";\n")
b.WriteString(indent + "};\n")
return b.String()
}
@@ -398,6 +396,14 @@ func terminate(entries []string) string {
return strings.Join(parts, " ")
}
// ForwardersClause renders a zone forwarders clause; empty means "forwarders { }" (no forwarding).
func ForwardersClause(entries []string) string {
if list := terminate(entries); list != "" {
return "forwarders { " + list + " }"
}
return "forwarders { }"
}
func yesno(b bool) string {
if b {
return "yes"
+16
View File
@@ -261,3 +261,19 @@ func TestRenderDeterministicWithShuffledForwards(t *testing.T) {
t.Fatalf("render must be independent of forward-zone input order:\n--- A ---\n%s\n--- B ---\n%s", pa, pb)
}
}
func TestRenderForwardZoneForwarders(t *testing.T) {
for _, tc := range []struct {
fwd []string
want string
}{
{nil, " forwarders { };\n"},
{[]string{"198.18.19.15", "198.18.19.16;"}, " forwarders { 198.18.19.15; 198.18.19.16; };\n"},
} {
z := bindv1alpha1.BindZone{Spec: bindv1alpha1.BindZoneSpec{ZoneName: "spamhaus.org", Type: bindv1alpha1.ZoneForward, Forwarders: tc.fwd}}
want := "zone \"spamhaus.org\" {\n type forward;\n forward only;\n" + tc.want + "};\n"
if got := renderForwardZone(z, ""); got != want {
t.Errorf("forwarders %v:\ngot %q\nwant %q", tc.fwd, got, want)
}
}
}
+1 -4
View File
@@ -218,10 +218,7 @@ func (r *BindZoneReconciler) buildZoneConfig(ctx context.Context, zone *bindv1al
parts = append(parts, fmt.Sprintf("primaries { %s }", terminateInline(zone.Spec.Primaries)))
}
case bindv1alpha1.ZoneForward:
parts = append(parts, "type forward", "forward only")
if len(zone.Spec.Forwarders) > 0 {
parts = append(parts, fmt.Sprintf("forwarders { %s }", terminateInline(zone.Spec.Forwarders)))
}
parts = append(parts, "type forward", "forward only", bind.ForwardersClause(zone.Spec.Forwarders))
case bindv1alpha1.ZoneStub:
parts = append(parts, "type stub", fmt.Sprintf("file \"%s\"", bind.ZoneFilePath(zone.Spec.ZoneName)))
if len(zone.Spec.Primaries) > 0 {
@@ -63,3 +63,23 @@ func TestBuildZoneConfigPrimaryNoNotifyTargets(t *testing.T) {
t.Errorf("did not expect notify clauses with no targets: %q", cfg)
}
}
func TestBuildZoneConfigForwardForwarders(t *testing.T) {
r := &BindZoneReconciler{}
for _, tc := range []struct {
fwd []string
want string
}{
{nil, "{ type forward; forward only; forwarders { }; };"},
{[]string{"198.18.19.15"}, "{ type forward; forward only; forwarders { 198.18.19.15; }; };"},
} {
zone := &bindv1alpha1.BindZone{Spec: bindv1alpha1.BindZoneSpec{ZoneName: "spamhaus.org", Type: bindv1alpha1.ZoneForward, Forwarders: tc.fwd}}
cfg, err := r.buildZoneConfig(context.Background(), zone, "", nil, "")
if err != nil {
t.Fatalf("buildZoneConfig: %v", err)
}
if cfg != tc.want {
t.Errorf("forwarders %v:\ngot %q\nwant %q", tc.fwd, cfg, tc.want)
}
}
}