@marcel91200 / caddy Mirror · read-only / commits / deedf8ab
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 14, 05:15 UTC

caddyhttp: Optionally use forwarded IP for remote_ip matcher

The remote_ip matcher was reading the X-Forwarded-For header by default, but this behavior was not documented in anything that was released. This is also a less secure default, as it is trivially easy to spoof request headers. Reading IPs from that header should be optional, and it should not be the default. This is technically a breaking change, but anyone relying on the undocumented behavior was just doing so by coincidence/luck up to this point since it was never in any released documentation. We'll still add a mention in the release notes about this.

Matthew Holt committed Dec 10, 2020 at 16:09 UTC deedf8abb036bdc096360bd6f06df17a6cff9799
1 file changed +22 -13
modules/caddyhttp/matchers.go
+22 -13
@@ -105,12 +105,16 @@ type (
105 MatchProtocol string
106
107 // MatchRemoteIP matches requests by client IP (or CIDR range).
108 - // If the X-Forwarded-For header is set, the first IP in that list
109 - // is used as the reference IP; otherwise, the remote IP of the
110 - // connection is the reference.
108 MatchRemoteIP struct {
109 + // The IPs or CIDR ranges to match.
110 Ranges []string `json:"ranges,omitempty"`
111
112 + // If true, prefer the first IP in the request's X-Forwarded-For
113 + // header, if present, rather than the immediate peer's IP, as
114 + // the reference IP against which to match. Note that it is easy
115 + // to spoof request headers. Default: false
116 + Forwarded bool `json:"forwarded,omitempty"`
117 +
118 cidrs []*net.IPNet
119 logger *zap.Logger
120 }
@@ -795,7 +799,16 @@ func (MatchRemoteIP) CaddyModule() caddy.ModuleInfo {
799 // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
800 func (m *MatchRemoteIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
801 for d.Next() {
798 - m.Ranges = append(m.Ranges, d.RemainingArgs()...)
802 + for d.NextArg() {
803 + if d.Val() == "forwarded" {
804 + if len(m.Ranges) > 0 {
805 + return d.Err("if used, 'forwarded' must be first argument")
806 + }
807 + m.Forwarded = true
808 + continue
809 + }
810 + m.Ranges = append(m.Ranges, d.Val())
811 + }
812 if d.NextBlock(0) {
813 return d.Err("malformed remote_ip matcher: blocks are not supported")
814 }
@@ -829,24 +842,20 @@ func (m *MatchRemoteIP) Provision(ctx caddy.Context) error {
842 }
843
844 func (m MatchRemoteIP) getClientIP(r *http.Request) (net.IP, error) {
832 - var remote string
833 - if fwdFor := r.Header.Get("X-Forwarded-For"); fwdFor != "" {
834 - remote = strings.TrimSpace(strings.Split(fwdFor, ",")[0])
835 - }
836 - if remote == "" {
837 - remote = r.RemoteAddr
845 + remote := r.RemoteAddr
846 + if m.Forwarded {
847 + if fwdFor := r.Header.Get("X-Forwarded-For"); fwdFor != "" {
848 + remote = strings.TrimSpace(strings.Split(fwdFor, ",")[0])
849 + }
850 }
839 -
851 ipStr, _, err := net.SplitHostPort(remote)
852 if err != nil {
853 ipStr = remote // OK; probably didn't have a port
854 }
844 -
855 ip := net.ParseIP(ipStr)
856 if ip == nil {
857 return nil, fmt.Errorf("invalid client IP address: %s", ipStr)
858 }
849 -
859 return ip, nil
860 }
861