Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to explicitly delegate FQDN's #27

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion src/sslip.io-dns-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 ';'")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"separate" is spelled with one "e". It was spelled correctly the first time, incorrectly the second.

Using ";" as a separator is tricky because it's a meaningful character to the sh/bash/zsh shells—it may trip up an unsophisticated user.

Also, the pre-existing -addresses flag uses a different way to set multiple records (= and ,). Is there a reason you chose to use a completely different paradigm (, and ;)?

var addresses = flag.String("addresses",
"sslip.io=78.46.204.247,"+
"sslip.io=2a01:4f8:c17:b8f::2,"+
Expand All @@ -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 \"<fqdn>=<nameservers>\"", 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)
}
Expand Down
67 changes: 60 additions & 7 deletions src/sslip.io-dns-server/xip/xip.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions src/sslip.io-dns-server/xip/xip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down