config_test.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. package firewall
  2. import (
  3. "bytes"
  4. "errors"
  5. "net/netip"
  6. "testing"
  7. "github.com/slackhq/nebula/config"
  8. "github.com/slackhq/nebula/iputil"
  9. "github.com/slackhq/nebula/test"
  10. "github.com/stretchr/testify/assert"
  11. "github.com/stretchr/testify/require"
  12. )
  13. func Test_parsePort(t *testing.T) {
  14. _, _, err := parsePort("")
  15. require.EqualError(t, err, "was not a number; ``")
  16. _, _, err = parsePort(" ")
  17. require.EqualError(t, err, "was not a number; ` `")
  18. _, _, err = parsePort("-")
  19. require.EqualError(t, err, "appears to be a range but could not be parsed; `-`")
  20. _, _, err = parsePort(" - ")
  21. require.EqualError(t, err, "appears to be a range but could not be parsed; ` - `")
  22. _, _, err = parsePort("a-b")
  23. require.EqualError(t, err, "beginning range was not a number; `a`")
  24. _, _, err = parsePort("1-b")
  25. require.EqualError(t, err, "ending range was not a number; `b`")
  26. s, e, err := parsePort(" 1 - 2 ")
  27. assert.Equal(t, int32(1), s)
  28. assert.Equal(t, int32(2), e)
  29. require.NoError(t, err)
  30. s, e, err = parsePort("0-1")
  31. assert.Equal(t, int32(0), s)
  32. assert.Equal(t, int32(0), e)
  33. require.NoError(t, err)
  34. s, e, err = parsePort("9919")
  35. assert.Equal(t, int32(9919), s)
  36. assert.Equal(t, int32(9919), e)
  37. require.NoError(t, err)
  38. s, e, err = parsePort("any")
  39. assert.Equal(t, int32(0), s)
  40. assert.Equal(t, int32(0), e)
  41. require.NoError(t, err)
  42. }
  43. // Test_parsePort_invalid covers inputs that must error. The named bug is
  44. // that int32(strconv.Atoi("4294967296")) truncates to 0 == PortAny,
  45. // silently turning a typo into a match-all-ports rule; the rest are
  46. // representative syntax/range probes.
  47. func Test_parsePort_invalid(t *testing.T) {
  48. tests := []struct {
  49. name string
  50. input string
  51. wantErrContains string
  52. }{
  53. // Numeric overflow (the named bug + boundary).
  54. {"named bug: 2^32 truncates to PortAny", "4294967296", "out of range"},
  55. {"just above max real port", "65536", "out of range"},
  56. // Negatives route through the range branch and hit the empty-half
  57. // guard; included as defense in depth so a future refactor cannot
  58. // accidentally reach the int32 cast.
  59. {"negative", "-1", "could not be parsed"},
  60. // Syntax probes.
  61. {"NUL between digits", "4\x002", "was not a number"},
  62. {"hex notation", "0x10", "was not a number"},
  63. {"scientific notation", "1e3", "was not a number"},
  64. {"leading whitespace", " 42", "was not a number"},
  65. {"fullwidth digits", "42", "was not a number"},
  66. // Range branch.
  67. {"range upper out of range", "1-65536", "ending range out of range"},
  68. {"range lower out of range", "65536-65537", "beginning range out of range"},
  69. {"range with negative upper", "1--1", "ending range was not a number"},
  70. }
  71. for _, tc := range tests {
  72. t.Run(tc.name, func(t *testing.T) {
  73. _, _, err := parsePort(tc.input)
  74. require.Error(t, err, "input %q must error", tc.input)
  75. require.ErrorContains(t, err, tc.wantErrContains)
  76. })
  77. }
  78. }
  79. // Test_parsePort_valid_boundaries locks in success cases at 0, 1, and 65535
  80. // so a future refactor cannot regress the boundaries.
  81. func Test_parsePort_valid_boundaries(t *testing.T) {
  82. tests := []struct {
  83. name string
  84. input string
  85. wantStart int32
  86. wantEnd int32
  87. }{
  88. {"zero is PortAny", "0", 0, 0},
  89. {"min real port", "1", 1, 1},
  90. {"max real port", "65535", 65535, 65535},
  91. {"range zero to max forces end to zero", "0-65535", 0, 0},
  92. {"range max to max", "65535-65535", 65535, 65535},
  93. {"range one to max", "1-65535", 1, 65535},
  94. {"range with whitespace inside", " 1 - 2 ", 1, 2},
  95. }
  96. for _, tc := range tests {
  97. t.Run(tc.name, func(t *testing.T) {
  98. s, e, err := parsePort(tc.input)
  99. require.NoError(t, err)
  100. assert.Equal(t, tc.wantStart, s, "start port")
  101. assert.Equal(t, tc.wantEnd, e, "end port")
  102. })
  103. }
  104. }
  105. func TestAddRulesFromConfig(t *testing.T) {
  106. l := test.NewLogger()
  107. // Test adding tcp rule
  108. conf := config.NewC(test.NewLogger())
  109. mf := &mockFirewall{}
  110. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": "tcp", "host": "a"}}}
  111. require.NoError(t, addRulesFromConfig(l, false, conf, mf))
  112. assert.Equal(t, addRuleCall{incoming: false, proto: iputil.IPProtocolTCP, startPort: 1, endPort: 1, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
  113. // Test adding udp rule
  114. conf = config.NewC(test.NewLogger())
  115. mf = &mockFirewall{}
  116. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": "udp", "host": "a"}}}
  117. require.NoError(t, addRulesFromConfig(l, false, conf, mf))
  118. assert.Equal(t, addRuleCall{incoming: false, proto: iputil.IPProtocolUDP, startPort: 1, endPort: 1, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
  119. // Test adding udplite, dccp, and sctp rules
  120. for name, proto := range map[string]uint8{"udplite": iputil.IPProtocolUDPLite, "dccp": iputil.IPProtocolDCCP, "sctp": iputil.IPProtocolSCTP} {
  121. conf = config.NewC(test.NewLogger())
  122. mf = &mockFirewall{}
  123. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1-2", "proto": name, "host": "a"}}}
  124. require.NoError(t, addRulesFromConfig(l, false, conf, mf), name)
  125. assert.Equal(t, addRuleCall{incoming: false, proto: proto, startPort: 1, endPort: 2, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall, name)
  126. }
  127. // Test adding icmp rule
  128. conf = config.NewC(test.NewLogger())
  129. mf = &mockFirewall{}
  130. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": "icmp", "host": "a"}}}
  131. require.NoError(t, addRulesFromConfig(l, false, conf, mf))
  132. assert.Equal(t, addRuleCall{incoming: false, proto: iputil.IPProtocolICMP, startPort: PortAny, endPort: PortAny, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
  133. // Test adding icmp rule no port
  134. conf = config.NewC(test.NewLogger())
  135. mf = &mockFirewall{}
  136. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"proto": "icmp", "host": "a"}}}
  137. require.NoError(t, addRulesFromConfig(l, false, conf, mf))
  138. assert.Equal(t, addRuleCall{incoming: false, proto: iputil.IPProtocolICMP, startPort: PortAny, endPort: PortAny, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
  139. // Test adding rules by protocol number, as yaml would give a string or an int
  140. for _, proto := range []any{"47", 47} {
  141. conf = config.NewC(test.NewLogger())
  142. mf = &mockFirewall{}
  143. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "any", "proto": proto, "host": "a"}}}
  144. require.NoError(t, addRulesFromConfig(l, false, conf, mf), proto)
  145. assert.Equal(t, addRuleCall{incoming: false, proto: 47, startPort: PortAny, endPort: PortAny, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall, proto)
  146. }
  147. // Test ICMP and ICMPv6 by number still ignore ports
  148. for _, proto := range []uint8{iputil.IPProtocolICMP, iputil.IPProtocolICMPv6} {
  149. conf = config.NewC(test.NewLogger())
  150. mf = &mockFirewall{}
  151. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": proto, "host": "a"}}}
  152. require.NoError(t, addRulesFromConfig(l, false, conf, mf), proto)
  153. assert.Equal(t, addRuleCall{incoming: false, proto: proto, startPort: PortAny, endPort: PortAny, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall, proto)
  154. }
  155. // Test protocols that aren't a known name or a number from 1 to 255
  156. for _, proto := range []string{"0", "256", "-1", "gre"} {
  157. conf = config.NewC(test.NewLogger())
  158. mf = &mockFirewall{}
  159. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "any", "proto": proto, "host": "a"}}}
  160. require.EqualError(t, addRulesFromConfig(l, false, conf, mf), "firewall.outbound rule #0; proto was not understood; `"+proto+"`")
  161. }
  162. // Test adding any rule
  163. conf = config.NewC(test.NewLogger())
  164. mf = &mockFirewall{}
  165. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "host": "a"}}}
  166. require.NoError(t, addRulesFromConfig(l, true, conf, mf))
  167. assert.Equal(t, addRuleCall{incoming: true, proto: ProtoAny, startPort: 1, endPort: 1, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
  168. // Test adding rule with cidr
  169. cidr := netip.MustParsePrefix("10.0.0.0/8")
  170. conf = config.NewC(test.NewLogger())
  171. mf = &mockFirewall{}
  172. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": cidr.String()}}}
  173. require.NoError(t, addRulesFromConfig(l, true, conf, mf))
  174. assert.Equal(t, addRuleCall{incoming: true, proto: ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: cidr.String(), localIp: ""}, mf.lastCall)
  175. // Test adding rule with local_cidr
  176. conf = config.NewC(test.NewLogger())
  177. mf = &mockFirewall{}
  178. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": cidr.String()}}}
  179. require.NoError(t, addRulesFromConfig(l, true, conf, mf))
  180. assert.Equal(t, addRuleCall{incoming: true, proto: ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "", localIp: cidr.String()}, mf.lastCall)
  181. // Test adding rule with cidr ipv6
  182. cidr6 := netip.MustParsePrefix("fd00::/8")
  183. conf = config.NewC(test.NewLogger())
  184. mf = &mockFirewall{}
  185. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": cidr6.String()}}}
  186. require.NoError(t, addRulesFromConfig(l, true, conf, mf))
  187. assert.Equal(t, addRuleCall{incoming: true, proto: ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: cidr6.String(), localIp: ""}, mf.lastCall)
  188. // Test adding rule with any cidr
  189. conf = config.NewC(test.NewLogger())
  190. mf = &mockFirewall{}
  191. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": "any"}}}
  192. require.NoError(t, addRulesFromConfig(l, true, conf, mf))
  193. assert.Equal(t, addRuleCall{incoming: true, proto: ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "any", localIp: ""}, mf.lastCall)
  194. // Test adding rule with junk cidr, which RulesBuilder rejects
  195. conf = config.NewC(test.NewLogger())
  196. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": "junk/junk"}}}
  197. require.EqualError(t, addRulesFromConfig(l, true, conf, NewRulesBuilder(l)), "firewall.inbound rule #0; cidr did not parse; netip.ParsePrefix(\"junk/junk\"): ParseAddr(\"junk\"): unable to parse IP")
  198. // Test adding rule with local_cidr ipv6
  199. conf = config.NewC(test.NewLogger())
  200. mf = &mockFirewall{}
  201. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": cidr6.String()}}}
  202. require.NoError(t, addRulesFromConfig(l, true, conf, mf))
  203. assert.Equal(t, addRuleCall{incoming: true, proto: ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "", localIp: cidr6.String()}, mf.lastCall)
  204. // Test adding rule with any local_cidr
  205. conf = config.NewC(test.NewLogger())
  206. mf = &mockFirewall{}
  207. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": "any"}}}
  208. require.NoError(t, addRulesFromConfig(l, true, conf, mf))
  209. assert.Equal(t, addRuleCall{incoming: true, proto: ProtoAny, startPort: 1, endPort: 1, groups: nil, localIp: "any"}, mf.lastCall)
  210. // Test adding rule with junk local_cidr, which RulesBuilder rejects
  211. conf = config.NewC(test.NewLogger())
  212. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": "junk/junk"}}}
  213. require.EqualError(t, addRulesFromConfig(l, true, conf, NewRulesBuilder(l)), "firewall.inbound rule #0; local_cidr did not parse; netip.ParsePrefix(\"junk/junk\"): ParseAddr(\"junk\"): unable to parse IP")
  214. // Test adding rule with ca_sha
  215. conf = config.NewC(test.NewLogger())
  216. mf = &mockFirewall{}
  217. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "ca_sha": "12312313123"}}}
  218. require.NoError(t, addRulesFromConfig(l, true, conf, mf))
  219. assert.Equal(t, addRuleCall{incoming: true, proto: ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "", localIp: "", caSha: "12312313123"}, mf.lastCall)
  220. // Test adding rule with ca_name
  221. conf = config.NewC(test.NewLogger())
  222. mf = &mockFirewall{}
  223. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "ca_name": "root01"}}}
  224. require.NoError(t, addRulesFromConfig(l, true, conf, mf))
  225. assert.Equal(t, addRuleCall{incoming: true, proto: ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "", localIp: "", caName: "root01"}, mf.lastCall)
  226. // Test single group
  227. conf = config.NewC(test.NewLogger())
  228. mf = &mockFirewall{}
  229. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "group": "a"}}}
  230. require.NoError(t, addRulesFromConfig(l, true, conf, mf))
  231. assert.Equal(t, addRuleCall{incoming: true, proto: ProtoAny, startPort: 1, endPort: 1, groups: []string{"a"}, ip: "", localIp: ""}, mf.lastCall)
  232. // Test single groups
  233. conf = config.NewC(test.NewLogger())
  234. mf = &mockFirewall{}
  235. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "groups": "a"}}}
  236. require.NoError(t, addRulesFromConfig(l, true, conf, mf))
  237. assert.Equal(t, addRuleCall{incoming: true, proto: ProtoAny, startPort: 1, endPort: 1, groups: []string{"a"}, ip: "", localIp: ""}, mf.lastCall)
  238. // Test multiple AND groups
  239. conf = config.NewC(test.NewLogger())
  240. mf = &mockFirewall{}
  241. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "groups": []string{"a", "b"}}}}
  242. require.NoError(t, addRulesFromConfig(l, true, conf, mf))
  243. assert.Equal(t, addRuleCall{incoming: true, proto: ProtoAny, startPort: 1, endPort: 1, groups: []string{"a", "b"}, ip: "", localIp: ""}, mf.lastCall)
  244. // Test Add error
  245. conf = config.NewC(test.NewLogger())
  246. mf = &mockFirewall{}
  247. mf.nextCallReturn = errors.New("test error")
  248. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "host": "a"}}}
  249. require.EqualError(t, addRulesFromConfig(l, true, conf, mf), "firewall.inbound rule #0; test error")
  250. }
  251. // TestRulesFromConfig_defaultLocalCIDR ensures rules without a local_cidr allow only the vpn networks when there are
  252. // unsafe networks, unless default_local_cidr_any is set, and that a rule's own local_cidr is kept either way
  253. func TestRulesFromConfig_defaultLocalCIDR(t *testing.T) {
  254. vpnNetworks := []netip.Prefix{netip.MustParsePrefix("10.1.0.5/16")}
  255. unsafeNetworks := []netip.Prefix{netip.MustParsePrefix("192.168.0.0/24")}
  256. vpnAddr := netip.MustParseAddr("10.1.9.9")
  257. unsafeAddr := netip.MustParseAddr("192.168.0.3")
  258. allows := func(lr *localRules, addr netip.Addr) bool {
  259. return lr.match(&Packet{LocalAddr: addr}, nil)
  260. }
  261. for _, tc := range []struct {
  262. name string
  263. unsafeNetworks []netip.Prefix
  264. defaultLocalCIDRAny bool
  265. wantUnsafe bool
  266. }{
  267. {"no unsafe networks", nil, false, true},
  268. {"unsafe networks", unsafeNetworks, false, false},
  269. {"unsafe networks and default_local_cidr_any", unsafeNetworks, true, true},
  270. } {
  271. t.Run(tc.name, func(t *testing.T) {
  272. conf := config.NewC(test.NewLogger())
  273. conf.Settings["firewall"] = map[string]any{
  274. "default_local_cidr_any": tc.defaultLocalCIDRAny,
  275. "inbound": []any{
  276. map[string]any{"port": "any", "proto": "any", "host": "any"},
  277. map[string]any{"port": "22", "proto": "tcp", "host": "any"},
  278. map[string]any{"port": "80", "proto": "tcp", "host": "any", "local_cidr": "192.168.0.0/24"},
  279. map[string]any{"port": "443", "proto": "tcp", "host": "any", "local_cidr": "any"},
  280. },
  281. }
  282. rules, err := RulesFromConfig(test.NewLogger(), conf, vpnNetworks, tc.unsafeNetworks)
  283. require.NoError(t, err)
  284. // The proto `any` rule, copied into a protocol with rules of its own and shared by one without
  285. for _, proto := range []uint8{iputil.IPProtocolTCP, 47} {
  286. lr := rules.In.protos[proto][PortAny].Any.Any
  287. assert.True(t, allows(lr, vpnAddr), "proto %d", proto)
  288. assert.Equal(t, tc.wantUnsafe, allows(lr, unsafeAddr), "proto %d", proto)
  289. }
  290. tcp := rules.In.protos[iputil.IPProtocolTCP]
  291. assert.True(t, allows(tcp[22].Any.Any, vpnAddr))
  292. assert.Equal(t, tc.wantUnsafe, allows(tcp[22].Any.Any, unsafeAddr))
  293. assert.False(t, allows(tcp[80].Any.Any, vpnAddr))
  294. assert.True(t, allows(tcp[80].Any.Any, unsafeAddr))
  295. assert.True(t, allows(tcp[443].Any.Any, vpnAddr))
  296. assert.True(t, allows(tcp[443].Any.Any, unsafeAddr))
  297. })
  298. }
  299. }
  300. func Test_convertRule(t *testing.T) {
  301. ob := &bytes.Buffer{}
  302. l := test.NewLoggerWithOutput(ob)
  303. // Ensure group array of 1 is converted and a warning is printed
  304. c := map[string]any{
  305. "group": []any{"group1"},
  306. }
  307. r, err := yamlToConfigRule(l, c, "test", 1)
  308. assert.Contains(t, ob.String(), "group was an array with a single value, converting to simple value")
  309. assert.Contains(t, ob.String(), "table=test")
  310. assert.Contains(t, ob.String(), "rule=1")
  311. require.NoError(t, err)
  312. assert.Equal(t, []string{"group1"}, r.Groups)
  313. // Ensure group array of > 1 is errord
  314. ob.Reset()
  315. c = map[string]any{
  316. "group": []any{"group1", "group2"},
  317. }
  318. r, err = yamlToConfigRule(l, c, "test", 1)
  319. assert.Empty(t, ob.String())
  320. require.Error(t, err, "group should contain a single value, an array with more than one entry was provided")
  321. // Make sure a well formed group is alright
  322. ob.Reset()
  323. c = map[string]any{
  324. "group": "group1",
  325. }
  326. r, err = yamlToConfigRule(l, c, "test", 1)
  327. require.NoError(t, err)
  328. assert.Equal(t, []string{"group1"}, r.Groups)
  329. }
  330. func Test_convertRuleSanity(t *testing.T) {
  331. ob := &bytes.Buffer{}
  332. l := test.NewLoggerWithOutput(ob)
  333. noWarningPlease := []map[string]any{
  334. {"group": "group1"},
  335. {"groups": []any{"group2"}},
  336. {"host": "bob"},
  337. {"cidr": "1.1.1.1/1"},
  338. {"groups": []any{"group2"}, "host": "bob"},
  339. {"cidr": "1.1.1.1/1", "host": "bob"},
  340. {"groups": []any{"group2"}, "cidr": "1.1.1.1/1"},
  341. {"groups": []any{"group2"}, "cidr": "1.1.1.1/1", "host": "bob"},
  342. }
  343. for _, c := range noWarningPlease {
  344. r, err := yamlToConfigRule(l, c, "test", 1)
  345. require.NoError(t, err)
  346. require.NoError(t, r.sanity(), "should not generate a sanity warning, %+v", c)
  347. }
  348. yesWarningPlease := []map[string]any{
  349. {"group": "group1"},
  350. {"groups": []any{"group2"}},
  351. {"cidr": "1.1.1.1/1"},
  352. {"groups": []any{"group2"}, "host": "bob"},
  353. {"cidr": "1.1.1.1/1", "host": "bob"},
  354. {"groups": []any{"group2"}, "cidr": "1.1.1.1/1"},
  355. {"groups": []any{"group2"}, "cidr": "1.1.1.1/1", "host": "bob"},
  356. }
  357. for _, c := range yesWarningPlease {
  358. c["host"] = "any"
  359. r, err := yamlToConfigRule(l, c, "test", 1)
  360. require.NoError(t, err)
  361. err = r.sanity()
  362. require.Error(t, err, "I wanted a warning: %+v", c)
  363. }
  364. //reset the list
  365. yesWarningPlease = []map[string]any{
  366. {"group": "group1"},
  367. {"groups": []any{"group2"}},
  368. {"cidr": "1.1.1.1/1"},
  369. {"groups": []any{"group2"}, "host": "bob"},
  370. {"cidr": "1.1.1.1/1", "host": "bob"},
  371. {"groups": []any{"group2"}, "cidr": "1.1.1.1/1"},
  372. {"groups": []any{"group2"}, "cidr": "1.1.1.1/1", "host": "bob"},
  373. }
  374. for _, c := range yesWarningPlease {
  375. r, err := yamlToConfigRule(l, c, "test", 1)
  376. require.NoError(t, err)
  377. r.Groups = append(r.Groups, "any")
  378. err = r.sanity()
  379. require.Error(t, err, "I wanted a warning: %+v", c)
  380. }
  381. }
  382. type addRuleCall struct {
  383. incoming bool
  384. proto uint8
  385. startPort int32
  386. endPort int32
  387. groups []string
  388. host string
  389. ip string
  390. localIp string
  391. caName string
  392. caSha string
  393. }
  394. type mockFirewall struct {
  395. lastCall addRuleCall
  396. nextCallReturn error
  397. }
  398. func (mf *mockFirewall) AddRule(incoming bool, proto uint8, startPort int32, endPort int32, groups []string, host string, ip, localIp, caName string, caSha string) error {
  399. mf.lastCall = addRuleCall{
  400. incoming: incoming,
  401. proto: proto,
  402. startPort: startPort,
  403. endPort: endPort,
  404. groups: groups,
  405. host: host,
  406. ip: ip,
  407. localIp: localIp,
  408. caName: caName,
  409. caSha: caSha,
  410. }
  411. err := mf.nextCallReturn
  412. mf.nextCallReturn = nil
  413. return err
  414. }