Archived
Validate and document Dynu-rejected documentation IP ranges
This commit is contained in:
@@ -32,7 +32,7 @@ Edit `terraform.tfvars` and set at least:
|
|||||||
- `dynu_root_domain`
|
- `dynu_root_domain`
|
||||||
- `test_suffix` (use a unique value per run)
|
- `test_suffix` (use a unique value per run)
|
||||||
|
|
||||||
Optional overrides include `test_ipv4`, `test_ipv6`, and `test_cname_target`.
|
Optional overrides include `test_ipv4`, `test_ipv6`, and `test_cname_target`. Use real routable IPs for `test_ipv4`/`test_ipv6`; Dynu rejects documentation ranges such as `192.0.2.0/24` and `2001:db8::/32`.
|
||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ dynu_root_domain = "example.com"
|
|||||||
# Set to a unique value per run so records are clearly disposable.
|
# Set to a unique value per run so records are clearly disposable.
|
||||||
test_suffix = "manual"
|
test_suffix = "manual"
|
||||||
|
|
||||||
# Safe documentation values for live test scenarios.
|
# Replace with real routable values for your environment (Dynu rejects documentation-only ranges).
|
||||||
test_ipv4 = "192.0.2.123"
|
test_ipv4 = "192.0.2.123"
|
||||||
test_ipv6 = "2001:db8::123"
|
test_ipv6 = "2001:db8::123"
|
||||||
test_cname_target = "example.com"
|
test_cname_target = "example.com"
|
||||||
|
|||||||
@@ -478,6 +478,13 @@ func validateDNSRecordContentForTypeWithKnowledge(recordType string, content *st
|
|||||||
diagnostics.AddError("Invalid DNS record content", fmt.Sprintf("Record type %q requires an IPv6 address, got %q.", normalizedType, trimmedContent))
|
diagnostics.AddError("Invalid DNS record content", fmt.Sprintf("Record type %q requires an IPv6 address, got %q.", normalizedType, trimmedContent))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if isDocumentationAddress(addr) {
|
||||||
|
diagnostics.AddError(
|
||||||
|
"Unsupported documentation IP address",
|
||||||
|
fmt.Sprintf("Dynu rejects documentation-only address ranges for live DNS records. Replace %q with a real routable address under your control.", trimmedContent),
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,6 +511,22 @@ func validateDNSRecordContentForTypeWithKnowledge(recordType string, content *st
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isDocumentationAddress(addr netip.Addr) bool {
|
||||||
|
if addr.Is4() {
|
||||||
|
v4 := addr.As4()
|
||||||
|
return (v4[0] == 192 && v4[1] == 0 && v4[2] == 2) ||
|
||||||
|
(v4[0] == 198 && v4[1] == 51 && v4[2] == 100) ||
|
||||||
|
(v4[0] == 203 && v4[1] == 0 && v4[2] == 113)
|
||||||
|
}
|
||||||
|
|
||||||
|
if addr.Is6() {
|
||||||
|
v6 := addr.As16()
|
||||||
|
return v6[0] == 0x20 && v6[1] == 0x01 && v6[2] == 0x0d && v6[3] == 0xb8
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func resolveDynamicIntent(recordType string, content types.String, dynamic types.Bool, diagnostics *diag.Diagnostics) (bool, bool) {
|
func resolveDynamicIntent(recordType string, content types.String, dynamic types.Bool, diagnostics *diag.Diagnostics) (bool, bool) {
|
||||||
normalizedType := strings.ToUpper(strings.TrimSpace(recordType))
|
normalizedType := strings.ToUpper(strings.TrimSpace(recordType))
|
||||||
contentPtr := stringPointerFromOptionalContent(content)
|
contentPtr := stringPointerFromOptionalContent(content)
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ func TestIntegrationResourceDNSRecordDynamicAStateStableAndTransitionToStatic(t
|
|||||||
}
|
}
|
||||||
|
|
||||||
updatePlan := state
|
updatePlan := state
|
||||||
updatePlan.Content = types.StringValue("192.0.2.42")
|
updatePlan.Content = types.StringValue("1.1.1.1")
|
||||||
updatePlan.Dynamic = types.BoolValue(false)
|
updatePlan.Dynamic = types.BoolValue(false)
|
||||||
|
|
||||||
plan = tfsdk.Plan{Schema: schemaResp.Schema}
|
plan = tfsdk.Plan{Schema: schemaResp.Schema}
|
||||||
@@ -214,7 +214,7 @@ func TestIntegrationResourceDNSRecordDynamicAStateStableAndTransitionToStatic(t
|
|||||||
if state.Dynamic.ValueBool() {
|
if state.Dynamic.ValueBool() {
|
||||||
t.Fatalf("expected static A after setting content")
|
t.Fatalf("expected static A after setting content")
|
||||||
}
|
}
|
||||||
if state.Content.ValueString() != "192.0.2.42" {
|
if state.Content.ValueString() != "1.1.1.1" {
|
||||||
t.Fatalf("expected static content after update, got %q", state.Content.ValueString())
|
t.Fatalf("expected static content after update, got %q", state.Content.ValueString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ func TestParseDNSRecordIDInvalid(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateDNSRecordContentForType(t *testing.T) {
|
func TestValidateDNSRecordContentForType(t *testing.T) {
|
||||||
ipv4 := "192.0.2.123"
|
ipv4 := "8.8.8.8"
|
||||||
ipv6 := "2001:db8::123"
|
ipv6 := "2606:4700:4700::1111"
|
||||||
nonEmpty := "hello"
|
nonEmpty := "hello"
|
||||||
blank := ""
|
blank := ""
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ func TestResolveDynamicIntent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
diags = diag.Diagnostics{}
|
diags = diag.Diagnostics{}
|
||||||
dynamic, ok = resolveDynamicIntent("A", types.StringValue("192.0.2.10"), types.BoolNull(), &diags)
|
dynamic, ok = resolveDynamicIntent("A", types.StringValue("8.8.4.4"), types.BoolNull(), &diags)
|
||||||
if !ok || dynamic || diags.HasError() {
|
if !ok || dynamic || diags.HasError() {
|
||||||
t.Fatalf("expected static A content to resolve to dynamic=false, got dynamic=%v ok=%v diags=%v", dynamic, ok, diags)
|
t.Fatalf("expected static A content to resolve to dynamic=false, got dynamic=%v ok=%v diags=%v", dynamic, ok, diags)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user