// 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 }
// 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 }
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 }
// 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) } }
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) } }
// 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 }
// 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 }