Procházet zdrojové kódy

syslog changes
* syslog now uses local syslog service exclusively
* added fail2ban integration (also using local syslog service)

Rod Hynes před 10 roky
rodič
revize
26731a3bb0
3 změnil soubory, kde provedl 62 přidání a 14 odebrání
  1. 29 8
      psiphon/server/config.go
  2. 27 6
      psiphon/server/log.go
  3. 6 0
      psiphon/server/sshService.go

+ 29 - 8
psiphon/server/config.go

@@ -73,18 +73,29 @@ type Config struct {
 	// panic, fatal, error, warn, info, debug
 	// panic, fatal, error, warn, info, debug
 	LogLevel string
 	LogLevel string
 
 
-	// SyslogAddress specifies the UDP address of a syslog
-	// service. When set, syslog is used for message logging.
-	SyslogAddress string
-
 	// SyslogFacility specifies the syslog facility to log to.
 	// SyslogFacility specifies the syslog facility to log to.
+	// When set, the local syslog service is used for message
+	// logging.
 	// Valid values include: "user", "local0", "local1", etc.
 	// Valid values include: "user", "local0", "local1", etc.
 	SyslogFacility string
 	SyslogFacility string
 
 
 	// SyslogTag specifies an optional tag for syslog log
 	// SyslogTag specifies an optional tag for syslog log
-	// messages. The default tag is "psiphon-server".
+	// messages. The default tag is "psiphon-server". The
+	// fail2ban logs, if enabled, also use this tag.
 	SyslogTag string
 	SyslogTag string
 
 
+	// Fail2BanFormat is a string format specifier for the
+	// log message format to use for fail2ban integration for
+	// blocking abusive clients by source IP address.
+	// When set, logs with this format are made to the AUTH
+	// facility with INFO severity in the local syslog server
+	// if clients fail to authenticate.
+	// The client's IP address is included with the log message.
+	// An example format specifier, which should be compatible
+	// with default SSH fail2ban configuration, is
+	// "Authentication failure for psiphon-client from %s".
+	Fail2BanFormat string
+
 	// DiscoveryValueHMACKey is the network-wide secret value
 	// DiscoveryValueHMACKey is the network-wide secret value
 	// used to determine a unique discovery strategy.
 	// used to determine a unique discovery strategy.
 	DiscoveryValueHMACKey string
 	DiscoveryValueHMACKey string
@@ -200,12 +211,18 @@ func (config *Config) RunObfuscatedSSHServer() bool {
 	return config.ObfuscatedSSHServerPort > 0
 	return config.ObfuscatedSSHServerPort > 0
 }
 }
 
 
-// RunObfuscatedSSHServer indicates whether to store per-session GeoIP information in
+// UseRedis indicates whether to store per-session GeoIP information in
 // redis. This is for integration with the legacy psi_web component.
 // redis. This is for integration with the legacy psi_web component.
 func (config *Config) UseRedis() bool {
 func (config *Config) UseRedis() bool {
 	return config.RedisServerAddress != ""
 	return config.RedisServerAddress != ""
 }
 }
 
 
+// UseFail2Ban indicates whether to log client IP addresses, in authentication
+// failure cases, to the local syslog service AUTH facility for use by fail2ban.
+func (config *Config) UseFail2Ban() bool {
+	return config.Fail2BanFormat != ""
+}
+
 // GetTrafficRules looks up the traffic rules for the specified country. If there
 // GetTrafficRules looks up the traffic rules for the specified country. If there
 // are no RegionalTrafficRules for the country, DefaultTrafficRules are returned.
 // are no RegionalTrafficRules for the country, DefaultTrafficRules are returned.
 func (config *Config) GetTrafficRules(targetCountryCode string) TrafficRules {
 func (config *Config) GetTrafficRules(targetCountryCode string) TrafficRules {
@@ -237,8 +254,12 @@ func LoadConfig(configJSONs [][]byte) (*Config, error) {
 		}
 		}
 	}
 	}
 
 
