_acme-challenge. in query triggers special NS record

Prior behavior was that the same trinity of NS records was returned for
every NS query:

- ns-aws.nono.io.
- ns-azure.nono.io.
- ns-gce.nono.io.

This commit introduces a change in that behavior: IF the NS query includes
the string `_acme-challenge.` AND the query has an embedded IP address
THEN the NS record returned is the query with the `_acme-challenge.`
stripped.

For example:
```
dig +short ns _acme-challenge.104.155.144.4.sslip.io
```
Would return:
```
104.155.144.4.sslip.io.
```

This is an attempt to enable
[DNS-01](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge)
challenge for wildcard certs from Let's Encrypt or other CAs
(Certificate Authorities).

Note that the embedded IP address would need to be routable (NOT 10.x
172.16-31.x, or 192.168.x).

Note that you would also need to run a DNS server such as
[acme-dns](https://github.com/joohoi/acme-dns) at that address.

Thanks @normanr !

[#6]
This commit is contained in:
Brian Cunnie
2020-12-21 07:08:55 -08:00
parent dd6126a6c4
commit 67e033f8f8
2 changed files with 44 additions and 2 deletions

View File

@@ -37,6 +37,7 @@ var (
ipv4REDashes = regexp.MustCompile(`(^|[.-])(((25[0-5]|(2[0-4]|1?[0-9])?[0-9])-){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9]))($|[.-])`)
// https://stackoverflow.com/questions/53497/regular-expression-that-matches-valid-ipv6-addresses
ipv6RE = regexp.MustCompile(`(^|[.-])(([0-9a-fA-F]{1,4}-){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}-){1,7}-|([0-9a-fA-F]{1,4}-){1,6}-[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}-){1,5}(-[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}-){1,4}(-[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}-){1,3}(-[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}-){1,2}(-[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}-((-[0-9a-fA-F]{1,4}){1,6})|-((-[0-9a-fA-F]{1,4}){1,7}|-)|fe80-(-[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]+|--(ffff(-0{1,4})?-)?((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9])|([0-9a-fA-F]{1,4}-){1,4}-((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9]))($|[.-])`)
dns01ChallengeRE = regexp.MustCompile(`_acme-challenge\.`)
ErrNotFound = errors.New("record not found")
// Use The Go Playground https://play.golang.org/p/G2BYkakyj-R
// to convert strings to dnsmessage.Name for easy cut-and-paste
@@ -340,7 +341,7 @@ func processQuestion(q dnsmessage.Question, b *dnsmessage.Builder) (logMessage s
TTL: 604800, // 60 * 60 * 24 * 7 == 1 week; long TTL, these IP addrs don't change
Length: 0,
}, mailExchanger)
logMessages = append(logMessages, strconv.Itoa(int(mailExchanger.Pref))+" "+string(mailExchanger.MX.Data[:mailExchanger.MX.Length]))
logMessages = append(logMessages, strconv.Itoa(int(mailExchanger.Pref))+" "+mailExchanger.MX.String())
if err != nil {
return
}
@@ -354,6 +355,7 @@ func processQuestion(q dnsmessage.Question, b *dnsmessage.Builder) (logMessage s
return
}
nameServers := NSResources(q.Name.String())
var logMessages []string
for _, nameServer := range nameServers {
err = b.NSResource(dnsmessage.ResourceHeader{
Name: q.Name,
@@ -362,8 +364,9 @@ func processQuestion(q dnsmessage.Question, b *dnsmessage.Builder) (logMessage s
TTL: 604800, // 60 * 60 * 24 * 7 == 1 week; long TTL, these IP addrs don't change
Length: 0,
}, nameServer)
logMessages = append(logMessages, nameServer.NS.String())
}
logMessage += "NS"
logMessage += strings.Join(logMessages, ", ")
}
case dnsmessage.TypeSOA:
{
@@ -483,6 +486,10 @@ func NameToAAAA(fqdnString string) ([]dnsmessage.AAAAResource, error) {
match := string(ipv6RE.FindSubmatch(fqdn)[2])
match = strings.Replace(match, "-", ":", -1)
ipv16address := net.ParseIP(match).To16()
if ipv16address == nil {
// We shouldn't reach here because `match` should always be valid, but we're not optimists
return nil, ErrNotFound
}
AAAAR := dnsmessage.AAAAResource{}
for i := range ipv16address {
@@ -516,6 +523,21 @@ func MxResources(fqdnString string) []dnsmessage.MXResource {
}
func NSResources(fqdnString string) []dnsmessage.NSResource {
if dns01ChallengeRE.Match([]byte(fqdnString)) {
strippedFqdn := dns01ChallengeRE.ReplaceAll([]byte(fqdnString), []byte{})
_, errIPv4 := NameToA(fqdnString)
_, errIPv6 := NameToAAAA(fqdnString)
if errIPv4 == nil || errIPv6 == nil {
var strippedFqdnData = [255]byte{}
copy(strippedFqdnData[:], strippedFqdn)
return []dnsmessage.NSResource{
{NS: dnsmessage.Name{
Length: uint8(len(strippedFqdn)),
Data: strippedFqdnData,
},
}}
}
}
return NameServers
}

View File

@@ -366,6 +366,26 @@ var _ = Describe("Xip", func() {
Expect(string(ns[1].NS.String())).To(Equal("ns-azure.nono.io."))
Expect(string(ns[2].NS.String())).To(Equal("ns-gce.nono.io."))
})
When(`the domain name contains "_acme-challenge."`, func() {
When("the domain name has an embedded IP", func() {
It(`returns an array of one NS record pointing to the domain name _sans_ "acme-challenge."`, func() {
randomDomain := "192.168.0.1." + random8ByteString() + ".com."
ns := xip.NSResources("_acme-challenge." + randomDomain)
Expect(len(ns)).To(Equal(1))
Expect(ns[0].NS.String()).To(Equal(randomDomain))
AResources, err := xip.NameToA(randomDomain)
Expect(err).ToNot(HaveOccurred())
Expect(AResources[0].A).To(Equal([4]byte{192, 168, 0, 1}))
})
})
When("the domain name does not have an embedded IP", func() {
It("returns the default trinity of nameservers", func() {
randomDomain := random8ByteString() + ".com."
ns := xip.NSResources("_acme-challenge." + randomDomain)
Expect(len(ns)).To(Equal(3))
})
})
})
})
Describe("SOAResource()", func() {