Skip to content
This repository was archived by the owner on Jan 6, 2025. It is now read-only.

Commit ddddfd4

Browse files
committed
Wrap proxyproto in package
1 parent 4b167ff commit ddddfd4

File tree

3 files changed

+190
-16
lines changed

3 files changed

+190
-16
lines changed

internal/proxyproto/proxyproto.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package proxyproto
2+
3+
import (
4+
"net"
5+
6+
"github.com/pires/go-proxyproto"
7+
)
8+
9+
// WrapProxy is a function that wraps a net.Conn around the PROXY tcp protocol. It is used for correctly reporting the originator IP address when a service is running behind a load balancer
10+
// In case proxy use is allowed the wrapped network connection is returned along with the IP address of the proxy that it is used. The wrapped network connection will return the IP address
11+
// of the client when RemoteAddr() is called
12+
//
13+
// conn is the network connection to wrap
14+
// proxyList is a list of addresses that are allowed to send proxy information
15+
//
16+
func WrapProxy(conn net.Conn, proxyList []string) (net.Conn, *net.TCPAddr, error) {
17+
if len(proxyList) == 0 {
18+
return conn, nil, nil
19+
}
20+
policyFunc := proxyproto.MustStrictWhiteListPolicy(proxyList)
21+
policy, err := policyFunc(conn.RemoteAddr())
22+
if err != nil {
23+
return nil, nil, err
24+
}
25+
if policy == proxyproto.REJECT || policy == proxyproto.IGNORE {
26+
// If it's not an approved proxy we should fail loudly, not silently
27+
return conn, nil, nil
28+
}
29+
tcpAddr := conn.RemoteAddr().(*net.TCPAddr)
30+
return proxyproto.NewConn(
31+
conn,
32+
proxyproto.WithPolicy(policy),
33+
), tcpAddr, nil
34+
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package proxyproto_test
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"net"
7+
"testing"
8+
"time"
9+
10+
"github.com/containerssh/libcontainerssh/internal/proxyproto"
11+
goproxyproto "github.com/pires/go-proxyproto"
12+
)
13+
14+
type fakeConn struct {
15+
remoteAddr string
16+
localAddr string
17+
pipeReader io.ReadCloser
18+
pipeWriter io.WriteCloser
19+
}
20+
21+
func NewFakeConn(clientAddr string, serverAddr string) (fakeConn, fakeConn) {
22+
clientPipeReader, clientPipeWriter := io.Pipe()
23+
serverPipeReader, serverPipeWriter := io.Pipe()
24+
return fakeConn{
25+
remoteAddr: clientAddr,
26+
localAddr: serverAddr,
27+
pipeReader: serverPipeReader,
28+
pipeWriter: clientPipeWriter,
29+
}, fakeConn{
30+
remoteAddr: serverAddr,
31+
localAddr: clientAddr,
32+
pipeReader: clientPipeReader,
33+
pipeWriter: serverPipeWriter,
34+
}
35+
}
36+
37+
func (f fakeConn) Read(b []byte) (n int, err error) {
38+
return f.pipeReader.Read(b)
39+
}
40+
41+
func (f fakeConn) Write(b []byte) (n int, err error) {
42+
return f.pipeWriter.Write(b)
43+
}
44+
45+
func (f fakeConn) Close() error {
46+
f.pipeWriter.Close()
47+
f.pipeReader.Close()
48+
return nil
49+
}
50+
51+
func (f fakeConn) LocalAddr() net.Addr {
52+
return &net.TCPAddr{
53+
IP: net.ParseIP(f.localAddr),
54+
}
55+
}
56+
57+
func (f fakeConn) RemoteAddr() net.Addr {
58+
return &net.TCPAddr{
59+
IP: net.ParseIP(f.remoteAddr),
60+
}
61+
}
62+
func (f fakeConn) SetDeadline(t time.Time) error {
63+
return fmt.Errorf("Unimplemented")
64+
}
65+
func (f fakeConn) SetReadDeadline(t time.Time) error {
66+
return fmt.Errorf("Unimplemented")
67+
}
68+
func (f fakeConn) SetWriteDeadline(t time.Time) error {
69+
return fmt.Errorf("Unimplemented")
70+
}
71+
72+
func TestProxyWithHeader(t *testing.T) {
73+
clientIP := "127.0.0.1"
74+
proxyIP := "127.0.0.2"
75+
serverIP := "127.0.0.3"
76+
77+
server, proxy := NewFakeConn(proxyIP, serverIP)
78+
wrappedConn, proxyAddr, err := proxyproto.WrapProxy(server, []string{proxyIP})
79+
if err != nil {
80+
t.Fatal(err)
81+
}
82+
83+
header := &goproxyproto.Header{
84+
Version: 1,
85+
Command: goproxyproto.PROXY,
86+
TransportProtocol: goproxyproto.TCPv4,
87+
SourceAddr: &net.TCPAddr{
88+
IP: net.ParseIP(clientIP),
89+
Port: 1000,
90+
},
91+
DestinationAddr: &net.TCPAddr{
92+
IP: net.ParseIP(proxyIP),
93+
Port: 2000,
94+
},
95+
}
96+
go func() {
97+
_, err := header.WriteTo(proxy)
98+
if err != nil {
99+
return
100+
}
101+
}()
102+
103+
if proxyAddr == nil {
104+
t.Fatalf("Proxy info was rejected")
105+
}
106+
if proxyAddr.String() != proxyIP+":0" {
107+
t.Fatalf("Unexpected proxy address %s, expected %s", proxyAddr, proxyIP)
108+
}
109+
if wrappedConn.RemoteAddr().String() != clientIP+":1000" {
110+
t.Fatalf("Header not accepted when it should be %s != %s", wrappedConn.RemoteAddr().String(), clientIP+":1000")
111+
}
112+
}
113+
114+
func TestProxyUnauthorizedHeader(t *testing.T) {
115+
clientIP := "127.0.0.1"
116+
proxyIP := "127.0.0.2"
117+
serverIP := "127.0.0.3"
118+
119+
server, proxy := NewFakeConn(proxyIP, serverIP)
120+
_, proxyAddr, err := proxyproto.WrapProxy(server, []string{"128.0.0.2"})
121+
if err != nil {
122+
t.Fatal(err)
123+
}
124+
125+
header := &goproxyproto.Header{
126+
Version: 1,
127+
Command: goproxyproto.PROXY,
128+
TransportProtocol: goproxyproto.TCPv4,
129+
SourceAddr: &net.TCPAddr{
130+
IP: net.ParseIP(clientIP),
131+
Port: 1000,
132+
},
133+
DestinationAddr: &net.TCPAddr{
134+
IP: net.ParseIP(proxyIP),
135+
Port: 2000,
136+
},
137+
}
138+
go func() {
139+
_, err := header.WriteTo(proxy)
140+
if err != nil {
141+
return
142+
}
143+
}()
144+
145+
if proxyAddr != nil {
146+
t.Fatalf("Proxy info was accepted when unauthorized")
147+
}
148+
}

