From 0cc25cf6ef92212d3d8de8571448825101126c6f Mon Sep 17 00:00:00 2001 From: Nikos Tsipinakis Date: Fri, 4 Mar 2022 16:08:38 +0100 Subject: [PATCH] Implement support for the PROXY protocol --- config/ssh.go | 3 +++ go.mod | 1 + go.sum | 2 ++ internal/sshserver/Server_test.go | 1 + internal/sshserver/serverImpl.go | 22 +++++++++++++++++++--- 5 files changed, 26 insertions(+), 3 deletions(-) diff --git a/config/ssh.go b/config/ssh.go index 549538be..5226f3e1 100644 --- a/config/ssh.go +++ b/config/ssh.go @@ -25,6 +25,9 @@ type SSHConfig struct { // See https://tools.ietf.org/html/rfc4253#page-4 section 4.2. Protocol Version Exchange // The trailing CR and LF characters should NOT be added to this string. ServerVersion SSHServerVersion `json:"serverVersion" yaml:"serverVersion" default:"SSH-2.0-ContainerSSH"` + // AllowedProxies is a list of IP addresses or CIDR ranges that are allowed to use the + // PROXY protocol to override the connection originator IP address. + AllowedProxies []string `json:"allowedProxies" yaml:"allowedProxies"` // Ciphers are the ciphers offered to the client. Ciphers SSHCipherList `json:"ciphers" yaml:"ciphers" default:"[\"chacha20-poly1305@openssh.com\",\"aes256-gcm@openssh.com\",\"aes128-gcm@openssh.com\",\"aes256-ctr\",\"aes192-ctr\",\"aes128-ctr\"]" comment:"SSHCipher suites to use"` // KexAlgorithms are the key exchange algorithms offered to the client. diff --git a/go.mod b/go.mod index 4eeb666f..6bc225d0 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/mitchellh/mapstructure v1.4.3 github.com/opencontainers/image-spec v1.0.2 github.com/oschwald/geoip2-golang v1.5.0 + github.com/pires/go-proxyproto v0.6.1 github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494 github.com/stretchr/testify v1.7.0 golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 diff --git a/go.sum b/go.sum index 7cac47ed..b096a581 100644 --- a/go.sum +++ b/go.sum @@ -628,6 +628,8 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9 github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pires/go-proxyproto v0.6.1 h1:EBupykFmo22SDjv4fQVQd2J9NOoLPmyZA/15ldOGkPw= +github.com/pires/go-proxyproto v0.6.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/internal/sshserver/Server_test.go b/internal/sshserver/Server_test.go index bde94938..2a03f685 100644 --- a/internal/sshserver/Server_test.go +++ b/internal/sshserver/Server_test.go @@ -22,6 +22,7 @@ import ( "github.com/containerssh/libcontainerssh/service" "github.com/stretchr/testify/assert" "golang.org/x/crypto/ssh" + "github.com/pires/go-proxyproto" ) //region Tests diff --git a/internal/sshserver/serverImpl.go b/internal/sshserver/serverImpl.go index e85c423f..20ba2b2e 100644 --- a/internal/sshserver/serverImpl.go +++ b/internal/sshserver/serverImpl.go @@ -15,6 +15,7 @@ import ( messageCodes "github.com/containerssh/libcontainerssh/message" "github.com/containerssh/libcontainerssh/service" "golang.org/x/crypto/ssh" + "github.com/pires/go-proxyproto" ) type serverImpl struct { @@ -54,11 +55,20 @@ func (s *serverImpl) RunWithLifecycle(lifecycle service.Lifecycle) error { Control: s.socketControl, } + useProxy := len(s.cfg.AllowedProxies) > 0 + netListener, err := listenConfig.Listen(lifecycle.Context(), "tcp", s.cfg.Listen) if err != nil { s.lock.Unlock() return messageCodes.Wrap(err, messageCodes.ESSHStartFailed, "failed to start SSH server on %s", s.cfg.Listen) } + if useProxy { + policy := proxyproto.MustStrictWhiteListPolicy(s.cfg.AllowedProxies) + netListener = &proxyproto.Listener{ + Listener: netListener, + Policy: policy, + } + } s.listenSocket = netListener s.lock.Unlock() if err := s.handler.OnReady(); err != nil { @@ -84,7 +94,13 @@ func (s *serverImpl) RunWithLifecycle(lifecycle service.Lifecycle) error { break } s.wg.Add(1) - go s.handleConnection(tcpConn) + logger := s.logger + if useProxy { + proxyConn := tcpConn.(*proxyproto.Conn) + proxyIp := proxyConn.Raw().RemoteAddr() + logger = logger.WithLabel("fromProxy", proxyIp.(*net.TCPAddr).IP.String()) + } + go s.handleConnection(logger, tcpConn) } lifecycle.Stopping() s.shuttingDown = true @@ -501,10 +517,10 @@ func (s *serverImpl) createPasswordCallback( return passwordCallback } -func (s *serverImpl) handleConnection(conn net.Conn) { +func (s *serverImpl) handleConnection(logger log.Logger, conn net.Conn) { addr := conn.RemoteAddr().(*net.TCPAddr) connectionID := GenerateConnectionID() - logger := s.logger. + logger = logger. WithLabel("remoteAddr", addr.IP.String()). WithLabel("connectionId", connectionID) handlerNetworkConnection, err := s.handler.OnNetworkConnection(*addr, connectionID)