mirror of
https://github.com/cunnie/sslip.io.git
synced 2025-10-10 10:00:06 +08:00
🐞 TXT Records: only ONE string per record
Previously we were returning one TXT record with multiple strings for _sslip.io_. That did not work for ProtonMail's domain verification. It seems a convention that each TXT record has one string. _google.com_, for example, has a separate TXT record for each string. It turns out I had misunderstood the [StackExchange](https://serverfault.com/questions/815841/multiple-txt-fields-for-same-subdomain) thread. fixes (from ProtonMail domain verification): > Verification did not succeed, please try again in an hour.
This commit is contained in:
@@ -31,7 +31,7 @@ type DomainCustomization struct {
|
|||||||
AAAA []dnsmessage.AAAAResource
|
AAAA []dnsmessage.AAAAResource
|
||||||
CNAME dnsmessage.CNAMEResource
|
CNAME dnsmessage.CNAMEResource
|
||||||
MX []dnsmessage.MXResource
|
MX []dnsmessage.MXResource
|
||||||
TXT dnsmessage.TXTResource
|
TXT []dnsmessage.TXTResource
|
||||||
}
|
}
|
||||||
|
|
||||||
type DomainCustomizations map[string]DomainCustomization
|
type DomainCustomizations map[string]DomainCustomization
|
||||||
@@ -80,14 +80,12 @@ var (
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// although multiple TXT records with multiple strings are allowed, we're sticking
|
// Although multiple TXT records with multiple strings are allowed, we're sticking
|
||||||
// with a single TXT record with multiple strings to simplify things, just like AWS
|
// with a multiple TXT records with a single string apiece because that's what ProtonMail requires
|
||||||
// does: https://serverfault.com/questions/815841/multiple-txt-fields-for-same-subdomain
|
// and that's what google.com does.
|
||||||
TXT: dnsmessage.TXTResource{
|
TXT: []dnsmessage.TXTResource{
|
||||||
TXT: []string{
|
{TXT: []string{"protonmail-verification=ce0ca3f5010aa7a2cf8bcc693778338ffde73e26"}}, // ProtonMail verification; don't delete
|
||||||
"protonmail-verification=ce0ca3f5010aa7a2cf8bcc693778338ffde73e26", // protonmail verification; don't delete
|
{TXT: []string{"v=spf1 include:_spf.protonmail.ch mx ~all"}}, // Sender Policy Framework
|
||||||
"v=spf1 include:_spf.protonmail.ch mx ~all",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// nameserver addresses; we get queries for those every once in a while
|
// nameserver addresses; we get queries for those every once in a while
|
||||||
@@ -307,29 +305,33 @@ func processQuestion(q dnsmessage.Question, b *dnsmessage.Builder) (logMessage s
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var txt dnsmessage.TXTResource
|
var txts []dnsmessage.TXTResource
|
||||||
txt, err = TXTResource(q.Name.String())
|
txts, err = TXTResources(q.Name.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = noAnswersOnlyAuthorities(q, b, &logMessage)
|
err = noAnswersOnlyAuthorities(q, b, &logMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = b.TXTResource(dnsmessage.ResourceHeader{
|
var logMessageTXTss []string
|
||||||
Name: q.Name,
|
for _, txt := range txts {
|
||||||
Type: dnsmessage.TypeTXT,
|
err = b.TXTResource(dnsmessage.ResourceHeader{
|
||||||
Class: dnsmessage.ClassINET,
|
Name: q.Name,
|
||||||
// aggressively expire (5 mins) TXT records, long enough to obtain a Let's Encrypt cert,
|
Type: dnsmessage.TypeTXT,
|
||||||
// but short enough to free up frequently-used domains (e.g. 192.168.0.1.sslip.io) for the next user
|
Class: dnsmessage.ClassINET,
|
||||||
TTL: 300,
|
// aggressively expire (5 mins) TXT records, long enough to obtain a Let's Encrypt cert,
|
||||||
Length: 0,
|
// but short enough to free up frequently-used domains (e.g. 192.168.0.1.sslip.io) for the next user
|
||||||
}, txt)
|
TTL: 300,
|
||||||
if err != nil {
|
Length: 0,
|
||||||
return
|
}, txt)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var logMessageTXTs []string
|
||||||
|
for _, TXTstring := range txt.TXT {
|
||||||
|
logMessageTXTs = append(logMessageTXTs, TXTstring)
|
||||||
|
}
|
||||||
|
logMessageTXTss = append(logMessageTXTss, `TXT "`+strings.Join(logMessageTXTs, `", "`)+`"`)
|
||||||
}
|
}
|
||||||
var logMessageTXTs []string
|
logMessage += strings.Join(logMessageTXTss, " ")
|
||||||
for _, TXTstring := range txt.TXT {
|
|
||||||
logMessageTXTs = append(logMessageTXTs, TXTstring)
|
|
||||||
}
|
|
||||||
logMessage += `TXT "` + strings.Join(logMessageTXTs, `", "`) + `"`
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
@@ -459,12 +461,12 @@ func SOAResource(fqdnString string) dnsmessage.SOAResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TXTResource(fqdnString string) (dnsmessage.TXTResource, error) {
|
func TXTResources(fqdnString string) ([]dnsmessage.TXTResource, error) {
|
||||||
// is it a customized TXT record? If so, return early
|
// is it a customized TXT record? If so, return early
|
||||||
if domain, ok := Customizations[fqdnString]; ok {
|
if domain, ok := Customizations[fqdnString]; ok {
|
||||||
return domain.TXT, nil
|
return domain.TXT, nil
|
||||||
}
|
}
|
||||||
return dnsmessage.TXTResource{}, ErrNotFound
|
return nil, ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func noAnswersOnlyAuthorities(q dnsmessage.Question, b *dnsmessage.Builder, logMessage *string) error {
|
func noAnswersOnlyAuthorities(q dnsmessage.Question, b *dnsmessage.Builder, logMessage *string) error {
|
||||||
|
@@ -334,27 +334,27 @@ var _ = Describe("Xip", func() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Describe("TXTResource()", func() {
|
Describe("TXTResources()", func() {
|
||||||
It("returns no TXT resources", func() {
|
It("returns no TXT resources", func() {
|
||||||
domain := "example.com."
|
domain := "example.com."
|
||||||
_, err := xip.TXTResource(domain)
|
_, err := xip.TXTResources(domain)
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(err).To(HaveOccurred())
|
||||||
})
|
})
|
||||||
When("queried for the sslip.io domain", func() {
|
When("queried for the sslip.io domain", func() {
|
||||||
It("returns mail-related TXT resources for the sslip.io domain", func() {
|
It("returns mail-related TXT resources for the sslip.io domain", func() {
|
||||||
domain := "sslip.io."
|
domain := "sslip.io."
|
||||||
txt, err := xip.TXTResource(domain)
|
txt, err := xip.TXTResources(domain)
|
||||||
Expect(err).To(Not(HaveOccurred()))
|
Expect(err).To(Not(HaveOccurred()))
|
||||||
Expect(len(txt.TXT)).To(Equal(2))
|
Expect(len(txt)).To(Equal(2))
|
||||||
Expect(txt.TXT[0]).To(MatchRegexp("protonmail-verification="))
|
Expect(txt[0].TXT[0]).To(MatchRegexp("protonmail-verification="))
|
||||||
Expect(txt.TXT[1]).To(MatchRegexp("v=spf1"))
|
Expect(txt[1].TXT[0]).To(MatchRegexp("v=spf1"))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
When("a domain has been customized", func() { // Unnecessary, but confirms Golang's behavior for me, a doubting Thomas
|
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."
|
customDomain := "some-crazy-domain-name-no-really.io."
|
||||||
xip.Customizations[customDomain] = xip.DomainCustomization{}
|
xip.Customizations[customDomain] = xip.DomainCustomization{}
|
||||||
It("returns no TXT resources", func() {
|
It("returns no TXT resources", func() {
|
||||||
_, err := xip.TXTResource(customDomain)
|
_, err := xip.TXTResources(customDomain)
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(err).To(HaveOccurred())
|
||||||
})
|
})
|
||||||
delete(xip.Customizations, customDomain) // clean-up
|
delete(xip.Customizations, customDomain) // clean-up
|
||||||
|
Reference in New Issue
Block a user