コード例 #1
0
ファイル: database.go プロジェクト: rafaeljusto/shelter
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)
}
コード例 #2
0
ファイル: mandatory.go プロジェクト: rafaeljusto/shelter
// 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
}
コード例 #3
0
ファイル: notification.go プロジェクト: rafaeljusto/shelter
// 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
}
コード例 #4
0
ファイル: notification.go プロジェクト: rafaeljusto/shelter
// 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)
		}
	}
}
コード例 #5
0
ファイル: scan.go プロジェクト: rafaeljusto/shelter
// 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)
	}
}