From f2562c5418320750fb76292841ef7962ac71cf21 Mon Sep 17 00:00:00 2001 From: Elliot Levin Date: Thu, 27 Apr 2023 02:13:59 +0000 Subject: [PATCH 1/3] add option to explicitly delegate FQDN's --- src/sslip.io-dns-server/main.go | 19 ++++++- src/sslip.io-dns-server/xip/xip.go | 67 ++++++++++++++++++++++--- src/sslip.io-dns-server/xip/xip_test.go | 4 +- 3 files changed, 80 insertions(+), 10 deletions(-) diff --git a/src/sslip.io-dns-server/main.go b/src/sslip.io-dns-server/main.go index 192034e4..aea26c17 100644 --- a/src/sslip.io-dns-server/main.go +++ b/src/sslip.io-dns-server/main.go @@ -17,6 +17,7 @@ func main() { var wg sync.WaitGroup var blocklistURL = flag.String("blocklistURL", "https://raw.githubusercontent.com/cunnie/sslip.io/main/etc/blocklist.txt", `URL containing a list of "forbidden" names/CIDRs`) var nameservers = flag.String("nameservers", "ns-aws.sslip.io.,ns-azure.sslip.io.,ns-gce.sslip.io.", "comma-separated list of nameservers") + var delegated = flag.String("delegate", "", "delegate specific FQDN's to comma-separated list of nameservers, seperate multiple mappings with ';'") var addresses = flag.String("addresses", "sslip.io=78.46.204.247,"+ "sslip.io=2a01:4f8:c17:b8f::2,"+ @@ -35,7 +36,23 @@ func main() { log.Printf("blocklist URL: %s, name servers: %s, bind port: %d, quiet: %t", *blocklistURL, *nameservers, *bindPort, *quiet) - x, logmessages := xip.NewXip(*blocklistURL, strings.Split(*nameservers, ","), strings.Split(*addresses, ",")) + var delegatedMap = map[string][]string{} + if *delegated != "" { + for _, item := range strings.Split(*delegated, ";") { + var mapping = strings.SplitN(item, "=", 2) + + if len(mapping) < 2 { + log.Fatalf("Invalid delegate mapping \"%s\", expecting format \"=\"", item) + return + } + + var fqdn = mapping[0] + var nameservers = strings.Split(mapping[1], ",") + delegatedMap[fqdn] = nameservers + } + } + + x, logmessages := xip.NewXip(*blocklistURL, strings.Split(*nameservers, ","), strings.Split(*addresses, ","), delegatedMap) for _, logmessage := range logmessages { log.Println(logmessage) } diff --git a/src/sslip.io-dns-server/xip/xip.go b/src/sslip.io-dns-server/xip/xip.go index f62c98bc..39a6a79a 100644 --- a/src/sslip.io-dns-server/xip/xip.go +++ b/src/sslip.io-dns-server/xip/xip.go @@ -25,12 +25,13 @@ import ( // Xip is meant to be a singleton that holds global state for the DNS server type Xip struct { - DnsAmplificationAttackDelay chan struct{} // for throttling metrics.status.sslip.io - Metrics Metrics // DNS server metrics - BlocklistStrings []string // list of blacklisted strings that shouldn't appear in public hostnames - BlocklistCDIRs []net.IPNet // list of blacklisted strings that shouldn't appear in public hostnames - BlocklistUpdated time.Time // The most recent time the Blocklist was updated - NameServers []dnsmessage.NSResource // The list of authoritative name servers (NS) + DnsAmplificationAttackDelay chan struct{} // for throttling metrics.status.sslip.io + Metrics Metrics // DNS server metrics + BlocklistStrings []string // list of blacklisted strings that shouldn't appear in public hostnames + BlocklistCDIRs []net.IPNet // list of blacklisted strings that shouldn't appear in public hostnames + BlocklistUpdated time.Time // The most recent time the Blocklist was updated + NameServers []dnsmessage.NSResource // The list of authoritative name servers (NS) + DelegatedFqdns map[string][]dnsmessage.NSResource // Map of FQDN's to delegate to mapped name servers } // Metrics contains the counters of the important/interesting queries @@ -164,7 +165,7 @@ type Response struct { } // NewXip follows convention for constructors: https://go.dev/doc/effective_go#allocation_new -func NewXip(blocklistURL string, nameservers []string, addresses []string) (x *Xip, logmessages []string) { +func NewXip(blocklistURL string, nameservers []string, addresses []string, delegatedMap map[string][]string) (x *Xip, logmessages []string) { x = &Xip{Metrics: Metrics{Start: time.Now()}} // Download the blocklist @@ -245,6 +246,31 @@ func NewXip(blocklistURL string, nameservers []string, addresses []string) (x *X logmessages = append(logmessages, fmt.Sprintf(`Adding record "%s=%s"`, host, ip)) } + // parse and set out delegated FQDN's + x.DelegatedFqdns = map[string][]dnsmessage.NSResource{} + for fqdn, nameservers := range delegatedMap { + // ensure FQDN is absolute + if fqdn[len(fqdn)-1] != '.' { + fqdn += "." + } + var nsResources = []dnsmessage.NSResource{} + for _, ns := range nameservers { + // all nameservers must be absolute (end in ".") + if ns[len(ns)-1] != '.' { + ns += "." + } + // nameservers must be DNS-compliant + nsName, err := dnsmessage.NewName(ns) + if err != nil { + logmessages = append(logmessages, fmt.Sprintf(`-delegated: ignoring invalid nameserver "%s"`, ns)) + continue + } + nsResources = append(nsResources, dnsmessage.NSResource{NS: nsName}) + logmessages = append(logmessages, fmt.Sprintf(`Adding nameserver "%s" to delegated FQDN "%s"`, ns, fqdn)) + } + x.DelegatedFqdns[fqdn] = nsResources + } + // We want to make sure that our DNS server isn't used in a DNS amplification attack. // The endpoint we're worried about is metrics.status.sslip.io, whose reply is // ~400 bytes with a query of ~100 bytes (4x amplification). We accomplish this by @@ -357,6 +383,33 @@ func (x *Xip) processQuestion(q dnsmessage.Question, srcAddr net.IP) (response R RCode: dnsmessage.RCodeSuccess, // assume success, may be replaced later }, } + // Check if domain is delegated to specified nameservers + delegatedNameservers, delegated := x.DelegatedFqdns[q.Name.String()] + if delegated { + var logMessages []string + logMessages = append(logMessages, fmt.Sprintf("domain \"%s\" is delegated to ", q.Name.String())) + response.Header.Authoritative = false + for _, nameServer := range delegatedNameservers { + response.Authorities = append(response.Authorities, + // 1 or more A records; A records > 1 only available via Customizations + func(b *dnsmessage.Builder) error { + err = b.NSResource(dnsmessage.ResourceHeader{ + Name: q.Name, + Type: dnsmessage.TypeNS, + Class: dnsmessage.ClassINET, + TTL: 604800, // 60 * 60 * 24 * 7 == 1 week; long TTL, these IP addrs don't change + Length: 0, + }, nameServer) + if err != nil { + return err + } + return nil + }) + logMessages = append(logMessages, nameServer.NS.String()) + } + return response, logMessage + "nil, " + strings.Join(logMessages, ", "), nil + } + if IsAcmeChallenge(q.Name.String()) && !x.blocklist(q.Name.String()) { // thanks, @NormanR // delegate everything to its stripped (remove "_acme-challenge.") address, e.g. diff --git a/src/sslip.io-dns-server/xip/xip_test.go b/src/sslip.io-dns-server/xip/xip_test.go index 1d60d0d4..3b0288e0 100644 --- a/src/sslip.io-dns-server/xip/xip_test.go +++ b/src/sslip.io-dns-server/xip/xip_test.go @@ -79,7 +79,7 @@ var _ = Describe("Xip", func() { 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{}) + var x, _ = xip.NewXip("file:///", []string{"ns-aws.sslip.io.", "ns-azure.sslip.io.", "ns-gce.sslip.io."}, []string{}, map[string][]string{}) It("returns the name servers", func() { randomDomain := random8ByteString() + ".com." ns := x.NSResources(randomDomain) @@ -111,7 +111,7 @@ var _ = Describe("Xip", func() { }) }) When("we override the default nameservers", func() { - var x, _ = xip.NewXip("file:///", []string{"mickey", "minn.ie.", "goo.fy"}, []string{}) + var x, _ = xip.NewXip("file:///", []string{"mickey", "minn.ie.", "goo.fy"}, []string{}, map[string][]string{}) It("returns the configured servers", func() { randomDomain := random8ByteString() + ".com." ns := x.NSResources(randomDomain) From e698a83c5ff9ad7a5379e2098c81e6161bbb879d Mon Sep 17 00:00:00 2001 From: Elliot Levin Date: Thu, 27 Apr 2023 12:41:49 +0000 Subject: [PATCH 2/3] fix binding --- src/sslip.io-dns-server/xip/xip.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sslip.io-dns-server/xip/xip.go b/src/sslip.io-dns-server/xip/xip.go index 39a6a79a..f7e94214 100644 --- a/src/sslip.io-dns-server/xip/xip.go +++ b/src/sslip.io-dns-server/xip/xip.go @@ -390,8 +390,8 @@ func (x *Xip) processQuestion(q dnsmessage.Question, srcAddr net.IP) (response R logMessages = append(logMessages, fmt.Sprintf("domain \"%s\" is delegated to ", q.Name.String())) response.Header.Authoritative = false for _, nameServer := range delegatedNameservers { + nameServer := nameServer response.Authorities = append(response.Authorities, - // 1 or more A records; A records > 1 only available via Customizations func(b *dnsmessage.Builder) error { err = b.NSResource(dnsmessage.ResourceHeader{ Name: q.Name, From a77e7ee5f47c1a7609ba7f1842d4c0cf6b944632 Mon Sep 17 00:00:00 2001 From: Elliot Levin Date: Thu, 27 Apr 2023 13:44:35 +0000 Subject: [PATCH 3/3] perform case-insensitive lookup --- src/sslip.io-dns-server/xip/xip.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sslip.io-dns-server/xip/xip.go b/src/sslip.io-dns-server/xip/xip.go index f7e94214..1e3c345c 100644 --- a/src/sslip.io-dns-server/xip/xip.go +++ b/src/sslip.io-dns-server/xip/xip.go @@ -384,7 +384,7 @@ func (x *Xip) processQuestion(q dnsmessage.Question, srcAddr net.IP) (response R }, } // Check if domain is delegated to specified nameservers - delegatedNameservers, delegated := x.DelegatedFqdns[q.Name.String()] + delegatedNameservers, delegated := x.DelegatedFqdns[strings.ToLower(q.Name.String())] if delegated { var logMessages []string logMessages = append(logMessages, fmt.Sprintf("domain \"%s\" is delegated to ", q.Name.String()))