Skip to content

Commit

Permalink
Add support for PROXY protocol v2 in TCP listener (#13540)
Browse files Browse the repository at this point in the history
* Add support for PROXY protocol v2 in TCP listener

I did not find tests for this so I added one trying to cover different
configurations to make sure I did not break something. As far as I know,
the behavior should be exactly the same as before except for one thing
when proxy_protocol_behavior is set to "deny_unauthorized", unauthorized
requests were previously silently reject because of https://github.com/armon/go-proxyproto/blob/7e956b284f0a/protocol.go#L81-L84
but it will now be logged.

Also fixes #9462 by adding
support for `PROXY UNKNOWN` for PROXY protocol v1.

Closes #3807

* Add changelog
  • Loading branch information
remilapeyre authored Mar 8, 2022
1 parent 4d217be commit 1d06d25
Show file tree
Hide file tree
Showing 6 changed files with 296 additions and 21 deletions.
4 changes: 4 additions & 0 deletions changelog/13540.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
```release-note:improvement
core: Vault now supports the PROXY protocol v2. Support for UNKNOWN connections
has also been added to the PROXY protocol v1.
```
263 changes: 257 additions & 6 deletions command/server/listener_tcp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import (
"testing"
"time"

"github.com/hashicorp/go-sockaddr"
"github.com/hashicorp/vault/internalshared/configutil"
"github.com/mitchellh/cli"
"github.com/pires/go-proxyproto"
)

func TestTCPListener(t *testing.T) {
Expand All @@ -28,7 +30,7 @@ func TestTCPListener(t *testing.T) {
return net.Dial("tcp", ln.Addr().String())
}

testListenerImpl(t, ln, connFn, "", 0)
testListenerImpl(t, ln, connFn, "", 0, "127.0.0.1", false)
}

// TestTCPListener_tls tests TLS generally
Expand Down Expand Up @@ -85,7 +87,7 @@ func TestTCPListener_tls(t *testing.T) {
}
}

testListenerImpl(t, ln, connFn(true), "foo.example.com", 0)
testListenerImpl(t, ln, connFn(true), "foo.example.com", 0, "127.0.0.1", false)

