mirror of
https://github.com/coredns/coredns.git
synced 2026-10-16 23:35:43 -04:00
The proxyproto plugin parsed PROXY protocol v2 headers only to recover the real client address; the TLV vector was discarded, so downstream plugins had no way to read values such as the AWS VPC endpoint ID, SSL client CN, or vendor-specific custom TLVs. Add an opt-in `tlv_metadata` option. When set, the connection's raw PPv2 TLV vector is captured and the plugin, acting as a metadata.Provider, publishes it under the `proxyproto/tlv` label as a hex-encoded string. Consumers decode and split it with proxyproto.SplitTLVs. The option is off by default, so existing deployments skip the extra per-connection processing and behave exactly as before. PROXY protocol v1 connections, which carry no TLVs, are unaffected. Capture spans all transports: dns:// (UDP and TCP), tls:// (DoT), https:// (DoH), grpc://, quic:// (DoQ), and https3:// (DoH3). QUIC-based transports require udp_session_tracking, since the PPv2 header arrives as a standalone first datagram ahead of the QUIC handshake. Includes unit tests for capture, the metadata provider (direct and through the real metadata chain), v1 compatibility, and option parsing, plus README and man page updates. Fixes #8633 Signed-off-by: Mekias Yohannes <mekias@amazon.com> Co-authored-by: Mekias Yohannes <mekias@amazon.com>
153 lines
5.4 KiB
Go
153 lines
5.4 KiB
Go
package proxyproto
|
|
|
|
import (
|
|
"net"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/coredns/caddy"
|
|
"github.com/coredns/coredns/core/dnsserver"
|
|
|
|
proxyprotoLib "github.com/pires/go-proxyproto"
|
|
)
|
|
|
|
func TestSetup(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
shouldErr bool
|
|
expectedRoot string // expected root, set to the controller. Empty for negative cases.
|
|
expectedErrContent string // substring from the expected error. Empty for positive cases.
|
|
config bool
|
|
sessionTrackingTTL time.Duration
|
|
sessionTrackingMaxSessions int
|
|
}{
|
|
// positive
|
|
{"proxyproto", false, "", "", true, 0, 0},
|
|
{"proxyproto {\nallow 127.0.0.1/8 ::1/128\n}", false, "", "", true, 0, 0},
|
|
{"proxyproto {\nallow 127.0.0.1/8 ::1/128\ndefault ignore\n}", false, "", "", true, 0, 0},
|
|
// Allow without any IPs is also valid
|
|
{"proxyproto {\nallow\n}", false, "", "", true, 0, 0},
|
|
// udp_session_tracking with TTL only (max sessions gonna use package default)
|
|
{"proxyproto {\nudp_session_tracking 28s\n}", false, "", "", true, 28 * time.Second, 0},
|
|
{"proxyproto {\nallow 10.0.0.0/8\nudp_session_tracking 1m\n}", false, "", "", true, time.Minute, 0},
|
|
// udp_session_tracking with explicit max sessions
|
|
{"proxyproto {\nudp_session_tracking 28s 20000\n}", false, "", "", true, 28 * time.Second, 20000},
|
|
{"proxyproto {\nallow 10.0.0.0/8\nudp_session_tracking 1m 500\n}", false, "", "", true, time.Minute, 500},
|
|
// negative
|
|
{"proxyproto {\nunknown\n}", true, "", "unknown option", false, 0, 0},
|
|
{"proxyproto extra_arg", true, "", "Wrong argument", false, 0, 0},
|
|
{"proxyproto {\nallow invalid_ip\n}", true, "", "invalid CIDR address", false, 0, 0},
|
|
{"proxyproto {\nallow 127.0.0.1/8\ndefault invalid_policy\n}", true, "", "Wrong argument", false, 0, 0},
|
|
// udp_session_tracking: missing TTL
|
|
{"proxyproto {\nudp_session_tracking\n}", true, "", "Wrong argument", false, 0, 0},
|
|
// udp_session_tracking: too many arguments
|
|
{"proxyproto {\nudp_session_tracking 28s 100 extra\n}", true, "", "Wrong argument", false, 0, 0},
|
|
// udp_session_tracking: bad TTL
|
|
{"proxyproto {\nudp_session_tracking notaduration\n}", true, "", "invalid duration", false, 0, 0},
|
|
// udp_session_tracking: bad max sessions
|
|
{"proxyproto {\nudp_session_tracking 28s notanumber\n}", true, "", "invalid max sessions", false, 0, 0},
|
|
{"proxyproto {\nudp_session_tracking 28s 0\n}", true, "", "invalid max sessions", false, 0, 0},
|
|
{"proxyproto {\nudp_session_tracking 28s -1\n}", true, "", "invalid max sessions", false, 0, 0},
|
|
}
|
|
for i, test := range tests {
|
|
c := caddy.NewTestController("dns", test.input)
|
|
err := setup(c)
|
|
cfg := dnsserver.GetConfig(c)
|
|
|
|
if test.config && cfg.ProxyProtoConnPolicy == nil {
|
|
t.Errorf("Test %d: Expected ProxyProtoConnPolicy to be configured for input %s", i, test.input)
|
|
}
|
|
if !test.config && cfg.ProxyProtoConnPolicy != nil {
|
|
t.Errorf("Test %d: Expected ProxyProtoConnPolicy to NOT be configured for input %s", i, test.input)
|
|
}
|
|
|
|
if cfg.ProxyProtoUDPSessionTrackingTTL != test.sessionTrackingTTL {
|
|
t.Errorf("Test %d: Expected ProxyProtoUDPSessionTrackingTTL %v, got %v for input %s",
|
|
i, test.sessionTrackingTTL, cfg.ProxyProtoUDPSessionTrackingTTL, test.input)
|
|
}
|
|
|
|
if cfg.ProxyProtoUDPSessionTrackingMaxSessions != test.sessionTrackingMaxSessions {
|
|
t.Errorf("Test %d: Expected ProxyProtoUDPSessionTrackingMaxSessions %d, got %d for input %s",
|
|
i, test.sessionTrackingMaxSessions, cfg.ProxyProtoUDPSessionTrackingMaxSessions, test.input)
|
|
}
|
|
|
|
if test.shouldErr && err == nil {
|
|
t.Errorf("Test %d: Expected error but found %s for input %s", i, err, test.input)
|
|
}
|
|
|
|
if err != nil {
|
|
if !test.shouldErr {
|
|
t.Errorf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err)
|
|
}
|
|
|
|
if !strings.Contains(err.Error(), test.expectedErrContent) {
|
|
t.Errorf("Test %d: Expected error to contain: %v, found error: %v, input: %s", i, test.expectedErrContent, err, test.input)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTLVMetadataFlag(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
shouldErr bool
|
|
expectTLV bool
|
|
}{
|
|
{"proxyproto {\ntlv_metadata\n}", false, true},
|
|
{"proxyproto {\nallow 10.0.0.0/8\ntlv_metadata\n}", false, true},
|
|
// absent -> disabled
|
|
{"proxyproto {\nallow 10.0.0.0/8\n}", false, false},
|
|
// takes no arguments
|
|
{"proxyproto {\ntlv_metadata on\n}", true, false},
|
|
}
|
|
for i, test := range tests {
|
|
c := caddy.NewTestController("dns", test.input)
|
|
err := setup(c)
|
|
if test.shouldErr && err == nil {
|
|
t.Errorf("Test %d: expected error, got none for %q", i, test.input)
|
|
continue
|
|
}
|
|
if !test.shouldErr && err != nil {
|
|
t.Errorf("Test %d: unexpected error for %q: %v", i, test.input, err)
|
|
continue
|
|
}
|
|
if test.shouldErr {
|
|
continue
|
|
}
|
|
cfg := dnsserver.GetConfig(c)
|
|
if cfg.ProxyProtoTLVMetadata != test.expectTLV {
|
|
t.Errorf("Test %d: ProxyProtoTLVMetadata = %v, want %v for %q", i, cfg.ProxyProtoTLVMetadata, test.expectTLV, test.input)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDefaultPolicyWithoutAllow(t *testing.T) {
|
|
c := caddy.NewTestController(
|
|
"dns",
|
|
"proxyproto {\ndefault reject\n}",
|
|
)
|
|
|
|
if err := setup(c); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cfg := dnsserver.GetConfig(c)
|
|
|
|
policy, err := cfg.ProxyProtoConnPolicy(
|
|
proxyprotoLib.ConnPolicyOptions{
|
|
Upstream: &net.TCPAddr{
|
|
IP: net.ParseIP("192.0.2.1"),
|
|
Port: 53,
|
|
},
|
|
},
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if policy != proxyprotoLib.REJECT {
|
|
t.Fatalf("Expected REJECT, got %v", policy)
|
|
}
|
|
}
|