1
Fork 0
mirror of https://github.com/caddyserver/caddy.git synced 2024-12-16 21:56:40 -05:00

proxyprotocol: use github.com/pires/go-proxyproto (#5915)

* proxyprotocol: use github.com/pires/go-proxyproto

* Fix typo: r/generelly/generally

Co-authored-by: Francis Lavoie <lavofr@gmail.com>

* add config options for `Deny` CIDR and fallback policy

* use `netip` package & trust unix sockets

---------

Co-authored-by: Francis Lavoie <lavofr@gmail.com>
This commit is contained in:
Mohammed Al Sahaf 2023-12-13 19:07:43 +03:00 committed by GitHub
parent 56c6b3f673
commit dc12bd9743
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 176 additions and 42 deletions

2
go.mod
View file

@ -14,7 +14,6 @@ require (
github.com/google/uuid v1.3.1 github.com/google/uuid v1.3.1
github.com/klauspost/compress v1.17.0 github.com/klauspost/compress v1.17.0
github.com/klauspost/cpuid/v2 v2.2.5 github.com/klauspost/cpuid/v2 v2.2.5
github.com/mastercactapus/proxyprotocol v0.0.4
github.com/mholt/acmez v1.2.0 github.com/mholt/acmez v1.2.0
github.com/prometheus/client_golang v1.15.1 github.com/prometheus/client_golang v1.15.1
github.com/quic-go/quic-go v0.40.0 github.com/quic-go/quic-go v0.40.0
@ -117,6 +116,7 @@ require (
github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pires/go-proxyproto v0.7.0
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/client_model v0.4.0 // indirect
github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/common v0.42.0 // indirect

4
go.sum
View file

@ -352,8 +352,6 @@ github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0Q
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
github.com/mastercactapus/proxyprotocol v0.0.4 h1:qSY75IZF30ZqIU9iW1ip3I7gTnm8wRAnGWqPxCBVgq0=
github.com/mastercactapus/proxyprotocol v0.0.4/go.mod h1:X8FRVEDZz9FkrIoL4QYTBF4Ka4ELwTv0sah0/5NxCPw=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
@ -433,6 +431,8 @@ github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9
github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU= github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=

View file

@ -15,11 +15,11 @@
package proxyprotocol package proxyprotocol
import ( import (
"fmt"
"net" "net"
"net/netip"
"time" "time"
"github.com/mastercactapus/proxyprotocol" goproxy "github.com/pires/go-proxyproto"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
) )
@ -38,32 +38,74 @@ type ListenerWrapper struct {
// Allow is an optional list of CIDR ranges to // Allow is an optional list of CIDR ranges to
// allow/require PROXY headers from. // allow/require PROXY headers from.
Allow []string `json:"allow,omitempty"` Allow []string `json:"allow,omitempty"`
allow []netip.Prefix
rules []proxyprotocol.Rule // Denby is an optional list of CIDR ranges to
// deny PROXY headers from.
Deny []string `json:"deny,omitempty"`
deny []netip.Prefix
// Accepted values are: ignore, use, reject, require, skip
// default: ignore
// Policy definitions are here: https://pkg.go.dev/github.com/pires/go-proxyproto@v0.7.0#Policy
FallbackPolicy Policy `json:"fallback_policy,omitempty"`
policy goproxy.PolicyFunc
} }
// Provision sets up the listener wrapper. // Provision sets up the listener wrapper.
func (pp *ListenerWrapper) Provision(ctx caddy.Context) error { func (pp *ListenerWrapper) Provision(ctx caddy.Context) error {
rules := make([]proxyprotocol.Rule, 0, len(pp.Allow)) for _, cidr := range pp.Allow {
for _, s := range pp.Allow { ipnet, err := netip.ParsePrefix(cidr)
_, n, err := net.ParseCIDR(s)
if err != nil { if err != nil {
return fmt.Errorf("invalid subnet '%s': %w", s, err) return err
} }
rules = append(rules, proxyprotocol.Rule{ pp.allow = append(pp.allow, ipnet)
Timeout: time.Duration(pp.Timeout), }
Subnet: n, for _, cidr := range pp.Deny {
}) ipnet, err := netip.ParsePrefix(cidr)
if err != nil {
return err
}
pp.deny = append(pp.deny, ipnet)
}
pp.policy = func(upstream net.Addr) (goproxy.Policy, error) {
// trust unix sockets
if network := upstream.Network(); caddy.IsUnixNetwork(network) {
return goproxy.USE, nil
}
ret := pp.FallbackPolicy
host, _, err := net.SplitHostPort(upstream.String())
if err != nil {
return goproxy.REJECT, err
} }
pp.rules = rules ip, err := netip.ParseAddr(host)
if err != nil {
return goproxy.REJECT, err
}
for _, ipnet := range pp.deny {
if ipnet.Contains(ip) {
return goproxy.REJECT, nil
}
}
for _, ipnet := range pp.allow {
if ipnet.Contains(ip) {
ret = PolicyUSE
break
}
}
return policyToGoProxyPolicy[ret], nil
}
return nil return nil
} }
// WrapListener adds PROXY protocol support to the listener. // WrapListener adds PROXY protocol support to the listener.
func (pp *ListenerWrapper) WrapListener(l net.Listener) net.Listener { func (pp *ListenerWrapper) WrapListener(l net.Listener) net.Listener {
pl := proxyprotocol.NewListener(l, time.Duration(pp.Timeout)) pl := &goproxy.Listener{
pl.SetFilter(pp.rules) Listener: l,
ReadHeaderTimeout: time.Duration(pp.Timeout),
}
pl.Policy = pp.policy
return pl return pl
} }

View file

@ -35,6 +35,8 @@ func (ListenerWrapper) CaddyModule() caddy.ModuleInfo {
// proxy_protocol { // proxy_protocol {
// timeout <duration> // timeout <duration>
// allow <IPs...> // allow <IPs...>
// deny <IPs...>
// fallback_policy <policy>
// } // }
func (w *ListenerWrapper) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { func (w *ListenerWrapper) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() { for d.Next() {
@ -57,7 +59,17 @@ func (w *ListenerWrapper) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
case "allow": case "allow":
w.Allow = append(w.Allow, d.RemainingArgs()...) w.Allow = append(w.Allow, d.RemainingArgs()...)
case "deny":
w.Deny = append(w.Deny, d.RemainingArgs()...)
case "fallback_policy":
if !d.NextArg() {
return d.ArgErr()
}
p, err := parsePolicy(d.Val())
if err != nil {
return d.WrapErr(err)
}
w.FallbackPolicy = p
default: default:
return d.ArgErr() return d.ArgErr()
} }

View file

@ -0,0 +1,82 @@
package proxyprotocol
import (
"errors"
"fmt"
"strings"
goproxy "github.com/pires/go-proxyproto"
)
type Policy int
// as defined in: https://pkg.go.dev/github.com/pires/go-proxyproto@v0.7.0#Policy
const (
// IGNORE address from PROXY header, but accept connection
PolicyIGNORE Policy = iota
// USE address from PROXY header
PolicyUSE
// REJECT connection when PROXY header is sent
// Note: even though the first read on the connection returns an error if
// a PROXY header is present, subsequent reads do not. It is the task of
// the code using the connection to handle that case properly.
PolicyREJECT
// REQUIRE connection to send PROXY header, reject if not present
// Note: even though the first read on the connection returns an error if
// a PROXY header is not present, subsequent reads do not. It is the task
// of the code using the connection to handle that case properly.
PolicyREQUIRE
// SKIP accepts a connection without requiring the PROXY header
// Note: an example usage can be found in the SkipProxyHeaderForCIDR
// function.
PolicySKIP
)
var policyToGoProxyPolicy = map[Policy]goproxy.Policy{
PolicyUSE: goproxy.USE,
PolicyIGNORE: goproxy.IGNORE,
PolicyREJECT: goproxy.REJECT,
PolicyREQUIRE: goproxy.REQUIRE,
PolicySKIP: goproxy.SKIP,
}
var policyMap = map[Policy]string{
PolicyUSE: "USE",
PolicyIGNORE: "IGNORE",
PolicyREJECT: "REJECT",
PolicyREQUIRE: "REQUIRE",
PolicySKIP: "SKIP",
}
var policyMapRev = map[string]Policy{
"USE": PolicyUSE,
"IGNORE": PolicyIGNORE,
"REJECT": PolicyREJECT,
"REQUIRE": PolicyREQUIRE,
"SKIP": PolicySKIP,
}
// MarshalText implements the text marshaller method.
func (x Policy) MarshalText() ([]byte, error) {
return []byte(policyMap[x]), nil
}
// UnmarshalText implements the text unmarshaller method.
func (x *Policy) UnmarshalText(text []byte) error {
name := string(text)
tmp, err := parsePolicy(name)
if err != nil {
return err
}
*x = tmp
return nil
}
func parsePolicy(name string) (Policy, error) {
if x, ok := policyMapRev[strings.ToUpper(name)]; ok {
return x, nil
}
return Policy(0), fmt.Errorf("%s is %w", name, errInvalidPolicy)
}
var errInvalidPolicy = errors.New("invalid policy")

View file

@ -28,7 +28,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/mastercactapus/proxyprotocol" "github.com/pires/go-proxyproto"
"go.uber.org/zap" "go.uber.org/zap"
"golang.org/x/net/http2" "golang.org/x/net/http2"
@ -207,44 +207,42 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e
if !ok { if !ok {
return nil, fmt.Errorf("failed to get proxy protocol info from context") return nil, fmt.Errorf("failed to get proxy protocol info from context")
} }
header := proxyproto.Header{
// The src and dst have to be of the some address family. As we don't know the original SourceAddr: &net.TCPAddr{
// dst address (it's kind of impossible to know) and this address is generelly of very IP: proxyProtocolInfo.AddrPort.Addr().AsSlice(),
Port: int(proxyProtocolInfo.AddrPort.Port()),
Zone: proxyProtocolInfo.AddrPort.Addr().Zone(),
},
}
// The src and dst have to be of the same address family. As we don't know the original
// dst address (it's kind of impossible to know) and this address is generally of very
// little interest, we just set it to all zeros. // little interest, we just set it to all zeros.
var destIP net.IP
switch { switch {
case proxyProtocolInfo.AddrPort.Addr().Is4(): case proxyProtocolInfo.AddrPort.Addr().Is4():
destIP = net.IPv4zero header.TransportProtocol = proxyproto.TCPv4
header.DestinationAddr = &net.TCPAddr{
IP: net.IPv4zero,
}
case proxyProtocolInfo.AddrPort.Addr().Is6(): case proxyProtocolInfo.AddrPort.Addr().Is6():
destIP = net.IPv6zero header.TransportProtocol = proxyproto.TCPv6
header.DestinationAddr = &net.TCPAddr{
IP: net.IPv6zero,
}
default: default:
return nil, fmt.Errorf("unexpected remote addr type in proxy protocol info") return nil, fmt.Errorf("unexpected remote addr type in proxy protocol info")
} }
// TODO: We should probably migrate away from net.IP to use netip.Addr,
// but due to the upstream dependency, we can't do that yet.
switch h.ProxyProtocol { switch h.ProxyProtocol {
case "v1": case "v1":
header := proxyprotocol.HeaderV1{ header.Version = 1
SrcIP: net.IP(proxyProtocolInfo.AddrPort.Addr().AsSlice()),
SrcPort: int(proxyProtocolInfo.AddrPort.Port()),
DestIP: destIP,
DestPort: 0,
}
caddyCtx.Logger().Debug("sending proxy protocol header v1", zap.Any("header", header)) caddyCtx.Logger().Debug("sending proxy protocol header v1", zap.Any("header", header))
_, err = header.WriteTo(conn)
case "v2": case "v2":
header := proxyprotocol.HeaderV2{ header.Version = 2
Command: proxyprotocol.CmdProxy,
Src: &net.TCPAddr{IP: net.IP(proxyProtocolInfo.AddrPort.Addr().AsSlice()), Port: int(proxyProtocolInfo.AddrPort.Port())},
Dest: &net.TCPAddr{IP: destIP, Port: 0},
}
caddyCtx.Logger().Debug("sending proxy protocol header v2", zap.Any("header", header)) caddyCtx.Logger().Debug("sending proxy protocol header v2", zap.Any("header", header))
_, err = header.WriteTo(conn)
default: default:
return nil, fmt.Errorf("unexpected proxy protocol version") return nil, fmt.Errorf("unexpected proxy protocol version")
} }
_, err = header.WriteTo(conn)
if err != nil { if err != nil {
// identify this error as one that occurred during // identify this error as one that occurred during
// dialing, which can be important when trying to // dialing, which can be important when trying to