func (i *Database) Before(w http.ResponseWriter, r *http.Request) { log.Debugf("Initializing database with the parameters: URIS - %v | Name - %s | Auth - %t | Username - %s", config.ShelterConfig.Database.URIs, config.ShelterConfig.Database.Name, config.ShelterConfig.Database.Auth.Enabled, config.ShelterConfig.Database.Auth.Username, ) database, databaseSession, err := mongodb.Open( config.ShelterConfig.Database.URIs, config.ShelterConfig.Database.Name, config.ShelterConfig.Database.Auth.Enabled, config.ShelterConfig.Database.Auth.Username, config.ShelterConfig.Database.Auth.Password, ) if err != nil { w.WriteHeader(http.StatusInternalServerError) log.Println("Error creating database connection. Details:", err) return } i.databaseHandler.SetDatabaseSession(databaseSession) i.databaseHandler.SetDatabase(database) }
// HTTPAuthorization garantees that the user was the only that really sent the information. Using a // group of information of the request and a shared secret the server can recreate the authorization // data and compare it with the header field. We are using the same approach that Amazon company // used in their Cloud API. More information can be found in // http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html#ConstructingTheAuthenticationHeader func HTTPAuthorization(r *http.Request, secretFinder func(string) (string, error)) (bool, error) { authorization := r.Header.Get("Authorization") authorization = strings.TrimSpace(authorization) // Authorization header is mandatory in all requests if len(authorization) == 0 { return false, ErrHTTPAuthorizationNotFound } // The authorization should always follow the format: "<namespace> <secretId>:<secret>" authorizationParts := strings.Split(authorization, " ") if len(authorizationParts) != 2 { return false, ErrInvalidHTTPAuthorization } namespace := authorizationParts[0] namespace = strings.TrimSpace(namespace) namespace = strings.ToLower(namespace) if namespace != SupportedNamespace { return false, ErrInvalidHTTPAuthorization } secretParts := strings.Split(authorizationParts[1], ":") if len(secretParts) != 2 { return false, ErrInvalidHTTPAuthorization } secretId := secretParts[0] secretId = strings.TrimSpace(secretId) secretId = strings.ToLower(secretId) stringToSign, err := BuildStringToSign(r, secretId) if err != nil { return false, err } secret, err := secretFinder(secretId) if err != nil { return false, err } signature := GenerateSignature(stringToSign, secret) if ok := signature == secretParts[1]; !ok { log.Debugf("REST server is expecting signature '%s' and got '%s'") return false, nil } return true, nil }
// Function used to notify a single domain. It can return error if there's a problem while // filling the template or sending the e-mail func notifyDomain(domain *model.Domain) error { from := config.ShelterConfig.Notification.From emailsPerLanguage := make(map[string][]string) for _, owner := range domain.Owners { emailsPerLanguage[owner.Language] = append(emailsPerLanguage[owner.Language], owner.Email.Address) } if len(emailsPerLanguage) == 0 { log.Infof("There's no owner to notify domain %s", domain.FQDN) } server := fmt.Sprintf("%s:%d", config.ShelterConfig.Notification.SMTPServer.Server, config.ShelterConfig.Notification.SMTPServer.Port, ) password := config.ShelterConfig.Notification.SMTPServer.Auth.Password if len(password) > 0 { var err error password, err = secret.Decrypt(password) if err != nil { return err } } for language, emails := range emailsPerLanguage { t := getTemplate(language) if t == nil { return ErrTemplateNotFound } domainMail := protocol.Domain{ Domain: *domain, From: config.ShelterConfig.Notification.From, To: strings.Join(emails, ","), } var msg bytes.Buffer if err := t.ExecuteTemplate(&msg, "notification", domainMail); err != nil { return err } // Remove extra new lines that can appear because of the template execution. Special // lines used for controlling the templates are removed but the new lines are left // behind msgBytes := bytes.TrimSpace(msg.Bytes()) msgBytes = extraSpaces.ReplaceAll(msgBytes, []byte("\n\n")) switch config.ShelterConfig.Notification.SMTPServer.Auth.Type { case config.AuthenticationTypePlain: log.Debugf("Sending notification for domain %s to %v via server %s with plain authentication", domain.FQDN, emails, server) auth := smtp.PlainAuth("", config.ShelterConfig.Notification.SMTPServer.Auth.Username, password, config.ShelterConfig.Notification.SMTPServer.Server, ) if err := smtp.SendMail(server, auth, from, emails, msgBytes); err != nil { return err } case config.AuthenticationTypeCRAMMD5Auth: log.Debugf("Sending notification for domain %s to %v via server %s with CRAM MD5 authentication", domain.FQDN, emails, server) auth := smtp.CRAMMD5Auth( config.ShelterConfig.Notification.SMTPServer.Auth.Username, password, ) if err := smtp.SendMail(server, auth, from, emails, msgBytes); err != nil { return err } default: log.Debugf("Sending notification for domain %s to %v via server %s without authentication", domain.FQDN, emails, server) if err := smtp.SendMail(server, nil, from, emails, msgBytes); err != nil { return err } } } return nil }
// Notify is responsable for selecting the domains that should be notified in the system. // It will send alert e-mails for each owner of a domain func Notify() { defer func() { // Something went really wrong while notifying the owners. Log the error stacktrace // and move out if r := recover(); r != nil { const size = 64 << 10 buf := make([]byte, size) buf = buf[:runtime.Stack(buf, false)] log.Printf("Panic detected while notifying the owners. Details: %v\n%s", r, buf) } }() log.Info("Start notification job") defer func() { log.Info("End notification job") }() log.Debugf("Initializing database with the parameters: URIS - %v | Name - %s | Auth - %t | Username - %s", config.ShelterConfig.Database.URIs, config.ShelterConfig.Database.Name, config.ShelterConfig.Database.Auth.Enabled, config.ShelterConfig.Database.Auth.Username, ) database, databaseSession, err := mongodb.Open( config.ShelterConfig.Database.URIs, config.ShelterConfig.Database.Name, config.ShelterConfig.Database.Auth.Enabled, config.ShelterConfig.Database.Auth.Username, config.ShelterConfig.Database.Auth.Password, ) if err != nil { log.Println("Error while initializing database. Details:", err) return } defer databaseSession.Close() domainDAO := dao.DomainDAO{ Database: database, } domainChannel, err := domainDAO.FindAllAsyncToBeNotified( config.ShelterConfig.Notification.NameserverErrorAlertDays, config.ShelterConfig.Notification.NameserverTimeoutAlertDays, config.ShelterConfig.Notification.DSErrorAlertDays, config.ShelterConfig.Notification.DSTimeoutAlertDays, // TODO: Should we move this configuration parameter to a place were both modules can // access it. This sounds better for configuration deployment config.ShelterConfig.Scan.VerificationIntervals.MaxExpirationAlertDays, ) if err != nil { log.Println("Error retrieving domains to notify. Details:", err) return } // Dispatch the asynchronous part of the method for { // Get domain from the database (one-by-one) domainResult := <-domainChannel // Detect errors while retrieving a specific domain. We are not going to stop all the // process when only one domain got an error if domainResult.Error != nil { log.Println("Error retrieving domain to notify. Details:", domainResult.Error) continue } // Problem detected while retrieving a domain or we don't have domains anymore if domainResult.Error != nil || domainResult.Domain == nil { break } if err := notifyDomain(domainResult.Domain); err != nil { log.Println("Error notifying a domain. Details:", err) } } }
// Function responsible for running the domain scan system, checking the configuration of each // domain in the database according to an algorithm. This method is synchronous and will return only // after the scan proccess is done func ScanDomains() { defer func() { // Something went really wrong while scanning the domains. Log the error stacktrace // and move out if r := recover(); r != nil { const size = 64 << 10 buf := make([]byte, size) buf = buf[:runtime.Stack(buf, false)] log.Printf("Panic detected while scanning domains. Details: %v\n%s", r, buf) } }() log.Info("Start scan job") defer func() { log.Info("End scan job") }() log.Debugf("Initializing database with the parameters: URIS - %v | Name - %s | Auth - %t | Username - %s", config.ShelterConfig.Database.URIs, config.ShelterConfig.Database.Name, config.ShelterConfig.Database.Auth.Enabled, config.ShelterConfig.Database.Auth.Username, ) database, databaseSession, err := mongodb.Open( config.ShelterConfig.Database.URIs, config.ShelterConfig.Database.Name, config.ShelterConfig.Database.Auth.Enabled, config.ShelterConfig.Database.Auth.Username, config.ShelterConfig.Database.Auth.Password, ) if err != nil { log.Println("Error while initializing database. Details:", err) return } defer databaseSession.Close() injector := NewInjector( database, config.ShelterConfig.Scan.DomainsBufferSize, config.ShelterConfig.Scan.VerificationIntervals.MaxOKDays, config.ShelterConfig.Scan.VerificationIntervals.MaxErrorDays, config.ShelterConfig.Scan.VerificationIntervals.MaxExpirationAlertDays, ) querierDispatcher := NewQuerierDispatcher( config.ShelterConfig.Scan.NumberOfQueriers, config.ShelterConfig.Scan.DomainsBufferSize, 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, ) collector := NewCollector( database, config.ShelterConfig.Scan.SaveAtOnce, ) // Create a new scan information model.StartNewScan() var scanGroup sync.WaitGroup errorsChannel := make(chan error, config.ShelterConfig.Scan.ErrorsBufferSize) domainsToQueryChannel := injector.Start(&scanGroup, errorsChannel) domainsToSaveChannel := querierDispatcher.Start(&scanGroup, domainsToQueryChannel) collector.Start(&scanGroup, domainsToSaveChannel, errorsChannel) // Keep track of errors for the scan information structure errorDetected := false go func() { for { select { case err := <-errorsChannel: // Detect the poison pill to finish the error listener go routine. This poison // pill should be sent after all parts of the scan are done and we are sure that // we don't have any error to log anymore if err == nil { return } else { errorDetected = true log.Println("Error detected while executing the scan. Details:", err) } } } }() // Wait for all parts of the scan to finish their job scanGroup.Wait() // Finish the error listener sending a poison pill errorsChannel <- nil scanDAO := dao.ScanDAO{ Database: database, } // Save the scan information for future reports if err := model.FinishAndSaveScan(errorDetected, scanDAO.Save); err != nil { log.Println("Error while saving scan information. Details:", err) } }