Add support for PROXY protocol v2 in TCP listener (#13540)
* 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 https://github.com/hashicorp/vault/issues/9462 by adding support for `PROXY UNKNOWN` for PROXY protocol v1. Closes https://github.com/hashicorp/vault/issues/3807 * Add changelog
This commit is contained in:
parent
baafd9ff38
commit
e89bbd51d9
|
@ -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.
|
||||
```
|
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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) {
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,12 +10,15 @@ 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 != "" {
|
||||
|
@ -23,6 +26,13 @@ func testListenerImpl(t *testing.T, ln net.Listener, connFn testListenerConnFn,
|
|||
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)
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
2
go.mod
2
go.mod
|
@ -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
|
||||
|
@ -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
|
||||
|
|
4
go.sum
4
go.sum
|
@ -205,8 +205,6 @@ github.com/armon/go-metrics v0.3.4/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4
|
|||
github.com/armon/go-metrics v0.3.9/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
|
||||
github.com/armon/go-metrics v0.3.10 h1:FR+drcQStOe+32sYyJYyZ7FIdgoGGBnwLl+flodp8Uo=
|
||||
github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
|
||||
github.com/armon/go-proxyproto v0.0.0-20210323213023-7e956b284f0a h1:AP/vsCIvJZ129pdm9Ek7bH7yutN3hByqsMoNrWAxRQc=
|
||||
github.com/armon/go-proxyproto v0.0.0-20210323213023-7e956b284f0a/go.mod h1:QmP9hvJ91BbJmGVGSbutW19IC0Q9phDCLGaomwTJbgU=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
|
||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
|
@ -1350,6 +1348,8 @@ github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9F
|
|||
github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pierrec/lz4/v4 v4.1.8 h1:ieHkV+i2BRzngO4Wd/3HGowuZStgq6QkPsD1eolNAO4=
|
||||
github.com/pierrec/lz4/v4 v4.1.8/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
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/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
|
||||
github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
package proxyutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
proxyproto "github.com/armon/go-proxyproto"
|
||||
"github.com/hashicorp/go-secure-stdlib/parseutil"
|
||||
sockaddr "github.com/hashicorp/go-sockaddr"
|
||||
proxyproto "github.com/pires/go-proxyproto"
|
||||
)
|
||||
|
||||
// ProxyProtoConfig contains configuration for the PROXY protocol
|
||||
|
@ -42,33 +43,33 @@ func WrapInProxyProto(listener net.Listener, config *ProxyProtoConfig) (net.List
|
|||
case "use_always":
|
||||
newLn = &proxyproto.Listener{
|
||||
Listener: listener,
|
||||
ProxyHeaderTimeout: 10 * time.Second,
|
||||
ReadHeaderTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
case "allow_authorized", "deny_unauthorized":
|
||||
newLn = &proxyproto.Listener{
|
||||
Listener: listener,
|
||||
ProxyHeaderTimeout: 10 * time.Second,
|
||||
SourceCheck: func(addr net.Addr) (bool, error) {
|
||||
ReadHeaderTimeout: 10 * time.Second,
|
||||
Policy: func(addr net.Addr) (proxyproto.Policy, error) {
|
||||
config.RLock()
|
||||
defer config.RUnlock()
|
||||
|
||||
sa, err := sockaddr.NewSockAddr(addr.String())
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error parsing remote address: %w", err)
|
||||
return proxyproto.REJECT, fmt.Errorf("error parsing remote address: %w", err)
|
||||
}
|
||||
|
||||
for _, authorizedAddr := range config.AuthorizedAddrs {
|
||||
if authorizedAddr.Contains(sa) {
|
||||
return true, nil
|
||||
return proxyproto.USE, nil
|
||||
}
|
||||
}
|
||||
|
||||
if config.Behavior == "allow_authorized" {
|
||||
return false, nil
|
||||
return proxyproto.IGNORE, nil
|
||||
}
|
||||
|
||||
return false, proxyproto.ErrInvalidUpstream
|
||||
return proxyproto.REJECT, errors.New(`upstream connection not trusted proxy_protocol_behavior is "deny_unauthorized"`)
|
||||
},
|
||||
}
|
||||
default:
|
||||
|
|
Loading…
Reference in New Issue