// Permission is responsable for checking if the user is allowed to send requests to the // Web client func (i *Permission) Before(w http.ResponseWriter, r *http.Request) { // When there's nobody in the whitelist, everybody is allowed if len(ACL) == 0 { return } var clientAddress string xff := r.Header.Get("X-Forwarded-For") xff = strings.TrimSpace(xff) if len(xff) > 0 { xffParts := strings.Split(xff, ",") if len(xffParts) == 1 { clientAddress = strings.TrimSpace(xffParts[0]) } else if len(xffParts) > 1 { clientAddress = strings.TrimSpace(xffParts[len(xffParts)-2]) } } else { clientAddress = strings.TrimSpace(r.Header.Get("X-Real-IP")) } if len(clientAddress) == 0 { var err error clientAddress, _, err = net.SplitHostPort(r.RemoteAddr) if err != nil { w.WriteHeader(http.StatusInternalServerError) log.Printf("Error checking CIDR whitelist. Details: Remote IP address '%s' could not be parsed. %s", r.RemoteAddr, err) return } } ip := net.ParseIP(clientAddress) if ip == nil { // Something wrong, because the REST server could not identify the remote address properly. This // is really awkward, because this is a responsability of the server, maybe this error will // never be throw w.WriteHeader(http.StatusInternalServerError) log.Printf("Error checking CIDR whitelist. Details: IP address '%s' could not be parsed.", clientAddress) return } for _, cidr := range ACL { if cidr.Contains(ip) { return } } w.WriteHeader(http.StatusForbidden) }
// We don't start scheduler in init function anymore because for tests we want to change // parameters before the scheduler starts func Start() { ticker := time.NewTicker(SchedulerExecutionInterval) go func() { defer func() { // Something went really wrong while scheduling. 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 scheduling. Details: %v\n%s", r, buf) } }() for { select { case <-ticker.C: jobsMutex.Lock() for index, job := range jobs { // The execution time is not going to be so exactly if time.Now().UTC().After(job.NextExecution) { // Next execution time is defined, let's use it as reference so that the job // is always executed near the desired time jobs[index].NextExecution = job.NextExecution.Add(job.Interval) go job.Task() } } jobsMutex.Unlock() } } }() }
func Start(listeners []net.Listener) error { // Initialize language configuration file if err := loadMessages(); err != nil { return err } // Initialize CIDR whitelist if err := loadACL(); err != nil { return err } // Handy logger should use the same logger of the Shelter system handy.Logger = log.Logger mux := handy.NewHandy() mux.Recover = func(r interface{}) { const size = 64 << 10 buf := make([]byte, size) buf = buf[:runtime.Stack(buf, false)] log.Printf("REST panic detected. Details: %v\n%s", r, buf) } for pattern, handler := range handler.Routes { mux.Handle(pattern, handler) } server := http.Server{ Handler: mux, ReadTimeout: time.Duration(config.ShelterConfig.RESTServer.Timeouts.ReadSeconds) * time.Second, WriteTimeout: time.Duration(config.ShelterConfig.RESTServer.Timeouts.WriteSeconds) * time.Second, } for _, v := range listeners { // We are not checking the error returned by Serve, because if we check for some // reason the HTTP server stop answering the requests go server.Serve(v) } return nil }
func Start(listeners []net.Listener) error { // Initialize CIDR whitelist if err := loadACL(); err != nil { return err } // Static handler must be called directly because it uses configuration file values to // configure the root path. For that reason we can't build it in the init function handler.StartStaticHandler() // Handy logger should use the same logger of the Shelter system handy.Logger = log.Logger mux := handy.NewHandy() mux.Recover = func(r interface{}) { const size = 64 << 10 buf := make([]byte, size) buf = buf[:runtime.Stack(buf, false)] log.Printf("Web client panic detected. Details: %v\n%s", r, buf) } for pattern, handler := range handler.Routes { mux.Handle(pattern, handler) } server := http.Server{ Handler: mux, } for _, v := range listeners { // We are not checking the error returned by Serve, because if we check for some // reason the HTTP server stop answering the requests go server.Serve(v) } 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) } }