Files
pocket-id/backend/internal/bootstrap/proxy_protocol.go
2026-07-17 10:37:47 +02:00

56 lines
1.9 KiB
Go

package bootstrap
import (
"fmt"
"log/slog"
"net"
"time"
"github.com/pires/go-proxyproto"
)
const proxyProtocolReadHeaderTimeout = 5 * time.Second
func newProxyProtocolListener(listener net.Listener, trustedProxies []string) (net.Listener, error) {
if len(trustedProxies) == 0 {
return listener, nil
}
// PROXY protocol transports client metadata on TCP connections before any TLS handshake
network := listener.Addr().Network()
if network != "tcp" && network != "tcp4" && network != "tcp6" {
return nil, fmt.Errorf("PROXY protocol requires a TCP listener, got %q", network)
}
// Require headers from configured network proxies and reject every other network peer
trustedProxyPolicy, err := proxyproto.TrustProxyHeaderFromRanges(trustedProxies)
if err != nil {
return nil, fmt.Errorf("failed to configure PROXY protocol trusted proxies: %w", err)
}
// We need to add another policy for loopback connections because healthcheck requests are made from the same host
// loopbackPolicy handles requests from trusted loopback like normal, but it also allows untrusted loopback connections to be accepted
// untrusted loopback connections aren't allowed to send PROXY headers though
loopbackPolicy, err := proxyproto.PolicyFromRanges(trustedProxies, proxyproto.USE, proxyproto.SKIP)
if err != nil {
return nil, fmt.Errorf("failed to configure PROXY protocol loopback proxies: %w", err)
}
// Create a policy which decides which of the two policies to use based on the upstream address
policy := func(options proxyproto.ConnPolicyOptions) (proxyproto.Policy, error) {
upstream, ok := options.Upstream.(*net.TCPAddr)
if ok && upstream.IP.IsLoopback() {
return loopbackPolicy(options)
}
return trustedProxyPolicy(options)
}
slog.Info("PROXY protocol enabled")
return &proxyproto.Listener{
Listener: listener,
ConnPolicy: policy,
ReadHeaderTimeout: proxyProtocolReadHeaderTimeout,
}, nil
}