mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-26 13:43:47 +03: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/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
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/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=
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
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"
|
"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
|
||||||
|
|
Loading…
Reference in a new issue