internal/sshserver/serverImpl.go

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ import (
1010

1111
"github.com/containerssh/libcontainerssh/auth"
1212
"github.com/containerssh/libcontainerssh/config"
13+
"github.com/containerssh/libcontainerssh/internal/proxyproto"
1314
ssh2 "github.com/containerssh/libcontainerssh/internal/ssh"
1415
"github.com/containerssh/libcontainerssh/log"
1516
messageCodes "github.com/containerssh/libcontainerssh/message"
1617
"github.com/containerssh/libcontainerssh/service"
17-
"github.com/pires/go-proxyproto"
1818
"golang.org/x/crypto/ssh"
1919
)
2020

@@ -55,20 +55,12 @@ func (s *serverImpl) RunWithLifecycle(lifecycle service.Lifecycle) error {
5555
Control: s.socketControl,
5656
}
5757

58-
useProxy := len(s.cfg.AllowedProxies) > 0
59-
6058
netListener, err := listenConfig.Listen(lifecycle.Context(), "tcp", s.cfg.Listen)
6159
if err != nil {
6260
s.lock.Unlock()
6361
return messageCodes.Wrap(err, messageCodes.ESSHStartFailed, "failed to start SSH server on %s", s.cfg.Listen)
6462
}
65-
if useProxy {
66-
policy := proxyproto.MustStrictWhiteListPolicy(s.cfg.AllowedProxies)
67-
netListener = &proxyproto.Listener{
68-
Listener: netListener,
69-
Policy: policy,
70-
}
71-
}
63+
7264
s.listenSocket = netListener
7365
s.lock.Unlock()
7466
if err := s.handler.OnReady(); err != nil {
@@ -87,19 +79,19 @@ func (s *serverImpl) RunWithLifecycle(lifecycle service.Lifecycle) error {
8779
s.logger.Info(messageCodes.NewMessage(messageCodes.MSSHServiceAvailable, "SSH server running on %s", s.cfg.Listen))
8880

8981
go s.handleListenSocketOnShutdown(lifecycle)
82+
9083
for {
9184
tcpConn, err := netListener.Accept()
9285
if err != nil {
9386
// Assume listen socket closed
9487
break
9588
}
96-
s.wg.Add(1)
97-
var proxy *net.TCPAddr
98-
if useProxy {
99-
proxyConn := tcpConn.(*proxyproto.Conn)
100-
proxy = proxyConn.Raw().RemoteAddr().(*net.TCPAddr)
89+
tcpConn, proxyAddr, err := proxyproto.WrapProxy(tcpConn, s.cfg.AllowedProxies)
90+
if err != nil {
91+
break
10192
}
102-
go s.handleConnection(tcpConn, proxy)
93+
s.wg.Add(1)
94+
go s.handleConnection(tcpConn, proxyAddr)
10395
}
10496
lifecycle.Stopping()
10597
s.shuttingDown = true

0 commit comments

Comments
 (0)