diff --git a/deploy/charts/cert-manager/crds/challenges.yaml b/deploy/charts/cert-manager/crds/challenges.yaml index cf3c63e92..dffb28610 100644 --- a/deploy/charts/cert-manager/crds/challenges.yaml +++ b/deploy/charts/cert-manager/crds/challenges.yaml @@ -326,7 +326,10 @@ spec: properties: nameserver: description: The IP address or hostname of an authoritative - DNS server supporting RFC2136. Required. + DNS server supporting RFC2136 in the form host:port. If + the host is an IPv6 address it must be enclosed in square + brackets (e.g [2001:db8::1]) ; port is optional. This + field is required. type: string tsigAlgorithm: description: 'The TSIG Algorithm configured in the DNS supporting diff --git a/deploy/charts/cert-manager/crds/clusterissuers.yaml b/deploy/charts/cert-manager/crds/clusterissuers.yaml index 710e4aa42..2bef8a063 100644 --- a/deploy/charts/cert-manager/crds/clusterissuers.yaml +++ b/deploy/charts/cert-manager/crds/clusterissuers.yaml @@ -380,7 +380,10 @@ spec: properties: nameserver: description: The IP address or hostname of an authoritative - DNS server supporting RFC2136. Required. + DNS server supporting RFC2136 in the form host:port. + If the host is an IPv6 address it must be enclosed + in square brackets (e.g [2001:db8::1]) ; port is + optional. This field is required. type: string tsigAlgorithm: description: 'The TSIG Algorithm configured in the diff --git a/deploy/charts/cert-manager/crds/issuers.yaml b/deploy/charts/cert-manager/crds/issuers.yaml index ed3d818a3..ba2740aac 100644 --- a/deploy/charts/cert-manager/crds/issuers.yaml +++ b/deploy/charts/cert-manager/crds/issuers.yaml @@ -380,7 +380,10 @@ spec: properties: nameserver: description: The IP address or hostname of an authoritative - DNS server supporting RFC2136. Required. + DNS server supporting RFC2136 in the form host:port. + If the host is an IPv6 address it must be enclosed + in square brackets (e.g [2001:db8::1]) ; port is + optional. This field is required. type: string tsigAlgorithm: description: 'The TSIG Algorithm configured in the diff --git a/deploy/manifests/00-crds.yaml b/deploy/manifests/00-crds.yaml index 3171f2b30..010a53938 100644 --- a/deploy/manifests/00-crds.yaml +++ b/deploy/manifests/00-crds.yaml @@ -1081,7 +1081,10 @@ spec: properties: nameserver: description: The IP address or hostname of an authoritative - DNS server supporting RFC2136. Required. + DNS server supporting RFC2136 in the form host:port. If + the host is an IPv6 address it must be enclosed in square + brackets (e.g [2001:db8::1]) ; port is optional. This + field is required. type: string tsigAlgorithm: description: 'The TSIG Algorithm configured in the DNS supporting @@ -2542,7 +2545,10 @@ spec: properties: nameserver: description: The IP address or hostname of an authoritative - DNS server supporting RFC2136. Required. + DNS server supporting RFC2136 in the form host:port. + If the host is an IPv6 address it must be enclosed + in square brackets (e.g [2001:db8::1]) ; port is + optional. This field is required. type: string tsigAlgorithm: description: 'The TSIG Algorithm configured in the @@ -4295,7 +4301,10 @@ spec: properties: nameserver: description: The IP address or hostname of an authoritative - DNS server supporting RFC2136. Required. + DNS server supporting RFC2136 in the form host:port. + If the host is an IPv6 address it must be enclosed + in square brackets (e.g [2001:db8::1]) ; port is + optional. This field is required. type: string tsigAlgorithm: description: 'The TSIG Algorithm configured in the diff --git a/pkg/apis/acme/v1alpha2/types_issuer.go b/pkg/apis/acme/v1alpha2/types_issuer.go index 6a6ed605e..1df9ca2ab 100644 --- a/pkg/apis/acme/v1alpha2/types_issuer.go +++ b/pkg/apis/acme/v1alpha2/types_issuer.go @@ -355,7 +355,10 @@ type ACMEIssuerDNS01ProviderAcmeDNS struct { // ACMEIssuerDNS01ProviderRFC2136 is a structure containing the // configuration for RFC2136 DNS type ACMEIssuerDNS01ProviderRFC2136 struct { - // The IP address or hostname of an authoritative DNS server supporting RFC2136. Required. + // The IP address or hostname of an authoritative DNS server supporting + // RFC2136 in the form host:port. If the host is an IPv6 address it must be + // enclosed in square brackets (e.g [2001:db8::1]) ; port is optional. + // This field is required. Nameserver string `json:"nameserver"` // The name of the secret containing the TSIG value. diff --git a/pkg/apis/acme/v1alpha3/types_issuer.go b/pkg/apis/acme/v1alpha3/types_issuer.go index b21c4ae24..d3dcfff7d 100644 --- a/pkg/apis/acme/v1alpha3/types_issuer.go +++ b/pkg/apis/acme/v1alpha3/types_issuer.go @@ -355,7 +355,10 @@ type ACMEIssuerDNS01ProviderAcmeDNS struct { // ACMEIssuerDNS01ProviderRFC2136 is a structure containing the // configuration for RFC2136 DNS type ACMEIssuerDNS01ProviderRFC2136 struct { - // The IP address or hostname of an authoritative DNS server supporting RFC2136. Required. + // The IP address or hostname of an authoritative DNS server supporting + // RFC2136 in the form host:port. If the host is an IPv6 address it must be + // enclosed in square brackets (e.g [2001:db8::1]) ; port is optional. + // This field is required. Nameserver string `json:"nameserver"` // The name of the secret containing the TSIG value. diff --git a/pkg/internal/apis/acme/types_issuer.go b/pkg/internal/apis/acme/types_issuer.go index 020b4bec2..632ae5155 100644 --- a/pkg/internal/apis/acme/types_issuer.go +++ b/pkg/internal/apis/acme/types_issuer.go @@ -314,7 +314,10 @@ type ACMEIssuerDNS01ProviderAcmeDNS struct { // ACMEIssuerDNS01ProviderRFC2136 is a structure containing the // configuration for RFC2136 DNS type ACMEIssuerDNS01ProviderRFC2136 struct { - // The IP address or hostname of an authoritative DNS server supporting RFC2136. Required. + // The IP address or hostname of an authoritative DNS server supporting + // RFC2136 in the form host:port. If the host is an IPv6 address it must be + // enclosed in square brackets (e.g [2001:db8::1]) ; port is optional. + // This field is required. Nameserver string // The name of the secret containing the TSIG value. diff --git a/pkg/internal/apis/certmanager/validation/issuer.go b/pkg/internal/apis/certmanager/validation/issuer.go index b7091be0e..c77694cb2 100644 --- a/pkg/internal/apis/certmanager/validation/issuer.go +++ b/pkg/internal/apis/certmanager/validation/issuer.go @@ -348,7 +348,7 @@ func ValidateACMEChallengeSolverDNS01(p *cmacme.ACMEChallengeSolverDNS01, fldPat el = append(el, field.Required(fldPath.Child("rfc2136", "nameserver"), "")) } else { if _, err := util.ValidNameserver(p.RFC2136.Nameserver); err != nil { - el = append(el, field.Invalid(fldPath.Child("rfc2136", "nameserver"), p.RFC2136.Nameserver, "nameserver must be an hostname or IP address in the form host[:port].")) + el = append(el, field.Invalid(fldPath.Child("rfc2136", "nameserver"), p.RFC2136.Nameserver, "nameserver must be set in the form host:port where host is an IPv4 address, an enclosed IPv6 address or a hostname and port is an optional port number.")) } } if len(p.RFC2136.TSIGAlgorithm) > 0 { diff --git a/pkg/internal/apis/certmanager/validation/issuer_test.go b/pkg/internal/apis/certmanager/validation/issuer_test.go index 99f132fe0..833ee8ea1 100644 --- a/pkg/internal/apis/certmanager/validation/issuer_test.go +++ b/pkg/internal/apis/certmanager/validation/issuer_test.go @@ -656,6 +656,34 @@ func TestValidateACMEIssuerDNS01Config(t *testing.T) { }, errs: []*field.Error{}, }, + "rfc2136 provider with unenclosed IPv6 nameserver": { + cfg: &cmacme.ACMEChallengeSolverDNS01{ + RFC2136: &cmacme.ACMEIssuerDNS01ProviderRFC2136{ + Nameserver: "2001:db8::1", + }, + }, + errs: []*field.Error{ + field.Invalid(fldPath.Child("rfc2136", "nameserver"), "2001:db8::1", "nameserver must be set in the form host:port where host is an IPv4 address, an enclosed IPv6 address or a hostname and port is an optional port number."), + }, + }, + "rfc2136 provider with empty IPv6 nameserver": { + cfg: &cmacme.ACMEChallengeSolverDNS01{ + RFC2136: &cmacme.ACMEIssuerDNS01ProviderRFC2136{ + Nameserver: "[]:53", + }, + }, + errs: []*field.Error{ + field.Invalid(fldPath.Child("rfc2136", "nameserver"), "[]:53", "nameserver must be set in the form host:port where host is an IPv4 address, an enclosed IPv6 address or a hostname and port is an optional port number."), + }, + }, + "rfc2136 provider with IPv6 nameserver": { + cfg: &cmacme.ACMEChallengeSolverDNS01{ + RFC2136: &cmacme.ACMEIssuerDNS01ProviderRFC2136{ + Nameserver: "[2001:db8::1]", + }, + }, + errs: []*field.Error{}, + }, "rfc2136 provider with FQDN nameserver": { cfg: &cmacme.ACMEChallengeSolverDNS01{ RFC2136: &cmacme.ACMEIssuerDNS01ProviderRFC2136{ @@ -679,7 +707,7 @@ func TestValidateACMEIssuerDNS01Config(t *testing.T) { }, }, errs: []*field.Error{ - field.Invalid(fldPath.Child("rfc2136", "nameserver"), ":53", "nameserver must be an hostname or IP address in the form host[:port]."), + field.Invalid(fldPath.Child("rfc2136", "nameserver"), ":53", "nameserver must be set in the form host:port where host is an IPv4 address, an enclosed IPv6 address or a hostname and port is an optional port number."), }, }, "rfc2136 provider using case-camel in algorithm": { diff --git a/pkg/internal/apis/certmanager/validation/util/nameserver.go b/pkg/internal/apis/certmanager/validation/util/nameserver.go index 766ba22b5..f0380d3c6 100644 --- a/pkg/internal/apis/certmanager/validation/util/nameserver.go +++ b/pkg/internal/apis/certmanager/validation/util/nameserver.go @@ -37,6 +37,9 @@ func ValidNameserver(nameserver string) (string, error) { // 8.8.8.8 "" "" missing port in address // 8.8.8.8: "8.8.8.8" "" // 8.8.8.8.8:53 "8.8.8.8" 53 + // [2001:db8::1] "" "" missing port in address + // [2001:db8::1]: "2001:db8::1" "" + // [2001:db8::1]:53 "2001:db8::1" 53 // nameserver.com "" "" missing port in address // nameserver.com: "nameserver.com" "" // nameserver.com:53 "nameserver.com" 53 @@ -44,19 +47,20 @@ func ValidNameserver(nameserver string) (string, error) { host, port, err := net.SplitHostPort(nameserver) if err != nil { if strings.Contains(err.Error(), "missing port") { - host = nameserver + // net.JoinHostPort expect IPv6 address to be unenclosed + host = strings.Trim(nameserver, "[]") + } else { + return "", fmt.Errorf("RFC2136 nameserver is invalid: %s", err.Error()) } } - if port == "" { - port = defaultRFC2136Port - } - if host == "" { return "", fmt.Errorf("RFC2136 nameserver has no host defined, %v", nameserver) } - nameserver = host + ":" + port + if port == "" { + port = defaultRFC2136Port + } - return nameserver, nil + return net.JoinHostPort(host, port), nil } diff --git a/pkg/issuer/acme/dns/rfc2136/rfc2136_test.go b/pkg/issuer/acme/dns/rfc2136/rfc2136_test.go index c20091e0e..9cfe497c4 100644 --- a/pkg/issuer/acme/dns/rfc2136/rfc2136_test.go +++ b/pkg/issuer/acme/dns/rfc2136/rfc2136_test.go @@ -181,6 +181,48 @@ func TestRFC2136NameserverIPv4WithPort(t *testing.T) { } } +func TestRFC2136NameserverIPv6NotEnclosed(t *testing.T) { + nameserver := "2001:db8::1" + _, err := NewDNSProviderCredentials(nameserver, "", rfc2136TestTsigKeyName, rfc2136TestTsigSecret) + assert.Error(t, err) +} + +func TestRFC2136NameserverIPv6Empty(t *testing.T) { + nameserver := "[]:53" + _, err := NewDNSProviderCredentials(nameserver, "", rfc2136TestTsigKeyName, rfc2136TestTsigSecret) + assert.Error(t, err) +} + +func TestRFC2136NameserverIPv6WithoutPort(t *testing.T) { + nameserver := "[2001:db8::1]" + dnsProvider, err := NewDNSProviderCredentials(nameserver, "", rfc2136TestTsigKeyName, rfc2136TestTsigSecret) + assert.NoError(t, err) + + if dnsProvider.nameserver != nameserver+":"+defaultPort { + t.Errorf("dnsProvider.nameserver to be %v:%v, but it is %v", nameserver, defaultPort, dnsProvider.nameserver) + } +} + +func TestRFC2136NameserverIPv6WithEmptyPort(t *testing.T) { + nameserver := "[2001:db8::1]:" + dnsProvider, err := NewDNSProviderCredentials(nameserver, "", rfc2136TestTsigKeyName, rfc2136TestTsigSecret) + assert.NoError(t, err) + + if dnsProvider.nameserver != nameserver+defaultPort { + t.Errorf("dnsProvider.nameserver to be %v%v, but it is %v", nameserver, defaultPort, dnsProvider.nameserver) + } +} + +func TestRFC2136NameserverIPv6WithPort(t *testing.T) { + nameserver := "[2001:db8::1]:12345" + dnsProvider, err := NewDNSProviderCredentials(nameserver, "", rfc2136TestTsigKeyName, rfc2136TestTsigSecret) + assert.NoError(t, err) + + if dnsProvider.nameserver != nameserver { + t.Errorf("dnsProvider.nameserver to be %v, but it is %v", nameserver, dnsProvider.nameserver) + } +} + func TestRFC2136NameserverFQDNWithoutPort(t *testing.T) { nameserver := "dns.example.net" dnsProvider, err := NewDNSProviderCredentials(nameserver, "", rfc2136TestTsigKeyName, rfc2136TestTsigSecret)