ip_test.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. //go:build unit
  2. package ip
  3. import (
  4. "net/http/httptest"
  5. "testing"
  6. "github.com/gin-gonic/gin"
  7. "github.com/stretchr/testify/require"
  8. )
  9. func TestGetTrustedClientIPUsesGinClientIP(t *testing.T) {
  10. gin.SetMode(gin.TestMode)
  11. r := gin.New()
  12. require.NoError(t, r.SetTrustedProxies(nil))
  13. r.GET("/t", func(c *gin.Context) {
  14. c.String(200, GetTrustedClientIP(c))
  15. })
  16. w := httptest.NewRecorder()
  17. req := httptest.NewRequest("GET", "/t", nil)
  18. req.RemoteAddr = "9.9.9.9:12345"
  19. req.Header.Set("X-Forwarded-For", "1.2.3.4")
  20. req.Header.Set("X-Real-IP", "1.2.3.4")
  21. req.Header.Set("CF-Connecting-IP", "1.2.3.4")
  22. r.ServeHTTP(w, req)
  23. require.Equal(t, 200, w.Code)
  24. require.Equal(t, "9.9.9.9", w.Body.String())
  25. }
  26. func TestGetClientIPPreservesLegacyDockerForwardedHeaders(t *testing.T) {
  27. gin.SetMode(gin.TestMode)
  28. r := gin.New()
  29. require.NoError(t, r.SetTrustedProxies(nil))
  30. r.GET("/t", func(c *gin.Context) {
  31. c.String(200, GetClientIP(c))
  32. })
  33. w := httptest.NewRecorder()
  34. req := httptest.NewRequest("GET", "/t", nil)
  35. req.RemoteAddr = "192.168.32.1:12345"
  36. req.Header.Set("X-Forwarded-For", "10.0.0.2, 203.0.113.42")
  37. req.Header.Set("X-Real-IP", "192.168.32.1")
  38. r.ServeHTTP(w, req)
  39. require.Equal(t, 200, w.Code)
  40. require.Equal(t, "203.0.113.42", w.Body.String())
  41. }
  42. func TestCheckIPRestrictionWithCompiledRules(t *testing.T) {
  43. whitelist := CompileIPRules([]string{"10.0.0.0/8", "192.168.1.2"})
  44. blacklist := CompileIPRules([]string{"10.1.1.1"})
  45. allowed, reason := CheckIPRestrictionWithCompiledRules("10.2.3.4", whitelist, blacklist)
  46. require.True(t, allowed)
  47. require.Equal(t, "", reason)
  48. allowed, reason = CheckIPRestrictionWithCompiledRules("10.1.1.1", whitelist, blacklist)
  49. require.False(t, allowed)
  50. require.Equal(t, "access denied", reason)
  51. }
  52. func TestCheckIPRestrictionWithCompiledRules_InvalidWhitelistStillDenies(t *testing.T) {
  53. // 与旧实现保持一致:白名单有配置但全无效时,最终应拒绝访问。
  54. invalidWhitelist := CompileIPRules([]string{"not-a-valid-pattern"})
  55. allowed, reason := CheckIPRestrictionWithCompiledRules("8.8.8.8", invalidWhitelist, nil)
  56. require.False(t, allowed)
  57. require.Equal(t, "access denied", reason)
  58. }
  59. func TestGetSecurityClientIPSwitchEnabledUsesLegacyHeaders(t *testing.T) {
  60. gin.SetMode(gin.TestMode)
  61. r := gin.New()
  62. require.NoError(t, r.SetTrustedProxies(nil))
  63. r.GET("/t", func(c *gin.Context) {
  64. c.String(200, GetSecurityClientIP(c, true))
  65. })
  66. w := httptest.NewRecorder()
  67. req := httptest.NewRequest("GET", "/t", nil)
  68. req.RemoteAddr = "9.9.9.9:12345"
  69. req.Header.Set("X-Real-IP", "1.2.3.4")
  70. r.ServeHTTP(w, req)
  71. require.Equal(t, 200, w.Code)
  72. require.Equal(t, "1.2.3.4", w.Body.String())
  73. }
  74. func TestGetSecurityClientIPCustomHeaderPrecedenceAndFallback(t *testing.T) {
  75. gin.SetMode(gin.TestMode)
  76. tests := []struct {
  77. name string
  78. trustForward bool
  79. headers []string
  80. requestHeaders map[string]string
  81. want string
  82. }{
  83. {
  84. name: "configured order precedes built-ins",
  85. trustForward: true,
  86. headers: []string{"X-CDN-First", "X-CDN-Second"},
  87. requestHeaders: map[string]string{
  88. "X-CDN-First": "198.51.100.10",
  89. "X-CDN-Second": "203.0.113.20",
  90. "CF-Connecting-IP": "8.8.8.8",
  91. },
  92. want: "198.51.100.10",
  93. },
  94. {
  95. name: "comma candidates skip invalid and private values",
  96. trustForward: true,
  97. headers: []string{"X-CDN-First", "X-CDN-Second"},
  98. requestHeaders: map[string]string{
  99. "X-CDN-First": "not-an-ip, 10.0.0.8",
  100. "X-CDN-Second": "also-bad, 203.0.113.9",
  101. },
  102. want: "203.0.113.9",
  103. },
  104. {
  105. name: "legacy public header wins over custom private fallback",
  106. trustForward: true,
  107. headers: []string{"X-CDN-IP"},
  108. requestHeaders: map[string]string{
  109. "X-CDN-IP": "10.0.0.8",
  110. "X-Real-IP": "1.2.3.4",
  111. },
  112. want: "1.2.3.4",
  113. },
  114. {
  115. name: "custom private fallback retains configured precedence",
  116. trustForward: true,
  117. headers: []string{"X-CDN-IP"},
  118. requestHeaders: map[string]string{
  119. "X-CDN-IP": "10.0.0.8",
  120. "X-Real-IP": "192.168.1.4",
  121. },
  122. want: "10.0.0.8",
  123. },
  124. {
  125. name: "invalid custom value continues to built-ins",
  126. trustForward: true,
  127. headers: []string{"X-CDN-IP"},
  128. requestHeaders: map[string]string{
  129. "X-CDN-IP": "1.2.3.4:443",
  130. "CF-Connecting-IP": "4.4.4.4",
  131. },
  132. want: "4.4.4.4",
  133. },
  134. {
  135. name: "invalid legacy values continue to a valid forwarded address",
  136. trustForward: true,
  137. requestHeaders: map[string]string{
  138. "CF-Connecting-IP": "unknown",
  139. "X-Real-IP": "proxy.internal",
  140. "X-Forwarded-For": "also-invalid, 203.0.113.50",
  141. },
  142. want: "203.0.113.50",
  143. },
  144. {
  145. name: "all invalid legacy values fall back to the connection address",
  146. trustForward: true,
  147. requestHeaders: map[string]string{
  148. "CF-Connecting-IP": "unknown",
  149. "X-Real-IP": "proxy.internal",
  150. "X-Forwarded-For": "also-invalid",
  151. },
  152. want: "9.9.9.9",
  153. },
  154. {
  155. name: "disabled mode ignores custom and legacy headers",
  156. trustForward: false,
  157. headers: []string{"X-CDN-IP"},
  158. requestHeaders: map[string]string{
  159. "X-CDN-IP": "1.2.3.4",
  160. "X-Real-IP": "4.4.4.4",
  161. },
  162. want: "9.9.9.9",
  163. },
  164. }
  165. for _, test := range tests {
  166. t.Run(test.name, func(t *testing.T) {
  167. r := gin.New()
  168. require.NoError(t, r.SetTrustedProxies(nil))
  169. r.GET("/t", func(c *gin.Context) {
  170. SetForwardedIPSettings(c, test.trustForward, test.headers)
  171. c.String(200, GetSecurityClientIP(c, !test.trustForward))
  172. })
  173. w := httptest.NewRecorder()
  174. req := httptest.NewRequest("GET", "/t", nil)
  175. req.RemoteAddr = "9.9.9.9:12345"
  176. for name, value := range test.requestHeaders {
  177. req.Header.Set(name, value)
  178. }
  179. r.ServeHTTP(w, req)
  180. require.Equal(t, test.want, w.Body.String())
  181. })
  182. }
  183. }
  184. func TestGetSecurityClientIPSwitchDisabledUsesConfiguredTrustedProxy(t *testing.T) {
  185. gin.SetMode(gin.TestMode)
  186. r := gin.New()
  187. require.NoError(t, r.SetTrustedProxies([]string{"9.9.9.9"}))
  188. r.GET("/t", func(c *gin.Context) { c.String(200, GetSecurityClientIP(c, false)) })
  189. w := httptest.NewRecorder()
  190. req := httptest.NewRequest("GET", "/t", nil)
  191. req.RemoteAddr = "9.9.9.9:12345"
  192. req.Header.Set("X-Forwarded-For", "1.2.3.4")
  193. r.ServeHTTP(w, req)
  194. require.Equal(t, "1.2.3.4", w.Body.String())
  195. }
  196. func TestGetClientIPSwitchDisabledUsesTrustedProxyChain(t *testing.T) {
  197. gin.SetMode(gin.TestMode)
  198. r := gin.New()
  199. require.NoError(t, r.SetTrustedProxies(nil))
  200. r.GET("/t", func(c *gin.Context) {
  201. SetLegacyForwardedIPTrust(c, false)
  202. c.String(200, GetClientIP(c))
  203. })
  204. w := httptest.NewRecorder()
  205. req := httptest.NewRequest("GET", "/t", nil)
  206. req.RemoteAddr = "9.9.9.9:12345"
  207. req.Header.Set("X-Real-IP", "1.2.3.4")
  208. r.ServeHTTP(w, req)
  209. require.Equal(t, "9.9.9.9", w.Body.String())
  210. }
  211. func TestGetSecurityClientIPRequestSnapshotCopiesCustomHeaders(t *testing.T) {
  212. gin.SetMode(gin.TestMode)
  213. r := gin.New()
  214. require.NoError(t, r.SetTrustedProxies(nil))
  215. r.GET("/t", func(c *gin.Context) {
  216. headers := []string{"X-Original-IP"}
  217. SetForwardedIPSettings(c, true, headers)
  218. headers[0] = "X-Mutated-IP"
  219. c.String(200, GetSecurityClientIP(c, false))
  220. })
  221. w := httptest.NewRecorder()
  222. req := httptest.NewRequest("GET", "/t", nil)
  223. req.RemoteAddr = "9.9.9.9:12345"
  224. req.Header.Set("X-Original-IP", "1.2.3.4")
  225. req.Header.Set("X-Mutated-IP", "4.4.4.4")
  226. r.ServeHTTP(w, req)
  227. require.Equal(t, "1.2.3.4", w.Body.String())
  228. }
  229. func TestGetSecurityClientIPRequestSnapshotOverridesLiveFallback(t *testing.T) {
  230. gin.SetMode(gin.TestMode)
  231. tests := []struct {
  232. name string
  233. requestTrust bool
  234. fallbackTrust bool
  235. want string
  236. }{
  237. {name: "captured secure mode wins", requestTrust: false, fallbackTrust: true, want: "9.9.9.9"},
  238. {name: "captured compatibility mode wins", requestTrust: true, fallbackTrust: false, want: "1.2.3.4"},
  239. }
  240. for _, test := range tests {
  241. t.Run(test.name, func(t *testing.T) {
  242. r := gin.New()
  243. require.NoError(t, r.SetTrustedProxies(nil))
  244. r.GET("/t", func(c *gin.Context) {
  245. SetLegacyForwardedIPTrust(c, test.requestTrust)
  246. c.String(200, GetSecurityClientIP(c, test.fallbackTrust))
  247. })
  248. w := httptest.NewRecorder()
  249. req := httptest.NewRequest("GET", "/t", nil)
  250. req.RemoteAddr = "9.9.9.9:12345"
  251. req.Header.Set("X-Real-IP", "1.2.3.4")
  252. r.ServeHTTP(w, req)
  253. require.Equal(t, test.want, w.Body.String())
  254. })
  255. }
  256. }