Files
nip/xip/xip_test.go
Brian Cunnie 1bdd03fd39 Promote Golang code to the root of the repo
- That's where the code is expected to be
- The only reason the code was buried two directories down was because
  it was originally a BOSH release
- There hasn't been a BOSH release in over two years; last one was Feb
  26, 2022
- Other than a slight adjustment to the relative location of
  `blocklist.txt` file in the integration tests, there were no other
  changes
2024-05-11 10:14:23 -07:00

424 lines
18 KiB
Go

package xip_test
import (
"math/rand"
"net"
"strings"
"xip/testhelper"
"xip/xip"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"golang.org/x/net/dns/dnsmessage"
)
var _ = Describe("Xip", func() {
var (
err error
)
rand.Seed(GinkgoRandomSeed()) // Set to ginkgo's seed so that it's different each test & we can reproduce failures if necessary
Describe("CNAMEResources()", func() {
It("returns nil by default", func() {
randomDomain := testhelper.Random8ByteString() + ".com."
cname := xip.CNAMEResource(randomDomain)
Expect(cname).To(BeNil())
})
When("querying one of sslip.io's DKIM CNAME's", func() {
It("returns the CNAME", func() {
cname := xip.CNAMEResource("protonmail._domainkey.SSlip.Io.")
Expect(cname.CNAME.String()).To(MatchRegexp("^protonmail\\.domainkey.*.domains\\.proton\\.ch\\.$"))
})
})
When("a domain has been customized but has no CNAMEs", func() {
It("returns nil", func() {
customizedDomain := testhelper.Random8ByteString() + ".com."
xip.Customizations[customizedDomain] = xip.DomainCustomization{}
cname := xip.CNAMEResource(customizedDomain)
Expect(cname).To(BeNil())
delete(xip.Customizations, customizedDomain)
})
})
When("a domain has been customized with CNAMES", func() {
It("returns CNAME resources", func() {
customizedDomain := testhelper.Random8ByteString() + ".com."
xip.Customizations[strings.ToLower(customizedDomain)] = xip.DomainCustomization{
CNAME: dnsmessage.CNAMEResource{
CNAME: dnsmessage.Name{
// google.com.
Length: 11,
Data: [255]byte{
103, 111, 111, 103, 108, 101, 46, 99, 111, 109, 46,
},
},
},
}
cname := xip.CNAMEResource(customizedDomain)
Expect(cname.CNAME.String()).To(Equal("google.com."))
delete(xip.Customizations, customizedDomain) // clean-up
})
})
})
Describe("MXResources()", func() {
It("returns the MX resource", func() {
randomDomain := testhelper.Random8ByteString() + ".com."
mx := xip.MXResources(randomDomain)
mxHostName := dnsmessage.MustNewName(randomDomain)
Expect(len(mx)).To(Equal(1))
Expect(mx[0].MX).To(Equal(mxHostName))
})
When("sslip.io is the domain being queried", func() {
It("returns sslip.io's custom MX records", func() {
mx := xip.MXResources("sslIP.iO.")
Expect(len(mx)).To(Equal(2))
Expect(mx[0].MX.Data).To(Equal(xip.Customizations["sslip.io."].MX[0].MX.Data))
})
})
})
Describe("NSResources()", func() {
When("we use the default nameservers", func() {
var x, _ = xip.NewXip("file:///", []string{"ns-aws.sslip.io.", "ns-azure.sslip.io.", "ns-gce.sslip.io."}, []string{})
It("returns the name servers", func() {
randomDomain := testhelper.Random8ByteString() + ".com."
ns := x.NSResources(randomDomain)
Expect(len(ns)).To(Equal(3))
Expect(ns[0].NS.String()).To(Equal("ns-aws.sslip.io."))
Expect(ns[1].NS.String()).To(Equal("ns-azure.sslip.io."))
Expect(ns[2].NS.String()).To(Equal("ns-gce.sslip.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." + testhelper.Random8ByteString() + ".com."
ns := x.NSResources("_acme-challenge." + randomDomain)
Expect(len(ns)).To(Equal(1))
Expect(ns[0].NS.String()).To(Equal(randomDomain))
aResources := xip.NameToA(randomDomain, true)
Expect(len(aResources)).To(Equal(1))
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 := "_acme-challenge." + testhelper.Random8ByteString() + ".com."
ns := x.NSResources(randomDomain)
Expect(len(ns)).To(Equal(3))
})
})
})
})
When("we override the default nameservers", func() {
var x, _ = xip.NewXip("file:///", []string{"mickey", "minn.ie.", "goo.fy"}, []string{})
It("returns the configured servers", func() {
randomDomain := testhelper.Random8ByteString() + ".com."
ns := x.NSResources(randomDomain)
Expect(len(ns)).To(Equal(3))
Expect(ns[0].NS.String()).To(Equal("mickey."))
Expect(ns[1].NS.String()).To(Equal("minn.ie."))
Expect(ns[2].NS.String()).To(Equal("goo.fy."))
})
})
})
Describe("SOAResource()", func() {
It("returns the SOA resource for the domain in question", func() {
randomDomain := testhelper.Random8ByteString() + ".com."
randomDomainName := dnsmessage.MustNewName(randomDomain)
soa := xip.SOAResource(randomDomainName)
Expect(soa.NS.Data).To(Equal(randomDomainName.Data))
})
})
Describe("TXTResources()", func() {
var x xip.Xip
It("returns an empty array for a random domain", func() {
randomDomain := testhelper.Random8ByteString() + ".com."
txts, err := x.TXTResources(randomDomain, nil)
Expect(err).To(Not(HaveOccurred()))
Expect(len(txts)).To(Equal(0))
})
When("queried for the sslip.io domain", func() {
It("returns mail-related TXT resources for the sslip.io domain", func() {
domain := "ssLip.iO."
txts, err := x.TXTResources(domain, nil)
Expect(err).To(Not(HaveOccurred()))
Expect(len(txts)).To(Equal(2))
Expect(txts[0].TXT[0]).To(MatchRegexp("protonmail-verification="))
Expect(txts[1].TXT[0]).To(MatchRegexp("v=spf1"))
})
})
When("a random domain has been customized w/out any TXT defaults", func() { // Unnecessary, but confirms Golang's behavior for me, a doubting Thomas
customizedDomain := testhelper.Random8ByteString() + ".com."
xip.Customizations[customizedDomain] = xip.DomainCustomization{}
It("returns no TXT resources", func() {
txts, err := x.TXTResources(customizedDomain, nil)
Expect(err).To(Not(HaveOccurred()))
Expect(len(txts)).To(Equal(0))
})
delete(xip.Customizations, customizedDomain) // clean-up
})
When(`the domain "ip.sslip.io" is queried`, func() {
It("returns the IP address of the querier", func() {
txts, err := x.TXTResources("ip.sslip.io.", net.IP{1, 1, 1, 1})
Expect(err).To(Not(HaveOccurred()))
Expect(len(txts)).To(Equal(1))
Expect(txts[0].TXT[0]).To(MatchRegexp("^1.1.1.1$"))
})
})
When(`a customized domain without a TXT entry is queried`, func() {
It("returns no records (and doesn't panic, either)", func() {
txts, err := x.TXTResources("ns.sslip.io.", nil)
Expect(err).To(Not(HaveOccurred()))
Expect(len(txts)).To(Equal(0))
})
})
})
Describe("NameToA()", func() {
xip.Customizations["custom.record."] = xip.DomainCustomization{A: []dnsmessage.AResource{
{A: [4]byte{78, 46, 204, 247}},
}}
DescribeTable("when it succeeds",
func(fqdn string, expectedA dnsmessage.AResource) {
ipv4Answers := xip.NameToA(fqdn, true)
Expect(len(ipv4Answers)).To(Equal(1))
Expect(ipv4Answers[0]).To(Equal(expectedA))
},
Entry("custom record", "CusTom.RecOrd.", dnsmessage.AResource{A: [4]byte{78, 46, 204, 247}}),
// dots
Entry("loopback", "127.0.0.1", dnsmessage.AResource{A: [4]byte{127, 0, 0, 1}}),
Entry("255 with domain", "255.254.253.252.com", dnsmessage.AResource{A: [4]byte{255, 254, 253, 252}}),
Entry(`"This" network, pre-and-post`, "nono.io.0.1.2.3.ssLIp.IO", dnsmessage.AResource{A: [4]byte{0, 1, 2, 3}}),
Entry("private network, two IPs, grabs the leftmost", "nono.io.172.16.0.30.172.31.255.255.sslip.io", dnsmessage.AResource{A: [4]byte{172, 16, 0, 30}}),
// dashes
Entry("shared address with dashes", "100-64-1-2", dnsmessage.AResource{A: [4]byte{100, 64, 1, 2}}),
Entry("link-local with domain", "169-254-168-253-com", dnsmessage.AResource{A: [4]byte{169, 254, 168, 253}}),
Entry("IETF protocol assignments with domain and www", "www-192-0-0-1-com", dnsmessage.AResource{A: [4]byte{192, 0, 0, 1}}),
// dots-and-dashes, mix-and-matches
Entry("Pandaxin's paradox", "minio-01.192-168-1-100.sslip.io", dnsmessage.AResource{A: [4]byte{192, 168, 1, 100}}),
)
DescribeTable("when it does NOT match an IP address",
func(fqdn string) {
ipv4Answers := xip.NameToA(fqdn, true)
Expect(len(ipv4Answers)).To(Equal(0))
},
Entry("empty string", ""),
Entry("bare domain", "nono.io"),
Entry("canonical domain", "sslip.io"),
Entry("www", "www.sslip.io"),
Entry("a lone number", "538.sslip.io"),
Entry("too big", "256.254.253.252"),
Entry("NS but no dot", "ns-aws.sslip.io"),
Entry("NS + cruft at beginning", "p-ns-aws.sslip.io"),
Entry("test-net address with dots-and-dashes mixed", "www-192.0-2.3.example-me.com"),
)
When("There is more than one A record", func() {
It("returns them all", func() {
fqdn := testhelper.Random8ByteString()
xip.Customizations[strings.ToLower(fqdn)] = xip.DomainCustomization{
A: []dnsmessage.AResource{
{A: [4]byte{1}},
{A: [4]byte{2}},
},
}
ipv4Answers := xip.NameToA(fqdn, true)
Expect(err).ToNot(HaveOccurred())
Expect(len(ipv4Answers)).To(Equal(2))
Expect(ipv4Answers[0].A).To(Equal([4]byte{1}))
Expect(ipv4Answers[1].A).To(Equal([4]byte{2}))
delete(xip.Customizations, fqdn)
})
})
When("There are multiple matches", func() {
It("returns the leftmost one", func() {
ipv4Answers := xip.NameToA("nono.io.127.0.0.1.192.168.0.1.sslip.io", true)
Expect(len(ipv4Answers)).To(Equal(1))
Expect(ipv4Answers[0]).
To(Equal(dnsmessage.AResource{A: [4]byte{127, 0, 0, 1}}))
})
})
When("There are matches with dashes and dots", func() {
It("returns the one with dashes", func() {
ipv4Answers := xip.NameToA("nono.io.127.0.0.1.192-168-0-1.sslip.io", true)
Expect(len(ipv4Answers)).To(Equal(1))
Expect(ipv4Answers[0]).
To(Equal(dnsmessage.AResource{A: [4]byte{192, 168, 0, 1}}))
})
})
})
Describe("IsAcmeChallenge()", func() {
When("the domain doesn't have '_acme-challenge.' in it", func() {
It("returns false", func() {
randomDomain := testhelper.Random8ByteString() + ".com."
Expect(xip.IsAcmeChallenge(randomDomain)).To(BeFalse())
})
It("returns false even when there are embedded IPs", func() {
randomDomain := "127.0.0.1." + testhelper.Random8ByteString() + ".com."
Expect(xip.IsAcmeChallenge(randomDomain)).To(BeFalse())
})
})
When("it has '_acme-challenge.' in it", func() {
When("it does NOT have any embedded IPs", func() {
It("returns false", func() {
randomDomain := "_acme-challenge." + testhelper.Random8ByteString() + ".com."
Expect(xip.IsAcmeChallenge(randomDomain)).To(BeFalse())
})
})
When("it has embedded IPs", func() {
It("returns true", func() {
randomDomain := "_acme-challenge.127.0.0.1." + testhelper.Random8ByteString() + ".com."
Expect(xip.IsAcmeChallenge(randomDomain)).To(BeTrue())
randomDomain = "_acme-challenge.fe80--1." + testhelper.Random8ByteString() + ".com."
Expect(xip.IsAcmeChallenge(randomDomain)).To(BeTrue())
})
When("it has random capitalization", func() {
It("returns true", func() {
randomDomain := "_AcMe-ChAlLeNgE.127.0.0.1." + testhelper.Random8ByteString() + ".com."
Expect(xip.IsAcmeChallenge(randomDomain)).To(BeTrue())
randomDomain = "_aCMe-cHAllENge.fe80--1." + testhelper.Random8ByteString() + ".com."
Expect(xip.IsAcmeChallenge(randomDomain)).To(BeTrue())
})
})
})
})
})
Describe("NameToAAAA()", func() {
DescribeTable("when it succeeds",
func(fqdn string, expectedAAAA dnsmessage.AAAAResource) {
ipv6Answers := xip.NameToAAAA(fqdn, true)
Expect(len(ipv6Answers)).To(Equal(1))
Expect(ipv6Answers[0]).To(Equal(expectedAAAA))
},
// dashes only
Entry("loopback", "--1", dnsmessage.AAAAResource{AAAA: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}}),
Entry("ff with domain", "fffe-fdfc-fbfa-f9f8-f7f6-f5f4-f3f2-f1f0.com", dnsmessage.AAAAResource{AAAA: [16]byte{255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245, 244, 243, 242, 241, 240}}),
Entry("ff with domain and pre", "www.fffe-fdfc-fbfa-f9f8-f7f6-f5f4-f3f2-f1f0.com", dnsmessage.AAAAResource{AAAA: [16]byte{255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245, 244, 243, 242, 241, 240}}),
Entry("ff with domain dashes", "1.www-fffe-fdfc-fbfa-f9f8-f7f6-f5f4-f3f2-f1f0-1.com", dnsmessage.AAAAResource{AAAA: [16]byte{255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245, 244, 243, 242, 241, 240}}),
Entry("Browsing the logs", "2006-41d0-2-e01e--56dB-3598.sSLIP.io.", dnsmessage.AAAAResource{AAAA: [16]byte{32, 6, 65, 208, 0, 2, 224, 30, 0, 0, 0, 0, 86, 219, 53, 152}}),
Entry("Browsing the logs", "1-2-3--4-5-6.sSLIP.io.", dnsmessage.AAAAResource{AAAA: [16]byte{0, 1, 0, 2, 0, 3, 0, 0, 0, 0, 0, 4, 0, 5, 0, 6}}),
Entry("Browsing the logs", "1--2-3-4-5-6.sSLIP.io.", dnsmessage.AAAAResource{AAAA: [16]byte{0, 1, 0, 0, 0, 0, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6}}),
)
DescribeTable("when it does not match an IP address",
func(fqdn string) {
ipv6Answers := xip.NameToAAAA(fqdn, true)
Expect(len(ipv6Answers)).To(Equal(0))
},
Entry("empty string", ""),
Entry("bare domain", "nono.io"),
Entry("canonical domain", "sslip.io"),
Entry("www", "www.sslip.io"),
Entry("a 1 without double-dash", "-1"),
Entry("too big", "--g"),
)
When("using randomly generated IPv6 addresses (fuzz testing)", func() {
It("should succeed every time", func() {
for i := 0; i < 10000; i++ {
addr := testhelper.RandomIPv6Address()
ipv6Answers := xip.NameToAAAA(strings.ReplaceAll(addr.String(), ":", "-"), true)
Expect(err).ToNot(HaveOccurred())
Expect(ipv6Answers[0].AAAA[:]).To(Equal([]uint8(addr)))
}
})
})
When("There is more than one AAAA record", func() {
It("returns them all", func() {
fqdn := testhelper.Random8ByteString()
xip.Customizations[strings.ToLower(fqdn)] = xip.DomainCustomization{
AAAA: []dnsmessage.AAAAResource{
{AAAA: [16]byte{1}},
{AAAA: [16]byte{2}},
},
}
ipv6Addrs := xip.NameToAAAA(fqdn, true)
Expect(len(ipv6Addrs)).To(Equal(2))
Expect(ipv6Addrs[0].AAAA).To(Equal([16]byte{1}))
Expect(ipv6Addrs[1].AAAA).To(Equal([16]byte{2}))
delete(xip.Customizations, fqdn)
})
})
})
Describe("ReadBlocklist()", func() {
It("strips comments", func() {
input := strings.NewReader("# a comment\n#another comment\nno-comments\n")
bls, blIPs, err := xip.ReadBlocklist(input)
Expect(err).ToNot(HaveOccurred())
Expect(bls).To(Equal([]string{"no-comments"}))
Expect(blIPs).To(BeNil())
})
It("strips blank lines", func() {
input := strings.NewReader("\n\n\nno-blank-lines")
bls, blIPs, err := xip.ReadBlocklist(input)
Expect(err).ToNot(HaveOccurred())
Expect(bls).To(Equal([]string{"no-blank-lines"}))
Expect(blIPs).To(BeNil())
})
It("lowercases names for comparison", func() {
input := strings.NewReader("NO-YELLING")
bls, blIPs, err := xip.ReadBlocklist(input)
Expect(err).ToNot(HaveOccurred())
Expect(bls).To(Equal([]string{"no-yelling"}))
Expect(blIPs).To(BeNil())
})
It("removes all non-allowable characters", func() {
input := strings.NewReader("\nalpha #comment # comment\nåß∂ # comment # comment\ndelta∆\n ... GAMMA∑µ®† ...#asdfasdf#asdfasdf")
bls, blIPs, err := xip.ReadBlocklist(input)
Expect(err).ToNot(HaveOccurred())
Expect(bls).To(Equal([]string{"alpha", "delta", "gamma"}))
Expect(blIPs).To(BeNil())
})
It("reads in IPv4 CIDRs", func() {
input := strings.NewReader("\n43.134.66.67/24 #asdfasdf")
bls, blIPs, err := xip.ReadBlocklist(input)
Expect(err).ToNot(HaveOccurred())
Expect(bls).To(BeNil())
Expect(blIPs).To(Equal([]net.IPNet{{IP: net.IP{43, 134, 66, 0}, Mask: net.IPMask{255, 255, 255, 0}}}))
})
It("reads in IPv6 CIDRs", func() {
input := strings.NewReader("\n 2600::/64 #asdfasdf")
bls, blIPs, err := xip.ReadBlocklist(input)
Expect(err).ToNot(HaveOccurred())
Expect(bls).To(BeNil())
Expect(blIPs).To(Equal([]net.IPNet{
{IP: net.IP{38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
Mask: net.IPMask{255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}}}))
})
})
Describe("IsPublic()", func() {
DescribeTable("when determining whether an IP is public or private",
func(ip net.IP, expectedPublic bool) {
Expect(xip.IsPublic(ip)).To(Equal(expectedPublic))
},
Entry("Google Nameserver IPv4", net.ParseIP("8.8.8.8"), true),
Entry("Google Nameserver IPv6", net.ParseIP("2001:4860:4860::8888"), true),
Entry("Apple Studio morgoth.nono.io", net.ParseIP("2601:646:100:69f0:7d:9069:ea74:e3a"), true),
Entry("External interface home.nono.io", net.ParseIP("2001:558:6045:109:892f:2df3:15e3:3184"), true),
Entry("RFC 1918 Section 3 10/8", net.ParseIP("10.9.9.30"), false),
Entry("RFC 1918 Section 3 172.16/12", net.ParseIP("172.31.255.255"), false),
Entry("RFC 1918 Section 3 192.168/16", net.ParseIP("192.168.0.1"), false),
Entry("RFC 4193 Section 8 fc00::/7", net.ParseIP("fdff::"), false),
Entry("CG-NAT 100.64/10", net.ParseIP("100.127.255.255"), false),
Entry("CG-NAT 100.64/10", net.ParseIP("100.128.0.0"), true),
Entry("link-local IPv4", net.ParseIP("169.254.169.254"), false),
Entry("not link-local IPv4", net.ParseIP("169.255.255.255"), true),
Entry("link-local IPv6", net.ParseIP("fe80::"), false),
Entry("loopback IPv4 127/8", net.ParseIP("127.127.127.127"), false),
Entry("loopback IPv6 ::1/128", net.ParseIP("::1"), false),
Entry("IPv4/IPv6 Translation internet", net.ParseIP("64:ff9b::"), true),
Entry("IPv4/IPv6 Translation private internet", net.ParseIP("64:ff9b:1::"), false),
Entry("IPv4/IPv6 Translation internet", net.ParseIP("64:ff9b::"), true),
Entry("Teredo Tunneling", net.ParseIP("2001::"), true),
Entry("ORCHIDv2 (?)", net.ParseIP("2001:20::"), false),
Entry("Documentation", net.ParseIP("2001:db8::"), false),
Entry("Private internets", net.ParseIP("fc00::"), false),
)
})
})