diff --git a/config/config.go b/config/config.go index cb34e5b19..63f01b23d 100644 --- a/config/config.go +++ b/config/config.go @@ -138,8 +138,17 @@ type ServerConfig struct { LogDockerTail string `ini:"db_log_docker_tail"` // Configures the collector to start a built-in syslog server that listens - // on the specifed "hostname:port" for Postgres log messages + // on the specified "hostname:port" for Postgres log messages LogSyslogServer string `ini:"db_log_syslog_server"` + // For TLS support for syslog server + LogSyslogServerCAFile string `ini:"db_log_syslog_server_ca_file"` + LogSyslogServerCertFile string `ini:"db_log_syslog_server_cert_file"` + LogSyslogServerKeyFile string `ini:"db_log_syslog_server_key_file"` + LogSyslogServerClientCAFile string `ini:"db_log_syslog_server_client_ca_file"` + LogSyslogServerCAContents string `ini:"db_log_syslog_server_ca_contents"` + LogSyslogServerCertContents string `ini:"db_log_syslog_server_cert_contents"` + LogSyslogServerKeyContents string `ini:"db_log_syslog_server_key_contents"` + LogSyslogServerClientCAContents string `ini:"db_log_syslog_server_client_ca_contents"` // Configures the collector to use the "pg_read_file" (superuser) or // "pganalyze.read_log_file" (helper) function to retrieve log data diff --git a/config/read.go b/config/read.go index 34964c266..17e14b876 100644 --- a/config/read.go +++ b/config/read.go @@ -233,6 +233,30 @@ func getDefaultConfig() *ServerConfig { if logSyslogServer := os.Getenv("LOG_SYSLOG_SERVER"); logSyslogServer != "" { config.LogSyslogServer = logSyslogServer } + if logSyslogServerCAFile := os.Getenv("LOG_SYSLOG_SERVER_CA_FILE"); logSyslogServerCAFile != "" { + config.LogSyslogServerCAFile = logSyslogServerCAFile + } + if logSyslogServerCertFile := os.Getenv("LOG_SYSLOG_SERVER_CERT_FILE"); logSyslogServerCertFile != "" { + config.LogSyslogServerCertFile = logSyslogServerCertFile + } + if logSyslogServerKeyFile := os.Getenv("LOG_SYSLOG_SERVER_KEY_FILE"); logSyslogServerKeyFile != "" { + config.LogSyslogServerKeyFile = logSyslogServerKeyFile + } + if logSyslogServerClientCAFile := os.Getenv("LOG_SYSLOG_SERVER_CLIENT_CA_FILE"); logSyslogServerClientCAFile != "" { + config.LogSyslogServerClientCAFile = logSyslogServerClientCAFile + } + if logSyslogServerCAContents := os.Getenv("LOG_SYSLOG_SERVER_CA_CONTENTS"); logSyslogServerCAContents != "" { + config.LogSyslogServerCAContents = logSyslogServerCAContents + } + if logSyslogServerCertContents := os.Getenv("LOG_SYSLOG_SERVER_CERT_CONTENTS"); logSyslogServerCertContents != "" { + config.LogSyslogServerCertContents = logSyslogServerCertContents + } + if logSyslogServerKeyContents := os.Getenv("LOG_SYSLOG_SERVER_KEY_CONTENTS"); logSyslogServerKeyContents != "" { + config.LogSyslogServerKeyContents = logSyslogServerKeyContents + } + if logSyslogServerClientCAContents := os.Getenv("LOG_SYSLOG_SERVER_CLIENT_CA_CONTENTS"); logSyslogServerClientCAContents != "" { + config.LogSyslogServerClientCAContents = logSyslogServerClientCAContents + } if alwaysCollectSystemData := os.Getenv("PGA_ALWAYS_COLLECT_SYSTEM_DATA"); alwaysCollectSystemData != "" { config.AlwaysCollectSystemData = parseConfigBool(alwaysCollectSystemData) } @@ -582,6 +606,34 @@ func preprocessConfig(config *ServerConfig) (*ServerConfig, error) { config.LogPgReadFile = true } + if config.LogSyslogServerCAContents != "" { + config.LogSyslogServerCAFile, err = writeValueToTempfile(config.LogSyslogServerCAContents) + if err != nil { + return config, err + } + } + + if config.LogSyslogServerCertContents != "" { + config.LogSyslogServerCertFile, err = writeValueToTempfile(config.LogSyslogServerCertContents) + if err != nil { + return config, err + } + } + + if config.LogSyslogServerKeyContents != "" { + config.LogSyslogServerKeyFile, err = writeValueToTempfile(config.LogSyslogServerKeyContents) + if err != nil { + return config, err + } + } + + if config.LogSyslogServerClientCAContents != "" { + config.LogSyslogServerClientCAFile, err = writeValueToTempfile(config.LogSyslogServerClientCAContents) + if err != nil { + return config, err + } + } + return config, nil } diff --git a/input/system/selfhosted/logs.go b/input/system/selfhosted/logs.go index a00cabd3d..5897f7bc6 100644 --- a/input/system/selfhosted/logs.go +++ b/input/system/selfhosted/logs.go @@ -157,7 +157,7 @@ func SetupLogTails(ctx context.Context, wg *sync.WaitGroup, globalCollectionOpts } } else if server.Config.LogSyslogServer != "" { logStream := setupLogTransformer(ctx, wg, server, globalCollectionOpts, prefixedLogger, parsedLogStream) - err := setupSyslogHandler(ctx, server.Config.LogSyslogServer, logStream, prefixedLogger) + err := setupSyslogHandler(ctx, server.Config, logStream, prefixedLogger) if err != nil { prefixedLogger.PrintError("ERROR - %s", err) } diff --git a/input/system/selfhosted/syslog_handler.go b/input/system/selfhosted/syslog_handler.go index e230a3a72..d0ff78dcd 100644 --- a/input/system/selfhosted/syslog_handler.go +++ b/input/system/selfhosted/syslog_handler.go @@ -2,29 +2,87 @@ package selfhosted import ( "context" + "crypto/tls" + "crypto/x509" + "fmt" + "os" "regexp" "strconv" "time" "gopkg.in/mcuadros/go-syslog.v2" + "github.com/pganalyze/collector/config" "github.com/pganalyze/collector/util" ) var logLinePartsRegexp = regexp.MustCompile(`^\[(\d+)-(\d+)\] (.*)`) var logLineNumberPartsRegexp = regexp.MustCompile(`^\[(\d+)-(\d+)\]$`) -func setupSyslogHandler(ctx context.Context, logSyslogServer string, out chan<- SelfHostedLogStreamItem, prefixedLogger *util.Logger) error { +func setupSyslogHandler(ctx context.Context, config config.ServerConfig, out chan<- SelfHostedLogStreamItem, prefixedLogger *util.Logger) error { + logSyslogServer := config.LogSyslogServer channel := make(syslog.LogPartsChannel) handler := syslog.NewChannelHandler(channel) server := syslog.NewServer() server.SetFormat(syslog.RFC5424) server.SetHandler(handler) - err := server.ListenTCP(logSyslogServer) - if err != nil { - return err + // Peer name verification is already handled by crypto/tls and is not required at the go-syslog level + // The defaultTlsPeerName in go-syslog can lead to false verification failures, so set nil to bypass + server.SetTlsPeerNameFunc(nil) + + if config.LogSyslogServerCertFile != "" { + serverCaPool := x509.NewCertPool() + clientCaPool := x509.NewCertPool() + if config.LogSyslogServerCAFile != "" { + ca, err := os.ReadFile(config.LogSyslogServerCAFile) + if err != nil { + return fmt.Errorf("failed to read a Certificate Authority: %s", err) + } + if ok := serverCaPool.AppendCertsFromPEM(ca); !ok { + return fmt.Errorf("failed to append a Certificate Authority") + } + } + if config.LogSyslogServerClientCAFile != "" { + ca, err := os.ReadFile(config.LogSyslogServerClientCAFile) + if err != nil { + return fmt.Errorf("failed to read a client Certificate Authority: %s", err) + } + if ok := clientCaPool.AppendCertsFromPEM(ca); !ok { + return fmt.Errorf("failed to append a client Certificate Authority") + } + } + + cert, err := os.ReadFile(config.LogSyslogServerCertFile) + if err != nil { + return fmt.Errorf("failed to read a certificate: %s", err) + } + key, err := os.ReadFile(config.LogSyslogServerKeyFile) + if err != nil { + return fmt.Errorf("failed to read a key: %s", err) + } + tlsCert, err := tls.X509KeyPair(cert, key) + if err != nil { + return err + } + + tlsConfig := tls.Config{ + ClientAuth: tls.VerifyClientCertIfGiven, + Certificates: []tls.Certificate{tlsCert}, + RootCAs: serverCaPool, + ClientCAs: clientCaPool, + } + err = server.ListenTCPTLS(logSyslogServer, &tlsConfig) + if err != nil { + return err + } + } else { + err := server.ListenTCP(logSyslogServer) + if err != nil { + return err + } } + server.Boot() go func(ctx context.Context, server *syslog.Server, channel syslog.LogPartsChannel) {