forked from TrueCloudLab/lego
Fixes zone lookup for domains that have a CNAME with the target in another zone
This commit is contained in:
parent
5720cba2c9
commit
d6197084fc
2 changed files with 59 additions and 38 deletions
|
@ -186,7 +186,7 @@ func lookupNameservers(fqdn string) ([]string, error) {
|
||||||
|
|
||||||
zone, err := FindZoneByFqdn(fqdn, RecursiveNameservers)
|
zone, err := FindZoneByFqdn(fqdn, RecursiveNameservers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("Could not determine the zone: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
r, err := dnsQuery(zone, dns.TypeNS, RecursiveNameservers, true)
|
r, err := dnsQuery(zone, dns.TypeNS, RecursiveNameservers, true)
|
||||||
|
@ -206,53 +206,54 @@ func lookupNameservers(fqdn string) ([]string, error) {
|
||||||
return nil, fmt.Errorf("Could not determine authoritative nameservers")
|
return nil, fmt.Errorf("Could not determine authoritative nameservers")
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindZoneByFqdn determines the zone of the given fqdn
|
// FindZoneByFqdn determines the zone apex for the given fqdn by recursing up the
|
||||||
|
// domain labels until the nameserver returns a SOA record in the answer section.
|
||||||
func FindZoneByFqdn(fqdn string, nameservers []string) (string, error) {
|
func FindZoneByFqdn(fqdn string, nameservers []string) (string, error) {
|
||||||
// Do we have it cached?
|
// Do we have it cached?
|
||||||
if zone, ok := fqdnToZone[fqdn]; ok {
|
if zone, ok := fqdnToZone[fqdn]; ok {
|
||||||
return zone, nil
|
return zone, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query the authoritative nameserver for a hopefully non-existing SOA record,
|
labelIndexes := dns.Split(fqdn)
|
||||||
// in the authority section of the reply it will have the SOA of the
|
for _, index := range labelIndexes {
|
||||||
// containing zone. rfc2308 has this to say on the subject:
|
domain := fqdn[index:]
|
||||||
// Name servers authoritative for a zone MUST include the SOA record of
|
// Give up if we have reached the TLD
|
||||||
// the zone in the authority section of the response when reporting an
|
if isTLD(domain) {
|
||||||
// NXDOMAIN or indicating that no data (NODATA) of the requested type exists
|
break
|
||||||
in, err := dnsQuery(fqdn, dns.TypeSOA, nameservers, true)
|
}
|
||||||
|
|
||||||
|
in, err := dnsQuery(domain, dns.TypeSOA, nameservers, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if in.Rcode != dns.RcodeNameError {
|
|
||||||
if in.Rcode != dns.RcodeSuccess {
|
// Any response code other than NOERROR and NXDOMAIN is treated as error
|
||||||
return "", fmt.Errorf("The NS returned %s for %s", dns.RcodeToString[in.Rcode], fqdn)
|
if in.Rcode != dns.RcodeNameError && in.Rcode != dns.RcodeSuccess {
|
||||||
|
return "", fmt.Errorf("Unexpected response code '%s' for %s",
|
||||||
|
dns.RcodeToString[in.Rcode], domain)
|
||||||
}
|
}
|
||||||
// We have a success, so one of the answers has to be a SOA RR
|
|
||||||
|
// Check if we got a SOA RR in the answer section
|
||||||
|
if in.Rcode == dns.RcodeSuccess {
|
||||||
for _, ans := range in.Answer {
|
for _, ans := range in.Answer {
|
||||||
if soa, ok := ans.(*dns.SOA); ok {
|
if soa, ok := ans.(*dns.SOA); ok {
|
||||||
return checkIfTLD(fqdn, soa)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Or it is NODATA, fall through to NXDOMAIN
|
|
||||||
}
|
|
||||||
// Search the authority section for our precious SOA RR
|
|
||||||
for _, ns := range in.Ns {
|
|
||||||
if soa, ok := ns.(*dns.SOA); ok {
|
|
||||||
return checkIfTLD(fqdn, soa)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("The NS did not return the expected SOA record in the authority section")
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkIfTLD(fqdn string, soa *dns.SOA) (string, error) {
|
|
||||||
zone := soa.Hdr.Name
|
zone := soa.Hdr.Name
|
||||||
// If we ended up on one of the TLDs, it means the domain did not exist.
|
|
||||||
publicsuffix, _ := publicsuffix.PublicSuffix(UnFqdn(zone))
|
|
||||||
if publicsuffix == UnFqdn(zone) {
|
|
||||||
return "", fmt.Errorf("Could not determine zone authoritatively")
|
|
||||||
}
|
|
||||||
fqdnToZone[fqdn] = zone
|
fqdnToZone[fqdn] = zone
|
||||||
return zone, nil
|
return zone, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("Could not find the start of authority")
|
||||||
|
}
|
||||||
|
|
||||||
|
func isTLD(domain string) bool {
|
||||||
|
publicsuffix, _ := publicsuffix.PublicSuffix(UnFqdn(domain))
|
||||||
|
if publicsuffix == UnFqdn(domain) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClearFqdnCache clears the cache of fqdn to zone mappings. Primarily used in testing.
|
// ClearFqdnCache clears the cache of fqdn to zone mappings. Primarily used in testing.
|
||||||
|
|
|
@ -35,18 +35,26 @@ var lookupNameserversTestsErr = []struct {
|
||||||
}{
|
}{
|
||||||
// invalid tld
|
// invalid tld
|
||||||
{"_null.n0n0.",
|
{"_null.n0n0.",
|
||||||
"Could not determine zone authoritatively",
|
"Could not determine the zone",
|
||||||
},
|
},
|
||||||
// invalid domain
|
// invalid domain
|
||||||
{"_null.com.",
|
{"_null.com.",
|
||||||
"Could not determine zone authoritatively",
|
"Could not determine the zone",
|
||||||
},
|
},
|
||||||
// invalid domain
|
// invalid domain
|
||||||
{"in-valid.co.uk.",
|
{"in-valid.co.uk.",
|
||||||
"Could not determine zone authoritatively",
|
"Could not determine the zone",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var findZoneByFqdnTests = []struct {
|
||||||
|
fqdn string
|
||||||
|
zone string
|
||||||
|
}{
|
||||||
|
{"mail.google.com.", "google.com."}, // domain is a CNAME
|
||||||
|
{"foo.google.com.", "google.com."}, // domain is a non-existent subdomain
|
||||||
|
}
|
||||||
|
|
||||||
var checkAuthoritativeNssTests = []struct {
|
var checkAuthoritativeNssTests = []struct {
|
||||||
fqdn, value string
|
fqdn, value string
|
||||||
ns []string
|
ns []string
|
||||||
|
@ -142,6 +150,18 @@ func TestLookupNameserversErr(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFindZoneByFqdn(t *testing.T) {
|
||||||
|
for _, tt := range findZoneByFqdnTests {
|
||||||
|
res, err := FindZoneByFqdn(tt.fqdn, RecursiveNameservers)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("FindZoneByFqdn failed for %s: %v", tt.fqdn, err)
|
||||||
|
}
|
||||||
|
if res != tt.zone {
|
||||||
|
t.Errorf("%s: got %s; want %s", tt.fqdn, res, tt.zone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCheckAuthoritativeNss(t *testing.T) {
|
func TestCheckAuthoritativeNss(t *testing.T) {
|
||||||
for _, tt := range checkAuthoritativeNssTests {
|
for _, tt := range checkAuthoritativeNssTests {
|
||||||
ok, _ := checkAuthoritativeNss(tt.fqdn, tt.value, tt.ns)
|
ok, _ := checkAuthoritativeNss(tt.fqdn, tt.value, tt.ns)
|
||||||
|
|
Loading…
Reference in a new issue