Skip to content

Commit

Permalink
TXTResource returns custom TXT resources
Browse files Browse the repository at this point in the history
In order to restore email service for the sslip.io domain, we need to
return custom TXT records.

The custom records are in the `xip.Customizations` variable. This lays
the groundwork for ACMEv2 wildcard DNS, which, IIRC, works via TXT
records.

Drive-by: removed an unused constant, `MxHost`. That information is
either in the `Customization` struct or generated on-the-fly.

fixes:

> Dear valued customer, We have disabled your domain sslip.io and all of its addresses. No emails will be received or sent for it.

[#6]
  • Loading branch information
cunnie committed Dec 16, 2020
1 parent 8b2d9e3 commit 2f4e0f2
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 3 deletions.
62 changes: 59 additions & 3 deletions bosh-release/src/xip/xip.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import (

const (
Hostmaster = "briancunnie.gmail.com."
MxHost = "mail.protonmail.ch."
)

// DomainCustomizations are values that are returned for specific queries.
Expand All @@ -27,14 +26,16 @@ const (
//
// Noticeably absent are the NS records and SOA records. They don't need to be customized
// because they are always the same, regardless of the domain being queried.
type DomainCustomizations map[string]struct {
type DomainCustomization struct {
A []dnsmessage.AResource
AAAA []dnsmessage.AAAAResource
CNAME dnsmessage.CNAMEResource
MX []dnsmessage.MXResource
TXT dnsmessage.TXTResource
}

type DomainCustomizations map[string]DomainCustomization

var (
ipv4REDots = regexp.MustCompile(`(^|[.-])(((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))($|[.-])`)
ipv4REDashes = regexp.MustCompile(`(^|[.-])(((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])-){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))($|[.-])`)
Expand Down Expand Up @@ -83,7 +84,10 @@ var (
// with a single TXT record with multiple strings to simplify things, just like AWS
// does: https://serverfault.com/questions/815841/multiple-txt-fields-for-same-subdomain
TXT: dnsmessage.TXTResource{
TXT: []string{"v=spf1 include:_spf.protonmail.ch mx ~all"},
TXT: []string{
"protonmail-verification=ce0ca3f5010aa7a2cf8bcc693778338ffde73e26", // protonmail verification; don't delete
"v=spf1 include:_spf.protonmail.ch mx ~all",
},
},
},
// nameserver addresses; we get queries for those every once in a while
Expand Down Expand Up @@ -323,6 +327,50 @@ func processQuestion(q dnsmessage.Question, b *dnsmessage.Builder) (logMessage s
}
logMessage += "SOA"
}
case dnsmessage.TypeTXT:
{
err = b.StartAnswers()
if err != nil {
return
}
var txt dnsmessage.TXTResource
txt, err = TXTResource(q.Name.String())
if err != nil {
err = b.StartAuthorities()
if err != nil {
return
}
err = b.SOAResource(dnsmessage.ResourceHeader{
Name: q.Name,
Type: dnsmessage.TypeSOA,
Class: dnsmessage.ClassINET,
TTL: 604800, // 60 * 60 * 24 * 7 == 1 week; it's not gonna change
Length: 0,
}, SOAResource(q.Name.String()))
if err != nil {
return
}
logMessage += "nil, SOA"
return
}
err = b.TXTResource(dnsmessage.ResourceHeader{
Name: q.Name,
Type: dnsmessage.TypeTXT,
Class: dnsmessage.ClassINET,
// aggressively expire (5 mins) TXT records, long enough to obtain a Let's Encrypt cert,
// but short enough to free up frequently-used domains (e.g. 192.168.0.1.sslip.io) for the next user
TTL: 300,
Length: 0,
}, txt)
if err != nil {
return
}
var logMessageTXTs []string
for _, TXTstring := range txt.TXT {
logMessageTXTs = append(logMessageTXTs, TXTstring)
}
logMessage += `TXT "` + strings.Join(logMessageTXTs, `", "`) + `"`
}
default:
{
// default is the same case as an A/AAAA record which is not found,
Expand Down Expand Up @@ -463,3 +511,11 @@ func SOAResource(fqdnString string) dnsmessage.SOAResource {
MinTTL: 300,
}
}

func TXTResource(fqdnString string) (dnsmessage.TXTResource, error) {
// is it a customized TXT record? If so, return early
if domain, ok := Customizations[fqdnString]; ok {
return domain.TXT, nil
}
return dnsmessage.TXTResource{}, ErrNotFound
}
27 changes: 27 additions & 0 deletions bosh-release/src/xip/xip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,33 @@ var _ = Describe("Xip", func() {
})
})

Describe("TXTResource()", func() {
It("returns no TXT resources", func() {
domain := "example.com."
_, err := xip.TXTResource(domain)
Expect(err).To(HaveOccurred())
})
When("queried for the sslip.io domain", func() {
It("returns mail-related TXT resources for the sslip.io domain", func() {
domain := "sslip.io."
txt, err := xip.TXTResource(domain)
Expect(err).To(Not(HaveOccurred()))
Expect(len(txt.TXT)).To(Equal(2))
Expect(txt.TXT[0]).To(MatchRegexp("protonmail-verification="))
Expect(txt.TXT[1]).To(MatchRegexp("v=spf1"))
})
})
When("a domain has been customized", func() { // Unnecessary, but confirms Golang's behavior for me, a doubting Thomas
customDomain := "some-crazy-domain-name-no-really.io."
xip.Customizations[customDomain] = xip.DomainCustomization{}
It("returns no TXT resources", func() {
_, err := xip.TXTResource(customDomain)
Expect(err).To(HaveOccurred())
})
delete(xip.Customizations, customDomain) // clean-up
})
})

Describe("NameToA()", func() {
DescribeTable("when it succeeds",
func(fqdn string, expectedA dnsmessage.AResource) {
Expand Down

0 comments on commit 2f4e0f2

Please sign in to comment.