rules_builder_test.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. package firewall
  2. import (
  3. "math"
  4. "net/netip"
  5. "testing"
  6. "github.com/slackhq/nebula/iputil"
  7. "github.com/slackhq/nebula/test"
  8. "github.com/stretchr/testify/assert"
  9. "github.com/stretchr/testify/require"
  10. )
  11. func TestRulesBuilder_AddRule(t *testing.T) {
  12. l := test.NewLogger()
  13. ti, err := netip.ParsePrefix("1.2.3.4/32")
  14. require.NoError(t, err)
  15. ti6, err := netip.ParsePrefix("fd12::34/128")
  16. require.NoError(t, err)
  17. rb := NewRulesBuilder(l)
  18. require.NoError(t, rb.AddRule(true, iputil.IPProtocolTCP, 1, 1, []string{}, "", "", "", "", ""))
  19. rules := rb.Build(nil, nil)
  20. // An empty rule is any
  21. assert.True(t, rules.In.protos[iputil.IPProtocolTCP][1].Any.Any.Any)
  22. assert.Empty(t, rules.In.protos[iputil.IPProtocolTCP][1].Any.Groups)
  23. assert.Empty(t, rules.In.protos[iputil.IPProtocolTCP][1].Any.Hosts)
  24. rb = NewRulesBuilder(l)
  25. require.NoError(t, rb.AddRule(true, iputil.IPProtocolUDP, 1, 1, []string{"g1"}, "", "", "", "", ""))
  26. rules = rb.Build(nil, nil)
  27. assert.Nil(t, rules.In.protos[iputil.IPProtocolUDP][1].Any.Any)
  28. assert.Contains(t, rules.In.protos[iputil.IPProtocolUDP][1].Any.Groups[0].Groups, "g1")
  29. assert.Empty(t, rules.In.protos[iputil.IPProtocolUDP][1].Any.Hosts)
  30. rb = NewRulesBuilder(l)
  31. require.NoError(t, rb.AddRule(true, iputil.IPProtocolICMP, 1, 1, []string{}, "h1", "", "", "", ""))
  32. require.NoError(t, rb.AddRule(true, iputil.IPProtocolICMPv6, 1, 1, []string{}, "h2", "", "", "", ""))
  33. rules = rb.Build(nil, nil)
  34. //no matter what port is given for icmp, it should end up as "any"
  35. assert.Nil(t, rules.In.protos[iputil.IPProtocolICMP][PortAny].Any.Any)
  36. assert.Empty(t, rules.In.protos[iputil.IPProtocolICMP][PortAny].Any.Groups)
  37. assert.Contains(t, rules.In.protos[iputil.IPProtocolICMP][PortAny].Any.Hosts, "h1")
  38. // ICMP and ICMPv6 share one set of rules
  39. assert.Contains(t, rules.In.protos[iputil.IPProtocolICMP][PortAny].Any.Hosts, "h2")
  40. assert.Contains(t, rules.In.protos[iputil.IPProtocolICMPv6][PortAny].Any.Hosts, "h1")
  41. // Any other protocol number gets its own rules
  42. rb = NewRulesBuilder(l)
  43. require.NoError(t, rb.AddRule(true, 47, 0, 0, []string{"g1"}, "", "", "", "", "")) // GRE
  44. rules = rb.Build(nil, nil)
  45. assert.Contains(t, rules.In.protos[47][PortAny].Any.Groups[0].Groups, "g1")
  46. assert.Nil(t, rules.In.protos[iputil.IPProtocolTCP])
  47. rb = NewRulesBuilder(l)
  48. require.NoError(t, rb.AddRule(false, ProtoAny, 1, 1, []string{}, "", ti.String(), "", "", ""))
  49. rules = rb.Build(nil, nil)
  50. assert.Nil(t, rules.Out.protos[ProtoAny][1].Any.Any)
  51. _, ok := rules.Out.protos[ProtoAny][1].Any.CIDR.Get(ti)
  52. assert.True(t, ok)
  53. rb = NewRulesBuilder(l)
  54. require.NoError(t, rb.AddRule(false, ProtoAny, 1, 1, []string{}, "", ti6.String(), "", "", ""))
  55. rules = rb.Build(nil, nil)
  56. assert.Nil(t, rules.Out.protos[ProtoAny][1].Any.Any)
  57. _, ok = rules.Out.protos[ProtoAny][1].Any.CIDR.Get(ti6)
  58. assert.True(t, ok)
  59. rb = NewRulesBuilder(l)
  60. require.NoError(t, rb.AddRule(false, ProtoAny, 1, 1, []string{}, "", "", ti.String(), "", ""))
  61. rules = rb.Build(nil, nil)
  62. assert.NotNil(t, rules.Out.protos[ProtoAny][1].Any.Any)
  63. ok = rules.Out.protos[ProtoAny][1].Any.Any.LocalCIDR.Get(ti)
  64. assert.True(t, ok)
  65. rb = NewRulesBuilder(l)
  66. require.NoError(t, rb.AddRule(false, ProtoAny, 1, 1, []string{}, "", "", ti6.String(), "", ""))
  67. rules = rb.Build(nil, nil)
  68. assert.NotNil(t, rules.Out.protos[ProtoAny][1].Any.Any)
  69. ok = rules.Out.protos[ProtoAny][1].Any.Any.LocalCIDR.Get(ti6)
  70. assert.True(t, ok)
  71. rb = NewRulesBuilder(l)
  72. require.NoError(t, rb.AddRule(true, iputil.IPProtocolUDP, 1, 1, []string{"g1"}, "", "", "", "ca-name", ""))
  73. rules = rb.Build(nil, nil)
  74. assert.Contains(t, rules.In.protos[iputil.IPProtocolUDP][1].CANames, "ca-name")
  75. rb = NewRulesBuilder(l)
  76. require.NoError(t, rb.AddRule(true, iputil.IPProtocolUDP, 1, 1, []string{"g1"}, "", "", "", "", "ca-sha"))
  77. rules = rb.Build(nil, nil)
  78. assert.Contains(t, rules.In.protos[iputil.IPProtocolUDP][1].CAShas, "ca-sha")
  79. rb = NewRulesBuilder(l)
  80. require.NoError(t, rb.AddRule(false, ProtoAny, 0, 0, []string{}, "any", "", "", "", ""))
  81. rules = rb.Build(nil, nil)
  82. assert.True(t, rules.Out.protos[ProtoAny][0].Any.Any.Any)
  83. rb = NewRulesBuilder(l)
  84. anyIp, err := netip.ParsePrefix("0.0.0.0/0")
  85. require.NoError(t, err)
  86. require.NoError(t, rb.AddRule(false, ProtoAny, 0, 0, []string{}, "", anyIp.String(), "", "", ""))
  87. rules = rb.Build(nil, nil)
  88. assert.Nil(t, rules.Out.protos[ProtoAny][0].Any.Any)
  89. table, ok := rules.Out.protos[ProtoAny][0].Any.CIDR.Lookup(netip.MustParseAddr("1.1.1.1"))
  90. assert.True(t, table.Any)
  91. table, ok = rules.Out.protos[ProtoAny][0].Any.CIDR.Lookup(netip.MustParseAddr("9::9"))
  92. assert.False(t, ok)
  93. rb = NewRulesBuilder(l)
  94. anyIp6, err := netip.ParsePrefix("::/0")
  95. require.NoError(t, err)
  96. require.NoError(t, rb.AddRule(false, ProtoAny, 0, 0, []string{}, "", anyIp6.String(), "", "", ""))
  97. rules = rb.Build(nil, nil)
  98. assert.Nil(t, rules.Out.protos[ProtoAny][0].Any.Any)
  99. table, ok = rules.Out.protos[ProtoAny][0].Any.CIDR.Lookup(netip.MustParseAddr("9::9"))
  100. assert.True(t, table.Any)
  101. table, ok = rules.Out.protos[ProtoAny][0].Any.CIDR.Lookup(netip.MustParseAddr("1.1.1.1"))
  102. assert.False(t, ok)
  103. rb = NewRulesBuilder(l)
  104. require.NoError(t, rb.AddRule(false, ProtoAny, 0, 0, []string{}, "", "any", "", "", ""))
  105. rules = rb.Build(nil, nil)
  106. assert.True(t, rules.Out.protos[ProtoAny][0].Any.Any.Any)
  107. rb = NewRulesBuilder(l)
  108. require.NoError(t, rb.AddRule(false, ProtoAny, 0, 0, []string{}, "", "", anyIp.String(), "", ""))
  109. rules = rb.Build(nil, nil)
  110. assert.False(t, rules.Out.protos[ProtoAny][0].Any.Any.Any)
  111. assert.True(t, rules.Out.protos[ProtoAny][0].Any.Any.LocalCIDR.Lookup(netip.MustParseAddr("1.1.1.1")))
  112. assert.False(t, rules.Out.protos[ProtoAny][0].Any.Any.LocalCIDR.Lookup(netip.MustParseAddr("9::9")))
  113. rb = NewRulesBuilder(l)
  114. require.NoError(t, rb.AddRule(false, ProtoAny, 0, 0, []string{}, "", "", anyIp6.String(), "", ""))
  115. rules = rb.Build(nil, nil)
  116. assert.False(t, rules.Out.protos[ProtoAny][0].Any.Any.Any)
  117. assert.True(t, rules.Out.protos[ProtoAny][0].Any.Any.LocalCIDR.Lookup(netip.MustParseAddr("9::9")))
  118. assert.False(t, rules.Out.protos[ProtoAny][0].Any.Any.LocalCIDR.Lookup(netip.MustParseAddr("1.1.1.1")))
  119. rb = NewRulesBuilder(l)
  120. require.NoError(t, rb.AddRule(false, ProtoAny, 0, 0, []string{}, "", "", "any", "", ""))
  121. rules = rb.Build(nil, nil)
  122. assert.True(t, rules.Out.protos[ProtoAny][0].Any.Any.Any)
  123. // Every protocol number is accepted
  124. rb = NewRulesBuilder(l)
  125. require.NoError(t, rb.AddRule(true, math.MaxUint8, 0, 0, []string{}, "", "", "", "", ""))
  126. rules = rb.Build(nil, nil)
  127. assert.True(t, rules.In.protos[math.MaxUint8][PortAny].Any.Any.Any)
  128. // Test error conditions, a bad rule is reported when it's added
  129. rb = NewRulesBuilder(l)
  130. require.Error(t, rb.AddRule(true, ProtoAny, 10, 0, []string{}, "", "", "", "", ""))
  131. require.Error(t, rb.AddRule(true, ProtoAny, 0, 0, []string{}, "", "junk", "", "", ""))
  132. require.Error(t, rb.AddRule(true, ProtoAny, 0, 0, []string{}, "", "", "junk", "", ""))
  133. // ICMP and ICMPv6 share one copy of each proto `any` rule
  134. rb = NewRulesBuilder(l)
  135. require.NoError(t, rb.AddRule(true, ProtoAny, PortAny, PortAny, []string{"default-group"}, "", "", "", "", ""))
  136. require.NoError(t, rb.AddRule(true, iputil.IPProtocolICMPv6, PortAny, PortAny, []string{"nope"}, "", "", "", "", ""))
  137. rules = rb.Build(nil, nil)
  138. assert.Len(t, rules.In.protos[iputil.IPProtocolICMP][PortAny].Any.Groups, 2)
  139. assert.Len(t, rules.In.protos[iputil.IPProtocolICMPv6][PortAny].Any.Groups, 2)
  140. // No rules allow nothing
  141. rules = NewRulesBuilder(l).Build(nil, nil)
  142. for proto := range rules.In.protos {
  143. assert.Nil(t, rules.In.protos[proto], "proto %d", proto)
  144. assert.Nil(t, rules.Out.protos[proto], "proto %d", proto)
  145. }
  146. }
  147. // TestRulesBuilder_sharedLocalCIDR ensures local cidrs shared between rules and ports are copied, not changed,
  148. // when another rule's local cidrs are added to them. The prefixes share their first octet so the copy has to reach
  149. // past the root of the shared table.
  150. func TestRulesBuilder_sharedLocalCIDR(t *testing.T) {
  151. vpnNetworks := []netip.Prefix{
  152. netip.MustParsePrefix("10.1.0.5/16"),
  153. netip.MustParsePrefix("10.2.0.5/16"),
  154. netip.MustParsePrefix("fd00:1::5/64"),
  155. }
  156. unsafeNetworks := []netip.Prefix{netip.MustParsePrefix("192.168.0.0/24")}
  157. vpn1 := netip.MustParseAddr("10.1.9.9")
  158. vpn2 := netip.MustParseAddr("10.2.9.9")
  159. vpn6 := netip.MustParseAddr("fd00:1::9")
  160. h1Extra := netip.MustParseAddr("10.3.0.1")
  161. h1Extra6 := netip.MustParseAddr("fd00:2::1")
  162. h2Own := netip.MustParseAddr("10.4.0.1")
  163. rb := NewRulesBuilder(test.NewLogger())
  164. // h1 allows the vpn networks by default on ports 1 and 2, and 10.3.0.0/16 and fd00:2::/64 as well on port 1
  165. require.NoError(t, rb.AddRule(true, iputil.IPProtocolTCP, 1, 2, nil, "h1", "", "", "", ""))
  166. require.NoError(t, rb.AddRule(true, iputil.IPProtocolTCP, 1, 1, nil, "h1", "", "10.3.0.0/16", "", ""))
  167. require.NoError(t, rb.AddRule(true, iputil.IPProtocolTCP, 1, 1, nil, "h1", "", "fd00:2::/64", "", ""))
  168. // h2 allows 10.4.0.0/16 on ports 1 and 2, and the vpn networks by default as well on port 1
  169. require.NoError(t, rb.AddRule(true, iputil.IPProtocolTCP, 1, 2, nil, "h2", "", "10.4.0.0/16", "", ""))
  170. require.NoError(t, rb.AddRule(true, iputil.IPProtocolTCP, 1, 1, nil, "h2", "", "", "", ""))
  171. // h3 only has the default
  172. require.NoError(t, rb.AddRule(true, iputil.IPProtocolTCP, 3, 3, nil, "h3", "", "", "", ""))
  173. rules := rb.Build(vpnNetworks, unsafeNetworks)
  174. for _, tc := range []struct {
  175. port int32
  176. host string
  177. addr netip.Addr
  178. want bool
  179. }{
  180. {1, "h1", vpn1, true},
  181. {1, "h1", vpn2, true},
  182. {1, "h1", vpn6, true},
  183. {1, "h1", h1Extra, true},
  184. {1, "h1", h1Extra6, true},
  185. {1, "h1", h2Own, false},
  186. {2, "h1", vpn1, true},
  187. {2, "h1", h1Extra, false},
  188. {2, "h1", h1Extra6, false},
  189. {1, "h2", h2Own, true},
  190. {1, "h2", vpn1, true},
  191. {1, "h2", vpn2, true},
  192. {1, "h2", vpn6, true},
  193. {1, "h2", h1Extra, false},
  194. {2, "h2", h2Own, true},
  195. {2, "h2", vpn1, false},
  196. {2, "h2", vpn6, false},
  197. {3, "h3", vpn1, true},
  198. {3, "h3", vpn2, true},
  199. {3, "h3", vpn6, true},
  200. {3, "h3", h1Extra, false},
  201. {3, "h3", h1Extra6, false},
  202. {3, "h3", h2Own, false},
  203. } {
  204. lr := rules.In.protos[iputil.IPProtocolTCP][tc.port].Any.Hosts[tc.host]
  205. assert.Equal(t, tc.want, lr.match(&Packet{LocalAddr: tc.addr}, nil), "port %d, %s, %s", tc.port, tc.host, tc.addr)
  206. }
  207. }