Skip to content

Commit

Permalink
add config options for Deny CIDR and fallback policy
Browse files Browse the repository at this point in the history
  • Loading branch information
mohammed90 committed Nov 1, 2023
1 parent 67e567b commit bc04e74
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 8 deletions.
53 changes: 46 additions & 7 deletions modules/caddyhttp/proxyprotocol/listenerwrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,59 @@ type ListenerWrapper struct {
// Allow is an optional list of CIDR ranges to
// allow/require PROXY headers from.
Allow []string `json:"allow,omitempty"`
allow []*net.IPNet

policies []goproxy.PolicyFunc
// Denby is an optional list of CIDR ranges to
// deny PROXY headers from.
Deny []string `json:"deny,omitempty"`
deny []*net.IPNet

// 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 {
if len(pp.Allow) > 0 {
allowlist, err := goproxy.LaxWhiteListPolicy(pp.Allow)
for _, cidr := range pp.Allow {
_, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
return err
}
pp.policies = append(pp.policies, allowlist)
pp.allow = append(pp.allow, ipnet)
}
for _, cidr := range pp.Deny {
_, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
return err
}
pp.deny = append(pp.deny, ipnet)
}
pp.policy = func(upstream net.Addr) (goproxy.Policy, error) {
ret := pp.FallbackPolicy
host, _, err := net.SplitHostPort(upstream.String())
if err != nil {
return goproxy.REJECT, err
}
ip := net.ParseIP(host)
if ip == 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
}
Expand All @@ -59,8 +100,6 @@ func (pp *ListenerWrapper) WrapListener(l net.Listener) net.Listener {
Listener: l,
ReadHeaderTimeout: time.Duration(pp.Timeout),
}
if len(pp.policies) > 0 {
pl.Policy = pp.policies[0]
}
pl.Policy = pp.policy
return pl
}
14 changes: 13 additions & 1 deletion modules/caddyhttp/proxyprotocol/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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()
}
Expand Down
82 changes: 82 additions & 0 deletions modules/caddyhttp/proxyprotocol/policy.go
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/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")

0 comments on commit bc04e74

Please sign in to comment.