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") } }
func TestDNSSECPolicyMissingSignature(t *testing.T) { dnskey, _, err := generateKeyAndSignZone("test.br.") if err != nil { t.Fatal(err) } ds := dnskey.ToDS(uint8(model.DSDigestTypeSHA1)) otherDNSKEY, otherRRSIG, err := generateKeyAndSignZone("test.br.") if err != nil { t.Fatal(err) } domain := &model.Domain{ DSSet: []model.DS{ { Keytag: dnskey.KeyTag(), Algorithm: convertKeyAlgorithm(dnskey.Algorithm), DigestType: model.DSDigestTypeSHA1, Digest: ds.Digest, }, }, } domainDSPolicy := NewDomainDSPolicy(domain) dnsResponseMessage := &dns.Msg{ Answer: []dns.RR{ otherDNSKEY, otherRRSIG, dnskey, }, } // We changed the behaviour of the system, allowing DNSKEYs without RRSIGs because of // key rollovers scenarios (pre-publish) if !domainDSPolicy.dnssecPolicy(dnsResponseMessage) { t.Errorf("Not allowing missing signature. Expected status %s and got %s", model.DSStatusToString(model.DSStatusOK), model.DSStatusToString(domain.DSSet[0].LastStatus)) } }
// Convert a DS of the system into a format with limited information to return it to the // user func toDSResponse(ds model.DS) DSResponse { return DSResponse{ Keytag: ds.Keytag, Algorithm: uint8(ds.Algorithm), Digest: ds.Digest, DigestType: uint8(ds.DigestType), ExpiresAt: ds.ExpiresAt, LastStatus: model.DSStatusToString(ds.LastStatus), LastCheckAt: ds.LastCheckAt, LastOKAt: ds.LastOKAt, } }
func TestToDSResponse(t *testing.T) { now := time.Now() ds := model.DS{ Keytag: 41674, Algorithm: model.DSAlgorithmRSASHA1, Digest: "eaa0978f38879db70a53f9ff1acf21d046a98b5c", DigestType: model.DSDigestTypeSHA1, LastStatus: model.DSStatusOK, LastCheckAt: now, LastOKAt: now, } dsResponse := toDSResponse(ds) if dsResponse.Keytag != 41674 { t.Error("Fail to convert keytag") } if dsResponse.Algorithm != 5 { t.Error("Fail to convert algorithm") } if dsResponse.Digest != "eaa0978f38879db70a53f9ff1acf21d046a98b5c" { t.Error("Fail to convert digest") } if dsResponse.DigestType != 1 { t.Error("Fail to convert digest type") } if dsResponse.LastStatus != model.DSStatusToString(model.DSStatusOK) { t.Error("Fail to convert last status") } if dsResponse.LastCheckAt.Unix() != now.Unix() || dsResponse.LastOKAt.Unix() != now.Unix() { t.Error("Fail to convert dates") } }
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 DS status (case insensitive) func dsStatusEquals(dsStatus model.DSStatus, expectedDSTextStatus string) bool { return strings.ToLower(model.DSStatusToString(dsStatus)) == strings.TrimSpace(strings.ToLower(expectedDSTextStatus)) }
// 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 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) } } }