ln, _, _, err = tcpListenerFactory(&configutil.Listener{
Address: "127.0.0.1:0",
Expand All @@ -110,7 +112,7 @@ func TestTCPListener_tls(t *testing.T) {
t.Fatalf("err: %s", err)
}

testListenerImpl(t, ln, connFn(false), "foo.example.com", 0)
testListenerImpl(t, ln, connFn(false), "foo.example.com", 0, "127.0.0.1", false)
}

func TestTCPListener_tls13(t *testing.T) {
Expand Down Expand Up @@ -167,7 +169,7 @@ func TestTCPListener_tls13(t *testing.T) {
}
}

testListenerImpl(t, ln, connFn(true), "foo.example.com", tls.VersionTLS13)
testListenerImpl(t, ln, connFn(true), "foo.example.com", tls.VersionTLS13, "127.0.0.1", false)

ln, _, _, err = tcpListenerFactory(&configutil.Listener{
Address: "127.0.0.1:0",
Expand All @@ -194,7 +196,7 @@ func TestTCPListener_tls13(t *testing.T) {
t.Fatalf("err: %s", err)
}

testListenerImpl(t, ln, connFn(false), "foo.example.com", tls.VersionTLS13)
testListenerImpl(t, ln, connFn(false), "foo.example.com", tls.VersionTLS13, "127.0.0.1", false)

ln, _, _, err = tcpListenerFactory(&configutil.Listener{
Address: "127.0.0.1:0",
Expand All @@ -208,5 +210,254 @@ func TestTCPListener_tls13(t *testing.T) {
t.Fatalf("err: %s", err)
}

testListenerImpl(t, ln, connFn(false), "foo.example.com", tls.VersionTLS12)
testListenerImpl(t, ln, connFn(false), "foo.example.com", tls.VersionTLS12, "127.0.0.1", false)
}

func TestTCPListener_proxyProtocol(t *testing.T) {
for name, tc := range map[string]struct {
Behavior string
Header *proxyproto.Header
AuthorizedAddr string
ExpectedAddr string
ExpectError bool
}{
"none-no-header": {
Behavior: "",
ExpectedAddr: "127.0.0.1",
Header: nil,
},
"none-v1": {
Behavior: "",
ExpectedAddr: "127.0.0.1",
ExpectError: true,
Header: &proxyproto.Header{
Version: 1,
Command: proxyproto.PROXY,
TransportProtocol: proxyproto.TCPv4,
SourceAddr: &net.TCPAddr{
IP: net.ParseIP("10.1.1.1"),
Port: 1000,
},
DestinationAddr: &net.TCPAddr{
IP: net.ParseIP("20.2.2.2"),
Port: 2000,
},
},
},
"none-v2": {
Behavior: "",
ExpectedAddr: "127.0.0.1",
ExpectError: true,
Header: &proxyproto.Header{
Version: 2,
Command: proxyproto.PROXY,
TransportProtocol: proxyproto.TCPv4,
SourceAddr: &net.TCPAddr{
IP: net.ParseIP("10.1.1.1"),
Port: 1000,
},
DestinationAddr: &net.TCPAddr{
IP: net.ParseIP("20.2.2.2"),
Port: 2000,
},
},
},

// use_always makes it possible to send the PROXY header but does not
// require it
"use_always-no-header": {
Behavior: "use_always",
ExpectedAddr: "127.0.0.1",
Header: nil,
},

"use_always-header-v1": {
Behavior: "use_always",
ExpectedAddr: "10.1.1.1",
Header: &proxyproto.Header{
Version: 1,
Command: proxyproto.PROXY,
TransportProtocol: proxyproto.TCPv4,
SourceAddr: &net.TCPAddr{
IP: net.ParseIP("10.1.1.1"),
Port: 1000,
},
DestinationAddr: &net.TCPAddr{
IP: net.ParseIP("20.2.2.2"),
Port: 2000,
},
},
},
"use_always-header-v1-unknown": {
Behavior: "use_always",
ExpectedAddr: "127.0.0.1",
Header: &proxyproto.Header{
Version: 1,
Command: proxyproto.PROXY,
TransportProtocol: proxyproto.UNSPEC,
},
},
"use_always-header-v2": {
Behavior: "use_always",
ExpectedAddr: "10.1.1.1",
Header: &proxyproto.Header{
Version: 2,
Command: proxyproto.PROXY,
TransportProtocol: proxyproto.TCPv4,
SourceAddr: &net.TCPAddr{
IP: net.ParseIP("10.1.1.1"),
Port: 1000,
},
DestinationAddr: &net.TCPAddr{
IP: net.ParseIP("20.2.2.2"),
Port: 2000,
},
},
},
"use_always-header-v2-unknown": {
Behavior: "use_always",
ExpectedAddr: "127.0.0.1",
Header: &proxyproto.Header{
Version: 2,
Command: proxyproto.LOCAL,
TransportProtocol: proxyproto.UNSPEC,
},
},
"allow_authorized-no-header-in": {
Behavior: "allow_authorized",
AuthorizedAddr: "127.0.0.1/32",
ExpectedAddr: "127.0.0.1",
},
"allow_authorized-no-header-not-in": {
Behavior: "allow_authorized",
AuthorizedAddr: "10.0.0.1/32",
ExpectedAddr: "127.0.0.1",
},
"allow_authorized-v1-in": {
Behavior: "allow_authorized",
AuthorizedAddr: "127.0.0.1/32",
ExpectedAddr: "10.1.1.1",
Header: &proxyproto.Header{
Version: 1,
Command: proxyproto.PROXY,
TransportProtocol: proxyproto.TCPv4,
SourceAddr: &net.TCPAddr{
IP: net.ParseIP("10.1.1.1"),
Port: 1000,
},
DestinationAddr: &net.TCPAddr{
IP: net.ParseIP("20.2.2.2"),
Port: 2000,
},
},
},

// allow_authorized still accepts the PROXY header when not in the
// authorized addresses but discards it silently
"allow_authorized-v1-not-in": {
Behavior: "allow_authorized",
AuthorizedAddr: "10.0.0.1/32",
ExpectedAddr: "127.0.0.1",
Header: &proxyproto.Header{
Version: 1,
Command: proxyproto.PROXY,
TransportProtocol: proxyproto.TCPv4,
SourceAddr: &net.TCPAddr{
IP: net.ParseIP("10.1.1.1"),
Port: 1000,
},
DestinationAddr: &net.TCPAddr{
IP: net.ParseIP("20.2.2.2"),
Port: 2000,
},
},
},

"deny_unauthorized-no-header-in": {
Behavior: "deny_unauthorized",
AuthorizedAddr: "127.0.0.1/32",
ExpectedAddr: "127.0.0.1",
},
"deny_unauthorized-no-header-not-in": {
Behavior: "deny_unauthorized",
AuthorizedAddr: "10.0.0.1/32",
ExpectedAddr: "127.0.0.1",
ExpectError: true,
},
"deny_unauthorized-v1-in": {
Behavior: "deny_unauthorized",
AuthorizedAddr: "127.0.0.1/32",
ExpectedAddr: "10.1.1.1",
Header: &proxyproto.Header{
Version: 1,
Command: proxyproto.PROXY,
TransportProtocol: proxyproto.TCPv4,
SourceAddr: &net.TCPAddr{
IP: net.ParseIP("10.1.1.1"),
Port: 1000,
},
DestinationAddr: &net.TCPAddr{
IP: net.ParseIP("20.2.2.2"),
Port: 2000,
},
},
},
"deny_unauthorized-v1-not-in": {
Behavior: "deny_unauthorized",
AuthorizedAddr: "10.0.0.1/32",
ExpectedAddr: "127.0.0.1",
ExpectError: true,
Header: &proxyproto.Header{
Version: 1,
Command: proxyproto.PROXY,
TransportProtocol: proxyproto.TCPv4,
SourceAddr: &net.TCPAddr{
IP: net.ParseIP("10.1.1.1"),
Port: 1000,
},
DestinationAddr: &net.TCPAddr{
IP: net.ParseIP("20.2.2.2"),
Port: 2000,
},
},
},
} {
t.Run(name, func(t *testing.T) {
proxyProtocolAuthorizedAddrs := []*sockaddr.SockAddrMarshaler{}
if tc.AuthorizedAddr != "" {
sockAddr, err := sockaddr.NewSockAddr(tc.AuthorizedAddr)
if err != nil {
t.Fatal(err)
}
proxyProtocolAuthorizedAddrs = append(
proxyProtocolAuthorizedAddrs,
&sockaddr.SockAddrMarshaler{SockAddr: sockAddr},
)
}

ln, _, _, err := tcpListenerFactory(&configutil.Listener{
Address: "127.0.0.1:0",
TLSDisable: true,
ProxyProtocolBehavior: tc.Behavior,
ProxyProtocolAuthorizedAddrs: proxyProtocolAuthorizedAddrs,
}, nil, cli.NewMockUi())
if err != nil {
t.Fatalf("err: %s", err)
}

connFn := func(lnReal net.Listener) (net.Conn, error) {
conn, err := net.Dial("tcp", ln.Addr().String())
if err != nil {
return nil, err
}

if tc.Header != nil {
_, err = tc.Header.WriteTo(conn)
}
return conn, err
}

testListenerImpl(t, ln, connFn, "", 0, tc.ExpectedAddr, tc.ExpectError)
})
}
}
27 changes: 23 additions & 4 deletions command/server/listener_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,29 @@ import (

type testListenerConnFn func(net.Listener) (net.Conn, error)

func testListenerImpl(t *testing.T, ln net.Listener, connFn testListenerConnFn, certName string, expectedVersion uint16) {
func testListenerImpl(t *testing.T, ln net.Listener, connFn testListenerConnFn, certName string, expectedVersion uint16, expectedAddr string, expectError bool) {
serverCh := make(chan net.Conn, 1)
go func() {
server, err := ln.Accept()
if err != nil {
t.Errorf("err: %s", err)
if !expectError {
t.Errorf("err: %s", err)
}
close(serverCh)
return
}
if certName != "" {
tlsConn := server.(*tls.Conn)
tlsConn.Handshake()
}
serverCh <- server
addr, _, err := net.SplitHostPort(server.RemoteAddr().String())
if err != nil {
t.Error(err)
}
if addr != expectedAddr {
t.Errorf("expected: %s, got: %s", expectedAddr, addr)
}
}()

client, err := connFn(ln)
Expand All @@ -45,6 +55,15 @@ func testListenerImpl(t *testing.T, ln net.Listener, connFn testListenerConnFn,
}

server := <-serverCh

if server == nil {
if !expectError {
// Something failed already so we abort the test early
t.Fatal("aborting test because the server did not accept the connection")
}
return
}

defer client.Close()
defer server.Close()

Expand All @@ -62,8 +81,8 @@ func testListenerImpl(t *testing.T, ln net.Listener, connFn testListenerConnFn,
client.Close()

<-copyCh
if buf.String() != "foo" {
t.Fatalf("bad: %v", buf.String())
if (buf.String() != "foo" && !expectError) || (buf.String() == "foo" && expectError) {
t.Fatalf("bad: %q, expectError: %t", buf.String(), expectError)
}
}

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ require (
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5
github.com/apple/foundationdb/bindings/go v0.0.0-20190411004307-cd5c9d91fad2
github.com/armon/go-metrics v0.3.10
github.com/armon/go-proxyproto v0.0.0-20210323213023-7e956b284f0a
github.com/armon/go-radix v1.0.0
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a
github.com/aws/aws-sdk-go v1.37.19
Expand Down Expand Up @@ -151,6 +150,7 @@ require (
github.com/ory/dockertest v3.3.5+incompatible
github.com/ory/dockertest/v3 v3.8.0
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pires/go-proxyproto v0.6.1
github.com/pkg/errors v0.9.1
github.com/posener/complete v1.2.3
github.com/pquerna/otp v1.2.1-0.20191009055518-468c2dd2b58d
Expand Down
Loading

0 comments on commit 1d06d25

Please sign in to comment.