From bb5278fe9bd6e3de295191c2d6e4ace3174d66ce Mon Sep 17 00:00:00 2001 From: Josh Souza Date: Fri, 30 Nov 2018 16:34:29 -0700 Subject: [PATCH 1/5] Permit peers to provide a cert chain (multiple certs). fixes #103 --- tlsconf/tlsconf.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tlsconf/tlsconf.go b/tlsconf/tlsconf.go index 48fc382..38f0734 100644 --- a/tlsconf/tlsconf.go +++ b/tlsconf/tlsconf.go @@ -72,7 +72,7 @@ func (l *ClientAuthListener) Accept() (c net.Conn, clientCN string, err error) { } peerCerts = tlsConn.ConnectionState().PeerCertificates - if len(peerCerts) != 1 { + if len(peerCerts) < 1 { err = errors.New("unexpected number of certificates presented by TLS client") goto CloseAndErr } From f724480c7b25936b7c766108f7913e90e78f7176 Mon Sep 17 00:00:00 2001 From: Josh Souza Date: Tue, 22 Jan 2019 10:09:24 -0800 Subject: [PATCH 2/5] Add documentation regarding using a certificate chain --- docs/configuration/transports.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/configuration/transports.rst b/docs/configuration/transports.rst index a705bc9..abd5347 100644 --- a/docs/configuration/transports.rst +++ b/docs/configuration/transports.rst @@ -77,6 +77,8 @@ Connect The ``tls`` transport uses TCP + TLS with client authentication using client certificates. The client identity is the common name (CN) presented in the client certificate. It is recommended to set up a dedicated CA infrastructure for this transport, e.g. using OpenVPN's `EasyRSA `_. +When utilizing a CA infrastructure, provide a full chain certificate with the sender's certificate first in the list, with each following certificate directly certifying the one preceding it, per `TLS's specification`. + For a simple 2-machine setup, see the :ref:`instructions below`. The implementation uses `Go's TLS library `_. From a7993d18c65206c14b37396edd09faea5370969a Mon Sep 17 00:00:00 2001 From: Christian Schwarz Date: Fri, 15 Mar 2019 17:17:25 +0100 Subject: [PATCH 3/5] transport/tls: clarify docs & error message language --- docs/configuration/transports.rst | 17 ++++++++++------- tlsconf/tlsconf.go | 2 +- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/configuration/transports.rst b/docs/configuration/transports.rst index 20d1b25..075e918 100644 --- a/docs/configuration/transports.rst +++ b/docs/configuration/transports.rst @@ -76,9 +76,8 @@ Connect The ``tls`` transport uses TCP + TLS with client authentication using client certificates. The client identity is the common name (CN) presented in the client certificate. -It is recommended to set up a dedicated CA infrastructure for this transport, e.g. using OpenVPN's `EasyRSA `_. -When utilizing a CA infrastructure, provide a full chain certificate with the sender's certificate first in the list, with each following certificate directly certifying the one preceding it, per `TLS's specification`. +It is recommended to set up a dedicated CA infrastructure for this transport, e.g. using OpenVPN's `EasyRSA `_. For a simple 2-machine setup, see the :ref:`instructions below`. The implementation uses `Go's TLS library `_. @@ -87,6 +86,10 @@ Since Go binaries are statically linked, you or your distribution need to recomp All file paths are resolved relative to the zrepl daemon's working directory. Specify absolute paths if you are unsure what directory that is (or find out from your init system). +If intermediate CAs are used, the **full chain** must be present in either in the ``ca`` file or the individual ``cert`` files. +Regardless, the client's certificate must be first in the ``cert`` file, with each following certificate directly certifying the one preceding it (see `TLS's specification `_). +This is the common default when using a CA management tool. + Serve ~~~~~ @@ -98,9 +101,9 @@ Serve serve: type: tls listen: ":8888" - ca: /etc/zrepl/ca.crt - cert: /etc/zrepl/prod.crt - key: /etc/zrepl/prod.key + ca: /etc/zrepl/ca.crt + cert: /etc/zrepl/prod.fullchain + key: /etc/zrepl/prod.key client_cns: - "laptop1" - "homeserver" @@ -118,8 +121,8 @@ Connect connect: type: tls address: "server1.foo.bar:8888" - ca: /etc/zrepl/ca.crt - cert: /etc/zrepl/backupserver.crt + ca: /etc/zrepl/ca.crt + cert: /etc/zrepl/backupserver.fullchain key: /etc/zrepl/backupserver.key server_cn: "server1" dial_timeout: # optional, default 10s diff --git a/tlsconf/tlsconf.go b/tlsconf/tlsconf.go index a5a4ea5..b1cb554 100644 --- a/tlsconf/tlsconf.go +++ b/tlsconf/tlsconf.go @@ -84,7 +84,7 @@ func (l *ClientAuthListener) Accept() (tcpConn *net.TCPConn, tlsConn *tls.Conn, peerCerts = tlsConn.ConnectionState().PeerCertificates if len(peerCerts) < 1 { - err = errors.New("unexpected number of certificates presented by TLS client") + err = errors.New("client must present full RFC5246:7.4.2 TLS client certificate chain") goto CloseAndErr } cn = peerCerts[0].Subject.CommonName From fc311a9fd6985b1c019ed7ab34b7657f6ab86e9b Mon Sep 17 00:00:00 2001 From: Ximalas Date: Fri, 1 Feb 2019 21:44:51 +0100 Subject: [PATCH 4/5] syslog logging: support setting facility in config --- config/config.go | 40 ++++++++++++++++++++++++++++++- config/config_global_test.go | 31 ++++++++++++++++++++++++ daemon/logging/build_logging.go | 1 + daemon/logging/logging_outlets.go | 3 ++- docs/configuration/logging.rst | 2 ++ 5 files changed, 75 insertions(+), 2 deletions(-) diff --git a/config/config.go b/config/config.go index 3c2131a..eb290b7 100644 --- a/config/config.go +++ b/config/config.go @@ -5,6 +5,7 @@ import ( "github.com/pkg/errors" "github.com/zrepl/yaml-config" "io/ioutil" + "log/syslog" "os" "reflect" "regexp" @@ -38,7 +39,7 @@ func (j JobEnum) Name() string { case *PullJob: name = v.Name case *SourceJob: name = v.Name default: - panic(fmt.Sprintf("unknownn job type %T", v)) + panic(fmt.Sprintf("unknown job type %T", v)) } return name } @@ -258,6 +259,7 @@ type StdoutLoggingOutlet struct { type SyslogLoggingOutlet struct { LoggingOutletCommon `yaml:",inline"` + Facility syslog.Priority `yaml:"facility,default=local0"` RetryInterval time.Duration `yaml:"retry_interval,positive,default=10s"` } @@ -284,6 +286,16 @@ type PrometheusMonitoring struct { Listen string `yaml:"listen"` } +type SyslogFacilityEnum struct { + Ret interface{} +} + +type SyslogFacilityEnumList []SyslogFacilityEnum + +type SyslogFacility struct { + Facility syslog.Priority +} + type GlobalControl struct { SockPath string `yaml:"sockpath,default=/var/run/zrepl/control"` } @@ -389,6 +401,32 @@ func (t *MonitoringEnum) UnmarshalYAML(u func(interface{}, bool) error) (err err return } +func (t *SyslogFacilityEnum) UnmarshalYAML(u func(interface{}, bool) error) (err error) { + t.Ret, err = enumUnmarshal(u, map[string]interface{}{ + "kern": &SyslogFacility{syslog.LOG_KERN}, + "user": &SyslogFacility{syslog.LOG_USER}, + "mail": &SyslogFacility{syslog.LOG_MAIL}, + "daemon": &SyslogFacility{syslog.LOG_DAEMON}, + "auth": &SyslogFacility{syslog.LOG_AUTH}, + "syslog": &SyslogFacility{syslog.LOG_SYSLOG}, + "lpr": &SyslogFacility{syslog.LOG_LPR}, + "news": &SyslogFacility{syslog.LOG_NEWS}, + "uucp": &SyslogFacility{syslog.LOG_UUCP}, + "cron": &SyslogFacility{syslog.LOG_CRON}, + "authpriv": &SyslogFacility{syslog.LOG_AUTHPRIV}, + "ftp": &SyslogFacility{syslog.LOG_FTP}, + "local0": &SyslogFacility{syslog.LOG_LOCAL0}, + "local1": &SyslogFacility{syslog.LOG_LOCAL1}, + "local2": &SyslogFacility{syslog.LOG_LOCAL2}, + "local3": &SyslogFacility{syslog.LOG_LOCAL3}, + "local4": &SyslogFacility{syslog.LOG_LOCAL4}, + "local5": &SyslogFacility{syslog.LOG_LOCAL5}, + "local6": &SyslogFacility{syslog.LOG_LOCAL6}, + "local7": &SyslogFacility{syslog.LOG_LOCAL7}, + }) + return +} + var ConfigFileDefaultLocations = []string{ "/etc/zrepl/zrepl.yml", "/usr/local/etc/zrepl/zrepl.yml", diff --git a/config/config_global_test.go b/config/config_global_test.go index e14b936..a6c4f7b 100644 --- a/config/config_global_test.go +++ b/config/config_global_test.go @@ -1,9 +1,11 @@ package config import ( + "fmt" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/zrepl/yaml-config" + "log/syslog" "testing" ) @@ -72,6 +74,35 @@ global: assert.Equal(t, ":9091", conf.Global.Monitoring[0].Ret.(*PrometheusMonitoring).Listen) } +func TestSyslogLoggingOutletFacility(t *testing.T) { + type SyslogFacilityPriority struct { + Facility string + Priority syslog.Priority + } + syslogFacilitiesPriorities := []SyslogFacilityPriority{ + {"kern", syslog.LOG_KERN}, {"daemon", syslog.LOG_DAEMON}, {"auth", syslog.LOG_AUTH}, + {"syslog", syslog.LOG_SYSLOG}, {"lpr", syslog.LOG_LPR}, {"news", syslog.LOG_NEWS}, + {"uucp", syslog.LOG_UUCP}, {"cron", syslog.LOG_CRON}, {"authpriv", syslog.LOG_AUTHPRIV}, + {"ftp", syslog.LOG_FTP}, {"local0", syslog.LOG_LOCAL0}, {"local1", syslog.LOG_LOCAL1}, + {"local2", syslog.LOG_LOCAL2}, {"local3", syslog.LOG_LOCAL3}, {"local4", syslog.LOG_LOCAL4}, + {"local5", syslog.LOG_LOCAL5}, {"local6", syslog.LOG_LOCAL6}, {"local7", syslog.LOG_LOCAL7}, + } + + for _, sFP := range syslogFacilitiesPriorities { + logcfg := fmt.Sprintf(` +global: + logging: + - type: syslog + level: info + format: human + facility: %s +`, sFP.Facility) + conf := testValidGlobalSection(t, logcfg) + assert.Equal(t, 1, len(*conf.Global.Logging)) + assert.Equal(t, sFP.Priority, (*conf.Global.Logging)[0].Ret.(*SyslogLoggingOutlet).Facility) + } +} + func TestLoggingOutletEnumList_SetDefaults(t *testing.T) { e := &LoggingOutletEnumList{} var i yaml.Defaulter = e diff --git a/daemon/logging/build_logging.go b/daemon/logging/build_logging.go index bcefec5..e4c2634 100644 --- a/daemon/logging/build_logging.go +++ b/daemon/logging/build_logging.go @@ -222,6 +222,7 @@ func parseSyslogOutlet(in *config.SyslogLoggingOutlet, formatter EntryFormatter) out = &SyslogOutlet{} out.Formatter = formatter out.Formatter.SetMetadataFlags(MetadataNone) + out.Facility = in.Facility out.RetryInterval = in.RetryInterval return out, nil } diff --git a/daemon/logging/logging_outlets.go b/daemon/logging/logging_outlets.go index 5a00d42..b03d008 100644 --- a/daemon/logging/logging_outlets.go +++ b/daemon/logging/logging_outlets.go @@ -124,6 +124,7 @@ func (h *TCPOutlet) WriteEntry(e logger.Entry) error { type SyslogOutlet struct { Formatter EntryFormatter RetryInterval time.Duration + Facility syslog.Priority writer *syslog.Writer lastConnectAttempt time.Time } @@ -142,7 +143,7 @@ func (o *SyslogOutlet) WriteEntry(entry logger.Entry) error { if now.Sub(o.lastConnectAttempt) < o.RetryInterval { return nil // not an error toward logger } - o.writer, err = syslog.New(syslog.LOG_LOCAL0, "zrepl") + o.writer, err = syslog.New(o.Facility, "zrepl") o.lastConnectAttempt = time.Now() if err != nil { o.writer = nil diff --git a/docs/configuration/logging.rst b/docs/configuration/logging.rst index f1a8466..a9077bb 100644 --- a/docs/configuration/logging.rst +++ b/docs/configuration/logging.rst @@ -147,6 +147,8 @@ Can only be specified once. - minimum :ref:`log level ` * - ``format`` - output :ref:`format ` + * - ``facility`` + - Which syslog facility to use (default = ``local0``) * - ``retry_interval`` - Interval between reconnection attempts to syslog (default = 0) From a0f301d7005835636d369be40e18c76357fa847e Mon Sep 17 00:00:00 2001 From: Christian Schwarz Date: Fri, 15 Mar 2019 17:44:41 +0100 Subject: [PATCH 5/5] syslog logging: fix priority parsing + add test for default facility --- config/config.go | 70 ++++++++++++++++++--------------- config/config_global_test.go | 3 +- daemon/logging/build_logging.go | 3 +- 3 files changed, 42 insertions(+), 34 deletions(-) diff --git a/config/config.go b/config/config.go index eb290b7..7ff0ca9 100644 --- a/config/config.go +++ b/config/config.go @@ -259,7 +259,7 @@ type StdoutLoggingOutlet struct { type SyslogLoggingOutlet struct { LoggingOutletCommon `yaml:",inline"` - Facility syslog.Priority `yaml:"facility,default=local0"` + Facility *SyslogFacility `yaml:"facility,optional,fromdefaults"` RetryInterval time.Duration `yaml:"retry_interval,positive,default=10s"` } @@ -286,15 +286,13 @@ type PrometheusMonitoring struct { Listen string `yaml:"listen"` } -type SyslogFacilityEnum struct { - Ret interface{} +type SyslogFacility syslog.Priority + +func (f *SyslogFacility) SetDefault() { + *f = SyslogFacility(syslog.LOG_LOCAL0) } -type SyslogFacilityEnumList []SyslogFacilityEnum - -type SyslogFacility struct { - Facility syslog.Priority -} +var _ yaml.Defaulter = (*SyslogFacility)(nil) type GlobalControl struct { SockPath string `yaml:"sockpath,default=/var/run/zrepl/control"` @@ -401,30 +399,38 @@ func (t *MonitoringEnum) UnmarshalYAML(u func(interface{}, bool) error) (err err return } -func (t *SyslogFacilityEnum) UnmarshalYAML(u func(interface{}, bool) error) (err error) { - t.Ret, err = enumUnmarshal(u, map[string]interface{}{ - "kern": &SyslogFacility{syslog.LOG_KERN}, - "user": &SyslogFacility{syslog.LOG_USER}, - "mail": &SyslogFacility{syslog.LOG_MAIL}, - "daemon": &SyslogFacility{syslog.LOG_DAEMON}, - "auth": &SyslogFacility{syslog.LOG_AUTH}, - "syslog": &SyslogFacility{syslog.LOG_SYSLOG}, - "lpr": &SyslogFacility{syslog.LOG_LPR}, - "news": &SyslogFacility{syslog.LOG_NEWS}, - "uucp": &SyslogFacility{syslog.LOG_UUCP}, - "cron": &SyslogFacility{syslog.LOG_CRON}, - "authpriv": &SyslogFacility{syslog.LOG_AUTHPRIV}, - "ftp": &SyslogFacility{syslog.LOG_FTP}, - "local0": &SyslogFacility{syslog.LOG_LOCAL0}, - "local1": &SyslogFacility{syslog.LOG_LOCAL1}, - "local2": &SyslogFacility{syslog.LOG_LOCAL2}, - "local3": &SyslogFacility{syslog.LOG_LOCAL3}, - "local4": &SyslogFacility{syslog.LOG_LOCAL4}, - "local5": &SyslogFacility{syslog.LOG_LOCAL5}, - "local6": &SyslogFacility{syslog.LOG_LOCAL6}, - "local7": &SyslogFacility{syslog.LOG_LOCAL7}, - }) - return +func (t *SyslogFacility) UnmarshalYAML(u func(interface{}, bool) error) (err error) { + var s string + if err := u(&s, true); err != nil { + return err + } + var level syslog.Priority + switch s { + case "kern": level = syslog.LOG_KERN + case "user": level = syslog.LOG_USER + case "mail": level = syslog.LOG_MAIL + case "daemon": level = syslog.LOG_DAEMON + case "auth": level = syslog.LOG_AUTH + case "syslog": level = syslog.LOG_SYSLOG + case "lpr": level = syslog.LOG_LPR + case "news": level = syslog.LOG_NEWS + case "uucp": level = syslog.LOG_UUCP + case "cron": level = syslog.LOG_CRON + case "authpriv": level = syslog.LOG_AUTHPRIV + case "ftp": level = syslog.LOG_FTP + case "local0": level = syslog.LOG_LOCAL0 + case "local1": level = syslog.LOG_LOCAL1 + case "local2": level = syslog.LOG_LOCAL2 + case "local3": level = syslog.LOG_LOCAL3 + case "local4": level = syslog.LOG_LOCAL4 + case "local5": level = syslog.LOG_LOCAL5 + case "local6": level = syslog.LOG_LOCAL6 + case "local7": level = syslog.LOG_LOCAL7 + default: + return fmt.Errorf("invalid syslog level: %q", s) + } + *t = SyslogFacility(level) + return nil } var ConfigFileDefaultLocations = []string{ diff --git a/config/config_global_test.go b/config/config_global_test.go index a6c4f7b..51204b0 100644 --- a/config/config_global_test.go +++ b/config/config_global_test.go @@ -80,6 +80,7 @@ func TestSyslogLoggingOutletFacility(t *testing.T) { Priority syslog.Priority } syslogFacilitiesPriorities := []SyslogFacilityPriority{ + {"", syslog.LOG_LOCAL0}, // default {"kern", syslog.LOG_KERN}, {"daemon", syslog.LOG_DAEMON}, {"auth", syslog.LOG_AUTH}, {"syslog", syslog.LOG_SYSLOG}, {"lpr", syslog.LOG_LPR}, {"news", syslog.LOG_NEWS}, {"uucp", syslog.LOG_UUCP}, {"cron", syslog.LOG_CRON}, {"authpriv", syslog.LOG_AUTHPRIV}, @@ -99,7 +100,7 @@ global: `, sFP.Facility) conf := testValidGlobalSection(t, logcfg) assert.Equal(t, 1, len(*conf.Global.Logging)) - assert.Equal(t, sFP.Priority, (*conf.Global.Logging)[0].Ret.(*SyslogLoggingOutlet).Facility) + assert.True(t, SyslogFacility(sFP.Priority) == *(*conf.Global.Logging)[0].Ret.(*SyslogLoggingOutlet).Facility) } } diff --git a/daemon/logging/build_logging.go b/daemon/logging/build_logging.go index e4c2634..ce90d3c 100644 --- a/daemon/logging/build_logging.go +++ b/daemon/logging/build_logging.go @@ -4,6 +4,7 @@ import ( "context" "crypto/tls" "crypto/x509" + "log/syslog" "os" "github.com/mattn/go-isatty" @@ -222,7 +223,7 @@ func parseSyslogOutlet(in *config.SyslogLoggingOutlet, formatter EntryFormatter) out = &SyslogOutlet{} out.Formatter = formatter out.Formatter.SetMetadataFlags(MetadataNone) - out.Facility = in.Facility + out.Facility = syslog.Priority(*in.Facility) out.RetryInterval = in.RetryInterval return out, nil }