1
0

netsafe_test.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. package netsafe
  2. import (
  3. "context"
  4. "net"
  5. "strings"
  6. "testing"
  7. )
  8. func TestIsBlockedIP(t *testing.T) {
  9. cases := []struct {
  10. ip string
  11. want bool
  12. }{
  13. {"127.0.0.1", true},
  14. {"::1", true},
  15. {"10.0.0.5", true},
  16. {"172.16.0.1", true},
  17. {"192.168.1.1", true},
  18. {"169.254.0.1", true},
  19. {"0.0.0.0", true},
  20. {"::", true},
  21. {"8.8.8.8", false},
  22. {"1.1.1.1", false},
  23. {"2606:4700:4700::1111", false},
  24. // IPv6 transition prefixes tunnel an arbitrary IPv4 destination that
  25. // Go's net.IP predicates do not see through (GHSA-cfpf-wmjp-gh6c).
  26. {"2002:7f00:0001::1", true}, // 6to4 -> 127.0.0.1
  27. {"2002:a9fe:a9fe::1", true}, // 6to4 -> 169.254.169.254
  28. {"64:ff9b::7f00:1", true}, // NAT64 well-known -> 127.0.0.1
  29. {"64:ff9b::a9fe:a9fe", true}, // NAT64 well-known -> 169.254.169.254
  30. {"64:ff9b:1::a9fe:a9fe", true}, // NAT64 local-use
  31. {"2001:0:dead:beef::80ff:fffe", true}, // Teredo -> 127.0.0.1
  32. {"100.64.0.1", true}, // CGNAT
  33. {"::ffff:100.64.0.1", true}, // CGNAT via 4-in-6
  34. {"fec0::1", true}, // site-local
  35. {"64:ff9b::8.8.8.8", false}, // NAT64 to a public host stays reachable
  36. {"2001:db8::1", false}, // documentation prefix is not Teredo
  37. }
  38. for _, c := range cases {
  39. t.Run(c.ip, func(t *testing.T) {
  40. ip := net.ParseIP(c.ip)
  41. if ip == nil {
  42. t.Fatalf("could not parse %q", c.ip)
  43. }
  44. if got := IsBlockedIP(ip); got != c.want {
  45. t.Fatalf("IsBlockedIP(%s) = %v, want %v", c.ip, got, c.want)
  46. }
  47. })
  48. }
  49. }
  50. func TestAllowPrivateFromContext_Default(t *testing.T) {
  51. if AllowPrivateFromContext(context.Background()) {
  52. t.Fatal("default context should report AllowPrivate=false")
  53. }
  54. }
  55. func TestAllowPrivateFromContext_RoundTrip(t *testing.T) {
  56. ctx := ContextWithAllowPrivate(context.Background(), true)
  57. if !AllowPrivateFromContext(ctx) {
  58. t.Fatal("expected AllowPrivate=true after ContextWithAllowPrivate(true)")
  59. }
  60. ctx = ContextWithAllowPrivate(ctx, false)
  61. if AllowPrivateFromContext(ctx) {
  62. t.Fatal("expected AllowPrivate=false after overriding with false")
  63. }
  64. }
  65. func TestNormalizeHost_Valid(t *testing.T) {
  66. cases := []struct {
  67. in string
  68. want string
  69. }{
  70. {"example.com", "example.com"},
  71. {" example.com ", "example.com"},
  72. {"a.b.c.example.com", "a.b.c.example.com"},
  73. {"10.0.0.1", "10.0.0.1"},
  74. {"[2606:4700:4700::1111]", "2606:4700:4700::1111"},
  75. {"2606:4700:4700::1111", "2606:4700:4700::1111"},
  76. }
  77. for _, c := range cases {
  78. t.Run(c.in, func(t *testing.T) {
  79. got, err := NormalizeHost(c.in)
  80. if err != nil {
  81. t.Fatalf("NormalizeHost(%q) returned error: %v", c.in, err)
  82. }
  83. if !strings.EqualFold(got, c.want) {
  84. t.Fatalf("NormalizeHost(%q) = %q, want %q", c.in, got, c.want)
  85. }
  86. })
  87. }
  88. }
  89. func TestNormalizeHost_Invalid(t *testing.T) {
  90. cases := []string{
  91. "",
  92. " ",
  93. "-leading-dash.com",
  94. "trailing-dash-.com",
  95. "bad host with spaces",
  96. "under_score.example.com",
  97. "exa$mple.com",
  98. strings.Repeat("a", 254),
  99. }
  100. for _, in := range cases {
  101. t.Run(in, func(t *testing.T) {
  102. if _, err := NormalizeHost(in); err == nil {
  103. t.Fatalf("NormalizeHost(%q) expected error, got nil", in)
  104. }
  105. })
  106. }
  107. }
  108. func TestSSRFGuardedDialContext_BlocksLiteralPrivateIP(t *testing.T) {
  109. _, err := SSRFGuardedDialContext(context.Background(), "tcp", "127.0.0.1:1")
  110. if err == nil {
  111. t.Fatal("expected dial to 127.0.0.1 to be blocked")
  112. }
  113. if !strings.Contains(err.Error(), "blocked") {
  114. t.Fatalf("expected 'blocked' in error, got: %v", err)
  115. }
  116. }
  117. func TestSSRFGuardedDialContext_AllowPrivateBypassesGuard(t *testing.T) {
  118. ctx := ContextWithAllowPrivate(context.Background(), true)
  119. _, err := SSRFGuardedDialContext(ctx, "tcp", "127.0.0.1:1")
  120. if err == nil {
  121. t.Fatal("dial to a closed loopback port should still fail at the connect step")
  122. }
  123. if strings.Contains(err.Error(), "blocked private/internal address") {
  124. t.Fatalf("expected guard to be bypassed when AllowPrivate=true, got: %v", err)
  125. }
  126. }
  127. func TestSSRFGuardedDialContext_BadAddress(t *testing.T) {
  128. if _, err := SSRFGuardedDialContext(context.Background(), "tcp", "no-port"); err == nil {
  129. t.Fatal("expected error for address without port")
  130. }
  131. }