Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement support for the PROXY protocol #108

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions auditlog/message/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ package message

// PayloadConnect is the payload for TypeConnect messages.
type PayloadConnect struct {
RemoteAddr string `json:"remoteAddr" yaml:"remoteAddr"` // RemoteAddr contains the IP address of the connecting user.
Country string `json:"country" yaml:"country"` // Country contains the country code looked up from the IP address. Contains "XX" if the lookup failed.
RemoteAddr string `json:"remoteAddr" yaml:"remoteAddr"` // RemoteAddr contains the IP address of the connecting user.
ProxyAddr string `json:"proxyAddr,omitempty" yaml:"proxyAddr"` // ProxyAddr contains the IP adress of the proxy used (if behind a load balancer)
Country string `json:"country" yaml:"country"` // Country contains the country code looked up from the IP address. Contains "XX" if the lookup failed.
}

// Equals compares two PayloadConnect datasets.
Expand Down
6 changes: 6 additions & 0 deletions auditlog/message/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ var typeToID = map[Type]string{
TypeAuthKeyboardInteractiveFailed: "auth_keyboard_interactive_failed",
TypeAuthKeyboardInteractiveBackendError: "auth_keyboard_interactive_backend_error",

TypeHandshakeFailed: "handshake_failed",
TypeHandshakeSuccessful: "handshake_successful",

TypeGlobalRequestUnknown: "global_request_unknown",
TypeNewChannel: "new_channel",
TypeNewChannelSuccessful: "new_channel_successful",
Expand Down Expand Up @@ -114,6 +117,9 @@ var typeToName = map[Type]string{
TypeAuthKeyboardInteractiveFailed: "Keyboard-interactive authentication failed",
TypeAuthKeyboardInteractiveBackendError: "Keyboard-interactive authentication backend error",

TypeHandshakeFailed: "Handshake failed",
TypeHandshakeSuccessful: "Handshake successful",

TypeGlobalRequestUnknown: "Unknown global request",
TypeNewChannel: "New channel request",
TypeNewChannelSuccessful: "New channel successful",
Expand Down
3 changes: 3 additions & 0 deletions config/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:"[\"[email protected]\",\"[email protected]\",\"[email protected]\",\"aes256-ctr\",\"aes192-ctr\",\"aes128-ctr\"]" comment:"SSHCipher suites to use"`
// KexAlgorithms are the key exchange algorithms offered to the client.
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
35 changes: 21 additions & 14 deletions internal/auditlog/codec/asciinema/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ func (e *encoder) Encode(messages <-chan message.Message, storage storage.Writer
startTime := int64(0)
headerWritten := false
var ip = ""
var proxy *string
var username *string
const shell = "/bin/sh"
for {
Expand All @@ -70,11 +71,12 @@ func (e *encoder) Encode(messages <-chan message.Message, storage storage.Writer
break
}
var err error
startTime, headerWritten, ip, username, err = e.encodeMessage(
startTime, headerWritten, ip, proxy, username, err = e.encodeMessage(
startTime,
msg,
&asciicastHeader,
ip,
proxy,
storage,
username,
headerWritten,
Expand Down Expand Up @@ -106,11 +108,12 @@ func (e *encoder) encodeMessage(
msg message.Message,
asciicastHeader *Header,
ip string,
proxy *string,
storage storage.Writer,
username *string,
headerWritten bool,
shell string,
) (int64, bool, string, *string, error) {
) (int64, bool, string, *string, *string, error) {
if msg.MessageType == message.TypeConnect {
startTime = msg.Timestamp
asciicastHeader.Timestamp = int(startTime / 1000000000)
Expand All @@ -121,11 +124,11 @@ func (e *encoder) encodeMessage(
case message.TypeConnect:
ip, username = e.handleConnect(storage, msg, startTime, country, username)
case message.TypeAuthPasswordSuccessful:
ip, username = e.handleAuthPasswordSuccessful(storage, msg, startTime, ip, country)
ip, username = e.handleAuthPasswordSuccessful(storage, msg, startTime, ip, proxy, country)
case message.TypeAuthPubKeySuccessful:
ip, username = e.handleAuthPubkeySuccessful(storage, msg, startTime, ip, country)
ip, username = e.handleAuthPubkeySuccessful(storage, msg, startTime, ip, proxy, country)
case message.TypeHandshakeSuccessful:
ip, username = e.handleHandshakeSuccessful(storage, msg, startTime, ip, country)
ip, username = e.handleHandshakeSuccessful(storage, msg, startTime, ip, proxy, country)
case message.TypeChannelRequestSetEnv:
payload := msg.Payload.(message.PayloadChannelRequestSetEnv)
asciicastHeader.Env[payload.Name] = payload.Value
Expand All @@ -142,36 +145,40 @@ func (e *encoder) encodeMessage(
startTime, headerWritten, err = e.handleIO(startTime, msg, asciicastHeader, headerWritten, shell, storage)
}
if err != nil {
return startTime, headerWritten, ip, username, err
return startTime, headerWritten, ip, proxy, username, err
}
return startTime, headerWritten, ip, username, nil
return startTime, headerWritten, ip, proxy, username, nil
}

func (e *encoder) handleConnect(storage storage.Writer, msg message.Message, startTime int64, country string, username *string) (string, *string) {
payload := msg.Payload.(message.PayloadConnect)
ip := payload.RemoteAddr
storage.SetMetadata(startTime/1000000000, ip, country, username)
var proxy *string
if payload.ProxyAddr != "" {
proxy = &payload.ProxyAddr
}
storage.SetMetadata(startTime/1000000000, ip, proxy, country, username)
return ip, username
}

func (e *encoder) handleAuthPasswordSuccessful(storage storage.Writer, msg message.Message, startTime int64, ip string, country string) (string, *string) {
func (e *encoder) handleAuthPasswordSuccessful(storage storage.Writer, msg message.Message, startTime int64, ip string, proxy *string, country string) (string, *string) {
payload := msg.Payload.(message.PayloadAuthPassword)
username := &payload.Username
storage.SetMetadata(startTime/1000000000, ip, country, username)
storage.SetMetadata(startTime/1000000000, ip, proxy, country, username)
return ip, username
}

func (e *encoder) handleAuthPubkeySuccessful(storage storage.Writer, msg message.Message, startTime int64, ip string, country string) (string, *string) {
func (e *encoder) handleAuthPubkeySuccessful(storage storage.Writer, msg message.Message, startTime int64, ip string, proxy *string, country string) (string, *string) {
payload := msg.Payload.(message.PayloadAuthPubKey)
username := &payload.Username
storage.SetMetadata(startTime/1000000000, ip, country, username)
storage.SetMetadata(startTime/1000000000, ip, proxy, country, username)
return ip, username
}

func (e *encoder) handleHandshakeSuccessful(storage storage.Writer, msg message.Message, startTime int64, ip string, country string) (string, *string) {
func (e *encoder) handleHandshakeSuccessful(storage storage.Writer, msg message.Message, startTime int64, ip string, proxy *string, country string) (string, *string) {
payload := msg.Payload.(message.PayloadHandshakeSuccessful)
username := &payload.Username
storage.SetMetadata(startTime/1000000000, ip, country, username)
storage.SetMetadata(startTime/1000000000, ip, proxy, country, username)
return ip, username
}

Expand Down
4 changes: 3 additions & 1 deletion internal/auditlog/codec/asciinema/encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type writer struct {
data bytes.Buffer
startTime int64
sourceIP string
proxyIP *string
username *string
wait chan bool
country string
Expand All @@ -43,9 +44,10 @@ func (w *writer) waitForClose() {
<-w.wait
}

func (w *writer) SetMetadata(startTime int64, sourceIP string, country string, username *string) {
func (w *writer) SetMetadata(startTime int64, sourceIP string, proxyIP *string, country string, username *string) {
w.startTime = startTime
w.sourceIP = sourceIP
w.proxyIP = proxyIP
w.username = username
w.country = country
}
Expand Down
23 changes: 14 additions & 9 deletions internal/auditlog/codec/binary/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func (e *encoder) Encode(messages <-chan message.Message, storage storage.Writer

startTime := int64(0)
var ip = ""
var proxy *string
var country = "XX"
var username *string
for {
Expand All @@ -58,7 +59,7 @@ func (e *encoder) Encode(messages <-chan message.Message, storage storage.Writer
if startTime == 0 {
startTime = msg.Timestamp
}
ip, country, username = e.storeMetadata(msg, storage, startTime, ip, country, username)
ip, proxy, country, username = e.storeMetadata(msg, storage, startTime, ip, proxy, country, username)
if err := encoder.Encode(&msg); err != nil {
return fmt.Errorf("failed to encode audit log message (%w)", err)
}
Expand All @@ -83,28 +84,32 @@ func (e *encoder) storeMetadata(
storage storage.Writer,
startTime int64,
ip string,
proxy *string,
country string,
username *string,
) (string, string, *string) {
) (string, *string, string, *string) {
switch msg.MessageType {
case message.TypeConnect:
remoteAddr := msg.Payload.(message.PayloadConnect).RemoteAddr
ip = remoteAddr
payload := msg.Payload.(message.PayloadConnect)
ip = payload.RemoteAddr
if payload.ProxyAddr != "" {
proxy = &payload.ProxyAddr
}
country := e.geoIPProvider.Lookup(net.ParseIP(ip))
storage.SetMetadata(startTime/1000000000, ip, country, username)
storage.SetMetadata(startTime/1000000000, ip, proxy, country, username)
case message.TypeAuthPasswordSuccessful:
u := msg.Payload.(message.PayloadAuthPassword).Username
username = &u
storage.SetMetadata(startTime/1000000000, ip, country, username)
storage.SetMetadata(startTime/1000000000, ip, proxy, country, username)
case message.TypeAuthPubKeySuccessful:
payload := msg.Payload.(message.PayloadAuthPubKey)
username = &payload.Username
storage.SetMetadata(startTime/1000000000, ip, country, username)
storage.SetMetadata(startTime/1000000000, ip, proxy, country, username)
case message.TypeHandshakeSuccessful:
payload := msg.Payload.(message.PayloadHandshakeSuccessful)
username = &payload.Username
storage.SetMetadata(startTime/1000000000, ip, country, username)
storage.SetMetadata(startTime/1000000000, ip, proxy, country, username)
}

return ip, country, username
return ip, proxy, country, username
}
2 changes: 1 addition & 1 deletion internal/auditlog/codec/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ func (s *storageWriterProxy) Close() error {
return s.backend.Close()
}

func (s *storageWriterProxy) SetMetadata(_ int64, _ string, _ string, _ *string) {
func (s *storageWriterProxy) SetMetadata(_ int64, _ string, _ *string, _ string, _ *string) {
// No metadata storage
}
2 changes: 1 addition & 1 deletion internal/auditlog/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
type Logger interface {
// OnConnect creates an audit log message for a new connection and simultaneously returns a connection object for
// connection-specific messages
OnConnect(connectionID message.ConnectionID, ip net.TCPAddr) (Connection, error)
OnConnect(connectionID message.ConnectionID, ip net.TCPAddr, proxy *net.TCPAddr) (Connection, error)
// Shutdown triggers all failing uploads to cancel, waits for all currently running uploads to finish, then returns.
// When the shutdownContext expires it will do its best to immediately upload any running background processes.
Shutdown(shutdownContext context.Context)
Expand Down
2 changes: 1 addition & 1 deletion internal/auditlog/logger_empty.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func (e *empty) OnNewChannelSuccess(_ message.ChannelID, _ string) Channel {
return e
}

func (e *empty) OnConnect(_ message.ConnectionID, _ net.TCPAddr) (Connection, error) {
func (e *empty) OnConnect(_ message.ConnectionID, _ net.TCPAddr, _ *net.TCPAddr) (Connection, error) {
return e, nil
}

Expand Down
7 changes: 6 additions & 1 deletion internal/auditlog/logger_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func (l *loggerImplementation) Shutdown(shutdownContext context.Context) {

//region Connection

func (l *loggerImplementation) OnConnect(connectionID message.ConnectionID, ip net.TCPAddr) (Connection, error) {
func (l *loggerImplementation) OnConnect(connectionID message.ConnectionID, ip net.TCPAddr, proxy *net.TCPAddr) (Connection, error) {
name := string(connectionID)
writer, err := l.storage.OpenWriter(name)
if err != nil {
Expand All @@ -76,12 +76,17 @@ func (l *loggerImplementation) OnConnect(connectionID message.ConnectionID, ip n
l.logger.Emergency(err)
}
}()
proxyAddr := ""
if proxy != nil {
proxyAddr = proxy.IP.String()
}
conn.log(message.Message{
ConnectionID: connectionID,
Timestamp: time.Now().UnixNano(),
MessageType: message.TypeConnect,
Payload: message.PayloadConnect{
RemoteAddr: ip.IP.String(),
ProxyAddr: proxyAddr,
Country: l.geoIPLookup.Lookup(ip.IP),
},
ChannelID: nil,
Expand Down
2 changes: 2 additions & 0 deletions internal/auditlog/logger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ func TestConnect(t *testing.T) {
Port: 2222,
Zone: "",
},
nil,
)
if err != nil {
assert.Fail(t, "failed to send connect message to logger", err)
Expand Down Expand Up @@ -246,6 +247,7 @@ func TestAuth(t *testing.T) {
Port: 2222,
Zone: "",
},
nil,
)
assert.Nil(t, err)
connection.OnAuthPassword("foo", []byte("bar"))
Expand Down
2 changes: 1 addition & 1 deletion internal/auditlog/storage/file/struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,5 @@ func (w *writer) Close() error {
return w.file.Close()
}

func (w *writer) SetMetadata(_ int64, _ string, _ string, _ *string) {
func (w *writer) SetMetadata(_ int64, _ string, _ *string, _ string, _ *string) {
}
2 changes: 1 addition & 1 deletion internal/auditlog/storage/none/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package none
type nullWriteCloser struct {
}

func (w *nullWriteCloser) SetMetadata(_ int64, _ string, _ string, _ *string) {
func (w *nullWriteCloser) SetMetadata(_ int64, _ string, _ *string, _ string, _ *string) {
}

func (w *nullWriteCloser) Write(p []byte) (n int, err error) {
Expand Down
8 changes: 7 additions & 1 deletion internal/auditlog/storage/s3/queue.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ var maxPartSize = uint(5 * 1024 * 1024 * 1024)
type queueEntryMetadata struct {
StartTime int64 `json:"startTime" yaml:"startTime"`
RemoteAddr string `json:"remoteAddr" yaml:"remoteAddr"`
ProxyAddr string `json:"proxyAddr,omitempty" yaml:"proxyAddr"`
Authenticated bool `json:"authenticated" yaml:"authenticated"`
Username string `json:"username" yaml:"username"`
Country string `json:"country" yaml:"country"`
Expand Down Expand Up @@ -215,9 +216,14 @@ func (q *uploadQueue) getMonitoringWriter(
return newMonitoringWriter(
writeHandle,
q.partSize,
func(startTime int64, remoteAddr string, country string, username *string) {
func(startTime int64, remoteAddr string, proxyIp *string, country string, username *string) {
entry.metadata.StartTime = startTime
entry.metadata.RemoteAddr = remoteAddr
if proxyIp == nil {
entry.metadata.ProxyAddr = ""
} else {
entry.metadata.ProxyAddr = *proxyIp
}
entry.metadata.Country = country
if username == nil {
entry.metadata.Authenticated = false
Expand Down
8 changes: 4 additions & 4 deletions internal/auditlog/storage/s3/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
func newMonitoringWriter(
backingWriter io.WriteCloser,
partSize uint,
onMetadata func(startTime int64, remoteAddr string, country string, username *string),
onMetadata func(startTime int64, remoteAddr string, proxyIp *string, country string, username *string),
onPart func(),
onClose func(),
) storage.Writer {
Expand All @@ -30,14 +30,14 @@ type monitoringWriter struct {
backingWriter io.WriteCloser
bytesWritten uint64
partSize uint
onMetadata func(startTime int64, remoteAddr string, country string, username *string)
onMetadata func(startTime int64, remoteAddr string, proxyIp *string, country string, username *string)
onPart func()
onClose func()
lastPart int
}

func (m *monitoringWriter) SetMetadata(startTime int64, sourceIP string, country string, username *string) {
m.onMetadata(startTime, sourceIP, country, username)
func (m *monitoringWriter) SetMetadata(startTime int64, sourceIP string, proxyIP *string, country string, username *string) {
m.onMetadata(startTime, sourceIP, proxyIP, country, username)
}

func (m *monitoringWriter) Write(p []byte) (n int, err error) {
Expand Down
3 changes: 2 additions & 1 deletion internal/auditlog/storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ type Writer interface {
//
// startTime is the time when the connection started in unix timestamp.
// sourceIp is the IP address the user connected from.
// proxyIp is the IP address the user connected with (or nil)
// country is the ISO country code or "XX" if the lookup failed.
// username is the username the user entered. The first time this method is called the username will be nil,
// may be called subsequently is the user authenticated.
SetMetadata(startTime int64, sourceIP string, country string, username *string)
SetMetadata(startTime int64, sourceIP string, proxyIp *string, country string, username *string)
}
Loading