Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 12, 19:30 UTC
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>
Mohammed Al Sahaf committed
Dec 13, 2023 at 19:07 UTC
dc12bd974367b1c15e6b8f3c6b446a208ac3546f
6 files changed
+176
-42
go.mod
+1
-1
@@ -14,7 +14,6 @@ require (
14
github.com/google/uuid v1.3.1
15
github.com/klauspost/compress v1.17.0
16
github.com/klauspost/cpuid/v2 v2.2.5
17
- github.com/mastercactapus/proxyprotocol v0.0.4
17
github.com/mholt/acmez v1.2.0
18
github.com/prometheus/client_golang v1.15.1
19
github.com/quic-go/quic-go v0.40.0
@@ -117,6 +116,7 @@ require (
116
github.com/mitchellh/copystructure v1.2.0 // indirect
117
github.com/mitchellh/go-ps v1.0.0 // indirect
118
github.com/mitchellh/reflectwalk v1.0.2 // indirect
119
+ github.com/pires/go-proxyproto v0.7.0
120
github.com/pkg/errors v0.9.1 // indirect
121
github.com/prometheus/client_model v0.4.0 // indirect
122
github.com/prometheus/common v0.42.0 // indirect
go.sum
+2
-2
@@ -352,8 +352,6 @@ github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0Q
352
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
353
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
354
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
355
-github.com/mastercactapus/proxyprotocol v0.0.4 h1:qSY75IZF30ZqIU9iW1ip3I7gTnm8wRAnGWqPxCBVgq0=
356
-github.com/mastercactapus/proxyprotocol v0.0.4/go.mod h1:X8FRVEDZz9FkrIoL4QYTBF4Ka4ELwTv0sah0/5NxCPw=
355
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
356
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
357
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
431
github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU=
432
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
433
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
434
+github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
435
+github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
436
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
437
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
438
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
modules/caddyhttp/proxyprotocol/listenerwrapper.go
+57
-15
@@ -15,11 +15,11 @@
15
package proxyprotocol
16
17
import (
18
- "fmt"
18
"net"
19
+ "net/netip"
20
"time"
21
22
- "github.com/mastercactapus/proxyprotocol"
22
+ goproxy "github.com/pires/go-proxyproto"
23
24
"github.com/caddyserver/caddy/v2"
25
)
@@ -38,32 +38,74 @@ type ListenerWrapper struct {
38
// Allow is an optional list of CIDR ranges to
39
// allow/require PROXY headers from.
40
Allow []string `json:"allow,omitempty"`
41
+ allow []netip.Prefix
42
42
- rules []proxyprotocol.Rule
43
+ // Denby is an optional list of CIDR ranges to
44
+ // deny PROXY headers from.
45
+ Deny []string `json:"deny,omitempty"`
46
+ deny []netip.Prefix
47
+
48
+ // Accepted values are: ignore, use, reject, require, skip
49
+ // default: ignore
50
+ // Policy definitions are here: https://pkg.go.dev/github.com/pires/go-proxyproto@v0.7.0#Policy
51
+ FallbackPolicy Policy `json:"fallback_policy,omitempty"`
52
+
53
+ policy goproxy.PolicyFunc
54
}
55
56
// Provision sets up the listener wrapper.
57
func (pp *ListenerWrapper) Provision(ctx caddy.Context) error {
47
- rules := make([]proxyprotocol.Rule, 0, len(pp.Allow))
48
- for _, s := range pp.Allow {
49
- _, n, err := net.ParseCIDR(s)
58
+ for _, cidr := range pp.Allow {
59
+ ipnet, err := netip.ParsePrefix(cidr)
60
if err != nil {
51
- return fmt.Errorf("invalid subnet '%s': %w", s, err)
61
+ return err
62
}
53
- rules = append(rules, proxyprotocol.Rule{
54
- Timeout: time.Duration(pp.Timeout),
55
- Subnet: n,
56
- })
63
+ pp.allow = append(pp.allow, ipnet)
64
}
65
+ for _, cidr := range pp.Deny {
66
+ ipnet, err := netip.ParsePrefix(cidr)
67
+ if err != nil {
68
+ return err
69
+ }
70
+ pp.deny = append(pp.deny, ipnet)
71
+ }
72
+ pp.policy = func(upstream net.Addr) (goproxy.Policy, error) {
73
+ // trust unix sockets
74
+ if network := upstream.Network(); caddy.IsUnixNetwork(network) {
75
+ return goproxy.USE, nil
76
+ }
77
+ ret := pp.FallbackPolicy
78
+ host, _, err := net.SplitHostPort(upstream.String())
79
+ if err != nil {
80
+ return goproxy.REJECT, err
81
+ }
82
59
- pp.rules = rules
60
-
83
+ ip, err := netip.ParseAddr(host)
84
+ if err != nil {
85
+ return goproxy.REJECT, err
86
+ }
87
+ for _, ipnet := range pp.deny {
88
+ if ipnet.Contains(ip) {
89
+ return goproxy.REJECT, nil
90
+ }
91
+ }
92
+ for _, ipnet := range pp.allow {
93
+ if ipnet.Contains(ip) {
94
+ ret = PolicyUSE
95
+ break
96
+ }
97
+ }
98
+ return policyToGoProxyPolicy[ret], nil
99
+ }
100
return nil
101
}
102
103
// WrapListener adds PROXY protocol support to the listener.
104
func (pp *ListenerWrapper) WrapListener(l net.Listener) net.Listener {
66
- pl := proxyprotocol.NewListener(l, time.Duration(pp.Timeout))
67
- pl.SetFilter(pp.rules)
105
+ pl := &goproxy.Listener{
106
+ Listener: l,
107
+ ReadHeaderTimeout: time.Duration(pp.Timeout),
108
+ }
109
+ pl.Policy = pp.policy
110
return pl
111
}
modules/caddyhttp/proxyprotocol/module.go
+13
-1
@@ -35,6 +35,8 @@ func (ListenerWrapper) CaddyModule() caddy.ModuleInfo {
35
// proxy_protocol {
36
// timeout <duration>
37
// allow <IPs...>
38
+// deny <IPs...>
39
+// fallback_policy <policy>
40
// }
41
func (w *ListenerWrapper) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
42
for d.Next() {
@@ -57,7 +59,17 @@ func (w *ListenerWrapper) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
59
60
case "allow":
61
w.Allow = append(w.Allow, d.RemainingArgs()...)
60
-
62
+ case "deny":
63
+ w.Deny = append(w.Deny, d.RemainingArgs()...)
64
+ case "fallback_policy":
65
+ if !d.NextArg() {
66
+ return d.ArgErr()
67
+ }
68
+ p, err := parsePolicy(d.Val())
69
+ if err != nil {
70
+ return d.WrapErr(err)
71
+ }
72
+ w.FallbackPolicy = p
73
default:
74
return d.ArgErr()
75
}
modules/caddyhttp/proxyprotocol/policy.go
new
+82
@@ -0,0 +1,82 @@
1
+package proxyprotocol
2
+
3
+import (
4
+ "errors"
5
+ "fmt"
6
+ "strings"
7
+
8
+ goproxy "github.com/pires/go-proxyproto"
9
+)
10
+
11
+type Policy int
12
+
13
+// as defined in: https://pkg.go.dev/github.com/pires/go-proxyproto@v0.7.0#Policy
14
+const (
15
+ // IGNORE address from PROXY header, but accept connection
16
+ PolicyIGNORE Policy = iota
17
+ // USE address from PROXY header
18
+ PolicyUSE
19
+ // REJECT connection when PROXY header is sent
20
+ // Note: even though the first read on the connection returns an error if
21
+ // a PROXY header is present, subsequent reads do not. It is the task of
22
+ // the code using the connection to handle that case properly.
23
+ PolicyREJECT
24
+ // REQUIRE connection to send PROXY header, reject if not present
25
+ // Note: even though the first read on the connection returns an error if
26
+ // a PROXY header is not present, subsequent reads do not. It is the task
27
+ // of the code using the connection to handle that case properly.
28
+ PolicyREQUIRE
29
+ // SKIP accepts a connection without requiring the PROXY header
30
+ // Note: an example usage can be found in the SkipProxyHeaderForCIDR
31
+ // function.
32
+ PolicySKIP
33
+)
34
+
35
+var policyToGoProxyPolicy = map[Policy]goproxy.Policy{
36
+ PolicyUSE: goproxy.USE,
37
+ PolicyIGNORE: goproxy.IGNORE,
38
+ PolicyREJECT: goproxy.REJECT,
39
+ PolicyREQUIRE: goproxy.REQUIRE,
40
+ PolicySKIP: goproxy.SKIP,
41
+}
42
+
43
+var policyMap = map[Policy]string{
44
+ PolicyUSE: "USE",
45
+ PolicyIGNORE: "IGNORE",
46
+ PolicyREJECT: "REJECT",
47
+ PolicyREQUIRE: "REQUIRE",
48
+ PolicySKIP: "SKIP",
49
+}
50
+
51
+var policyMapRev = map[string]Policy{
52
+ "USE": PolicyUSE,
53
+ "IGNORE": PolicyIGNORE,
54
+ "REJECT": PolicyREJECT,
55
+ "REQUIRE": PolicyREQUIRE,
56
+ "SKIP": PolicySKIP,
57
+}
58
+
59
+// MarshalText implements the text marshaller method.
60
+func (x Policy) MarshalText() ([]byte, error) {
61
+ return []byte(policyMap[x]), nil
62
+}
63
+
64
+// UnmarshalText implements the text unmarshaller method.
65
+func (x *Policy) UnmarshalText(text []byte) error {
66
+ name := string(text)
67
+ tmp, err := parsePolicy(name)
68
+ if err != nil {
69
+ return err
70
+ }
71
+ *x = tmp
72
+ return nil
73
+}
74
+
75
+func parsePolicy(name string) (Policy, error) {
76
+ if x, ok := policyMapRev[strings.ToUpper(name)]; ok {
77
+ return x, nil
78
+ }
79
+ return Policy(0), fmt.Errorf("%s is %w", name, errInvalidPolicy)
80
+}
81
+
82
+var errInvalidPolicy = errors.New("invalid policy")
modules/caddyhttp/reverseproxy/httptransport.go
+21
-23
@@ -28,7 +28,7 @@ import (
28
"strings"
29
"time"
30
31
- "github.com/mastercactapus/proxyprotocol"
31
+ "github.com/pires/go-proxyproto"
32
"go.uber.org/zap"
33
"golang.org/x/net/http2"
34
@@ -207,44 +207,42 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e
207
if !ok {
208
return nil, fmt.Errorf("failed to get proxy protocol info from context")
209
}
210
-
211
- // The src and dst have to be of the some address family. As we don't know the original
212
- // dst address (it's kind of impossible to know) and this address is generelly of very
210
+ header := proxyproto.Header{
211
+ SourceAddr: &net.TCPAddr{
212
+ IP: proxyProtocolInfo.AddrPort.Addr().AsSlice(),
213
+ Port: int(proxyProtocolInfo.AddrPort.Port()),
214
+ Zone: proxyProtocolInfo.AddrPort.Addr().Zone(),
215
+ },
216
+ }
217
+ // The src and dst have to be of the same address family. As we don't know the original
218
+ // dst address (it's kind of impossible to know) and this address is generally of very
219
// little interest, we just set it to all zeros.
214
- var destIP net.IP
220
switch {
221
case proxyProtocolInfo.AddrPort.Addr().Is4():
217
- destIP = net.IPv4zero
222
+ header.TransportProtocol = proxyproto.TCPv4
223
+ header.DestinationAddr = &net.TCPAddr{
224
+ IP: net.IPv4zero,
225
+ }
226
case proxyProtocolInfo.AddrPort.Addr().Is6():
219
- destIP = net.IPv6zero
227
+ header.TransportProtocol = proxyproto.TCPv6
228
+ header.DestinationAddr = &net.TCPAddr{
229
+ IP: net.IPv6zero,
230
+ }
231
default:
232
return nil, fmt.Errorf("unexpected remote addr type in proxy protocol info")
233
}
234
224
- // TODO: We should probably migrate away from net.IP to use netip.Addr,
225
- // but due to the upstream dependency, we can't do that yet.
235
switch h.ProxyProtocol {
236
case "v1":
228
- header := proxyprotocol.HeaderV1{
229
- SrcIP: net.IP(proxyProtocolInfo.AddrPort.Addr().AsSlice()),
230
- SrcPort: int(proxyProtocolInfo.AddrPort.Port()),
231
- DestIP: destIP,
232
- DestPort: 0,
233
- }
237
+ header.Version = 1
238
caddyCtx.Logger().Debug("sending proxy protocol header v1", zap.Any("header", header))
235
- _, err = header.WriteTo(conn)
239
case "v2":
237
- header := proxyprotocol.HeaderV2{
238
- Command: proxyprotocol.CmdProxy,
239
- Src: &net.TCPAddr{IP: net.IP(proxyProtocolInfo.AddrPort.Addr().AsSlice()), Port: int(proxyProtocolInfo.AddrPort.Port())},
240
- Dest: &net.TCPAddr{IP: destIP, Port: 0},
241
- }
240
+ header.Version = 2
241
caddyCtx.Logger().Debug("sending proxy protocol header v2", zap.Any("header", header))
243
- _, err = header.WriteTo(conn)
242
default:
243
return nil, fmt.Errorf("unexpected proxy protocol version")
244
}
247
-
245
+ _, err = header.WriteTo(conn)
246
if err != nil {
247
// identify this error as one that occurred during
248
// dialing, which can be important when trying to