diff --git a/bosh-release/src/xip/xip.go b/bosh-release/src/xip/xip.go index b2392f75..2f84f36c 100644 --- a/bosh-release/src/xip/xip.go +++ b/bosh-release/src/xip/xip.go @@ -16,7 +16,6 @@ import ( const ( Hostmaster = "briancunnie.gmail.com." - MxHost = "mail.protonmail.ch." ) // DomainCustomizations are values that are returned for specific queries. @@ -27,7 +26,7 @@ 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 @@ -35,6 +34,8 @@ type DomainCustomizations map[string]struct { 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]))($|[.-])`) @@ -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 @@ -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, @@ -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 +} diff --git a/bosh-release/src/xip/xip_test.go b/bosh-release/src/xip/xip_test.go index 2184d253..2f4fece7 100644 --- a/bosh-release/src/xip/xip_test.go +++ b/bosh-release/src/xip/xip_test.go @@ -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) {