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:
parent
56c6b3f673
commit
dc12bd9743
6 changed files with 176 additions and 42 deletions
2
go.mod
2
go.mod
|
@ -14,7 +14,6 @@ require (
|
|||
github.com/google/uuid v1.3.1
|
||||
github.com/klauspost/compress v1.17.0
|
||||
github.com/klauspost/cpuid/v2 v2.2.5
|
||||
github.com/mastercactapus/proxyprotocol v0.0.4
|
||||
github.com/mholt/acmez v1.2.0
|
||||
github.com/prometheus/client_golang v1.15.1
|
||||
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/go-ps v1.0.0 // 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/prometheus/client_model v0.4.0 // indirect
|
||||
github.com/prometheus/common v0.42.0 // indirect
|
||||
|
|
4
go.sum
4
go.sum
|
@ -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/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
|
||||
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.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
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/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/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.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
|
|
|
@ -15,11 +15,11 @@
|
|||
package proxyprotocol
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/mastercactapus/proxyprotocol"
|
||||
goproxy "github.com/pires/go-proxyproto"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
)
|
||||
|
@ -38,32 +38,74 @@ type ListenerWrapper struct {
|
|||
// Allow is an optional list of CIDR ranges to
|
||||
// allow/require PROXY headers from.
|
||||
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.
|
||||
func (pp *ListenerWrapper) Provision(ctx caddy.Context) error {
|
||||
rules := make([]proxyprotocol.Rule, 0, len(pp.Allow))
|
||||
for _, s := range pp.Allow {
|
||||
_, n, err := net.ParseCIDR(s)
|
||||
for _, cidr := range pp.Allow {
|
||||
ipnet, err := netip.ParsePrefix(cidr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid subnet '%s': %w", s, err)
|
||||
return err
|
||||
}
|
||||
rules = append(rules, proxyprotocol.Rule{
|
||||
Timeout: time.Duration(pp.Timeout),
|
||||
Subnet: n,
|
||||
})
|
||||
pp.allow = append(pp.allow, ipnet)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// WrapListener adds PROXY protocol support to the listener.
|
||||
func (pp *ListenerWrapper) WrapListener(l net.Listener) net.Listener {
|
||||
pl := proxyprotocol.NewListener(l, time.Duration(pp.Timeout))
|
||||
pl.SetFilter(pp.rules)
|
||||
pl := &goproxy.Listener{
|
||||
Listener: l,
|
||||
ReadHeaderTimeout: time.Duration(pp.Timeout),
|
||||
}
|
||||
pl.Policy = pp.policy
|
||||
return pl
|
||||
}
|
||||
|
|
|
@ -35,6 +35,8 @@ func (ListenerWrapper) CaddyModule() caddy.ModuleInfo {
|
|||
// proxy_protocol {
|
||||
// timeout <duration>
|
||||
// allow <IPs...>
|
||||
// deny <IPs...>
|
||||
// fallback_policy <policy>
|
||||
// }
|
||||
func (w *ListenerWrapper) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
for d.Next() {
|
||||
|
@ -57,7 +59,17 @@ func (w *ListenerWrapper) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|||
|
||||
case "allow":
|
||||
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:
|
||||
return d.ArgErr()
|
||||
}
|
||||
|
|
82
modules/caddyhttp/proxyprotocol/policy.go
Normal file
82
modules/caddyhttp/proxyprotocol/policy.go
Normal 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")
|
|
@ -28,7 +28,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mastercactapus/proxyprotocol"
|
||||
"github.com/pires/go-proxyproto"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/net/http2"
|
||||
|
||||
|
@ -207,44 +207,42 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e
|
|||
if !ok {
|
||||
return nil, fmt.Errorf("failed to get proxy protocol info from context")
|
||||
}
|
||||
|
||||
// The src and dst have to be of the some address family. As we don't know the original
|
||||
// dst address (it's kind of impossible to know) and this address is generelly of very
|
||||
header := proxyproto.Header{
|
||||
SourceAddr: &net.TCPAddr{
|
||||
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.
|
||||
var destIP net.IP
|
||||
switch {
|
||||
case proxyProtocolInfo.AddrPort.Addr().Is4():
|
||||
destIP = net.IPv4zero
|
||||
header.TransportProtocol = proxyproto.TCPv4
|
||||
header.DestinationAddr = &net.TCPAddr{
|
||||
IP: net.IPv4zero,
|
||||
}
|
||||
case proxyProtocolInfo.AddrPort.Addr().Is6():
|
||||
destIP = net.IPv6zero
|
||||
header.TransportProtocol = proxyproto.TCPv6
|
||||
header.DestinationAddr = &net.TCPAddr{
|
||||
IP: net.IPv6zero,
|
||||
}
|
||||
default:
|
||||
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 {
|
||||
case "v1":
|
||||
header := proxyprotocol.HeaderV1{
|
||||
SrcIP: net.IP(proxyProtocolInfo.AddrPort.Addr().AsSlice()),
|
||||
SrcPort: int(proxyProtocolInfo.AddrPort.Port()),
|
||||
DestIP: destIP,
|
||||
DestPort: 0,
|
||||
}
|
||||
header.Version = 1
|
||||
caddyCtx.Logger().Debug("sending proxy protocol header v1", zap.Any("header", header))
|
||||
_, err = header.WriteTo(conn)
|
||||
case "v2":
|
||||
header := proxyprotocol.HeaderV2{
|
||||
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},
|
||||
}
|
||||
header.Version = 2
|
||||
caddyCtx.Logger().Debug("sending proxy protocol header v2", zap.Any("header", header))
|
||||
_, err = header.WriteTo(conn)
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected proxy protocol version")
|
||||
}
|
||||
|
||||
_, err = header.WriteTo(conn)
|
||||
if err != nil {
|
||||
// identify this error as one that occurred during
|
||||
// dialing, which can be important when trying to
|
||||
|
|
Loading…
Reference in a new issue