@@ -173,6 +173,19 @@ type Server struct {
173
173
// remote IP address.
174
174
ClientIPHeaders []string `json:"client_ip_headers,omitempty"`
175
175
176
+ // If greater than zero, enables strict ClientIPHeaders
177
+ // (default X-Forwarded-For) parsing. If enabled, the
178
+ // ClientIPHeaders will be parsed from right to left, and
179
+ // the first value that is both valid and doesn't match the
180
+ // trusted proxy list will be used as client IP. If zero,
181
+ // the ClientIPHeaders will be parsed from left to right,
182
+ // and the first value that is a valid IP address will be
183
+ // used as client IP.
184
+ //
185
+ // This depends on `trusted_proxies` being configured.
186
+ // This option is disabled by default.
187
+ TrustedProxiesStrict int `json:"trusted_proxies_strict,omitempty"`
188
+
176
189
// Enables access logging and configures how access logs are handled
177
190
// in this server. To minimally enable access logs, simply set this
178
191
// to a non-null, empty struct.
@@ -839,17 +852,28 @@ func determineTrustedProxy(r *http.Request, s *Server) (bool, string) {
839
852
if s .trustedProxies == nil {
840
853
return false , ipAddr .String ()
841
854
}
842
- for _ , ipRange := range s .trustedProxies .GetIPRanges (r ) {
843
- if ipRange .Contains (ipAddr ) {
844
- // We trust the proxy, so let's try to
845
- // determine the real client IP
846
- return true , trustedRealClientIP (r , s .ClientIPHeaders , ipAddr .String ())
855
+
856
+ if isTrustedClientIP (ipAddr , s .trustedProxies .GetIPRanges (r )) {
857
+ if s .TrustedProxiesStrict > 0 {
858
+ return true , strictUntrustedClientIp (r , s .ClientIPHeaders , s .trustedProxies .GetIPRanges (r ), ipAddr .String ())
847
859
}
860
+ return true , trustedRealClientIP (r , s .ClientIPHeaders , ipAddr .String ())
848
861
}
849
862
850
863
return false , ipAddr .String ()
851
864
}
852
865
866
+ // isTrustedClientIP returns true if the given IP address is
867
+ // in the list of trusted IP ranges.
868
+ func isTrustedClientIP (ipAddr netip.Addr , trusted []netip.Prefix ) bool {
869
+ for _ , ipRange := range trusted {
870
+ if ipRange .Contains (ipAddr ) {
871
+ return true
872
+ }
873
+ }
874
+ return false
875
+ }
876
+
853
877
// trustedRealClientIP finds the client IP from the request assuming it is
854
878
// from a trusted client. If there is no client IP headers, then the
855
879
// direct remote address is returned. If there are client IP headers,
@@ -884,6 +908,29 @@ func trustedRealClientIP(r *http.Request, headers []string, clientIP string) str
884
908
return clientIP
885
909
}
886
910
911
+ // strictUntrustedClientIp iterates through the list of client IP headers,
912
+ // parses them from right-to-left, and returns the first valid IP address
913
+ // that is untrusted. If no valid IP address is found, then the direct
914
+ // remote address is returned.
915
+ func strictUntrustedClientIp (r * http.Request , headers []string , trusted []netip.Prefix , clientIP string ) string {
916
+ for _ , headerName := range headers {
917
+ ips := strings .Split (strings .Join (r .Header .Values (headerName ), "," ), "," )
918
+
919
+ for i := len (ips ) - 1 ; i >= 0 ; i -- {
920
+ ip , _ , _ := strings .Cut (strings .TrimSpace (ips [i ]), "%" )
921
+ ipAddr , err := netip .ParseAddr (ip )
922
+ if err != nil {
923
+ continue
924
+ }
925
+ if ! isTrustedClientIP (ipAddr , trusted ) {
926
+ return ipAddr .String ()
927
+ }
928
+ }
929
+ }
930
+
931
+ return clientIP
932
+ }
933
+
887
934
// cloneURL makes a copy of r.URL and returns a
888
935
// new value that doesn't reference the original.
889
936
func cloneURL (from , to * url.URL ) {
0 commit comments