Skip to content

Commit

Permalink
pkg/auth: support PROXY protocol in authservice
Browse files Browse the repository at this point in the history
This change implements support for PROXY protocol HTTPS requests in
authservice. This allows it to accurately identify the source IP of
requests that have been redirected by a load balancer.

Resolves #357

Change-Id: I5471adce19a1d402e0d9e4f63be8cb899ecbb791
  • Loading branch information
jewharton committed Feb 15, 2024
1 parent 69511cd commit 4749179
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 1 deletion.
3 changes: 3 additions & 0 deletions cmd/authservice/config.yaml.lock
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@ node.replication-limit: 1000
# maximum size that the incoming POST request body with access grant can be
# post-size-limit: 4.0 KiB

# TLS address to listen on for PROXY protocol requests
# proxy-addr-tls: :20005

# comma separated list of public urls for the server TLS certificates (e.g. https://auth.example.com,https://auth.us1.example.com)
public-url: []

Expand Down
36 changes: 35 additions & 1 deletion pkg/auth/peer.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"net/url"
"time"

"github.com/pires/go-proxyproto"
"github.com/spacemonkeygo/monkit/v3"
"github.com/zeebo/errs"
"go.uber.org/zap"
Expand Down Expand Up @@ -56,6 +57,8 @@ type Config struct {
DRPCListenAddr string `user:"true" help:"public DRPC address to listen on" default:":20002"`
DRPCListenAddrTLS string `user:"true" help:"public DRPC+TLS address to listen on" default:":20003"`

ProxyAddrTLS string `help:"TLS address to listen on for PROXY protocol requests" default:":20005"`

CertFile string `user:"true" help:"server certificate file" default:""`
KeyFile string `user:"true" help:"server key file" default:""`
PublicURL []string `user:"true" help:"comma separated list of public urls for the server TLS certificates (e.g. https://auth.example.com,https://auth.us1.example.com)"`
Expand Down Expand Up @@ -90,6 +93,8 @@ type Peer struct {
drpcListener net.Listener
drpcTLSListener net.Listener

proxyTLSListener net.Listener

config Config
areSatsDynamic bool
endpoint *url.URL
Expand Down Expand Up @@ -181,7 +186,7 @@ func New(ctx context.Context, log *zap.Logger, config Config, configDir string)
return nil, errs.Wrap(err)
}

var httpsListener, drpcTLSListener net.Listener
var httpsListener, drpcTLSListener, proxyTLSListener net.Listener
if tlsConfig != nil {
httpsListener, err = tls.Listen("tcp", config.ListenAddrTLS, tlsConfig)
if err != nil {
Expand All @@ -191,6 +196,20 @@ func New(ctx context.Context, log *zap.Logger, config Config, configDir string)
if err != nil {
return nil, errs.Wrap(err)
}

if config.ProxyAddrTLS != "" {
proxyListener, err := net.Listen("tcp", config.ProxyAddrTLS)
if err != nil {
return nil, errs.Wrap(err)
}

proxyTLSListener = tls.NewListener(&proxyproto.Listener{
Listener: proxyListener,
Policy: func(upstream net.Addr) (proxyproto.Policy, error) {
return proxyproto.REQUIRE, nil
},
}, tlsConfig)
}
}

return &Peer{
Expand All @@ -207,6 +226,8 @@ func New(ctx context.Context, log *zap.Logger, config Config, configDir string)
drpcListener: drpcListener,
drpcTLSListener: drpcTLSListener,

proxyTLSListener: proxyTLSListener,

config: config,
areSatsDynamic: areSatsDynamic,
endpoint: endpoint,
Expand Down Expand Up @@ -302,6 +323,11 @@ func (p *Peer) Run(ctx context.Context) (err error) {
return p.ServeHTTP(groupCtx, p.httpsListener)
})

group.Go(func() error {
p.log.Info("Starting HTTPS (PROXY protocol) server", zap.String("address", p.proxyTLSListener.Addr().String()))
return p.ServeHTTP(groupCtx, p.proxyTLSListener)
})

group.Go(func() error {
return p.ServeDRPC(groupCtx, p.drpcTLSListener)
})
Expand Down Expand Up @@ -330,6 +356,9 @@ func (p *Peer) Close() error {
if p.drpcTLSListener != nil {
_ = p.drpcTLSListener.Close()
}
if p.proxyTLSListener != nil {
_ = p.proxyTLSListener.Close()
}

return errs.Wrap(p.storage.Close())
}
Expand Down Expand Up @@ -393,6 +422,11 @@ func (p *Peer) DRPCTLSAddress() string {
return p.drpcTLSListener.Addr().String()
}

// ProxyAddressTLS returns the TLS address for the PROXY protocol listener.
func (p *Peer) ProxyAddressTLS() string {
return p.proxyTLSListener.Addr().String()
}

func reloadSatelliteList(ctx context.Context, log *zap.Logger, adb *authdb.Database, allowedSatellites []string) {
log.Debug("Reloading allowed satellite list")
allowedSatelliteURLs, _, err := nodelist.Resolve(ctx, allowedSatellites)
Expand Down
98 changes: 98 additions & 0 deletions pkg/auth/peer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"net"
"net/http"
Expand All @@ -20,12 +21,19 @@ import (
"testing"
"time"

"github.com/pires/go-proxyproto"
"github.com/stretchr/testify/require"
"github.com/zeebo/errs"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"go.uber.org/zap/zaptest"
"go.uber.org/zap/zaptest/observer"

"storj.io/common/errs2"
"storj.io/common/pb"
"storj.io/common/rpc"
"storj.io/common/testcontext"
"storj.io/common/testrand"
"storj.io/edge/pkg/auth/badgerauth"
)

Expand Down Expand Up @@ -231,6 +239,95 @@ func TestPeer_TLSDRPC(t *testing.T) {
require.Equal(t, "endpoint", registerAccessResponse.Endpoint)
}

func TestPeer_ProxyProtocol(t *testing.T) {
ctx := testcontext.NewWithTimeout(t, time.Minute)
defer ctx.Cleanup()

certFile, keyFile, certificatePEM, _ := createSelfSignedCertificateFile(t, "localhost")

stdCore := zapcore.NewCore(
zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()),
zapcore.Lock(os.Stdout),
zap.DebugLevel,
)
observedCore, observedLogs := observer.New(zap.DebugLevel)
logger := zap.New(zapcore.NewTee(stdCore, observedCore))

p, err := New(ctx, logger, Config{
Endpoint: "https://example.com",
AllowedSatellites: []string{testrand.NodeID().String() + "@127.0.0.1:7777"},
KVBackend: "badger://",
ProxyAddrTLS: "127.0.0.1:0",
CertFile: certFile.Name(),
KeyFile: keyFile.Name(),
Node: badgerauth.Config{
FirstStart: true,
ReplicationInterval: 5 * time.Second,
},
}, "")
require.NoError(t, err)

serverCtx, serverCancel := context.WithCancel(ctx)
defer serverCancel()

ctx.Go(func() error {
return errs2.IgnoreCanceled(p.Run(serverCtx))
})

expectedClientAddr := &net.TCPAddr{
IP: net.IPv4(11, 22, 33, 44),
}

certPool := x509.NewCertPool()
certPool.AppendCertsFromPEM(certificatePEM)

// send a PROXY protocol request to the server
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: certPool,
},
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
d := &net.Dialer{}
conn, err := d.DialContext(ctx, network, addr)
if err != nil {
return nil, err
}

tcpAddr, err := net.ResolveTCPAddr(network, addr)
if err != nil {
return nil, errs.Combine(err, conn.Close())
}

header := proxyproto.HeaderProxyFromAddrs(0, expectedClientAddr, tcpAddr)
if _, err = header.WriteTo(conn); err != nil {
return nil, errs.Combine(err, conn.Close())
}

return conn, nil
},
},
}

req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://%s/v1/health/startup", p.ProxyAddressTLS()), nil)
require.NoError(t, err)

resp, err := client.Do(req)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())

// ensure the server was able to log the client's IP
logs := observedLogs.All()
require.NotEmpty(t, logs)

logs = observedLogs.FilterMessage("request").All()
require.NotEmpty(t, logs)

fields, ok := logs[0].ContextMap()["httpRequest"].(map[string]interface{})
require.True(t, ok)
require.Equal(t, expectedClientAddr.IP.String(), fields["remoteIp"])
}

func createSelfSignedCertificateFile(t *testing.T, hostname string) (certFile *os.File, keyFile *os.File, certificatePEM []byte, privateKeyPEM []byte) {
certificatePEM, privateKeyPEM = createSelfSignedCertificate(t, hostname)

Expand All @@ -256,6 +353,7 @@ func createSelfSignedCertificate(t *testing.T, hostname string) (certificatePEM
CommonName: hostname,
},
DNSNames: []string{hostname},
IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1)},
SerialNumber: big.NewInt(1337),
BasicConstraintsValid: false,
IsCA: true,
Expand Down

0 comments on commit 4749179

Please sign in to comment.