config_test.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  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 name and by number, still ignore ports
  148. for _, tc := range []struct {
  149. given any
  150. want uint8
  151. }{
  152. {"icmp", iputil.IPProtocolICMP},
  153. {"icmpv6", iputil.IPProtocolICMPv6},
  154. {iputil.IPProtocolICMP, iputil.IPProtocolICMP},
  155. {iputil.IPProtocolICMPv6, iputil.IPProtocolICMPv6},
  156. } {
  157. conf = config.NewC(test.NewLogger())
  158. mf = &mockFirewall{}
  159. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": tc.given, "host": "a"}}}
  160. require.NoError(t, addRulesFromConfig(l, false, conf, mf), tc.given)
  161. assert.Equal(t, addRuleCall{incoming: false, proto: tc.want, startPort: PortAny, endPort: PortAny, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall, tc.given)
  162. }
  163. // Test protocols that aren't a known name or a number from 1 to 255
  164. for _, proto := range []string{"0", "256", "-1", "gre"} {
  165. conf = config.NewC(test.NewLogger())
  166. mf = &mockFirewall{}
  167. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "any", "proto": proto, "host": "a"}}}
  168. require.EqualError(t, addRulesFromConfig(l, false, conf, mf), "firewall.outbound rule #0; proto was not understood; `"+proto+"`")
  169. }
  170. // Test adding any rule
  171. conf = config.NewC(test.NewLogger())
  172. mf = &mockFirewall{}
  173. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "host": "a"}}}
  174. require.NoError(t, addRulesFromConfig(l, true, conf, mf))
  175. assert.Equal(t, addRuleCall{incoming: true, proto: ProtoAny, startPort: 1, endPort: 1, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
  176. // Test adding rule with cidr
  177. cidr := netip.MustParsePrefix("10.0.0.0/8")
  178. conf = config.NewC(test.NewLogger())
  179. mf = &mockFirewall{}
  180. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": cidr.String()}}}
  181. require.NoError(t, addRulesFromConfig(l, true, conf, mf))
  182. assert.Equal(t, addRuleCall{incoming: true, proto: ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: cidr.String(), localIp: ""}, mf.lastCall)
  183. // Test adding rule with local_cidr
  184. conf = config.NewC(test.NewLogger())
  185. mf = &mockFirewall{}
  186. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": cidr.String()}}}
  187. require.NoError(t, addRulesFromConfig(l, true, conf, mf))
  188. assert.Equal(t, addRuleCall{incoming: true, proto: ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "", localIp: cidr.String()}, mf.lastCall)
  189. // Test adding rule with cidr ipv6
  190. cidr6 := netip.MustParsePrefix("fd00::/8")
  191. conf = config.NewC(test.NewLogger())
  192. mf = &mockFirewall{}
  193. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": cidr6.String()}}}
  194. require.NoError(t, addRulesFromConfig(l, true, conf, mf))
  195. assert.Equal(t, addRuleCall{incoming: true, proto: ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: cidr6.String(), localIp: ""}, mf.lastCall)
  196. // Test adding rule with any cidr
  197. conf = config.NewC(test.NewLogger())
  198. mf = &mockFirewall{}
  199. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": "any"}}}
  200. require.NoError(t, addRulesFromConfig(l, true, conf, mf))
  201. assert.Equal(t, addRuleCall{incoming: true, proto: ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "any", localIp: ""}, mf.lastCall)
  202. // Test adding rule with junk cidr, which RulesBuilder rejects
  203. conf = config.NewC(test.NewLogger())
  204. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": "junk/junk"}}}
  205. 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")
  206. // Test adding rule with local_cidr ipv6
  207. conf = config.NewC(test.NewLogger())
  208. mf = &mockFirewall{}
  209. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": cidr6.String()}}}
  210. require.NoError(t, addRulesFromConfig(l, true, conf, mf))
  211. assert.Equal(t, addRuleCall{incoming: true, proto: ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "", localIp: cidr6.String()}, mf.lastCall)
  212. // Test adding rule with any local_cidr
  213. conf = config.NewC(test.NewLogger())
  214. mf = &mockFirewall{}
  215. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": "any"}}}
  216. require.NoError(t, addRulesFromConfig(l, true, conf, mf))
  217. assert.Equal(t, addRuleCall{incoming: true, proto: ProtoAny, startPort: 1, endPort: 1, groups: nil, localIp: "any"}, mf.lastCall)
  218. // Test adding rule with junk local_cidr, which RulesBuilder rejects
  219. conf = config.NewC(test.NewLogger())
  220. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": "junk/junk"}}}
  221. 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")
  222. // Test adding rule with ca_sha
  223. conf = config.NewC(test.NewLogger())
  224. mf = &mockFirewall{}
  225. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "ca_sha": "12312313123"}}}
  226. require.NoError(t, addRulesFromConfig(l, true, conf, mf))
  227. assert.Equal(t, addRuleCall{incoming: true, proto: ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "", localIp: "", caSha: "12312313123"}, mf.lastCall)
  228. // Test adding rule with ca_name
  229. conf = config.NewC(test.NewLogger())
  230. mf = &mockFirewall{}
  231. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "ca_name": "root01"}}}
  232. require.NoError(t, addRulesFromConfig(l, true, conf, mf))
  233. assert.Equal(t, addRuleCall{incoming: true, proto: ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "", localIp: "", caName: "root01"}, mf.lastCall)
  234. // Test single group
  235. conf = config.NewC(test.NewLogger())
  236. mf = &mockFirewall{}
  237. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "group": "a"}}}
  238. require.NoError(t, addRulesFromConfig(l, true, conf, mf))
  239. assert.Equal(t, addRuleCall{incoming: true, proto: ProtoAny, startPort: 1, endPort: 1, groups: []string{"a"}, ip: "", localIp: ""}, mf.lastCall)
  240. // Test single groups
  241. conf = config.NewC(test.NewLogger())
  242. mf = &mockFirewall{}
  243. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "groups": "a"}}}
  244. require.NoError(t, addRulesFromConfig(l, true, conf, mf))
  245. assert.Equal(t, addRuleCall{incoming: true, proto: ProtoAny, startPort: 1, endPort: 1, groups: []string{"a"}, ip: "", localIp: ""}, mf.lastCall)
  246. // Test multiple AND groups
  247. conf = config.NewC(test.NewLogger())
  248. mf = &mockFirewall{}
  249. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "groups": []string{"a", "b"}}}}
  250. require.NoError(t, addRulesFromConfig(l, true, conf, mf))
  251. assert.Equal(t, addRuleCall{incoming: true, proto: ProtoAny, startPort: 1, endPort: 1, groups: []string{"a", "b"}, ip: "", localIp: ""}, mf.lastCall)
  252. // Test group and groups values that used to crash: an empty group array is rejected, groups entries that
  253. // aren't strings are read as strings, and a null groups is no groups
  254. conf = config.NewC(test.NewLogger())
  255. mf = &mockFirewall{}
  256. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "group": []any{}}}}
  257. require.EqualError(t, addRulesFromConfig(l, true, conf, mf), "firewall.inbound rule #0; group should contain a single value, an empty array was provided")
  258. conf = config.NewC(test.NewLogger())
  259. mf = &mockFirewall{}
  260. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "groups": []any{1, 2}}}}
  261. require.NoError(t, addRulesFromConfig(l, true, conf, mf))
  262. assert.Equal(t, []string{"1", "2"}, mf.lastCall.groups)
  263. conf = config.NewC(test.NewLogger())
  264. mf = &mockFirewall{}
  265. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "groups": nil, "host": "a"}}}
  266. require.NoError(t, addRulesFromConfig(l, true, conf, mf))
  267. assert.Empty(t, mf.lastCall.groups)
  268. assert.Equal(t, "a", mf.lastCall.host)
  269. // Test Add error
  270. conf = config.NewC(test.NewLogger())
  271. mf = &mockFirewall{}
  272. mf.nextCallReturn = errors.New("test error")
  273. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "host": "a"}}}
  274. require.EqualError(t, addRulesFromConfig(l, true, conf, mf), "firewall.inbound rule #0; test error")
  275. }
  276. // TestRulesFromConfig_defaultLocalCIDR ensures rules without a local_cidr allow only the vpn networks when there are
  277. // unsafe networks, unless default_local_cidr_any is set, and that a rule's own local_cidr is kept either way
  278. func TestRulesFromConfig_defaultLocalCIDR(t *testing.T) {
  279. vpnNetworks := []netip.Prefix{netip.MustParsePrefix("10.1.0.5/16")}
  280. unsafeNetworks := []netip.Prefix{netip.MustParsePrefix("192.168.0.0/24")}
  281. vpnAddr := netip.MustParseAddr("10.1.9.9")
  282. unsafeAddr := netip.MustParseAddr("192.168.0.3")
  283. // ruleOnPort returns the rule on proto whose port clause is exactly port, chosen from the rules that a packet
  284. // on port is checked against.
  285. ruleOnPort := func(t *testing.T, table *Table, proto uint8, port int32) *rule {
  286. t.Helper()
  287. for _, r := range table.rulesAt(proto, port) {
  288. if r.startPort == port {
  289. return r
  290. }
  291. }
  292. require.Failf(t, "no rule found", "proto %d port %d", proto, port)
  293. return nil
  294. }
  295. for _, tc := range []struct {
  296. name string
  297. unsafeNetworks []netip.Prefix
  298. defaultLocalCIDRAny bool
  299. wantUnsafe bool
  300. }{
  301. {"no unsafe networks", nil, false, true},
  302. {"unsafe networks", unsafeNetworks, false, false},
  303. {"unsafe networks and default_local_cidr_any", unsafeNetworks, true, true},
  304. } {
  305. t.Run(tc.name, func(t *testing.T) {
  306. conf := config.NewC(test.NewLogger())
  307. conf.Settings["firewall"] = map[string]any{
  308. "default_local_cidr_any": tc.defaultLocalCIDRAny,
  309. "inbound": []any{
  310. map[string]any{"port": "any", "proto": "any", "host": "any"},
  311. map[string]any{"port": "22", "proto": "tcp", "host": "any"},
  312. map[string]any{"port": "80", "proto": "tcp", "host": "any", "local_cidr": "192.168.0.0/24"},
  313. map[string]any{"port": "443", "proto": "tcp", "host": "any", "local_cidr": "any"},
  314. },
  315. }
  316. rules, err := RulesFromConfig(test.NewLogger(), conf, vpnNetworks, tc.unsafeNetworks)
  317. require.NoError(t, err)
  318. // The proto `any` rule, as indexed by a protocol with rules of its own and as shared by one without.
  319. for _, proto := range []uint8{iputil.IPProtocolTCP, 47} {
  320. r := ruleOnPort(t, rules.In, proto, PortAny)
  321. assert.True(t, r.matchLocal(vpnAddr), "proto %d", proto)
  322. assert.Equal(t, tc.wantUnsafe, r.matchLocal(unsafeAddr), "proto %d", proto)
  323. }
  324. r := ruleOnPort(t, rules.In, iputil.IPProtocolTCP, 22)
  325. assert.True(t, r.matchLocal(vpnAddr))
  326. assert.Equal(t, tc.wantUnsafe, r.matchLocal(unsafeAddr))
  327. r = ruleOnPort(t, rules.In, iputil.IPProtocolTCP, 80)
  328. assert.False(t, r.matchLocal(vpnAddr))
  329. assert.True(t, r.matchLocal(unsafeAddr))
  330. r = ruleOnPort(t, rules.In, iputil.IPProtocolTCP, 443)
  331. assert.True(t, r.matchLocal(vpnAddr))
  332. assert.True(t, r.matchLocal(unsafeAddr))
  333. })
  334. }
  335. }
  336. func Test_convertRule(t *testing.T) {
  337. ob := &bytes.Buffer{}
  338. l := test.NewLoggerWithOutput(ob)
  339. // Ensure group array of 1 is converted and a warning is printed
  340. c := map[string]any{
  341. "group": []any{"group1"},
  342. }
  343. r, err := yamlToConfigRule(l, c, "test", 1)
  344. assert.Contains(t, ob.String(), "group was an array with a single value, converting to simple value")
  345. assert.Contains(t, ob.String(), "table=test")
  346. assert.Contains(t, ob.String(), "rule=1")
  347. require.NoError(t, err)
  348. assert.Equal(t, []string{"group1"}, r.Groups)
  349. // Ensure group array of > 1 is errord
  350. ob.Reset()
  351. c = map[string]any{
  352. "group": []any{"group1", "group2"},
  353. }
  354. r, err = yamlToConfigRule(l, c, "test", 1)
  355. assert.Empty(t, ob.String())
  356. require.Error(t, err, "group should contain a single value, an array with more than one entry was provided")
  357. // Make sure a well formed group is alright
  358. ob.Reset()
  359. c = map[string]any{
  360. "group": "group1",
  361. }
  362. r, err = yamlToConfigRule(l, c, "test", 1)
  363. require.NoError(t, err)
  364. assert.Equal(t, []string{"group1"}, r.Groups)
  365. }
  366. func Test_convertRuleSanity(t *testing.T) {
  367. ob := &bytes.Buffer{}
  368. l := test.NewLoggerWithOutput(ob)
  369. noWarningPlease := []map[string]any{
  370. {"group": "group1"},
  371. {"groups": []any{"group2"}},
  372. {"host": "bob"},
  373. {"cidr": "1.1.1.1/1"},
  374. {"groups": []any{"group2"}, "host": "bob"},
  375. {"cidr": "1.1.1.1/1", "host": "bob"},
  376. {"groups": []any{"group2"}, "cidr": "1.1.1.1/1"},
  377. {"groups": []any{"group2"}, "cidr": "1.1.1.1/1", "host": "bob"},
  378. }
  379. for _, c := range noWarningPlease {
  380. r, err := yamlToConfigRule(l, c, "test", 1)
  381. require.NoError(t, err)
  382. require.NoError(t, r.sanity(), "should not generate a sanity warning, %+v", c)
  383. }
  384. yesWarningPlease := []map[string]any{
  385. {"group": "group1"},
  386. {"groups": []any{"group2"}},
  387. {"cidr": "1.1.1.1/1"},
  388. {"groups": []any{"group2"}, "host": "bob"},
  389. {"cidr": "1.1.1.1/1", "host": "bob"},
  390. {"groups": []any{"group2"}, "cidr": "1.1.1.1/1"},
  391. {"groups": []any{"group2"}, "cidr": "1.1.1.1/1", "host": "bob"},
  392. }
  393. for _, c := range yesWarningPlease {
  394. c["host"] = "any"
  395. r, err := yamlToConfigRule(l, c, "test", 1)
  396. require.NoError(t, err)
  397. err = r.sanity()
  398. require.Error(t, err, "I wanted a warning: %+v", c)
  399. }
  400. //reset the list
  401. yesWarningPlease = []map[string]any{
  402. {"group": "group1"},
  403. {"groups": []any{"group2"}},
  404. {"cidr": "1.1.1.1/1"},
  405. {"groups": []any{"group2"}, "host": "bob"},
  406. {"cidr": "1.1.1.1/1", "host": "bob"},
  407. {"groups": []any{"group2"}, "cidr": "1.1.1.1/1"},
  408. {"groups": []any{"group2"}, "cidr": "1.1.1.1/1", "host": "bob"},
  409. }
  410. for _, c := range yesWarningPlease {
  411. r, err := yamlToConfigRule(l, c, "test", 1)
  412. require.NoError(t, err)
  413. r.Groups = append(r.Groups, "any")
  414. err = r.sanity()
  415. require.Error(t, err, "I wanted a warning: %+v", c)
  416. }
  417. }
  418. type addRuleCall struct {
  419. incoming bool
  420. proto uint8
  421. startPort int32
  422. endPort int32
  423. groups []string
  424. host string
  425. ip string
  426. localIp string
  427. caName string
  428. caSha string
  429. }
  430. type mockFirewall struct {
  431. lastCall addRuleCall
  432. nextCallReturn error
  433. }
  434. func (mf *mockFirewall) AddRule(incoming bool, proto uint8, startPort int32, endPort int32, groups []string, host string, ip, localIp, caName string, caSha string) error {
  435. mf.lastCall = addRuleCall{
  436. incoming: incoming,
  437. proto: proto,
  438. startPort: startPort,
  439. endPort: endPort,
  440. groups: groups,
  441. host: host,
  442. ip: ip,
  443. localIp: localIp,
  444. caName: caName,
  445. caSha: caSha,
  446. }
  447. err := mf.nextCallReturn
  448. mf.nextCallReturn = nil
  449. return err
  450. }