Beispiel #1
0
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")
	}
}
Beispiel #2
0
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))
	}
}
Beispiel #3
0
// 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,
	}
}
Beispiel #4
0
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")
	}
}
Beispiel #5
0
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)
	}
}
Beispiel #6
0
// 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))
}
Beispiel #7
0
// 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
			}
		}
	}()
}
Beispiel #8
0
// 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)
}
Beispiel #9
0
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)
		}
	}
}