func TestScanToScanResponse(t *testing.T) { scan := model.Scan{ Status: model.ScanStatusExecuted, StartedAt: time.Now().Add(-1 * time.Hour), FinishedAt: time.Now().Add(-30 * time.Minute), DomainsScanned: 10, DomainsWithDNSSECScanned: 4, NameserverStatistics: map[string]uint64{ model.NameserverStatusToString(model.NameserverStatusOK): 16, model.NameserverStatusToString(model.NameserverStatusTimeout): 4, }, DSStatistics: map[string]uint64{ model.DSStatusToString(model.DSStatusOK): 3, model.DSStatusToString(model.DSStatusExpiredSignature): 1, }, } scanResponse := ScanToScanResponse(scan) if scanResponse.Status != "EXECUTED" { t.Error("Status is not being translated correctly for a scan") } if scanResponse.DomainsToBeScanned != 0 { t.Error("Domains to be scanned field was not converted correctly") } if scanResponse.DomainsScanned != 10 { t.Error("Domains scanned field was not converted correctly") } if scanResponse.DomainsWithDNSSECScanned != 4 { t.Error("Domains with DNSSEC scanned field was not converted correctly") } if !scanResponse.StartedAt.Equal(scan.StartedAt) { t.Error("Started time was not converted correctly") } if !scanResponse.FinishedAt.Equal(scan.FinishedAt) { t.Error("Finished time was not converted correctly") } if scanResponse.NameserverStatistics["OK"] != 16 || scanResponse.NameserverStatistics["TIMEOUT"] != 4 { t.Error("Nameserver statistics weren't converted correctly") } if scanResponse.DSStatistics["OK"] != 3 || scanResponse.DSStatistics["EXPSIG"] != 1 { t.Error("DS statistics weren't converted correctly") } if len(scanResponse.Links) != 1 || scanResponse.Links[0].HRef != fmt.Sprintf("/scan/%s", scan.StartedAt.Format(time.RFC3339Nano)) { t.Error("Links weren't added correctly") } }
// Convert a nameserver of the system into a format with limited information to return it // to the user func toNameserverResponse(nameserver model.Nameserver) NameserverResponse { ipv4 := "" if len(nameserver.IPv4) > 0 { ipv4 = nameserver.IPv4.String() } ipv6 := "" if len(nameserver.IPv6) > 0 { ipv6 = nameserver.IPv6.String() } return NameserverResponse{ Host: nameserver.Host, IPv4: ipv4, IPv6: ipv6, LastStatus: model.NameserverStatusToString(nameserver.LastStatus), LastCheckAt: nameserver.LastCheckAt, LastOKAt: nameserver.LastOKAt, } }
func TestToNameserverResponse(t *testing.T) { now := time.Now() nameserver := model.Nameserver{ Host: "ns1.example.com.br.", IPv4: net.ParseIP("127.0.0.1"), IPv6: net.ParseIP("::1"), LastStatus: model.NameserverStatusOK, LastCheckAt: now, LastOKAt: now, } nameserverResponse := toNameserverResponse(nameserver) if nameserverResponse.Host != "ns1.example.com.br." { t.Error("Fail to convert host") } if nameserverResponse.IPv4 != "127.0.0.1" { t.Error("Fail to convert IPv4") } if nameserverResponse.IPv6 != "::1" { t.Error("Fail to convert IPv6") } if nameserverResponse.LastStatus != model.NameserverStatusToString(model.NameserverStatusOK) { t.Error("Fail to convert last status") } if nameserverResponse.LastCheckAt.Unix() != now.Unix() || nameserverResponse.LastOKAt.Unix() != now.Unix() { t.Error("Fail to convert dates") } }
func scanDomain() { dns.HandleFunc("example.com.br.", func(w dns.ResponseWriter, dnsRequestMessage *dns.Msg) { defer w.Close() dnsResponseMessage := &dns.Msg{ MsgHdr: dns.MsgHdr{ Authoritative: true, }, Question: dnsRequestMessage.Question, Answer: []dns.RR{ &dns.SOA{ Hdr: dns.RR_Header{ Name: "example.com.br.", Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 86400, }, Ns: "ns1.example.com.br.", Mbox: "rafael.justo.net.br.", Serial: 2013112600, Refresh: 86400, Retry: 86400, Expire: 86400, Minttl: 900, }, }, } dnsResponseMessage.SetReply(dnsRequestMessage) w.WriteMsg(dnsResponseMessage) }) var client http.Client url := "" if len(config.ShelterConfig.WebClient.Listeners) > 0 { url = fmt.Sprintf("http://%s:%d", config.ShelterConfig.WebClient.Listeners[0].IP, config.ShelterConfig.WebClient.Listeners[0].Port) } if len(url) == 0 { utils.Fatalln("There's no interface to connect to", nil) } content := `{ "Nameservers": [ { "Host": "ns1.example.com.br.", "ipv4": "127.0.0.1" }, { "Host": "ns2.example.com.br.", "ipv4": "127.0.0.1" } ] }` r, err := http.NewRequest("PUT", fmt.Sprintf("%s%s", url, "/domain/example.com.br./verification"), strings.NewReader(content)) if err != nil { utils.Fatalln("Error creating the HTTP request", err) } utils.BuildHTTPHeader(r, []byte(content)) response, err := client.Do(r) if err != nil { utils.Fatalln("Error sending request", err) } responseContent, err := ioutil.ReadAll(response.Body) if err != nil { utils.Fatalln("Error reading response content", err) } if response.StatusCode != http.StatusOK { utils.Fatalln("Error scanning domain", errors.New(string(responseContent))) } var domainResponse protocol.DomainResponse if err := json.Unmarshal(responseContent, &domainResponse); err != nil { utils.Fatalln("Error decoding domain response", err) } if len(domainResponse.Nameservers) != 2 { utils.Fatalln("Wrong number of nameservers", nil) } if domainResponse.Nameservers[0].LastStatus != model.NameserverStatusToString(model.NameserverStatusOK) { utils.Fatalln("Scan did not work for ns1", nil) } if domainResponse.Nameservers[1].LastStatus != model.NameserverStatusToString(model.NameserverStatusOK) { utils.Fatalln("Scan did not work for ns2", nil) } }
func domainWithErrors(config ScanCollectorTestConfigFile, database *mgo.Database) { domainsToSave := make(chan *model.Domain, config.Scan.DomainsBufferSize) domainsToSave <- &model.Domain{ FQDN: "br.", Nameservers: []model.Nameserver{ { Host: "ns1.br", IPv4: net.ParseIP("127.0.0.1"), LastStatus: model.NameserverStatusTimeout, }, }, DSSet: []model.DS{ { Keytag: 1234, Algorithm: model.DSAlgorithmRSASHA1NSEC3, DigestType: model.DSDigestTypeSHA1, Digest: "EAA0978F38879DB70A53F9FF1ACF21D046A98B5C", LastStatus: model.DSStatusExpiredSignature, }, }, } domainsToSave <- nil model.StartNewScan() runScan(config, database, domainsToSave) domainDAO := dao.DomainDAO{ Database: database, } domain, err := domainDAO.FindByFQDN("br.") if err != nil { utils.Fatalln("Error loading domain with problems", err) } if len(domain.Nameservers) == 0 { utils.Fatalln("Error saving nameservers", nil) } if domain.Nameservers[0].LastStatus != model.NameserverStatusTimeout { utils.Fatalln("Error setting status in the nameserver", nil) } if len(domain.DSSet) == 0 { utils.Fatalln("Error saving the DS set", nil) } if domain.DSSet[0].LastStatus != model.DSStatusExpiredSignature { utils.Fatalln("Error setting status in the DS", nil) } if err := domainDAO.RemoveByFQDN("br."); err != nil { utils.Fatalln("Error removing test domain", err) } currentScan := model.GetCurrentScan() if currentScan.DomainsScanned != 1 || currentScan.DomainsWithDNSSECScanned != 1 { utils.Fatalln("Not counting domains for scan progress when there're errors", nil) } if currentScan.NameserverStatistics[model.NameserverStatusToString(model.NameserverStatusTimeout)] != 1 || currentScan.DSStatistics[model.DSStatusToString(model.DSStatusExpiredSignature)] != 1 { utils.Fatalln("Not counting statistics properly when there're errors", nil) } }
// Auxiliary function for template that compares two nameserver status (case insensitive) func nameserverStatusEquals(nameserverStatus model.NameserverStatus, expectedNameserverTextStatus string) bool { return strings.ToLower(model.NameserverStatusToString(nameserverStatus)) == strings.TrimSpace(strings.ToLower(expectedNameserverTextStatus)) }
// This method is the last part of the scan, when the new state of the domain object is // persisted back to the database. It receives a go routine control group to sinalize to // the main thread when the scan ends, a domain channel to receive each domain that need // to be save and an error channel to send back all errors while persisting the data. It // was created to be asynchronous and finish after receiving a poison pill from querier // dispatcher func (c *Collector) Start(scanGroup *sync.WaitGroup, domainsToSaveChannel chan *model.Domain, errorsChannel chan error) { // Add one more to the group of scan go routines scanGroup.Add(1) go func() { // Initialize Domain DAO using injected database connection domainDAO := dao.DomainDAO{ Database: c.Database, } // Add a safety check to avoid an infinite loop if c.SaveAtOnce == 0 { c.SaveAtOnce = 1 } finished := false nameserverStatistics := make(map[string]uint64) dsStatistics := make(map[string]uint64) for { // Using make for faster allocation domains := make([]*model.Domain, 0, c.SaveAtOnce) for i := 0; i < c.SaveAtOnce; i++ { domain := <-domainsToSaveChannel // Detect poison pill. We don't return from function here because we can still // have some domains to save in the domains array if domain == nil { finished = true break } // Count this domain for the scan information to estimate the scan progress model.FinishAnalyzingDomainForScan(len(domain.DSSet) > 0) // Keep track of nameservers statistics for _, nameserver := range domain.Nameservers { status := model.NameserverStatusToString(nameserver.LastStatus) nameserverStatistics[status] += 1 } // Keep track of DS statistics for _, ds := range domain.DSSet { status := model.DSStatusToString(ds.LastStatus) dsStatistics[status] += 1 } domains = append(domains, domain) } domainsResults := domainDAO.SaveMany(domains) for _, domainResult := range domainsResults { if domainResult.Error != nil { // Error channel should have a buffer or this will block the collector until // someone check this error. One question here is that we are returning the // error, but not telling wich domain got the error, we should improve the error // communication system between the go routines errorsChannel <- domainResult.Error } } // Now that everything is done, check if we received a poison pill if finished { model.StoreStatisticsOfTheScan(nameserverStatistics, dsStatistics) scanGroup.Done() return } } }() }
// Generates a report with the result of a scan in the root zone file, it should be last // last thing from the test, because it changes the DNS test port to the original one for // real tests func inputScanReport(config ScanQuerierTestConfigFile) { // Move back to default port, because we are going to query the world for real to check // querier performance scan.DNSPort = 53 // As we are using the same domains repeatedly we should be careful about how many // requests we send to only one host to avoid abuses. This value should be beteween 5 // and 10 scan.MaxQPSPerHost = 5 report := " # | Total | QPS | Memory (MB)\n" + "---------------------------------------------------\n" domains, err := readInputFile(config.Report.InputFile) if err != nil { utils.Fatalln("Error while loading input data for report", err) } nameserverStatusCounter := 0 nameserversStatus := make(map[model.NameserverStatus]int) dsStatusCounter := 0 dsSetStatus := make(map[model.DSStatus]int) totalDuration, queriesPerSecond, nameserversStatus, dsSetStatus := calculateScanQuerierDurations(config, domains) var memStats runtime.MemStats runtime.ReadMemStats(&memStats) report += fmt.Sprintf("% -8d | %16s | %4d | %14.2f\n", len(domains), time.Duration(int64(totalDuration)).String(), queriesPerSecond, float64(memStats.Alloc)/float64(MB), ) report += "\nNameserver Status\n" + "-----------------\n" for _, counter := range nameserversStatus { nameserverStatusCounter += counter } for status, counter := range nameserversStatus { report += fmt.Sprintf("%16s: % 3.2f%%\n", model.NameserverStatusToString(status), (float64(counter*100) / float64(nameserverStatusCounter)), ) } report += "\nDS Status\n" + "---------\n" for _, counter := range dsSetStatus { dsStatusCounter += counter } for status, counter := range dsSetStatus { report += fmt.Sprintf("%16s: % 3.2f%%\n", model.DSStatusToString(status), (float64(counter*100) / float64(dsStatusCounter)), ) } utils.WriteReport(config.Report.OutputFile, report) }
func brDomainWithoutDNSSEC(domainDAO dao.DomainDAO) { domain := model.Domain{ FQDN: "br.", Nameservers: []model.Nameserver{ { Host: "a.dns.br.", IPv4: net.ParseIP("200.160.0.10"), IPv6: net.ParseIP("2001:12ff::10"), }, { Host: "b.dns.br.", IPv4: net.ParseIP("200.189.41.10"), }, { Host: "c.dns.br.", IPv4: net.ParseIP("200.192.233.10"), }, { Host: "d.dns.br.", IPv4: net.ParseIP("200.219.154.10"), IPv6: net.ParseIP("2001:12f8:4::10"), }, { Host: "f.dns.br.", IPv4: net.ParseIP("200.219.159.10"), }, }, // We are going to add the current DNSKEYs from .br but we are not going to check it. // This is because there's a strange case that when it found a problem on a DS (such // as bit SEP) it does not check other nameservers DSSet: []model.DS{ { Keytag: 41674, Algorithm: model.DSAlgorithmRSASHA1, DigestType: model.DSDigestTypeSHA256, Digest: "6ec74914376b4f383ede3840088ae1d7bf13a19bfc51465cc2da57618889416a", }, { Keytag: 57207, Algorithm: model.DSAlgorithmRSASHA1, DigestType: model.DSDigestTypeSHA256, Digest: "d46f059860d31a0965f925ac6ff97ed0975f33a14e2d01ec5ab5dd543624d307", }, }, } var err error if err = domainDAO.Save(&domain); err != nil { utils.Fatalln("Error saving the domain", err) } scan.ScanDomains() domain, err = domainDAO.FindByFQDN(domain.FQDN) if err != nil { utils.Fatalln("Didn't find scanned domain", err) } for _, nameserver := range domain.Nameservers { if nameserver.LastStatus != model.NameserverStatusOK { utils.Fatalln(fmt.Sprintf("Fail to validate a supposedly well configured nameserver '%s'. Found status: %s", nameserver.Host, model.NameserverStatusToString(nameserver.LastStatus)), nil) } } if err := domainDAO.RemoveByFQDN(domain.FQDN); err != nil { utils.Fatalln(fmt.Sprintf("Error removing domain %s", domain.FQDN), err) } }
func domainWithNoErrorsOnTheFly() { domain, dnskey, rrsig, lastCheckAt, lastOKAt := generateAndSignDomain("br.") dns.HandleFunc("br.", func(w dns.ResponseWriter, dnsRequestMessage *dns.Msg) { defer w.Close() if dnsRequestMessage.Question[0].Qtype == dns.TypeSOA { dnsResponseMessage := &dns.Msg{ MsgHdr: dns.MsgHdr{ Authoritative: true, }, Question: dnsRequestMessage.Question, Answer: []dns.RR{ &dns.SOA{ Hdr: dns.RR_Header{ Name: "br.", Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 86400, }, Ns: "ns1.br.", Mbox: "rafael.justo.net.br.", Serial: 2013112600, Refresh: 86400, Retry: 86400, Expire: 86400, Minttl: 900, }, }, } dnsResponseMessage.SetReply(dnsRequestMessage) w.WriteMsg(dnsResponseMessage) } else if dnsRequestMessage.Question[0].Qtype == dns.TypeDNSKEY { dnsResponseMessage := &dns.Msg{ MsgHdr: dns.MsgHdr{ Authoritative: true, }, Question: dnsRequestMessage.Question, Answer: []dns.RR{ dnskey, rrsig, }, } dnsResponseMessage.SetReply(dnsRequestMessage) w.WriteMsg(dnsResponseMessage) } }) scan.ScanDomain(&domain) for _, nameserver := range domain.Nameservers { if nameserver.LastStatus != model.NameserverStatusOK { utils.Fatalln(fmt.Sprintf("Fail to validate a supposedly well configured nameserver '%s'. Found status: %s", nameserver.Host, model.NameserverStatusToString(nameserver.LastStatus)), nil) } if nameserver.LastCheckAt.Before(lastCheckAt) || nameserver.LastCheckAt.Equal(lastCheckAt) { utils.Fatalln(fmt.Sprintf("Last check date was not updated in nameserver '%s'", nameserver.Host), nil) } if nameserver.LastOKAt.Before(lastOKAt) || nameserver.LastOKAt.Equal(lastOKAt) { utils.Fatalln(fmt.Sprintf("Last OK date was not updated in nameserver '%s'", nameserver.Host), nil) } } for _, ds := range domain.DSSet { if ds.LastStatus != model.DSStatusOK { utils.Fatalln(fmt.Sprintf("Fail to validate a supposedly well configured DS %d. "+ "Found status: %s", ds.Keytag, model.DSStatusToString(ds.LastStatus)), nil) } if ds.LastCheckAt.Before(lastCheckAt) || ds.LastCheckAt.Equal(lastCheckAt) { utils.Fatalln(fmt.Sprintf("Last check date was not updated in DS %d", ds.Keytag), nil) } if ds.LastOKAt.Before(lastOKAt) || ds.LastOKAt.Equal(lastOKAt) { utils.Fatalln(fmt.Sprintf("Last OK date was not updated in DS %d", ds.Keytag), nil) } } }
func scanDomain() { dns.HandleFunc("example.com.br.", func(w dns.ResponseWriter, dnsRequestMessage *dns.Msg) { defer w.Close() dnsResponseMessage := &dns.Msg{ MsgHdr: dns.MsgHdr{ Authoritative: true, }, Question: dnsRequestMessage.Question, Answer: []dns.RR{ &dns.SOA{ Hdr: dns.RR_Header{ Name: "example.com.br.", Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 86400, }, Ns: "ns1.example.com.br.", Mbox: "rafael.justo.net.br.", Serial: 2013112600, Refresh: 86400, Retry: 86400, Expire: 86400, Minttl: 900, }, }, } dnsResponseMessage.SetReply(dnsRequestMessage) w.WriteMsg(dnsResponseMessage) }) mux := handy.NewHandy() h := new(handler.DomainVerificationHandler) mux.Handle("/domain/{fqdn}/verification", func() handy.Handler { return h }) requestContent := `{ "Nameservers": [ { "Host": "ns1.example.com.br.", "ipv4": "127.0.0.1" }, { "Host": "ns2.example.com.br.", "ipv4": "127.0.0.1" } ] }` r, err := http.NewRequest("PUT", "/domain/example.com.br./verification", strings.NewReader(requestContent)) if err != nil { utils.Fatalln("Error creating the HTTP request", err) } utils.BuildHTTPHeader(r, []byte(requestContent)) w := httptest.NewRecorder() mux.ServeHTTP(w, r) responseContent, err := ioutil.ReadAll(w.Body) if err != nil { utils.Fatalln("Error reading response body", err) } if w.Code != http.StatusOK { utils.Fatalln(fmt.Sprintf("Error scanning domain. "+ "Expected %d and got %d", http.StatusOK, w.Code), errors.New(string(responseContent))) } if len(h.Response.Nameservers) != 2 { utils.Fatalln("Wrong number of nameservers", nil) } if h.Response.Nameservers[0].LastStatus != model.NameserverStatusToString(model.NameserverStatusOK) { utils.Fatalln("Scan did not work for ns1", nil) } if h.Response.Nameservers[1].LastStatus != model.NameserverStatusToString(model.NameserverStatusOK) { utils.Fatalln("Scan did not work for ns2", nil) } }