rules_builder_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. package firewall
  2. import (
  3. "fmt"
  4. "math"
  5. "net/netip"
  6. "testing"
  7. "time"
  8. "github.com/slackhq/nebula/cert"
  9. "github.com/slackhq/nebula/cert_test"
  10. "github.com/slackhq/nebula/iputil"
  11. "github.com/slackhq/nebula/test"
  12. "github.com/stretchr/testify/assert"
  13. "github.com/stretchr/testify/require"
  14. )
  15. // rulesAt returns the rules that a packet on proto with the given port is checked against, in id order.
  16. // port may also be PortAny or PortFragment.
  17. func (t *Table) rulesAt(proto uint8, port int32) []*rule {
  18. pi := t.protos[proto]
  19. if pi == nil {
  20. return nil
  21. }
  22. p := &Packet{Protocol: proto, Fragment: port == PortFragment}
  23. if port > 0 {
  24. p.LocalPort = uint16(port)
  25. }
  26. var rules []*rule
  27. for id := range pi.whichMatch(p, true).all() {
  28. rules = append(rules, &t.rules[id])
  29. }
  30. return rules
  31. }
  32. // oneRuleAt returns the single rule that rulesAt finds, and fails the test when there is not exactly one.
  33. func oneRuleAt(t *testing.T, table *Table, proto uint8, key int32) *rule {
  34. t.Helper()
  35. rules := table.rulesAt(proto, key)
  36. require.Len(t, rules, 1)
  37. return rules[0]
  38. }
  39. func TestRulesBuilder_AddRule(t *testing.T) {
  40. l := test.NewLogger()
  41. ti := netip.MustParsePrefix("1.2.3.4/32")
  42. ti6 := netip.MustParsePrefix("fd12::34/128")
  43. rb := NewRulesBuilder(l)
  44. require.NoError(t, rb.AddRule(true, iputil.IPProtocolTCP, 1, 1, []string{}, "", "", "", "", ""))
  45. rules := rb.Build(nil, nil)
  46. // An empty rule is any
  47. r := oneRuleAt(t, rules.In, iputil.IPProtocolTCP, 1)
  48. assert.True(t, r.remoteAny)
  49. assert.Empty(t, r.groups)
  50. assert.Empty(t, r.host)
  51. // It applies only to its own port.
  52. assert.Empty(t, rules.In.rulesAt(iputil.IPProtocolTCP, 2))
  53. assert.Empty(t, rules.In.rulesAt(iputil.IPProtocolTCP, PortAny))
  54. rb = NewRulesBuilder(l)
  55. require.NoError(t, rb.AddRule(true, iputil.IPProtocolUDP, 1, 1, []string{"g1"}, "", "", "", "", ""))
  56. rules = rb.Build(nil, nil)
  57. r = oneRuleAt(t, rules.In, iputil.IPProtocolUDP, 1)
  58. assert.False(t, r.remoteAny)
  59. assert.Equal(t, []string{"g1"}, r.groups)
  60. assert.Empty(t, r.host)
  61. rb = NewRulesBuilder(l)
  62. require.NoError(t, rb.AddRule(true, iputil.IPProtocolICMP, 1, 1, []string{}, "h1", "", "", "", ""))
  63. require.NoError(t, rb.AddRule(true, iputil.IPProtocolICMPv6, 1, 1, []string{}, "h2", "", "", "", ""))
  64. rules = rb.Build(nil, nil)
  65. // An ICMP rule is port `any` regardless of the port given, and ICMP and ICMPv6 share one set of rules.
  66. for _, proto := range []uint8{iputil.IPProtocolICMP, iputil.IPProtocolICMPv6} {
  67. rs := rules.In.rulesAt(proto, PortAny)
  68. require.Len(t, rs, 2, "proto %d", proto)
  69. assert.False(t, rs[0].remoteAny)
  70. assert.Equal(t, "h1", rs[0].host)
  71. assert.Equal(t, "h2", rs[1].host)
  72. }
  73. assert.Same(t, rules.In.protos[iputil.IPProtocolICMP], rules.In.protos[iputil.IPProtocolICMPv6])
  74. // Any other protocol number gets its own rules
  75. rb = NewRulesBuilder(l)
  76. require.NoError(t, rb.AddRule(true, 47, 0, 0, []string{"g1"}, "", "", "", "", "")) // GRE
  77. rules = rb.Build(nil, nil)
  78. assert.Equal(t, []string{"g1"}, oneRuleAt(t, rules.In, 47, PortAny).groups)
  79. assert.Nil(t, rules.In.protos[iputil.IPProtocolTCP])
  80. // A proto `any` rule with a port applies to every protocol with ports, and ProtoAny itself has none.
  81. rb = NewRulesBuilder(l)
  82. require.NoError(t, rb.AddRule(false, ProtoAny, 1, 1, []string{}, "", ti.String(), "", "", ""))
  83. rules = rb.Build(nil, nil)
  84. assert.Empty(t, rules.Out.rulesAt(ProtoAny, 1))
  85. r = oneRuleAt(t, rules.Out, iputil.IPProtocolTCP, 1)
  86. assert.False(t, r.remoteAny)
  87. assert.Equal(t, ti, r.cidr)
  88. rb = NewRulesBuilder(l)
  89. require.NoError(t, rb.AddRule(false, ProtoAny, 1, 1, []string{}, "", ti6.String(), "", "", ""))
  90. rules = rb.Build(nil, nil)
  91. r = oneRuleAt(t, rules.Out, iputil.IPProtocolTCP, 1)
  92. assert.False(t, r.remoteAny)
  93. assert.Equal(t, ti6, r.cidr)
  94. rb = NewRulesBuilder(l)
  95. require.NoError(t, rb.AddRule(false, ProtoAny, 1, 1, []string{}, "", "", ti.String(), "", ""))
  96. rules = rb.Build(nil, nil)
  97. r = oneRuleAt(t, rules.Out, iputil.IPProtocolTCP, 1)
  98. assert.True(t, r.remoteAny)
  99. assert.False(t, r.localAny)
  100. assert.Equal(t, []netip.Prefix{ti}, r.local)
  101. rb = NewRulesBuilder(l)
  102. require.NoError(t, rb.AddRule(false, ProtoAny, 1, 1, []string{}, "", "", ti6.String(), "", ""))
  103. rules = rb.Build(nil, nil)
  104. r = oneRuleAt(t, rules.Out, iputil.IPProtocolTCP, 1)
  105. assert.True(t, r.remoteAny)
  106. assert.False(t, r.localAny)
  107. assert.Equal(t, []netip.Prefix{ti6}, r.local)
  108. rb = NewRulesBuilder(l)
  109. require.NoError(t, rb.AddRule(true, iputil.IPProtocolUDP, 1, 1, []string{"g1"}, "", "", "", "ca-name", ""))
  110. rules = rb.Build(nil, nil)
  111. assert.Equal(t, "ca-name", oneRuleAt(t, rules.In, iputil.IPProtocolUDP, 1).caName)
  112. rb = NewRulesBuilder(l)
  113. require.NoError(t, rb.AddRule(true, iputil.IPProtocolUDP, 1, 1, []string{"g1"}, "", "", "", "", "ca-sha"))
  114. rules = rb.Build(nil, nil)
  115. assert.Equal(t, "ca-sha", oneRuleAt(t, rules.In, iputil.IPProtocolUDP, 1).caSha)
  116. rb = NewRulesBuilder(l)
  117. require.NoError(t, rb.AddRule(false, ProtoAny, 0, 0, []string{}, "any", "", "", "", ""))
  118. rules = rb.Build(nil, nil)
  119. assert.True(t, oneRuleAt(t, rules.Out, ProtoAny, 0).remoteAny)
  120. // 0.0.0.0/0 allows any IPv4 address, but no IPv6 address.
  121. rb = NewRulesBuilder(l)
  122. require.NoError(t, rb.AddRule(false, ProtoAny, 0, 0, []string{}, "", "0.0.0.0/0", "", "", ""))
  123. rules = rb.Build(nil, nil)
  124. r = oneRuleAt(t, rules.Out, ProtoAny, 0)
  125. assert.False(t, r.remoteAny)
  126. assert.True(t, r.matchRemote(netip.MustParseAddr("1.1.1.1"), nil))
  127. assert.False(t, r.matchRemote(netip.MustParseAddr("9::9"), nil))
  128. // ::/0 allows any IPv6 address, but no IPv4 address.
  129. rb = NewRulesBuilder(l)
  130. require.NoError(t, rb.AddRule(false, ProtoAny, 0, 0, []string{}, "", "::/0", "", "", ""))
  131. rules = rb.Build(nil, nil)
  132. r = oneRuleAt(t, rules.Out, ProtoAny, 0)
  133. assert.False(t, r.remoteAny)
  134. assert.True(t, r.matchRemote(netip.MustParseAddr("9::9"), nil))
  135. assert.False(t, r.matchRemote(netip.MustParseAddr("1.1.1.1"), nil))
  136. rb = NewRulesBuilder(l)
  137. require.NoError(t, rb.AddRule(false, ProtoAny, 0, 0, []string{}, "", "any", "", "", ""))
  138. rules = rb.Build(nil, nil)
  139. assert.True(t, oneRuleAt(t, rules.Out, ProtoAny, 0).remoteAny)
  140. // A local cidr is likewise limited to its own address family.
  141. rb = NewRulesBuilder(l)
  142. require.NoError(t, rb.AddRule(false, ProtoAny, 0, 0, []string{}, "", "", "0.0.0.0/0", "", ""))
  143. rules = rb.Build(nil, nil)
  144. r = oneRuleAt(t, rules.Out, ProtoAny, 0)
  145. assert.False(t, r.localAny)
  146. assert.True(t, r.matchLocal(netip.MustParseAddr("1.1.1.1")))
  147. assert.False(t, r.matchLocal(netip.MustParseAddr("9::9")))
  148. rb = NewRulesBuilder(l)
  149. require.NoError(t, rb.AddRule(false, ProtoAny, 0, 0, []string{}, "", "", "::/0", "", ""))
  150. rules = rb.Build(nil, nil)
  151. r = oneRuleAt(t, rules.Out, ProtoAny, 0)
  152. assert.False(t, r.localAny)
  153. assert.True(t, r.matchLocal(netip.MustParseAddr("9::9")))
  154. assert.False(t, r.matchLocal(netip.MustParseAddr("1.1.1.1")))
  155. rb = NewRulesBuilder(l)
  156. require.NoError(t, rb.AddRule(false, ProtoAny, 0, 0, []string{}, "", "", "any", "", ""))
  157. rules = rb.Build(nil, nil)
  158. assert.True(t, oneRuleAt(t, rules.Out, ProtoAny, 0).localAny)
  159. // Every protocol number is accepted
  160. rb = NewRulesBuilder(l)
  161. require.NoError(t, rb.AddRule(true, math.MaxUint8, 0, 0, []string{}, "", "", "", "", ""))
  162. rules = rb.Build(nil, nil)
  163. assert.True(t, oneRuleAt(t, rules.In, math.MaxUint8, PortAny).remoteAny)
  164. // Test error conditions, a bad rule is reported when it's added
  165. rb = NewRulesBuilder(l)
  166. require.Error(t, rb.AddRule(true, ProtoAny, 10, 0, []string{}, "", "", "", "", ""))
  167. require.Error(t, rb.AddRule(true, ProtoAny, PortFragment-1, 0, []string{}, "", "", "", "", ""))
  168. require.Error(t, rb.AddRule(true, ProtoAny, 1, math.MaxUint16+1, []string{}, "", "", "", "", ""))
  169. require.Error(t, rb.AddRule(true, ProtoAny, 0, 0, []string{}, "", "junk", "", "", ""))
  170. require.Error(t, rb.AddRule(true, ProtoAny, 0, 0, []string{}, "", "", "junk", "", ""))
  171. // ICMP and ICMPv6 share one copy of each proto `any` rule
  172. rb = NewRulesBuilder(l)
  173. require.NoError(t, rb.AddRule(true, ProtoAny, PortAny, PortAny, []string{"default-group"}, "", "", "", "", ""))
  174. require.NoError(t, rb.AddRule(true, iputil.IPProtocolICMPv6, PortAny, PortAny, []string{"nope"}, "", "", "", "", ""))
  175. rules = rb.Build(nil, nil)
  176. assert.Len(t, rules.In.rules, 2)
  177. assert.Len(t, rules.In.rulesAt(iputil.IPProtocolICMP, PortAny), 2)
  178. assert.Len(t, rules.In.rulesAt(iputil.IPProtocolICMPv6, PortAny), 2)
  179. // No rules allow nothing
  180. rules = NewRulesBuilder(l).Build(nil, nil)
  181. for proto := range rules.In.protos {
  182. assert.Nil(t, rules.In.protos[proto], "proto %d", proto)
  183. assert.Nil(t, rules.Out.protos[proto], "proto %d", proto)
  184. }
  185. }
  186. // TestRulesBuilder_protoIndex checks which packets each kind of port clause covers, and which protocols
  187. // share an index.
  188. func TestRulesBuilder_protoIndex(t *testing.T) {
  189. rb := NewRulesBuilder(test.NewLogger())
  190. require.NoError(t, rb.AddRule(true, ProtoAny, PortAny, PortAny, nil, "any", "", "", "", ""))
  191. require.NoError(t, rb.AddRule(true, iputil.IPProtocolTCP, 22, 22, nil, "port", "", "", "", ""))
  192. require.NoError(t, rb.AddRule(true, iputil.IPProtocolTCP, 1024, math.MaxUint16, nil, "range", "", "", "", ""))
  193. require.NoError(t, rb.AddRule(true, iputil.IPProtocolTCP, PortFragment, PortFragment, nil, "fragment", "", "", "", ""))
  194. // A range that includes port 0 is port `any`.
  195. require.NoError(t, rb.AddRule(true, iputil.IPProtocolUDP, 0, 100, nil, "zero", "", "", "", ""))
  196. // GRE has no ports, so a port rule on it is rejected rather than never applying, and `any` and `fragment` apply
  197. require.Error(t, rb.AddRule(true, 47, 22, 22, nil, "gre", "", "", "", ""))
  198. require.NoError(t, rb.AddRule(true, 47, PortFragment, PortFragment, nil, "gre", "", "", "", ""))
  199. rules := rb.Build(nil, nil)
  200. hosts := func(proto uint8, key int32) []string {
  201. var hosts []string
  202. for _, r := range rules.In.rulesAt(proto, key) {
  203. hosts = append(hosts, r.host)
  204. }
  205. return hosts
  206. }
  207. assert.Equal(t, []string{"any", "fragment"}, hosts(iputil.IPProtocolTCP, PortFragment))
  208. assert.Equal(t, []string{"any"}, hosts(iputil.IPProtocolTCP, PortAny))
  209. assert.Equal(t, []string{"any"}, hosts(iputil.IPProtocolTCP, 21))
  210. assert.Equal(t, []string{"any", "port"}, hosts(iputil.IPProtocolTCP, 22))
  211. assert.Equal(t, []string{"any"}, hosts(iputil.IPProtocolTCP, 23))
  212. assert.Equal(t, []string{"any"}, hosts(iputil.IPProtocolTCP, 1023))
  213. assert.Equal(t, []string{"any", "range"}, hosts(iputil.IPProtocolTCP, 1024))
  214. assert.Equal(t, []string{"any", "range"}, hosts(iputil.IPProtocolTCP, math.MaxUint16))
  215. assert.Equal(t, []string{"any", "zero"}, hosts(iputil.IPProtocolUDP, PortFragment))
  216. assert.Equal(t, []string{"any", "zero"}, hosts(iputil.IPProtocolUDP, PortAny))
  217. assert.Equal(t, []string{"any", "zero"}, hosts(iputil.IPProtocolUDP, math.MaxUint16))
  218. // A protocol without ports sees only the port `any` rules, and the port `fragment` rules for a fragment.
  219. assert.False(t, rules.In.protos[47].hasPorts)
  220. assert.Equal(t, []string{"any"}, hosts(47, PortAny))
  221. assert.Equal(t, []string{"any"}, hosts(47, 22))
  222. assert.Equal(t, []string{"any", "gre"}, hosts(47, PortFragment))
  223. assert.Equal(t, []string{"any"}, hosts(iputil.IPProtocolICMP, PortFragment))
  224. // Protocols without rules of their own share an index of the proto `any` rules: one for protocols with
  225. // ports and one for protocols without.
  226. assert.True(t, rules.In.protos[iputil.IPProtocolSCTP].hasPorts)
  227. assert.Equal(t, []string{"any"}, hosts(iputil.IPProtocolSCTP, 5000))
  228. assert.Same(t, rules.In.protos[iputil.IPProtocolSCTP], rules.In.protos[iputil.IPProtocolDCCP])
  229. assert.NotSame(t, rules.In.protos[iputil.IPProtocolSCTP], rules.In.protos[iputil.IPProtocolTCP])
  230. assert.Equal(t, []string{"any"}, hosts(50, PortAny))
  231. assert.Same(t, rules.In.protos[ProtoAny], rules.In.protos[50])
  232. assert.Same(t, rules.In.protos[ProtoAny], rules.In.protos[iputil.IPProtocolICMP])
  233. assert.NotSame(t, rules.In.protos[ProtoAny], rules.In.protos[iputil.IPProtocolSCTP])
  234. assert.Nil(t, rules.Out.protos[iputil.IPProtocolTCP])
  235. }
  236. // TestRulesBuilder_manyRules checks that rule ids beyond the first byte of a rule set are indexed correctly.
  237. func TestRulesBuilder_manyRules(t *testing.T) {
  238. rb := NewRulesBuilder(test.NewLogger())
  239. const n = 130
  240. for i := range n {
  241. require.NoError(t, rb.AddRule(true, iputil.IPProtocolTCP, int32(i+1), int32(i+1), nil, fmt.Sprintf("h%d", i), "", "", "", ""))
  242. }
  243. require.NoError(t, rb.AddRule(true, ProtoAny, PortAny, PortAny, nil, "any", "", "", "", ""))
  244. rules := rb.Build(nil, nil)
  245. assert.Equal(t, ruleSetLen(n+1), rules.In.protos[iputil.IPProtocolTCP].byPort.setLen)
  246. // Ids are the order the rules were added in, so the proto `any` rule comes last
  247. for i := range n {
  248. rs := rules.In.rulesAt(iputil.IPProtocolTCP, int32(i+1))
  249. require.Len(t, rs, 2, "port %d", i+1)
  250. assert.Equal(t, fmt.Sprintf("h%d", i), rs[0].host)
  251. assert.Equal(t, "any", rs[1].host)
  252. }
  253. assert.Len(t, rules.In.rulesAt(iputil.IPProtocolTCP, n+1), 1)
  254. }
  255. // TestRulesBuilder_defaultLocalCIDR checks which local addresses rules allow when there are unsafe networks.
  256. // A rule without a local cidr allows only the vpn networks, and a rule with one allows only that cidr.
  257. func TestRulesBuilder_defaultLocalCIDR(t *testing.T) {
  258. vpnNetworks := []netip.Prefix{
  259. netip.MustParsePrefix("10.1.0.5/16"),
  260. netip.MustParsePrefix("10.2.0.5/16"),
  261. netip.MustParsePrefix("fd00:1::5/64"),
  262. }
  263. unsafeNetworks := []netip.Prefix{netip.MustParsePrefix("192.168.0.0/24")}
  264. vpn1 := netip.MustParseAddr("10.1.9.9")
  265. vpn2 := netip.MustParseAddr("10.2.9.9")
  266. vpn6 := netip.MustParseAddr("fd00:1::9")
  267. h1Extra := netip.MustParseAddr("10.3.0.1")
  268. h1Extra6 := netip.MustParseAddr("fd00:2::1")
  269. h2Own := netip.MustParseAddr("10.4.0.1")
  270. rb := NewRulesBuilder(test.NewLogger())
  271. // 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
  272. require.NoError(t, rb.AddRule(true, iputil.IPProtocolTCP, 1, 2, nil, "h1", "", "", "", ""))
  273. require.NoError(t, rb.AddRule(true, iputil.IPProtocolTCP, 1, 1, nil, "h1", "", "10.3.0.0/16", "", ""))
  274. require.NoError(t, rb.AddRule(true, iputil.IPProtocolTCP, 1, 1, nil, "h1", "", "fd00:2::/64", "", ""))
  275. // h2 allows 10.4.0.0/16 on ports 1 and 2, and the vpn networks by default as well on port 1
  276. require.NoError(t, rb.AddRule(true, iputil.IPProtocolTCP, 1, 2, nil, "h2", "", "10.4.0.0/16", "", ""))
  277. require.NoError(t, rb.AddRule(true, iputil.IPProtocolTCP, 1, 1, nil, "h2", "", "", "", ""))
  278. // h3 only has the default
  279. require.NoError(t, rb.AddRule(true, iputil.IPProtocolTCP, 3, 3, nil, "h3", "", "", "", ""))
  280. rules := rb.Build(vpnNetworks, unsafeNetworks)
  281. ca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil)
  282. caPool := cert.NewCAPool()
  283. require.NoError(t, caPool.AddCA(ca))
  284. certs := map[string]*cert.CachedCertificate{}
  285. for _, name := range []string{"h1", "h2", "h3"} {
  286. crt, _, _, _ := cert_test.NewTestCert(cert.Version2, cert.Curve_CURVE25519, ca, caKey, name, time.Time{}, time.Time{}, []netip.Prefix{netip.MustParsePrefix("10.1.0.9/16")}, nil, nil)
  287. c, err := caPool.VerifyCertificate(time.Now(), crt)
  288. require.NoError(t, err)
  289. certs[name] = c
  290. }
  291. for _, tc := range []struct {
  292. port uint16
  293. host string
  294. addr netip.Addr
  295. want bool
  296. }{
  297. {1, "h1", vpn1, true},
  298. {1, "h1", vpn2, true},
  299. {1, "h1", vpn6, true},
  300. {1, "h1", h1Extra, true},
  301. {1, "h1", h1Extra6, true},
  302. {1, "h1", h2Own, false},
  303. {2, "h1", vpn1, true},
  304. {2, "h1", h1Extra, false},
  305. {2, "h1", h1Extra6, false},
  306. {1, "h2", h2Own, true},
  307. {1, "h2", vpn1, true},
  308. {1, "h2", vpn2, true},
  309. {1, "h2", vpn6, true},
  310. {1, "h2", h1Extra, false},
  311. {2, "h2", h2Own, true},
  312. {2, "h2", vpn1, false},
  313. {2, "h2", vpn6, false},
  314. {3, "h3", vpn1, true},
  315. {3, "h3", vpn2, true},
  316. {3, "h3", vpn6, true},
  317. {3, "h3", h1Extra, false},
  318. {3, "h3", h1Extra6, false},
  319. {3, "h3", h2Own, false},
  320. // Only h3 is allowed on port 3.
  321. {3, "h1", vpn1, false},
  322. } {
  323. p := &Packet{Protocol: iputil.IPProtocolTCP, LocalPort: tc.port, LocalAddr: tc.addr, RemoteAddr: netip.MustParseAddr("10.1.0.9")}
  324. assert.Equal(t, tc.want, rules.In.Match(p, true, certs[tc.host], caPool), "port %d, %s, %s", tc.port, tc.host, tc.addr)
  325. }
  326. }