mirror of https://github.com/gin-gonic/gin.git
TrustedProxies: Add default IPv6 support and refactor (#2967)
This commit is contained in:
parent
830a63d244
commit
0be805a675
52
context.go
52
context.go
|
@ -757,10 +757,14 @@ func (c *Context) ClientIP() string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteIP, trusted := c.RemoteIP()
|
// It also checks if the remoteIP is a trusted proxy or not.
|
||||||
|
// In order to perform this validation, it will see if the IP is contained within at least one of the CIDR blocks
|
||||||
|
// defined by Engine.SetTrustedProxies()
|
||||||
|
remoteIP := net.ParseIP(c.RemoteIP())
|
||||||
if remoteIP == nil {
|
if remoteIP == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
trusted := c.engine.isTrustedProxy(remoteIP)
|
||||||
|
|
||||||
if trusted && c.engine.ForwardedByClientIP && c.engine.RemoteIPHeaders != nil {
|
if trusted && c.engine.ForwardedByClientIP && c.engine.RemoteIPHeaders != nil {
|
||||||
for _, headerName := range c.engine.RemoteIPHeaders {
|
for _, headerName := range c.engine.RemoteIPHeaders {
|
||||||
|
@ -773,53 +777,13 @@ func (c *Context) ClientIP() string {
|
||||||
return remoteIP.String()
|
return remoteIP.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Engine) isTrustedProxy(ip net.IP) bool {
|
|
||||||
if e.trustedCIDRs != nil {
|
|
||||||
for _, cidr := range e.trustedCIDRs {
|
|
||||||
if cidr.Contains(ip) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoteIP parses the IP from Request.RemoteAddr, normalizes and returns the IP (without the port).
|
// RemoteIP parses the IP from Request.RemoteAddr, normalizes and returns the IP (without the port).
|
||||||
// It also checks if the remoteIP is a trusted proxy or not.
|
func (c *Context) RemoteIP() string {
|
||||||
// In order to perform this validation, it will see if the IP is contained within at least one of the CIDR blocks
|
|
||||||
// defined by Engine.SetTrustedProxies()
|
|
||||||
func (c *Context) RemoteIP() (net.IP, bool) {
|
|
||||||
ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr))
|
ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false
|
return ""
|
||||||
}
|
}
|
||||||
remoteIP := net.ParseIP(ip)
|
return ip
|
||||||
if remoteIP == nil {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
return remoteIP, c.engine.isTrustedProxy(remoteIP)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Engine) validateHeader(header string) (clientIP string, valid bool) {
|
|
||||||
if header == "" {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
items := strings.Split(header, ",")
|
|
||||||
for i := len(items) - 1; i >= 0; i-- {
|
|
||||||
ipStr := strings.TrimSpace(items[i])
|
|
||||||
ip := net.ParseIP(ipStr)
|
|
||||||
if ip == nil {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
// X-Forwarded-For is appended by proxy
|
|
||||||
// Check IPs in reverse order and stop when find untrusted proxy
|
|
||||||
if (i == 0) || (!e.isTrustedProxy(ip)) {
|
|
||||||
return ipStr, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContentType returns the Content-Type header of the request.
|
// ContentType returns the Content-Type header of the request.
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
|
@ -1404,6 +1405,11 @@ func TestContextClientIP(t *testing.T) {
|
||||||
// Tests exercising the TrustedProxies functionality
|
// Tests exercising the TrustedProxies functionality
|
||||||
resetContextForClientIPTests(c)
|
resetContextForClientIPTests(c)
|
||||||
|
|
||||||
|
// IPv6 support
|
||||||
|
c.Request.RemoteAddr = "[::1]:12345"
|
||||||
|
assert.Equal(t, "20.20.20.20", c.ClientIP())
|
||||||
|
|
||||||
|
resetContextForClientIPTests(c)
|
||||||
// No trusted proxies
|
// No trusted proxies
|
||||||
_ = c.engine.SetTrustedProxies([]string{})
|
_ = c.engine.SetTrustedProxies([]string{})
|
||||||
c.engine.RemoteIPHeaders = []string{"X-Forwarded-For"}
|
c.engine.RemoteIPHeaders = []string{"X-Forwarded-For"}
|
||||||
|
@ -1500,6 +1506,7 @@ func resetContextForClientIPTests(c *Context) {
|
||||||
c.Request.Header.Set("CF-Connecting-IP", "60.60.60.60")
|
c.Request.Header.Set("CF-Connecting-IP", "60.60.60.60")
|
||||||
c.Request.RemoteAddr = " 40.40.40.40:42123 "
|
c.Request.RemoteAddr = " 40.40.40.40:42123 "
|
||||||
c.engine.TrustedPlatform = ""
|
c.engine.TrustedPlatform = ""
|
||||||
|
c.engine.trustedCIDRs = defaultTrustedCIDRs
|
||||||
c.engine.AppEngine = false
|
c.engine.AppEngine = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2051,7 +2058,8 @@ func TestRemoteIPFail(t *testing.T) {
|
||||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
c.Request, _ = http.NewRequest("POST", "/", nil)
|
c.Request, _ = http.NewRequest("POST", "/", nil)
|
||||||
c.Request.RemoteAddr = "[:::]:80"
|
c.Request.RemoteAddr = "[:::]:80"
|
||||||
ip, trust := c.RemoteIP()
|
ip := net.ParseIP(c.RemoteIP())
|
||||||
|
trust := c.engine.isTrustedProxy(ip)
|
||||||
assert.Nil(t, ip)
|
assert.Nil(t, ip)
|
||||||
assert.False(t, trust)
|
assert.False(t, trust)
|
||||||
}
|
}
|
||||||
|
|
51
gin.go
51
gin.go
|
@ -11,7 +11,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"reflect"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
@ -28,7 +27,16 @@ var (
|
||||||
|
|
||||||
var defaultPlatform string
|
var defaultPlatform string
|
||||||
|
|
||||||
var defaultTrustedCIDRs = []*net.IPNet{{IP: net.IP{0x0, 0x0, 0x0, 0x0}, Mask: net.IPMask{0x0, 0x0, 0x0, 0x0}}} // 0.0.0.0/0
|
var defaultTrustedCIDRs = []*net.IPNet{
|
||||||
|
{ // 0.0.0.0/0 (IPv4)
|
||||||
|
IP: net.IP{0x0, 0x0, 0x0, 0x0},
|
||||||
|
Mask: net.IPMask{0x0, 0x0, 0x0, 0x0},
|
||||||
|
},
|
||||||
|
{ // ::/0 (IPv6)
|
||||||
|
IP: net.IP{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
|
||||||
|
Mask: net.IPMask{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// HandlerFunc defines the handler used by gin middleware as return value.
|
// HandlerFunc defines the handler used by gin middleware as return value.
|
||||||
type HandlerFunc func(*Context)
|
type HandlerFunc func(*Context)
|
||||||
|
@ -399,9 +407,9 @@ func (engine *Engine) SetTrustedProxies(trustedProxies []string) error {
|
||||||
return engine.parseTrustedProxies()
|
return engine.parseTrustedProxies()
|
||||||
}
|
}
|
||||||
|
|
||||||
// isUnsafeTrustedProxies compares Engine.trustedCIDRs and defaultTrustedCIDRs, it's not safe if equal (returns true)
|
// isUnsafeTrustedProxies checks if Engine.trustedCIDRs contains all IPs, it's not safe if it has (returns true)
|
||||||
func (engine *Engine) isUnsafeTrustedProxies() bool {
|
func (engine *Engine) isUnsafeTrustedProxies() bool {
|
||||||
return reflect.DeepEqual(engine.trustedCIDRs, defaultTrustedCIDRs)
|
return engine.isTrustedProxy(net.ParseIP("0.0.0.0")) || engine.isTrustedProxy(net.ParseIP("::"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseTrustedProxies parse Engine.trustedProxies to Engine.trustedCIDRs
|
// parseTrustedProxies parse Engine.trustedProxies to Engine.trustedCIDRs
|
||||||
|
@ -411,6 +419,41 @@ func (engine *Engine) parseTrustedProxies() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isTrustedProxy will check whether the IP address is included in the trusted list according to Engine.trustedCIDRs
|
||||||
|
func (engine *Engine) isTrustedProxy(ip net.IP) bool {
|
||||||
|
if engine.trustedCIDRs == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, cidr := range engine.trustedCIDRs {
|
||||||
|
if cidr.Contains(ip) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateHeader will parse X-Forwarded-For header and return the trusted client IP address
|
||||||
|
func (engine *Engine) validateHeader(header string) (clientIP string, valid bool) {
|
||||||
|
if header == "" {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
items := strings.Split(header, ",")
|
||||||
|
for i := len(items) - 1; i >= 0; i-- {
|
||||||
|
ipStr := strings.TrimSpace(items[i])
|
||||||
|
ip := net.ParseIP(ipStr)
|
||||||
|
if ip == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// X-Forwarded-For is appended by proxy
|
||||||
|
// Check IPs in reverse order and stop when find untrusted proxy
|
||||||
|
if (i == 0) || (!engine.isTrustedProxy(ip)) {
|
||||||
|
return ipStr, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
// parseIP parse a string representation of an IP and returns a net.IP with the
|
// parseIP parse a string representation of an IP and returns a net.IP with the
|
||||||
// minimum byte representation or nil if input is invalid.
|
// minimum byte representation or nil if input is invalid.
|
||||||
func parseIP(ip string) net.IP {
|
func parseIP(ip string) net.IP {
|
||||||
|
|
Loading…
Reference in New Issue