Beispiel #1
0
// Save the domain object in the database and by consequence will also save the
// nameservers and ds set. On creation the domain object is going to receive the id that
// refers to the entry in the database
func (dao DomainDAO) Save(domain *model.Domain) error {
	// Check if the programmer forgot to set the database in DomainDAO object
	if dao.Database == nil {
		return ErrDomainDAOUndefinedDatabase
	}

	// When creating a new domain object, the id will be probably nil (or kind of new
	// according to bson.ObjectId), so we must initialize it
	if len(domain.Id.Hex()) == 0 {
		domain.Id = bson.NewObjectId()
	}

	// Every time we modified a domain object we increase the revision counter to identify
	// changes in high level structures. Maybe a better approach would be doing this on the
	// MongoDB server side, check out the link http://docs.mongodb.org/manual/tutorial
	// /optimize-query-performance-with-indexes-and-projections/ - Use the Increment
	// Operator to Perform Operations Server-Side
	domain.Revision += 1

	// Store the last time that the object was modified
	domain.LastModifiedAt = time.Now().UTC()

	// Upsert try to update the collection entry if exists, if not, it creates a new entry.
	// For all the domain objects we are going to use the collection "domain". We also avoid
	// concurency adding the revision as a paremeter for updating the entry
	_, err := dao.Database.C(domainDAOCollection).Upsert(bson.M{
		"_id":      domain.Id,
		"revision": domain.Revision - 1,
	}, domain)

	return err
}
Beispiel #2
0
// Function to mock a domain object
func newDomain() model.Domain {
	var domain model.Domain
	domain.FQDN = "rafael.net.br"

	domain.Nameservers = []model.Nameserver{
		{
			Host: "ns1.rafael.net.br",
			IPv4: net.ParseIP("127.0.0.1"),
			IPv6: net.ParseIP("::1"),
		},
		{
			Host: "ns2.rafael.net.br",
			IPv4: net.ParseIP("127.0.0.2"),
		},
	}

	domain.DSSet = []model.DS{
		{
			Keytag:    1234,
			Algorithm: model.DSAlgorithmRSASHA1,
			Digest:    "A790A11EA430A85DA77245F091891F73AA740483",
		},
	}

	owner, _ := mail.ParseAddress("*****@*****.**")
	domain.Owners = []model.Owner{
		{
			Email:    owner,
			Language: "pt-BR",
		},
	}

	return domain
}
Beispiel #3
0
func generateAndSignDomain(fqdn string) (
	model.Domain, *dns.DNSKEY, *dns.RRSIG, time.Time, time.Time,
) {
	dnskey, rrsig, err := utils.GenerateKSKAndSignZone(fqdn)
	if err != nil {
		utils.Fatalln("Error creating KSK DNSSEC keys and signatures", err)
	}
	ds := dnskey.ToDS(uint8(model.DSDigestTypeSHA1))

	domain := model.Domain{
		FQDN: fqdn,
		Nameservers: []model.Nameserver{
			{
				Host: fmt.Sprintf("ns1.%s", fqdn),
				IPv4: net.ParseIP("127.0.0.1"),
			},
		},
		DSSet: []model.DS{
			{
				Keytag:     dnskey.KeyTag(),
				Algorithm:  utils.ConvertKeyAlgorithm(dnskey.Algorithm),
				DigestType: model.DSDigestTypeSHA1,
				Digest:     ds.Digest,
			},
		},
	}

	owner, _ := mail.ParseAddress("*****@*****.**")
	domain.Owners = []model.Owner{
		{
			Email:    owner,
			Language: "pt-BR",
		},
	}

	lastCheckAt := time.Now().Add(-72 * time.Hour)
	lastOKAt := lastCheckAt.Add(-24 * time.Hour)

	// Set all nameservers with error and the last check equal of the error check interval,
	// this will force the domain to be checked
	for index, _ := range domain.Nameservers {
		domain.Nameservers[index].LastCheckAt = lastCheckAt
		domain.Nameservers[index].LastOKAt = lastOKAt
		domain.Nameservers[index].LastStatus = model.NameserverStatusServerFailure
	}

	// Set all DS records with error and the last check equal of the error check interval,
	// this will force the domain to be checked
	for index, _ := range domain.DSSet {
		domain.DSSet[index].LastCheckAt = lastCheckAt
		domain.DSSet[index].LastOKAt = lastOKAt
		domain.DSSet[index].LastStatus = model.DSStatusTimeout
	}

	return domain, dnskey, rrsig, lastCheckAt, lastOKAt
}
Beispiel #4
0
// Function to mock a domain
func generateAndSaveDomain(fqdn string, domainDAO dao.DomainDAO, dnskey *dns.DNSKEY) {
	ds := dnskey.ToDS(uint8(model.DSDigestTypeSHA1))

	domain := model.Domain{
		FQDN: fqdn,
		Nameservers: []model.Nameserver{
			{
				Host: fmt.Sprintf("ns1.%s", fqdn),
				IPv4: net.ParseIP("127.0.0.1"),
			},
		},
		DSSet: []model.DS{
			{
				Keytag:     dnskey.KeyTag(),
				Algorithm:  utils.ConvertKeyAlgorithm(dnskey.Algorithm),
				DigestType: model.DSDigestTypeSHA1,
				Digest:     ds.Digest,
			},
		},
	}

	owner, _ := mail.ParseAddress("*****@*****.**")
	domain.Owners = []model.Owner{
		{
			Email:    owner,
			Language: "pt-BR",
		},
	}

	lastCheckAt := time.Now().Add(-72 * time.Hour)
	lastOKAt := lastCheckAt.Add(-24 * time.Hour)

	// Set all nameservers with error and the last check equal of the error check interval,
	// this will force the domain to be checked
	for index, _ := range domain.Nameservers {
		domain.Nameservers[index].LastCheckAt = lastCheckAt
		domain.Nameservers[index].LastOKAt = lastOKAt
		domain.Nameservers[index].LastStatus = model.NameserverStatusServerFailure
	}

	// Set all DS records with error and the last check equal of the error check interval,
	// this will force the domain to be checked
	for index, _ := range domain.DSSet {
		domain.DSSet[index].LastCheckAt = lastCheckAt
		domain.DSSet[index].LastOKAt = lastOKAt
		domain.DSSet[index].LastStatus = model.DSStatusTimeout
	}

	if err := domainDAO.Save(&domain); err != nil {
		utils.Fatalln(fmt.Sprintf("Fail to save domain %s", domain.FQDN), err)
	}
}
Beispiel #5
0
func TestToDomainResponse(t *testing.T) {
	email, err := mail.ParseAddress("*****@*****.**")
	if err != nil {
		t.Fatal(err)
	}

	domain := model.Domain{
		FQDN: "xn--exmpl-4qa6c.com.br.",
		Nameservers: []model.Nameserver{
			{
				Host:       "ns1.example.com.br.",
				LastStatus: model.NameserverStatusTimeout,
			},
		},
		DSSet: []model.DS{
			{
				Keytag:     41674,
				Algorithm:  5,
				Digest:     "EAA0978F38879DB70A53F9FF1ACF21D046A98B5C",
				DigestType: 2,
				LastStatus: model.DSStatusTimeout,
			},
		},
		Owners: []model.Owner{
			{
				Email:    email,
				Language: fmt.Sprintf("%s-%s", model.LanguageTypePT, model.RegionTypeBR),
			},
		},
	}

	domainResponse := ToDomainResponse(domain, true)

	if domainResponse.FQDN != "exâmplé.com.br." {
		t.Error("Fail to convert FQDN")
	}

	if len(domainResponse.Nameservers) != 1 {
		t.Error("Fail to convert nameservers")
	}

	if len(domainResponse.DSSet) != 1 {
		t.Error("Fail to convert the DS set")
	}

	if len(domainResponse.Owners) != 1 ||
		domainResponse.Owners[0].Email != "*****@*****.**" ||
		domainResponse.Owners[0].Language != "pt-BR" {

		t.Error("Fail to convert owners")
	}

	if len(domainResponse.Links) != 1 {
		t.Error("Wrong number of links")
	}

	domainResponse = ToDomainResponse(domain, false)

	if len(domainResponse.Links) != 0 {
		t.Error("Shouldn't return links when the object doesn't exist in the system")
	}

	// Testing case problem
	domain.FQDN = "XN--EXMPL-4QA6C.COM.BR."
	domainResponse = ToDomainResponse(domain, false)

	if domainResponse.FQDN != "exâmplé.com.br." {
		t.Errorf("Should convert to unicode even in upper case. "+
			"Expected '%s' and got '%s'", "exâmplé.com.br.", domainResponse.FQDN)
	}

	// Testing an invalid FQDN ACE format
	domain.FQDN = "xn--x1x2x3x4x5.com.br."
	domainResponse = ToDomainResponse(domain, false)

	if domainResponse.FQDN != "xn--x1x2x3x4x5.com.br." {
		t.Errorf("Should keep the ACE format when there's an error converting to unicode. "+
			"Expected '%s' and got '%s'", "xn--x1x2x3x4x5.com.br.", domainResponse.FQDN)
	}
}
Beispiel #6
0
// Merge is used to merge a domain request object sent by the user into a domain object of
// the database. It can return errors related to merge problems that are problem caused by
// data format of the user input
func Merge(domain model.Domain, domainRequest DomainRequest) (model.Domain, error) {
	var err error
	if domainRequest.FQDN, err = model.NormalizeDomainName(domainRequest.FQDN); err != nil {
		return domain, err
	}

	// Detect when the domain object is empty, that is the case when we are creating a new
	// domain in the Shelter project
	if len(domain.FQDN) == 0 {
		domain.FQDN = domainRequest.FQDN

	} else {
		// Cannot merge domains with different FQDNs
		if domain.FQDN != domainRequest.FQDN {
			return domain, ErrDomainsFQDNDontMatch
		}
	}

	nameservers, err := toNameserversModel(domainRequest.Nameservers)
	if err != nil {
		return domain, err
	}

	for index, userNameserver := range nameservers {
		for _, nameserver := range domain.Nameservers {
			if nameserver.Host == userNameserver.Host {
				// Found the same nameserver in the user domain object, maybe the user updated the
				// IP addresses
				nameserver.IPv4 = userNameserver.IPv4
				nameserver.IPv6 = userNameserver.IPv6
				nameservers[index] = nameserver
				break
			}
		}
	}
	domain.Nameservers = nameservers

	dsSet, err := toDSSetModel(domainRequest.DSSet)
	if err != nil {
		return domain, err
	}

	dnskeysDSSet, err := dnskeysRequestsToDSSetModel(domain.FQDN, domainRequest.DNSKEYS)
	if err != nil {
		return domain, err
	}
	dsSet = append(dsSet, dnskeysDSSet...)

	for index, userDS := range dsSet {
		for _, ds := range domain.DSSet {
			if ds.Keytag == userDS.Keytag {
				// Found the same DS in the user domain object
				ds.Algorithm = userDS.Algorithm
				ds.Digest = userDS.Digest
				ds.DigestType = userDS.DigestType
				dsSet[index] = ds
				break
			}
		}
	}
	domain.DSSet = dsSet

	// We can replace the whole structure of the e-mail every time that a new UPDATE arrives
	// because there's no extra information in server side that we need to keep
	domain.Owners, err = toOwnersModel(domainRequest.Owners)
	if err != nil {
		return domain, err
	}

	return domain, nil
}
Beispiel #7
0
// Send DNS requests to fill a domain object from the information found on the DNS authoritative
// nameservers. This is very usefull to make it easier for the user to fill forms with the domain
// information. The domain must be already delegated by a registry to this function works, because
// it uses a recursive DNS
func QueryDomain(fqdn string) (model.Domain, error) {
	domain := model.Domain{
		FQDN: fqdn,
	}

	querier := newQuerier(
		config.ShelterConfig.Scan.UDPMaxSize,
		time.Duration(config.ShelterConfig.Scan.Timeouts.DialSeconds)*time.Second,
		time.Duration(config.ShelterConfig.Scan.Timeouts.ReadSeconds)*time.Second,
		time.Duration(config.ShelterConfig.Scan.Timeouts.WriteSeconds)*time.Second,
		config.ShelterConfig.Scan.ConnectionRetries,
	)

	resolver := fmt.Sprintf("%s:%d",
		config.ShelterConfig.Scan.Resolver.Address,
		config.ShelterConfig.Scan.Resolver.Port,
	)

	var dnsRequestMessage dns.Msg
	dnsRequestMessage.SetQuestion(fqdn, dns.TypeNS)
	dnsRequestMessage.RecursionDesired = true

	// Allow retrieving domain information when there's a DNSSEC problem in the chain-of-trust
	dnsRequestMessage.CheckingDisabled = true

	dnsResponseMsg, err := querier.sendDNSRequest(resolver, &dnsRequestMessage)
	if err != nil {
		return domain, err
	}

	for _, answer := range dnsResponseMsg.Answer {
		nsRecord, ok := answer.(*dns.NS)
		if !ok {
			continue
		}

		domain.Nameservers = append(domain.Nameservers, model.Nameserver{
			Host: nsRecord.Ns,
		})
	}

	for index, nameserver := range domain.Nameservers {
		// Don't need to retrieve glue records if not necessary
		if !strings.HasSuffix(nameserver.Host, domain.FQDN) {
			continue
		}

		dnsRequestMessage.SetQuestion(nameserver.Host, dns.TypeA)
		dnsResponseMsg, err = querier.sendDNSRequest(resolver, &dnsRequestMessage)
		if err != nil {
			return domain, err
		}

		for _, answer := range dnsResponseMsg.Answer {
			ipv4Record, ok := answer.(*dns.A)
			if !ok {
				continue
			}

			domain.Nameservers[index].IPv4 = ipv4Record.A
		}

		dnsRequestMessage.SetQuestion(nameserver.Host, dns.TypeAAAA)
		dnsResponseMsg, err = querier.sendDNSRequest(resolver, &dnsRequestMessage)
		if err != nil {
			return domain, err
		}

		for _, answer := range dnsResponseMsg.Answer {
			ipv6Record, ok := answer.(*dns.AAAA)
			if !ok {
				continue
			}

			domain.Nameservers[index].IPv6 = ipv6Record.AAAA
		}
	}

	// We are going to retrieve the DNSKEYs from the user zone, and generate the DS records from it.
	// This is good if the user wants to use the Shelter project as a easy-to-fill domain registration
	// form
	dnsRequestMessage.SetQuestion(fqdn, dns.TypeDNSKEY)

	dnsResponseMsg, err = querier.sendDNSRequest(resolver, &dnsRequestMessage)
	if err != nil {
		return domain, err
	}

	for _, answer := range dnsResponseMsg.Answer {
		dnskeyRecord, ok := answer.(*dns.DNSKEY)
		if !ok {
			continue
		}

		// Only add DNSKEYs with bit SEP on
		if (dnskeyRecord.Flags & dns.SEP) == 0 {
			continue
		}

		dsRecord := dnskeyRecord.ToDS(uint8(DefaultDigestType))

		domain.DSSet = append(domain.DSSet, model.DS{
			Keytag:     dsRecord.KeyTag,
			Algorithm:  model.DSAlgorithm(dsRecord.Algorithm),
			DigestType: model.DSDigestType(dsRecord.DigestType),
			Digest:     dsRecord.Digest,
		})
	}

	return domain, nil
}