+	if config.Fail2BanFormat != "" && strings.Count(config.Fail2BanFormat, "%s") != 1 {
+		return nil, errors.New("Fail2BanFormat must have one '%%s' placeholder")
+	}
+
 	if config.ServerIPAddress == "" {
 	if config.ServerIPAddress == "" {
-		return nil, errors.New("server IP address is missing from config file")
+		return nil, errors.New("ServerIPAddress is missing from config file")
 	}
 	}
 
 
 	if config.WebServerPort > 0 && (config.WebServerSecret == "" || config.WebServerCertificate == "" ||
 	if config.WebServerPort > 0 && (config.WebServerSecret == "" || config.WebServerCertificate == "" ||
@@ -374,9 +395,9 @@ func GenerateConfig(params *GenerateConfigParams) ([]byte, []byte, error) {
 
 
 	config := &Config{
 	config := &Config{
 		LogLevel:                DEFAULT_LOG_LEVEL,
 		LogLevel:                DEFAULT_LOG_LEVEL,
-		SyslogAddress:           "",
 		SyslogFacility:          "",
 		SyslogFacility:          "",
 		SyslogTag:               DEFAULT_SYSLOG_TAG,
 		SyslogTag:               DEFAULT_SYSLOG_TAG,
+		Fail2BanFormat:          "",
 		DiscoveryValueHMACKey:   "",
 		DiscoveryValueHMACKey:   "",
 		GeoIPDatabaseFilename:   DEFAULT_GEO_IP_DATABASE_FILENAME,
 		GeoIPDatabaseFilename:   DEFAULT_GEO_IP_DATABASE_FILENAME,
 		ServerIPAddress:         serverIPaddress,
 		ServerIPAddress:         serverIPaddress,

+ 27 - 6
psiphon/server/log.go

@@ -20,6 +20,7 @@
 package server
 package server
 
 
 import (
 import (
+	"fmt"
 	"io"
 	"io"
 	"log/syslog"
 	"log/syslog"
 	"os"
 	"os"
@@ -69,10 +70,14 @@ func NewLogWriter() *io.PipeWriter {
 }
 }
 
 
 var log *ContextLogger
 var log *ContextLogger
+var fail2BanFormat string
+var fail2BanWriter *syslog.Writer
 
 
 // InitLogging configures a logger according to the specified
 // InitLogging configures a logger according to the specified
 // config params. If not called, the default logger set by the
 // config params. If not called, the default logger set by the
 // package init() is used.
 // package init() is used.
+// When configured, InitLogging also establishes a local syslog
+// logger specifically for fail2ban integration.
 // Concurrenty note: should only be called from the main
 // Concurrenty note: should only be called from the main
 // goroutine.
 // goroutine.
 func InitLogging(config *Config) error {
 func InitLogging(config *Config) error {
@@ -86,14 +91,10 @@ func InitLogging(config *Config) error {
 
 
 	var syslogHook *logrus_syslog.SyslogHook
 	var syslogHook *logrus_syslog.SyslogHook
 
 
-	if config.SyslogAddress != "" {
+	if config.SyslogFacility != "" {
 
 
 		syslogHook, err = logrus_syslog.NewSyslogHook(
 		syslogHook, err = logrus_syslog.NewSyslogHook(
-			"udp",
-			config.SyslogAddress,
-			getSyslogPriority(config),
-			config.SyslogTag)
-
+			"", "", getSyslogPriority(config), config.SyslogTag)
 		if err != nil {
 		if err != nil {
 			return psiphon.ContextError(err)
 			return psiphon.ContextError(err)
 		}
 		}
@@ -110,9 +111,29 @@ func InitLogging(config *Config) error {
 		},
 		},
 	}
 	}
 
 
+	if config.Fail2BanFormat != "" {
+		fail2BanFormat = config.Fail2BanFormat
+		fail2BanWriter, err = syslog.Dial(
+			"", "", syslog.LOG_AUTH|syslog.LOG_INFO, config.SyslogTag)
+		if err != nil {
+			return psiphon.ContextError(err)
+		}
+	}
+
 	return nil
 	return nil
 }
 }
 
 
+// LogFail2Ban logs a message to the local syslog service AUTH
+// facility with INFO severity using the format specified by
+// config.Fail2BanFormat and the given client IP address. This
+// is for integration with fail2ban for blocking abusive
+// clients by source IP address. When set, the tag in
+// config.SyslogTag is used.
+func LogFail2Ban(clientIPAddress string) {
+	fail2BanWriter.Info(
+		fmt.Sprintf(fail2BanFormat, clientIPAddress))
+}
+
 // getSyslogPriority determines golang's syslog "priority" value
 // getSyslogPriority determines golang's syslog "priority" value
 // based on the provided config.
 // based on the provided config.
 func getSyslogPriority(config *Config) syslog.Priority {
 func getSyslogPriority(config *Config) syslog.Priority {

+ 6 - 0
psiphon/server/sshService.go

@@ -544,6 +544,12 @@ func (sshClient *sshClient) passwordCallback(conn ssh.ConnMetadata, password []b
 
 
 func (sshClient *sshClient) authLogCallback(conn ssh.ConnMetadata, method string, err error) {
 func (sshClient *sshClient) authLogCallback(conn ssh.ConnMetadata, method string, err error) {
 	if err != nil {
 	if err != nil {
+		if sshClient.sshServer.config.UseFail2Ban() {
+			clientIPAddress := psiphon.IPAddressFromAddr(conn.RemoteAddr())
+			if clientIPAddress != "" {
+				LogFail2Ban(clientIPAddress)
+			}
+		}
 		log.WithContextFields(LogFields{"error": err, "method": method}).Warning("authentication failed")
 		log.WithContextFields(LogFields{"error": err, "method": method}).Warning("authentication failed")
 	} else {
 	} else {
 		log.WithContextFields(LogFields{"error": err, "method": method}).Info("authentication success")
 		log.WithContextFields(LogFields{"error": err, "method": method}).Info("authentication success")