-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
proxyprotocol: use github.com/pires/go-proxyproto #5915
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
fbfcb35
fb6589f
67e567b
bc04e74
8facd51
3a63b42
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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/[email protected]#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 | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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/[email protected]#Policy | ||
| const ( | ||
| // IGNORE address from PROXY header, but accept connection | ||
| PolicyIGNORE Policy = iota | ||
| // USE address from PROXY header | ||
| PolicyUSE | ||
|
Comment on lines
+17
to
+18
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't do Caddy and I only do the bare minimum of Go. This came to my attention via haproxy/haproxy#2450 and if I understand the code correctly, the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I also find
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the user knows their Caddy instance is accessible on public networks, they should configure
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
We're only concerned with HTTP and TLS in this case, so that's not a concern. We have a different implementation for https://github.com/mholt/caddy-l4 which allows any TCP/UDP traffic. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
My understanding is that the Now consider the following:
If instead the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry for coming late to this discussion. The long-winded discussion is getting me lost. It's also hard to follow as it's going on multiple links covering multiple related yet separate subjects and multiple technologies.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Summary
I believe I summarized the whole discussion here: mholt/caddy-l4#176 (comment)
with additional clarification in a collapsed details block:
I then expand a bit on policy concerns, but wrap it up with:
I don't think I got around to raising that bug report. I had been preparing one but my browser crashed. Traefik exampleThis was months ago so I can't quite recall all the details and don't have time to refresh my memory 😓 (below is based off recall of past discussion, I may slip up) The default with Traefik for Layer 4 traffic forwarding (the only concern here) is to blindly forward IIRC.
For clarity with the Traefik example:
It's similar for Caddy, but I use Traefik due to the Layer 4 support for both inbound and outbound, as that is where PROXY protocol applies. Layer 4 is a little bit muddy with Caddy IIRC which is presently more oriented around Layer 7 for the most part (unless using the L4 plugin). Keep
|
||
| // 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. | ||
|
WeidiDeng marked this conversation as resolved.
|
||
| 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") | ||






Uh oh!
There was an error while loading. Please reload this page.