1
0

firewall_test.go 57 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593
  1. package nebula
  2. import (
  3. "bytes"
  4. "errors"
  5. "log/slog"
  6. "math"
  7. "net/netip"
  8. "testing"
  9. "time"
  10. "github.com/gaissmai/bart"
  11. "github.com/slackhq/nebula/cert"
  12. "github.com/slackhq/nebula/config"
  13. "github.com/slackhq/nebula/firewall"
  14. "github.com/slackhq/nebula/test"
  15. "github.com/stretchr/testify/assert"
  16. "github.com/stretchr/testify/require"
  17. )
  18. func TestNewFirewall(t *testing.T) {
  19. l := test.NewLogger()
  20. c := &dummyCert{}
  21. fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c)
  22. conntrack := fw.Conntrack
  23. assert.NotNil(t, conntrack)
  24. assert.NotNil(t, conntrack.Conns)
  25. assert.NotNil(t, conntrack.TimerWheel)
  26. assert.NotNil(t, fw.InRules)
  27. assert.NotNil(t, fw.OutRules)
  28. assert.Equal(t, time.Second, fw.TCPTimeout)
  29. assert.Equal(t, time.Minute, fw.UDPTimeout)
  30. assert.Equal(t, time.Hour, fw.DefaultTimeout)
  31. assert.Equal(t, time.Hour, conntrack.TimerWheel.wheelDuration)
  32. assert.Equal(t, time.Hour, conntrack.TimerWheel.wheelDuration)
  33. assert.Equal(t, 3602, conntrack.TimerWheel.wheelLen)
  34. fw = NewFirewall(l, time.Second, time.Hour, time.Minute, c)
  35. assert.Equal(t, time.Hour, conntrack.TimerWheel.wheelDuration)
  36. assert.Equal(t, 3602, conntrack.TimerWheel.wheelLen)
  37. fw = NewFirewall(l, time.Hour, time.Second, time.Minute, c)
  38. assert.Equal(t, time.Hour, conntrack.TimerWheel.wheelDuration)
  39. assert.Equal(t, 3602, conntrack.TimerWheel.wheelLen)
  40. fw = NewFirewall(l, time.Hour, time.Minute, time.Second, c)
  41. assert.Equal(t, time.Hour, conntrack.TimerWheel.wheelDuration)
  42. assert.Equal(t, 3602, conntrack.TimerWheel.wheelLen)
  43. fw = NewFirewall(l, time.Minute, time.Hour, time.Second, c)
  44. assert.Equal(t, time.Hour, conntrack.TimerWheel.wheelDuration)
  45. assert.Equal(t, 3602, conntrack.TimerWheel.wheelLen)
  46. fw = NewFirewall(l, time.Minute, time.Second, time.Hour, c)
  47. assert.Equal(t, time.Hour, conntrack.TimerWheel.wheelDuration)
  48. assert.Equal(t, 3602, conntrack.TimerWheel.wheelLen)
  49. }
  50. func TestFirewall_AddRule(t *testing.T) {
  51. ob := &bytes.Buffer{}
  52. l := test.NewLoggerWithOutput(ob)
  53. c := &dummyCert{}
  54. fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c)
  55. assert.NotNil(t, fw.InRules)
  56. assert.NotNil(t, fw.OutRules)
  57. ti, err := netip.ParsePrefix("1.2.3.4/32")
  58. require.NoError(t, err)
  59. ti6, err := netip.ParsePrefix("fd12::34/128")
  60. require.NoError(t, err)
  61. require.NoError(t, fw.AddRule(true, firewall.ProtoTCP, 1, 1, []string{}, "", "", "", "", ""))
  62. // An empty rule is any
  63. assert.True(t, fw.InRules.TCP[1].Any.Any.Any)
  64. assert.Empty(t, fw.InRules.TCP[1].Any.Groups)
  65. assert.Empty(t, fw.InRules.TCP[1].Any.Hosts)
  66. fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
  67. require.NoError(t, fw.AddRule(true, firewall.ProtoUDP, 1, 1, []string{"g1"}, "", "", "", "", ""))
  68. assert.Nil(t, fw.InRules.UDP[1].Any.Any)
  69. assert.Contains(t, fw.InRules.UDP[1].Any.Groups[0].Groups, "g1")
  70. assert.Empty(t, fw.InRules.UDP[1].Any.Hosts)
  71. fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
  72. require.NoError(t, fw.AddRule(true, firewall.ProtoICMP, 1, 1, []string{}, "h1", "", "", "", ""))
  73. //no matter what port is given for icmp, it should end up as "any"
  74. assert.Nil(t, fw.InRules.ICMP[firewall.PortAny].Any.Any)
  75. assert.Empty(t, fw.InRules.ICMP[firewall.PortAny].Any.Groups)
  76. assert.Contains(t, fw.InRules.ICMP[firewall.PortAny].Any.Hosts, "h1")
  77. fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
  78. require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 1, 1, []string{}, "", ti.String(), "", "", ""))
  79. assert.Nil(t, fw.OutRules.AnyProto[1].Any.Any)
  80. _, ok := fw.OutRules.AnyProto[1].Any.CIDR.Get(ti)
  81. assert.True(t, ok)
  82. fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
  83. require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 1, 1, []string{}, "", ti6.String(), "", "", ""))
  84. assert.Nil(t, fw.OutRules.AnyProto[1].Any.Any)
  85. _, ok = fw.OutRules.AnyProto[1].Any.CIDR.Get(ti6)
  86. assert.True(t, ok)
  87. fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
  88. require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 1, 1, []string{}, "", "", ti.String(), "", ""))
  89. assert.NotNil(t, fw.OutRules.AnyProto[1].Any.Any)
  90. ok = fw.OutRules.AnyProto[1].Any.Any.LocalCIDR.Get(ti)
  91. assert.True(t, ok)
  92. fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
  93. require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 1, 1, []string{}, "", "", ti6.String(), "", ""))
  94. assert.NotNil(t, fw.OutRules.AnyProto[1].Any.Any)
  95. ok = fw.OutRules.AnyProto[1].Any.Any.LocalCIDR.Get(ti6)
  96. assert.True(t, ok)
  97. fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
  98. require.NoError(t, fw.AddRule(true, firewall.ProtoUDP, 1, 1, []string{"g1"}, "", "", "", "ca-name", ""))
  99. assert.Contains(t, fw.InRules.UDP[1].CANames, "ca-name")
  100. fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
  101. require.NoError(t, fw.AddRule(true, firewall.ProtoUDP, 1, 1, []string{"g1"}, "", "", "", "", "ca-sha"))
  102. assert.Contains(t, fw.InRules.UDP[1].CAShas, "ca-sha")
  103. fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
  104. require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, "any", "", "", "", ""))
  105. assert.True(t, fw.OutRules.AnyProto[0].Any.Any.Any)
  106. fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
  107. anyIp, err := netip.ParsePrefix("0.0.0.0/0")
  108. require.NoError(t, err)
  109. require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, "", anyIp.String(), "", "", ""))
  110. assert.Nil(t, fw.OutRules.AnyProto[0].Any.Any)
  111. table, ok := fw.OutRules.AnyProto[0].Any.CIDR.Lookup(netip.MustParseAddr("1.1.1.1"))
  112. assert.True(t, table.Any)
  113. table, ok = fw.OutRules.AnyProto[0].Any.CIDR.Lookup(netip.MustParseAddr("9::9"))
  114. assert.False(t, ok)
  115. fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
  116. anyIp6, err := netip.ParsePrefix("::/0")
  117. require.NoError(t, err)
  118. require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, "", anyIp6.String(), "", "", ""))
  119. assert.Nil(t, fw.OutRules.AnyProto[0].Any.Any)
  120. table, ok = fw.OutRules.AnyProto[0].Any.CIDR.Lookup(netip.MustParseAddr("9::9"))
  121. assert.True(t, table.Any)
  122. table, ok = fw.OutRules.AnyProto[0].Any.CIDR.Lookup(netip.MustParseAddr("1.1.1.1"))
  123. assert.False(t, ok)
  124. fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
  125. require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, "", "any", "", "", ""))
  126. assert.True(t, fw.OutRules.AnyProto[0].Any.Any.Any)
  127. fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
  128. require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, "", "", anyIp.String(), "", ""))
  129. assert.False(t, fw.OutRules.AnyProto[0].Any.Any.Any)
  130. assert.True(t, fw.OutRules.AnyProto[0].Any.Any.LocalCIDR.Lookup(netip.MustParseAddr("1.1.1.1")))
  131. assert.False(t, fw.OutRules.AnyProto[0].Any.Any.LocalCIDR.Lookup(netip.MustParseAddr("9::9")))
  132. fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
  133. require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, "", "", anyIp6.String(), "", ""))
  134. assert.False(t, fw.OutRules.AnyProto[0].Any.Any.Any)
  135. assert.True(t, fw.OutRules.AnyProto[0].Any.Any.LocalCIDR.Lookup(netip.MustParseAddr("9::9")))
  136. assert.False(t, fw.OutRules.AnyProto[0].Any.Any.LocalCIDR.Lookup(netip.MustParseAddr("1.1.1.1")))
  137. fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
  138. require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, "", "", "any", "", ""))
  139. assert.True(t, fw.OutRules.AnyProto[0].Any.Any.Any)
  140. // Test error conditions
  141. fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
  142. require.Error(t, fw.AddRule(true, math.MaxUint8, 0, 0, []string{}, "", "", "", "", ""))
  143. require.Error(t, fw.AddRule(true, firewall.ProtoAny, 10, 0, []string{}, "", "", "", "", ""))
  144. }
  145. func TestFirewall_Drop(t *testing.T) {
  146. ob := &bytes.Buffer{}
  147. l := test.NewLoggerWithOutput(ob)
  148. myVpnNetworksTable := new(bart.Lite)
  149. myVpnNetworksTable.Insert(netip.MustParsePrefix("1.1.1.1/8"))
  150. p := firewall.Packet{
  151. LocalAddr: netip.MustParseAddr("1.2.3.4"),
  152. RemoteAddr: netip.MustParseAddr("1.2.3.4"),
  153. LocalPort: 10,
  154. RemotePort: 90,
  155. Protocol: firewall.ProtoUDP,
  156. Fragment: false,
  157. }
  158. c := dummyCert{
  159. name: "host1",
  160. networks: []netip.Prefix{netip.MustParsePrefix("1.2.3.4/24")},
  161. groups: []string{"default-group"},
  162. issuer: "signer-shasum",
  163. }
  164. h := HostInfo{
  165. ConnectionState: &ConnectionState{
  166. peerCert: &cert.CachedCertificate{
  167. Certificate: &c,
  168. InvertedGroups: map[string]struct{}{"default-group": {}},
  169. },
  170. },
  171. vpnAddrs: []netip.Addr{netip.MustParseAddr("1.2.3.4")},
  172. }
  173. h.buildNetworks(myVpnNetworksTable, &c)
  174. fw := NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
  175. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"any"}, "", "", "", "", ""))
  176. cp := cert.NewCAPool()
  177. // Drop outbound
  178. assert.Equal(t, ErrNoMatchingRule, fw.Drop(p, false, &h, cp, nil))
  179. // Allow inbound
  180. resetConntrack(fw)
  181. require.NoError(t, fw.Drop(p, true, &h, cp, nil))
  182. // Allow outbound because conntrack
  183. require.NoError(t, fw.Drop(p, false, &h, cp, nil))
  184. // test remote mismatch
  185. oldRemote := p.RemoteAddr
  186. p.RemoteAddr = netip.MustParseAddr("1.2.3.10")
  187. assert.Equal(t, fw.Drop(p, false, &h, cp, nil), ErrInvalidRemoteIP)
  188. p.RemoteAddr = oldRemote
  189. // ensure signer doesn't get in the way of group checks
  190. fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
  191. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "", "signer-shasum"))
  192. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "", "signer-shasum-bad"))
  193. assert.Equal(t, fw.Drop(p, true, &h, cp, nil), ErrNoMatchingRule)
  194. // test caSha doesn't drop on match
  195. fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
  196. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "", "signer-shasum-bad"))
  197. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "", "signer-shasum"))
  198. require.NoError(t, fw.Drop(p, true, &h, cp, nil))
  199. // ensure ca name doesn't get in the way of group checks
  200. cp.CAs["signer-shasum"] = &cert.CachedCertificate{Certificate: &dummyCert{name: "ca-good"}}
  201. fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
  202. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "ca-good", ""))
  203. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "ca-good-bad", ""))
  204. assert.Equal(t, fw.Drop(p, true, &h, cp, nil), ErrNoMatchingRule)
  205. // test caName doesn't drop on match
  206. cp.CAs["signer-shasum"] = &cert.CachedCertificate{Certificate: &dummyCert{name: "ca-good"}}
  207. fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
  208. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "ca-good-bad", ""))
  209. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "ca-good", ""))
  210. require.NoError(t, fw.Drop(p, true, &h, cp, nil))
  211. }
  212. func TestFirewall_DropV6(t *testing.T) {
  213. ob := &bytes.Buffer{}
  214. l := test.NewLoggerWithOutput(ob)
  215. myVpnNetworksTable := new(bart.Lite)
  216. myVpnNetworksTable.Insert(netip.MustParsePrefix("fd00::/7"))
  217. p := firewall.Packet{
  218. LocalAddr: netip.MustParseAddr("fd12::34"),
  219. RemoteAddr: netip.MustParseAddr("fd12::34"),
  220. LocalPort: 10,
  221. RemotePort: 90,
  222. Protocol: firewall.ProtoUDP,
  223. Fragment: false,
  224. }
  225. c := dummyCert{
  226. name: "host1",
  227. networks: []netip.Prefix{netip.MustParsePrefix("fd12::34/120")},
  228. groups: []string{"default-group"},
  229. issuer: "signer-shasum",
  230. }
  231. h := HostInfo{
  232. ConnectionState: &ConnectionState{
  233. peerCert: &cert.CachedCertificate{
  234. Certificate: &c,
  235. InvertedGroups: map[string]struct{}{"default-group": {}},
  236. },
  237. },
  238. vpnAddrs: []netip.Addr{netip.MustParseAddr("fd12::34")},
  239. }
  240. h.buildNetworks(myVpnNetworksTable, &c)
  241. fw := NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
  242. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"any"}, "", "", "", "", ""))
  243. cp := cert.NewCAPool()
  244. // Drop outbound
  245. assert.Equal(t, ErrNoMatchingRule, fw.Drop(p, false, &h, cp, nil))
  246. // Allow inbound
  247. resetConntrack(fw)
  248. require.NoError(t, fw.Drop(p, true, &h, cp, nil))
  249. // Allow outbound because conntrack
  250. require.NoError(t, fw.Drop(p, false, &h, cp, nil))
  251. // test remote mismatch
  252. oldRemote := p.RemoteAddr
  253. p.RemoteAddr = netip.MustParseAddr("fd12::56")
  254. assert.Equal(t, fw.Drop(p, false, &h, cp, nil), ErrInvalidRemoteIP)
  255. p.RemoteAddr = oldRemote
  256. // ensure signer doesn't get in the way of group checks
  257. fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
  258. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "", "signer-shasum"))
  259. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "", "signer-shasum-bad"))
  260. assert.Equal(t, fw.Drop(p, true, &h, cp, nil), ErrNoMatchingRule)
  261. // test caSha doesn't drop on match
  262. fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
  263. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "", "signer-shasum-bad"))
  264. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "", "signer-shasum"))
  265. require.NoError(t, fw.Drop(p, true, &h, cp, nil))
  266. // ensure ca name doesn't get in the way of group checks
  267. cp.CAs["signer-shasum"] = &cert.CachedCertificate{Certificate: &dummyCert{name: "ca-good"}}
  268. fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
  269. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "ca-good", ""))
  270. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "ca-good-bad", ""))
  271. assert.Equal(t, fw.Drop(p, true, &h, cp, nil), ErrNoMatchingRule)
  272. // test caName doesn't drop on match
  273. cp.CAs["signer-shasum"] = &cert.CachedCertificate{Certificate: &dummyCert{name: "ca-good"}}
  274. fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
  275. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "ca-good-bad", ""))
  276. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "ca-good", ""))
  277. require.NoError(t, fw.Drop(p, true, &h, cp, nil))
  278. }
  279. func BenchmarkFirewallTable_match(b *testing.B) {
  280. f := &Firewall{}
  281. ft := FirewallTable{
  282. TCP: firewallPort{},
  283. }
  284. pfix := netip.MustParsePrefix("172.1.1.1/32")
  285. _ = ft.TCP.addRule(f, 10, 10, []string{"good-group"}, "good-host", pfix.String(), "", "", "")
  286. _ = ft.TCP.addRule(f, 100, 100, []string{"good-group"}, "good-host", "", pfix.String(), "", "")
  287. pfix6 := netip.MustParsePrefix("fd11::11/128")
  288. _ = ft.TCP.addRule(f, 10, 10, []string{"good-group"}, "good-host", pfix6.String(), "", "", "")
  289. _ = ft.TCP.addRule(f, 100, 100, []string{"good-group"}, "good-host", "", pfix6.String(), "", "")
  290. cp := cert.NewCAPool()
  291. b.Run("fail on proto", func(b *testing.B) {
  292. // This benchmark is showing us the cost of failing to match the protocol
  293. c := &cert.CachedCertificate{
  294. Certificate: &dummyCert{},
  295. }
  296. for n := 0; n < b.N; n++ {
  297. assert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoUDP}, true, c, cp))
  298. }
  299. })
  300. b.Run("pass proto, fail on port", func(b *testing.B) {
  301. // This benchmark is showing us the cost of matching a specific protocol but failing to match the port
  302. c := &cert.CachedCertificate{
  303. Certificate: &dummyCert{},
  304. }
  305. for n := 0; n < b.N; n++ {
  306. assert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 1}, true, c, cp))
  307. }
  308. })
  309. b.Run("pass proto, port, fail on local CIDR", func(b *testing.B) {
  310. c := &cert.CachedCertificate{
  311. Certificate: &dummyCert{},
  312. }
  313. ip := netip.MustParsePrefix("9.254.254.254/32")
  314. for n := 0; n < b.N; n++ {
  315. assert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 100, LocalAddr: ip.Addr()}, true, c, cp))
  316. }
  317. })
  318. b.Run("pass proto, port, fail on local CIDRv6", func(b *testing.B) {
  319. c := &cert.CachedCertificate{
  320. Certificate: &dummyCert{},
  321. }
  322. ip := netip.MustParsePrefix("fd99::99/128")
  323. for n := 0; n < b.N; n++ {
  324. assert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 100, LocalAddr: ip.Addr()}, true, c, cp))
  325. }
  326. })
  327. b.Run("pass proto, port, any local CIDR, fail all group, name, and cidr", func(b *testing.B) {
  328. c := &cert.CachedCertificate{
  329. Certificate: &dummyCert{
  330. name: "nope",
  331. networks: []netip.Prefix{netip.MustParsePrefix("9.254.254.245/32")},
  332. },
  333. InvertedGroups: map[string]struct{}{"nope": {}},
  334. }
  335. for n := 0; n < b.N; n++ {
  336. assert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 10}, true, c, cp))
  337. }
  338. })
  339. b.Run("pass proto, port, any local CIDRv6, fail all group, name, and cidr", func(b *testing.B) {
  340. c := &cert.CachedCertificate{
  341. Certificate: &dummyCert{
  342. name: "nope",
  343. networks: []netip.Prefix{netip.MustParsePrefix("fd99::99/128")},
  344. },
  345. InvertedGroups: map[string]struct{}{"nope": {}},
  346. }
  347. for n := 0; n < b.N; n++ {
  348. assert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 10}, true, c, cp))
  349. }
  350. })
  351. b.Run("pass proto, port, specific local CIDR, fail all group, name, and cidr", func(b *testing.B) {
  352. c := &cert.CachedCertificate{
  353. Certificate: &dummyCert{
  354. name: "nope",
  355. networks: []netip.Prefix{netip.MustParsePrefix("9.254.254.245/32")},
  356. },
  357. InvertedGroups: map[string]struct{}{"nope": {}},
  358. }
  359. for n := 0; n < b.N; n++ {
  360. assert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 100, LocalAddr: pfix.Addr()}, true, c, cp))
  361. }
  362. })
  363. b.Run("pass proto, port, specific local CIDRv6, fail all group, name, and cidr", func(b *testing.B) {
  364. c := &cert.CachedCertificate{
  365. Certificate: &dummyCert{
  366. name: "nope",
  367. networks: []netip.Prefix{netip.MustParsePrefix("fd99::99/128")},
  368. },
  369. InvertedGroups: map[string]struct{}{"nope": {}},
  370. }
  371. for n := 0; n < b.N; n++ {
  372. assert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 100, LocalAddr: pfix6.Addr()}, true, c, cp))
  373. }
  374. })
  375. b.Run("pass on group on any local cidr", func(b *testing.B) {
  376. c := &cert.CachedCertificate{
  377. Certificate: &dummyCert{
  378. name: "nope",
  379. },
  380. InvertedGroups: map[string]struct{}{"good-group": {}},
  381. }
  382. for n := 0; n < b.N; n++ {
  383. assert.True(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 10}, true, c, cp))
  384. }
  385. })
  386. b.Run("pass on group on specific local cidr", func(b *testing.B) {
  387. c := &cert.CachedCertificate{
  388. Certificate: &dummyCert{
  389. name: "nope",
  390. },
  391. InvertedGroups: map[string]struct{}{"good-group": {}},
  392. }
  393. for n := 0; n < b.N; n++ {
  394. assert.True(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 100, LocalAddr: pfix.Addr()}, true, c, cp))
  395. }
  396. })
  397. b.Run("pass on group on specific local cidr6", func(b *testing.B) {
  398. c := &cert.CachedCertificate{
  399. Certificate: &dummyCert{
  400. name: "nope",
  401. },
  402. InvertedGroups: map[string]struct{}{"good-group": {}},
  403. }
  404. for n := 0; n < b.N; n++ {
  405. assert.True(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 100, LocalAddr: pfix6.Addr()}, true, c, cp))
  406. }
  407. })
  408. b.Run("pass on name", func(b *testing.B) {
  409. c := &cert.CachedCertificate{
  410. Certificate: &dummyCert{
  411. name: "good-host",
  412. },
  413. InvertedGroups: map[string]struct{}{"nope": {}},
  414. }
  415. for n := 0; n < b.N; n++ {
  416. ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 10}, true, c, cp)
  417. }
  418. })
  419. }
  420. func TestFirewall_Drop2(t *testing.T) {
  421. ob := &bytes.Buffer{}
  422. l := test.NewLoggerWithOutput(ob)
  423. myVpnNetworksTable := new(bart.Lite)
  424. myVpnNetworksTable.Insert(netip.MustParsePrefix("1.1.1.1/8"))
  425. p := firewall.Packet{
  426. LocalAddr: netip.MustParseAddr("1.2.3.4"),
  427. RemoteAddr: netip.MustParseAddr("1.2.3.4"),
  428. LocalPort: 10,
  429. RemotePort: 90,
  430. Protocol: firewall.ProtoUDP,
  431. Fragment: false,
  432. }
  433. network := netip.MustParsePrefix("1.2.3.4/24")
  434. c := cert.CachedCertificate{
  435. Certificate: &dummyCert{
  436. name: "host1",
  437. networks: []netip.Prefix{network},
  438. },
  439. InvertedGroups: map[string]struct{}{"default-group": {}, "test-group": {}},
  440. }
  441. h := HostInfo{
  442. ConnectionState: &ConnectionState{
  443. peerCert: &c,
  444. },
  445. vpnAddrs: []netip.Addr{network.Addr()},
  446. }
  447. h.buildNetworks(myVpnNetworksTable, c.Certificate)
  448. c1 := cert.CachedCertificate{
  449. Certificate: &dummyCert{
  450. name: "host1",
  451. networks: []netip.Prefix{network},
  452. },
  453. InvertedGroups: map[string]struct{}{"default-group": {}, "test-group-not": {}},
  454. }
  455. h1 := HostInfo{
  456. vpnAddrs: []netip.Addr{network.Addr()},
  457. ConnectionState: &ConnectionState{
  458. peerCert: &c1,
  459. },
  460. }
  461. h1.buildNetworks(myVpnNetworksTable, c1.Certificate)
  462. fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
  463. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group", "test-group"}, "", "", "", "", ""))
  464. cp := cert.NewCAPool()
  465. // h1/c1 lacks the proper groups
  466. require.ErrorIs(t, fw.Drop(p, true, &h1, cp, nil), ErrNoMatchingRule)
  467. // c has the proper groups
  468. resetConntrack(fw)
  469. require.NoError(t, fw.Drop(p, true, &h, cp, nil))
  470. }
  471. func TestFirewall_Drop3(t *testing.T) {
  472. ob := &bytes.Buffer{}
  473. l := test.NewLoggerWithOutput(ob)
  474. myVpnNetworksTable := new(bart.Lite)
  475. myVpnNetworksTable.Insert(netip.MustParsePrefix("1.1.1.1/8"))
  476. p := firewall.Packet{
  477. LocalAddr: netip.MustParseAddr("1.2.3.4"),
  478. RemoteAddr: netip.MustParseAddr("1.2.3.4"),
  479. LocalPort: 1,
  480. RemotePort: 1,
  481. Protocol: firewall.ProtoUDP,
  482. Fragment: false,
  483. }
  484. network := netip.MustParsePrefix("1.2.3.4/24")
  485. c := cert.CachedCertificate{
  486. Certificate: &dummyCert{
  487. name: "host-owner",
  488. networks: []netip.Prefix{network},
  489. },
  490. }
  491. c1 := cert.CachedCertificate{
  492. Certificate: &dummyCert{
  493. name: "host1",
  494. networks: []netip.Prefix{network},
  495. issuer: "signer-sha-bad",
  496. },
  497. }
  498. h1 := HostInfo{
  499. ConnectionState: &ConnectionState{
  500. peerCert: &c1,
  501. },
  502. vpnAddrs: []netip.Addr{network.Addr()},
  503. }
  504. h1.buildNetworks(myVpnNetworksTable, c1.Certificate)
  505. c2 := cert.CachedCertificate{
  506. Certificate: &dummyCert{
  507. name: "host2",
  508. networks: []netip.Prefix{network},
  509. issuer: "signer-sha",
  510. },
  511. }
  512. h2 := HostInfo{
  513. ConnectionState: &ConnectionState{
  514. peerCert: &c2,
  515. },
  516. vpnAddrs: []netip.Addr{network.Addr()},
  517. }
  518. h2.buildNetworks(myVpnNetworksTable, c2.Certificate)
  519. c3 := cert.CachedCertificate{
  520. Certificate: &dummyCert{
  521. name: "host3",
  522. networks: []netip.Prefix{network},
  523. issuer: "signer-sha-bad",
  524. },
  525. }
  526. h3 := HostInfo{
  527. ConnectionState: &ConnectionState{
  528. peerCert: &c3,
  529. },
  530. vpnAddrs: []netip.Addr{network.Addr()},
  531. }
  532. h3.buildNetworks(myVpnNetworksTable, c3.Certificate)
  533. fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
  534. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, "host1", "", "", "", ""))
  535. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, "", "", "", "", "signer-sha"))
  536. cp := cert.NewCAPool()
  537. // c1 should pass because host match
  538. require.NoError(t, fw.Drop(p, true, &h1, cp, nil))
  539. // c2 should pass because ca sha match
  540. resetConntrack(fw)
  541. require.NoError(t, fw.Drop(p, true, &h2, cp, nil))
  542. // c3 should fail because no match
  543. resetConntrack(fw)
  544. assert.Equal(t, fw.Drop(p, true, &h3, cp, nil), ErrNoMatchingRule)
  545. // Test a remote address match
  546. fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
  547. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, "", "1.2.3.4/24", "", "", ""))
  548. require.NoError(t, fw.Drop(p, true, &h1, cp, nil))
  549. }
  550. func TestFirewall_Drop3V6(t *testing.T) {
  551. ob := &bytes.Buffer{}
  552. l := test.NewLoggerWithOutput(ob)
  553. myVpnNetworksTable := new(bart.Lite)
  554. myVpnNetworksTable.Insert(netip.MustParsePrefix("fd00::/7"))
  555. p := firewall.Packet{
  556. LocalAddr: netip.MustParseAddr("fd12::34"),
  557. RemoteAddr: netip.MustParseAddr("fd12::34"),
  558. LocalPort: 1,
  559. RemotePort: 1,
  560. Protocol: firewall.ProtoUDP,
  561. Fragment: false,
  562. }
  563. network := netip.MustParsePrefix("fd12::34/120")
  564. c := cert.CachedCertificate{
  565. Certificate: &dummyCert{
  566. name: "host-owner",
  567. networks: []netip.Prefix{network},
  568. },
  569. }
  570. h := HostInfo{
  571. ConnectionState: &ConnectionState{
  572. peerCert: &c,
  573. },
  574. vpnAddrs: []netip.Addr{network.Addr()},
  575. }
  576. h.buildNetworks(myVpnNetworksTable, c.Certificate)
  577. // Test a remote address match
  578. fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
  579. cp := cert.NewCAPool()
  580. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, "", "fd12::34/120", "", "", ""))
  581. require.NoError(t, fw.Drop(p, true, &h, cp, nil))
  582. }
  583. func TestFirewall_DropConntrackReload(t *testing.T) {
  584. ob := &bytes.Buffer{}
  585. l := test.NewLoggerWithOutput(ob)
  586. myVpnNetworksTable := new(bart.Lite)
  587. myVpnNetworksTable.Insert(netip.MustParsePrefix("1.1.1.1/8"))
  588. p := firewall.Packet{
  589. LocalAddr: netip.MustParseAddr("1.2.3.4"),
  590. RemoteAddr: netip.MustParseAddr("1.2.3.4"),
  591. LocalPort: 10,
  592. RemotePort: 90,
  593. Protocol: firewall.ProtoUDP,
  594. Fragment: false,
  595. }
  596. network := netip.MustParsePrefix("1.2.3.4/24")
  597. c := cert.CachedCertificate{
  598. Certificate: &dummyCert{
  599. name: "host1",
  600. networks: []netip.Prefix{network},
  601. groups: []string{"default-group"},
  602. issuer: "signer-shasum",
  603. },
  604. InvertedGroups: map[string]struct{}{"default-group": {}},
  605. }
  606. h := HostInfo{
  607. ConnectionState: &ConnectionState{
  608. peerCert: &c,
  609. },
  610. vpnAddrs: []netip.Addr{network.Addr()},
  611. }
  612. h.buildNetworks(myVpnNetworksTable, c.Certificate)
  613. fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
  614. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"any"}, "", "", "", "", ""))
  615. cp := cert.NewCAPool()
  616. // Drop outbound
  617. assert.Equal(t, fw.Drop(p, false, &h, cp, nil), ErrNoMatchingRule)
  618. // Allow inbound
  619. resetConntrack(fw)
  620. require.NoError(t, fw.Drop(p, true, &h, cp, nil))
  621. // Allow outbound because conntrack
  622. require.NoError(t, fw.Drop(p, false, &h, cp, nil))
  623. oldFw := fw
  624. fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
  625. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 10, 10, []string{"any"}, "", "", "", "", ""))
  626. fw.Conntrack = oldFw.Conntrack
  627. fw.rulesVersion = oldFw.rulesVersion + 1
  628. // Allow outbound because conntrack and new rules allow port 10
  629. require.NoError(t, fw.Drop(p, false, &h, cp, nil))
  630. oldFw = fw
  631. fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
  632. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 11, 11, []string{"any"}, "", "", "", "", ""))
  633. fw.Conntrack = oldFw.Conntrack
  634. fw.rulesVersion = oldFw.rulesVersion + 1
  635. // Drop outbound because conntrack doesn't match new ruleset
  636. assert.Equal(t, fw.Drop(p, false, &h, cp, nil), ErrNoMatchingRule)
  637. }
  638. func TestFirewall_ICMPPortBehavior(t *testing.T) {
  639. ob := &bytes.Buffer{}
  640. l := test.NewLoggerWithOutput(ob)
  641. myVpnNetworksTable := new(bart.Lite)
  642. myVpnNetworksTable.Insert(netip.MustParsePrefix("1.1.1.1/8"))
  643. network := netip.MustParsePrefix("1.2.3.4/24")
  644. c := cert.CachedCertificate{
  645. Certificate: &dummyCert{
  646. name: "host1",
  647. networks: []netip.Prefix{network},
  648. groups: []string{"default-group"},
  649. issuer: "signer-shasum",
  650. },
  651. InvertedGroups: map[string]struct{}{"default-group": {}},
  652. }
  653. h := HostInfo{
  654. ConnectionState: &ConnectionState{
  655. peerCert: &c,
  656. },
  657. vpnAddrs: []netip.Addr{network.Addr()},
  658. }
  659. h.buildNetworks(myVpnNetworksTable, c.Certificate)
  660. cp := cert.NewCAPool()
  661. templ := firewall.Packet{
  662. LocalAddr: netip.MustParseAddr("1.2.3.4"),
  663. RemoteAddr: netip.MustParseAddr("1.2.3.4"),
  664. Protocol: firewall.ProtoICMP,
  665. Fragment: false,
  666. }
  667. t.Run("ICMP allowed", func(t *testing.T) {
  668. fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
  669. require.NoError(t, fw.AddRule(true, firewall.ProtoICMP, 0, 0, []string{"any"}, "", "", "", "", ""))
  670. t.Run("zero ports", func(t *testing.T) {
  671. p := templ.Copy()
  672. p.LocalPort = 0
  673. p.RemotePort = 0
  674. // Drop outbound
  675. assert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
  676. // Allow inbound
  677. resetConntrack(fw)
  678. require.NoError(t, fw.Drop(*p, true, &h, cp, nil))
  679. //now also allow outbound
  680. require.NoError(t, fw.Drop(*p, false, &h, cp, nil))
  681. })
  682. t.Run("nonzero ports", func(t *testing.T) {
  683. p := templ.Copy()
  684. p.LocalPort = 0xabcd
  685. p.RemotePort = 0x1234
  686. // Drop outbound
  687. assert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
  688. // Allow inbound
  689. resetConntrack(fw)
  690. require.NoError(t, fw.Drop(*p, true, &h, cp, nil))
  691. //now also allow outbound
  692. require.NoError(t, fw.Drop(*p, false, &h, cp, nil))
  693. })
  694. })
  695. t.Run("Any proto, some ports allowed", func(t *testing.T) {
  696. fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
  697. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 80, 444, []string{"any"}, "", "", "", "", ""))
  698. t.Run("zero ports, still blocked", func(t *testing.T) {
  699. p := templ.Copy()
  700. p.LocalPort = 0
  701. p.RemotePort = 0
  702. // Drop outbound
  703. assert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
  704. // Allow inbound
  705. resetConntrack(fw)
  706. assert.Equal(t, fw.Drop(*p, true, &h, cp, nil), ErrNoMatchingRule)
  707. //now also allow outbound
  708. assert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
  709. })
  710. t.Run("nonzero ports, still blocked", func(t *testing.T) {
  711. p := templ.Copy()
  712. p.LocalPort = 0xabcd
  713. p.RemotePort = 0x1234
  714. // Drop outbound
  715. assert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
  716. // Allow inbound
  717. resetConntrack(fw)
  718. assert.Equal(t, fw.Drop(*p, true, &h, cp, nil), ErrNoMatchingRule)
  719. //now also allow outbound
  720. assert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
  721. })
  722. t.Run("nonzero, matching ports, still blocked", func(t *testing.T) {
  723. p := templ.Copy()
  724. p.LocalPort = 80
  725. p.RemotePort = 80
  726. // Drop outbound
  727. assert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
  728. // Allow inbound
  729. resetConntrack(fw)
  730. assert.Equal(t, fw.Drop(*p, true, &h, cp, nil), ErrNoMatchingRule)
  731. //now also allow outbound
  732. assert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
  733. })
  734. })
  735. t.Run("Any proto, any port", func(t *testing.T) {
  736. fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
  737. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"any"}, "", "", "", "", ""))
  738. t.Run("zero ports, allowed", func(t *testing.T) {
  739. resetConntrack(fw)
  740. p := templ.Copy()
  741. p.LocalPort = 0
  742. p.RemotePort = 0
  743. // Drop outbound
  744. assert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
  745. // Allow inbound
  746. resetConntrack(fw)
  747. require.NoError(t, fw.Drop(*p, true, &h, cp, nil))
  748. //now also allow outbound
  749. require.NoError(t, fw.Drop(*p, false, &h, cp, nil))
  750. })
  751. t.Run("nonzero ports, allowed", func(t *testing.T) {
  752. resetConntrack(fw)
  753. p := templ.Copy()
  754. p.LocalPort = 0xabcd
  755. p.RemotePort = 0x1234
  756. // Drop outbound
  757. assert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
  758. // Allow inbound
  759. resetConntrack(fw)
  760. require.NoError(t, fw.Drop(*p, true, &h, cp, nil))
  761. //now also allow outbound
  762. require.NoError(t, fw.Drop(*p, false, &h, cp, nil))
  763. //different ID is blocked
  764. p.RemotePort++
  765. require.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
  766. })
  767. })
  768. }
  769. func TestFirewall_DropIPSpoofing(t *testing.T) {
  770. ob := &bytes.Buffer{}
  771. l := test.NewLoggerWithOutput(ob)
  772. myVpnNetworksTable := new(bart.Lite)
  773. myVpnNetworksTable.Insert(netip.MustParsePrefix("192.0.2.1/24"))
  774. c := cert.CachedCertificate{
  775. Certificate: &dummyCert{
  776. name: "host-owner",
  777. networks: []netip.Prefix{netip.MustParsePrefix("192.0.2.1/24")},
  778. },
  779. }
  780. c1 := cert.CachedCertificate{
  781. Certificate: &dummyCert{
  782. name: "host",
  783. networks: []netip.Prefix{netip.MustParsePrefix("192.0.2.2/24")},
  784. unsafeNetworks: []netip.Prefix{netip.MustParsePrefix("198.51.100.0/24")},
  785. },
  786. }
  787. h1 := HostInfo{
  788. ConnectionState: &ConnectionState{
  789. peerCert: &c1,
  790. },
  791. vpnAddrs: []netip.Addr{c1.Certificate.Networks()[0].Addr()},
  792. }
  793. h1.buildNetworks(myVpnNetworksTable, c1.Certificate)
  794. fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
  795. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, "", "", "", "", ""))
  796. cp := cert.NewCAPool()
  797. // Packet spoofed by `c1`. Note that the remote addr is not a valid one.
  798. p := firewall.Packet{
  799. LocalAddr: netip.MustParseAddr("192.0.2.1"),
  800. RemoteAddr: netip.MustParseAddr("192.0.2.3"),
  801. LocalPort: 1,
  802. RemotePort: 1,
  803. Protocol: firewall.ProtoUDP,
  804. Fragment: false,
  805. }
  806. assert.Equal(t, fw.Drop(p, true, &h1, cp, nil), ErrInvalidRemoteIP)
  807. }
  808. func BenchmarkLookup(b *testing.B) {
  809. ml := func(m map[string]struct{}, a [][]string) {
  810. for n := 0; n < b.N; n++ {
  811. for _, sg := range a {
  812. found := false
  813. for _, g := range sg {
  814. if _, ok := m[g]; !ok {
  815. found = false
  816. break
  817. }
  818. found = true
  819. }
  820. if found {
  821. return
  822. }
  823. }
  824. }
  825. }
  826. b.Run("array to map best", func(b *testing.B) {
  827. m := map[string]struct{}{
  828. "1ne": {},
  829. "2wo": {},
  830. "3hr": {},
  831. "4ou": {},
  832. "5iv": {},
  833. "6ix": {},
  834. }
  835. a := [][]string{
  836. {"1ne", "2wo", "3hr", "4ou", "5iv", "6ix"},
  837. {"one", "2wo", "3hr", "4ou", "5iv", "6ix"},
  838. {"one", "two", "3hr", "4ou", "5iv", "6ix"},
  839. {"one", "two", "thr", "4ou", "5iv", "6ix"},
  840. {"one", "two", "thr", "fou", "5iv", "6ix"},
  841. {"one", "two", "thr", "fou", "fiv", "6ix"},
  842. {"one", "two", "thr", "fou", "fiv", "six"},
  843. }
  844. for n := 0; n < b.N; n++ {
  845. ml(m, a)
  846. }
  847. })
  848. b.Run("array to map worst", func(b *testing.B) {
  849. m := map[string]struct{}{
  850. "one": {},
  851. "two": {},
  852. "thr": {},
  853. "fou": {},
  854. "fiv": {},
  855. "six": {},
  856. }
  857. a := [][]string{
  858. {"1ne", "2wo", "3hr", "4ou", "5iv", "6ix"},
  859. {"one", "2wo", "3hr", "4ou", "5iv", "6ix"},
  860. {"one", "two", "3hr", "4ou", "5iv", "6ix"},
  861. {"one", "two", "thr", "4ou", "5iv", "6ix"},
  862. {"one", "two", "thr", "fou", "5iv", "6ix"},
  863. {"one", "two", "thr", "fou", "fiv", "6ix"},
  864. {"one", "two", "thr", "fou", "fiv", "six"},
  865. }
  866. for n := 0; n < b.N; n++ {
  867. ml(m, a)
  868. }
  869. })
  870. }
  871. func Test_parsePort(t *testing.T) {
  872. _, _, err := parsePort("")
  873. require.EqualError(t, err, "was not a number; ``")
  874. _, _, err = parsePort(" ")
  875. require.EqualError(t, err, "was not a number; ` `")
  876. _, _, err = parsePort("-")
  877. require.EqualError(t, err, "appears to be a range but could not be parsed; `-`")
  878. _, _, err = parsePort(" - ")
  879. require.EqualError(t, err, "appears to be a range but could not be parsed; ` - `")
  880. _, _, err = parsePort("a-b")
  881. require.EqualError(t, err, "beginning range was not a number; `a`")
  882. _, _, err = parsePort("1-b")
  883. require.EqualError(t, err, "ending range was not a number; `b`")
  884. s, e, err := parsePort(" 1 - 2 ")
  885. assert.Equal(t, int32(1), s)
  886. assert.Equal(t, int32(2), e)
  887. require.NoError(t, err)
  888. s, e, err = parsePort("0-1")
  889. assert.Equal(t, int32(0), s)
  890. assert.Equal(t, int32(0), e)
  891. require.NoError(t, err)
  892. s, e, err = parsePort("9919")
  893. assert.Equal(t, int32(9919), s)
  894. assert.Equal(t, int32(9919), e)
  895. require.NoError(t, err)
  896. s, e, err = parsePort("any")
  897. assert.Equal(t, int32(0), s)
  898. assert.Equal(t, int32(0), e)
  899. require.NoError(t, err)
  900. }
  901. // Test_parsePort_invalid covers inputs that must error. The named bug is
  902. // that int32(strconv.Atoi("4294967296")) truncates to 0 == firewall.PortAny,
  903. // silently turning a typo into a match-all-ports rule; the rest are
  904. // representative syntax/range probes.
  905. func Test_parsePort_invalid(t *testing.T) {
  906. tests := []struct {
  907. name string
  908. input string
  909. wantErrContains string
  910. }{
  911. // Numeric overflow (the named bug + boundary).
  912. {"named bug: 2^32 truncates to PortAny", "4294967296", "out of range"},
  913. {"just above max real port", "65536", "out of range"},
  914. // Negatives route through the range branch and hit the empty-half
  915. // guard; included as defense in depth so a future refactor cannot
  916. // accidentally reach the int32 cast.
  917. {"negative", "-1", "could not be parsed"},
  918. // Syntax probes.
  919. {"NUL between digits", "4\x002", "was not a number"},
  920. {"hex notation", "0x10", "was not a number"},
  921. {"scientific notation", "1e3", "was not a number"},
  922. {"leading whitespace", " 42", "was not a number"},
  923. {"fullwidth digits", "42", "was not a number"},
  924. // Range branch.
  925. {"range upper out of range", "1-65536", "ending range out of range"},
  926. {"range lower out of range", "65536-65537", "beginning range out of range"},
  927. {"range with negative upper", "1--1", "ending range was not a number"},
  928. }
  929. for _, tc := range tests {
  930. t.Run(tc.name, func(t *testing.T) {
  931. _, _, err := parsePort(tc.input)
  932. require.Error(t, err, "input %q must error", tc.input)
  933. require.ErrorContains(t, err, tc.wantErrContains)
  934. })
  935. }
  936. }
  937. // Test_parsePort_valid_boundaries locks in success cases at 0, 1, and 65535
  938. // so a future refactor cannot regress the boundaries.
  939. func Test_parsePort_valid_boundaries(t *testing.T) {
  940. tests := []struct {
  941. name string
  942. input string
  943. wantStart int32
  944. wantEnd int32
  945. }{
  946. {"zero is PortAny", "0", 0, 0},
  947. {"min real port", "1", 1, 1},
  948. {"max real port", "65535", 65535, 65535},
  949. {"range zero to max forces end to zero", "0-65535", 0, 0},
  950. {"range max to max", "65535-65535", 65535, 65535},
  951. {"range one to max", "1-65535", 1, 65535},
  952. {"range with whitespace inside", " 1 - 2 ", 1, 2},
  953. }
  954. for _, tc := range tests {
  955. t.Run(tc.name, func(t *testing.T) {
  956. s, e, err := parsePort(tc.input)
  957. require.NoError(t, err)
  958. assert.Equal(t, tc.wantStart, s, "start port")
  959. assert.Equal(t, tc.wantEnd, e, "end port")
  960. })
  961. }
  962. }
  963. func TestNewFirewallFromConfig(t *testing.T) {
  964. l := test.NewLogger()
  965. // Test a bad rule definition
  966. c := &dummyCert{}
  967. cs, err := newCertState(cert.Version2, nil, c, false, cert.Curve_CURVE25519, nil, "aes")
  968. require.NoError(t, err)
  969. conf := config.NewC(test.NewLogger())
  970. conf.Settings["firewall"] = map[string]any{"outbound": "asdf"}
  971. _, err = NewFirewallFromConfig(l, cs, conf)
  972. require.EqualError(t, err, "firewall.outbound failed to parse, should be an array of rules")
  973. // Test both port and code
  974. conf = config.NewC(test.NewLogger())
  975. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "code": "2"}}}
  976. _, err = NewFirewallFromConfig(l, cs, conf)
  977. require.EqualError(t, err, "firewall.outbound rule #0; only one of port or code should be provided")
  978. // Test missing host, group, cidr, ca_name and ca_sha
  979. conf = config.NewC(test.NewLogger())
  980. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{}}}
  981. _, err = NewFirewallFromConfig(l, cs, conf)
  982. require.EqualError(t, err, "firewall.outbound rule #0; at least one of host, group, cidr, local_cidr, ca_name, or ca_sha must be provided")
  983. // Test code/port error
  984. conf = config.NewC(test.NewLogger())
  985. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"code": "a", "host": "testh", "proto": "any"}}}
  986. _, err = NewFirewallFromConfig(l, cs, conf)
  987. require.EqualError(t, err, "firewall.outbound rule #0; code was not a number; `a`")
  988. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "a", "host": "testh", "proto": "any"}}}
  989. _, err = NewFirewallFromConfig(l, cs, conf)
  990. require.EqualError(t, err, "firewall.outbound rule #0; port was not a number; `a`")
  991. // Test proto error
  992. conf = config.NewC(test.NewLogger())
  993. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"code": "1", "host": "testh"}}}
  994. _, err = NewFirewallFromConfig(l, cs, conf)
  995. require.EqualError(t, err, "firewall.outbound rule #0; proto was not understood; ``")
  996. // Test cidr parse error
  997. conf = config.NewC(test.NewLogger())
  998. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"code": "1", "cidr": "testh", "proto": "any"}}}
  999. _, err = NewFirewallFromConfig(l, cs, conf)
  1000. require.EqualError(t, err, "firewall.outbound rule #0; cidr did not parse; netip.ParsePrefix(\"testh\"): no '/'")
  1001. // Test local_cidr parse error
  1002. conf = config.NewC(test.NewLogger())
  1003. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"code": "1", "local_cidr": "testh", "proto": "any"}}}
  1004. _, err = NewFirewallFromConfig(l, cs, conf)
  1005. require.EqualError(t, err, "firewall.outbound rule #0; local_cidr did not parse; netip.ParsePrefix(\"testh\"): no '/'")
  1006. // Test both group and groups
  1007. conf = config.NewC(test.NewLogger())
  1008. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "group": "a", "groups": []string{"b", "c"}}}}
  1009. _, err = NewFirewallFromConfig(l, cs, conf)
  1010. require.EqualError(t, err, "firewall.inbound rule #0; only one of group or groups should be defined, both provided")
  1011. }
  1012. func TestAddFirewallRulesFromConfig(t *testing.T) {
  1013. l := test.NewLogger()
  1014. // Test adding tcp rule
  1015. conf := config.NewC(test.NewLogger())
  1016. mf := &mockFirewall{}
  1017. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": "tcp", "host": "a"}}}
  1018. require.NoError(t, AddFirewallRulesFromConfig(l, false, conf, mf))
  1019. assert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoTCP, startPort: 1, endPort: 1, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
  1020. // Test adding udp rule
  1021. conf = config.NewC(test.NewLogger())
  1022. mf = &mockFirewall{}
  1023. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": "udp", "host": "a"}}}
  1024. require.NoError(t, AddFirewallRulesFromConfig(l, false, conf, mf))
  1025. assert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoUDP, startPort: 1, endPort: 1, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
  1026. // Test adding icmp rule
  1027. conf = config.NewC(test.NewLogger())
  1028. mf = &mockFirewall{}
  1029. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": "icmp", "host": "a"}}}
  1030. require.NoError(t, AddFirewallRulesFromConfig(l, false, conf, mf))
  1031. assert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoICMP, startPort: firewall.PortAny, endPort: firewall.PortAny, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
  1032. // Test adding icmp rule no port
  1033. conf = config.NewC(test.NewLogger())
  1034. mf = &mockFirewall{}
  1035. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"proto": "icmp", "host": "a"}}}
  1036. require.NoError(t, AddFirewallRulesFromConfig(l, false, conf, mf))
  1037. assert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoICMP, startPort: firewall.PortAny, endPort: firewall.PortAny, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
  1038. // Test adding any rule
  1039. conf = config.NewC(test.NewLogger())
  1040. mf = &mockFirewall{}
  1041. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "host": "a"}}}
  1042. require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
  1043. assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
  1044. // Test adding rule with cidr
  1045. cidr := netip.MustParsePrefix("10.0.0.0/8")
  1046. conf = config.NewC(test.NewLogger())
  1047. mf = &mockFirewall{}
  1048. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": cidr.String()}}}
  1049. require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
  1050. assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: cidr.String(), localIp: ""}, mf.lastCall)
  1051. // Test adding rule with local_cidr
  1052. conf = config.NewC(test.NewLogger())
  1053. mf = &mockFirewall{}
  1054. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": cidr.String()}}}
  1055. require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
  1056. assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "", localIp: cidr.String()}, mf.lastCall)
  1057. // Test adding rule with cidr ipv6
  1058. cidr6 := netip.MustParsePrefix("fd00::/8")
  1059. conf = config.NewC(test.NewLogger())
  1060. mf = &mockFirewall{}
  1061. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": cidr6.String()}}}
  1062. require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
  1063. assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: cidr6.String(), localIp: ""}, mf.lastCall)
  1064. // Test adding rule with any cidr
  1065. conf = config.NewC(test.NewLogger())
  1066. mf = &mockFirewall{}
  1067. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": "any"}}}
  1068. require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
  1069. assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "any", localIp: ""}, mf.lastCall)
  1070. // Test adding rule with junk cidr
  1071. conf = config.NewC(test.NewLogger())
  1072. mf = &mockFirewall{}
  1073. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": "junk/junk"}}}
  1074. require.EqualError(t, AddFirewallRulesFromConfig(l, true, conf, mf), "firewall.inbound rule #0; cidr did not parse; netip.ParsePrefix(\"junk/junk\"): ParseAddr(\"junk\"): unable to parse IP")
  1075. // Test adding rule with local_cidr ipv6
  1076. conf = config.NewC(test.NewLogger())
  1077. mf = &mockFirewall{}
  1078. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": cidr6.String()}}}
  1079. require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
  1080. assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "", localIp: cidr6.String()}, mf.lastCall)
  1081. // Test adding rule with any local_cidr
  1082. conf = config.NewC(test.NewLogger())
  1083. mf = &mockFirewall{}
  1084. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": "any"}}}
  1085. require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
  1086. assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, localIp: "any"}, mf.lastCall)
  1087. // Test adding rule with junk local_cidr
  1088. conf = config.NewC(test.NewLogger())
  1089. mf = &mockFirewall{}
  1090. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": "junk/junk"}}}
  1091. require.EqualError(t, AddFirewallRulesFromConfig(l, true, conf, mf), "firewall.inbound rule #0; local_cidr did not parse; netip.ParsePrefix(\"junk/junk\"): ParseAddr(\"junk\"): unable to parse IP")
  1092. // Test adding rule with ca_sha
  1093. conf = config.NewC(test.NewLogger())
  1094. mf = &mockFirewall{}
  1095. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "ca_sha": "12312313123"}}}
  1096. require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
  1097. assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "", localIp: "", caSha: "12312313123"}, mf.lastCall)
  1098. // Test adding rule with ca_name
  1099. conf = config.NewC(test.NewLogger())
  1100. mf = &mockFirewall{}
  1101. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "ca_name": "root01"}}}
  1102. require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
  1103. assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "", localIp: "", caName: "root01"}, mf.lastCall)
  1104. // Test single group
  1105. conf = config.NewC(test.NewLogger())
  1106. mf = &mockFirewall{}
  1107. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "group": "a"}}}
  1108. require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
  1109. assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: []string{"a"}, ip: "", localIp: ""}, mf.lastCall)
  1110. // Test single groups
  1111. conf = config.NewC(test.NewLogger())
  1112. mf = &mockFirewall{}
  1113. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "groups": "a"}}}
  1114. require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
  1115. assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: []string{"a"}, ip: "", localIp: ""}, mf.lastCall)
  1116. // Test multiple AND groups
  1117. conf = config.NewC(test.NewLogger())
  1118. mf = &mockFirewall{}
  1119. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "groups": []string{"a", "b"}}}}
  1120. require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
  1121. assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: []string{"a", "b"}, ip: "", localIp: ""}, mf.lastCall)
  1122. // Test Add error
  1123. conf = config.NewC(test.NewLogger())
  1124. mf = &mockFirewall{}
  1125. mf.nextCallReturn = errors.New("test error")
  1126. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "host": "a"}}}
  1127. require.EqualError(t, AddFirewallRulesFromConfig(l, true, conf, mf), "firewall.inbound rule #0; `test error`")
  1128. }
  1129. func TestFirewall_convertRule(t *testing.T) {
  1130. ob := &bytes.Buffer{}
  1131. l := test.NewLoggerWithOutput(ob)
  1132. // Ensure group array of 1 is converted and a warning is printed
  1133. c := map[string]any{
  1134. "group": []any{"group1"},
  1135. }
  1136. r, err := convertRule(l, c, "test", 1)
  1137. assert.Contains(t, ob.String(), "group was an array with a single value, converting to simple value")
  1138. assert.Contains(t, ob.String(), "table=test")
  1139. assert.Contains(t, ob.String(), "rule=1")
  1140. require.NoError(t, err)
  1141. assert.Equal(t, []string{"group1"}, r.Groups)
  1142. // Ensure group array of > 1 is errord
  1143. ob.Reset()
  1144. c = map[string]any{
  1145. "group": []any{"group1", "group2"},
  1146. }
  1147. r, err = convertRule(l, c, "test", 1)
  1148. assert.Empty(t, ob.String())
  1149. require.Error(t, err, "group should contain a single value, an array with more than one entry was provided")
  1150. // Make sure a well formed group is alright
  1151. ob.Reset()
  1152. c = map[string]any{
  1153. "group": "group1",
  1154. }
  1155. r, err = convertRule(l, c, "test", 1)
  1156. require.NoError(t, err)
  1157. assert.Equal(t, []string{"group1"}, r.Groups)
  1158. }
  1159. func TestFirewall_convertRuleSanity(t *testing.T) {
  1160. ob := &bytes.Buffer{}
  1161. l := test.NewLoggerWithOutput(ob)
  1162. noWarningPlease := []map[string]any{
  1163. {"group": "group1"},
  1164. {"groups": []any{"group2"}},
  1165. {"host": "bob"},
  1166. {"cidr": "1.1.1.1/1"},
  1167. {"groups": []any{"group2"}, "host": "bob"},
  1168. {"cidr": "1.1.1.1/1", "host": "bob"},
  1169. {"groups": []any{"group2"}, "cidr": "1.1.1.1/1"},
  1170. {"groups": []any{"group2"}, "cidr": "1.1.1.1/1", "host": "bob"},
  1171. }
  1172. for _, c := range noWarningPlease {
  1173. r, err := convertRule(l, c, "test", 1)
  1174. require.NoError(t, err)
  1175. require.NoError(t, r.sanity(), "should not generate a sanity warning, %+v", c)
  1176. }
  1177. yesWarningPlease := []map[string]any{
  1178. {"group": "group1"},
  1179. {"groups": []any{"group2"}},
  1180. {"cidr": "1.1.1.1/1"},
  1181. {"groups": []any{"group2"}, "host": "bob"},
  1182. {"cidr": "1.1.1.1/1", "host": "bob"},
  1183. {"groups": []any{"group2"}, "cidr": "1.1.1.1/1"},
  1184. {"groups": []any{"group2"}, "cidr": "1.1.1.1/1", "host": "bob"},
  1185. }
  1186. for _, c := range yesWarningPlease {
  1187. c["host"] = "any"
  1188. r, err := convertRule(l, c, "test", 1)
  1189. require.NoError(t, err)
  1190. err = r.sanity()
  1191. require.Error(t, err, "I wanted a warning: %+v", c)
  1192. }
  1193. //reset the list
  1194. yesWarningPlease = []map[string]any{
  1195. {"group": "group1"},
  1196. {"groups": []any{"group2"}},
  1197. {"cidr": "1.1.1.1/1"},
  1198. {"groups": []any{"group2"}, "host": "bob"},
  1199. {"cidr": "1.1.1.1/1", "host": "bob"},
  1200. {"groups": []any{"group2"}, "cidr": "1.1.1.1/1"},
  1201. {"groups": []any{"group2"}, "cidr": "1.1.1.1/1", "host": "bob"},
  1202. }
  1203. for _, c := range yesWarningPlease {
  1204. r, err := convertRule(l, c, "test", 1)
  1205. require.NoError(t, err)
  1206. r.Groups = append(r.Groups, "any")
  1207. err = r.sanity()
  1208. require.Error(t, err, "I wanted a warning: %+v", c)
  1209. }
  1210. }
  1211. type testcase struct {
  1212. h *HostInfo
  1213. p firewall.Packet
  1214. c cert.Certificate
  1215. err error
  1216. }
  1217. func (c *testcase) Test(t *testing.T, fw *Firewall) {
  1218. t.Helper()
  1219. cp := cert.NewCAPool()
  1220. resetConntrack(fw)
  1221. err := fw.Drop(c.p, true, c.h, cp, nil)
  1222. if c.err == nil {
  1223. require.NoError(t, err, "failed to not drop remote address %s", c.p.RemoteAddr)
  1224. } else {
  1225. require.ErrorIs(t, c.err, err, "failed to drop remote address %s", c.p.RemoteAddr)
  1226. }
  1227. }
  1228. func buildTestCase(setup testsetup, err error, theirPrefixes ...netip.Prefix) testcase {
  1229. c1 := dummyCert{
  1230. name: "host1",
  1231. networks: theirPrefixes,
  1232. groups: []string{"default-group"},
  1233. issuer: "signer-shasum",
  1234. }
  1235. h := HostInfo{
  1236. ConnectionState: &ConnectionState{
  1237. peerCert: &cert.CachedCertificate{
  1238. Certificate: &c1,
  1239. InvertedGroups: map[string]struct{}{"default-group": {}},
  1240. },
  1241. },
  1242. vpnAddrs: make([]netip.Addr, len(theirPrefixes)),
  1243. }
  1244. for i := range theirPrefixes {
  1245. h.vpnAddrs[i] = theirPrefixes[i].Addr()
  1246. }
  1247. h.buildNetworks(setup.myVpnNetworksTable, &c1)
  1248. p := firewall.Packet{
  1249. LocalAddr: setup.c.Networks()[0].Addr(), //todo?
  1250. RemoteAddr: theirPrefixes[0].Addr(),
  1251. LocalPort: 10,
  1252. RemotePort: 90,
  1253. Protocol: firewall.ProtoUDP,
  1254. Fragment: false,
  1255. }
  1256. return testcase{
  1257. h: &h,
  1258. p: p,
  1259. c: &c1,
  1260. err: err,
  1261. }
  1262. }
  1263. type testsetup struct {
  1264. c dummyCert
  1265. myVpnNetworksTable *bart.Lite
  1266. fw *Firewall
  1267. }
  1268. func newSetup(t *testing.T, l *slog.Logger, myPrefixes ...netip.Prefix) testsetup {
  1269. c := dummyCert{
  1270. name: "me",
  1271. networks: myPrefixes,
  1272. groups: []string{"default-group"},
  1273. issuer: "signer-shasum",
  1274. }
  1275. return newSetupFromCert(t, l, c)
  1276. }
  1277. func newSetupFromCert(t *testing.T, l *slog.Logger, c dummyCert) testsetup {
  1278. myVpnNetworksTable := new(bart.Lite)
  1279. for _, prefix := range c.Networks() {
  1280. myVpnNetworksTable.Insert(prefix)
  1281. }
  1282. fw := NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
  1283. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"any"}, "", "", "", "", ""))
  1284. return testsetup{
  1285. c: c,
  1286. fw: fw,
  1287. myVpnNetworksTable: myVpnNetworksTable,
  1288. }
  1289. }
  1290. func TestFirewall_Drop_EnforceIPMatch(t *testing.T) {
  1291. t.Parallel()
  1292. ob := &bytes.Buffer{}
  1293. l := test.NewLoggerWithOutput(ob)
  1294. myPrefix := netip.MustParsePrefix("1.1.1.1/8")
  1295. // for now, it's okay that these are all "incoming", the logic this test tries to check doesn't care about in/out
  1296. t.Run("allow inbound all matching", func(t *testing.T) {
  1297. t.Parallel()
  1298. setup := newSetup(t, l, myPrefix)
  1299. tc := buildTestCase(setup, nil, netip.MustParsePrefix("1.2.3.4/24"))
  1300. tc.Test(t, setup.fw)
  1301. })
  1302. t.Run("allow inbound local matching", func(t *testing.T) {
  1303. t.Parallel()
  1304. setup := newSetup(t, l, myPrefix)
  1305. tc := buildTestCase(setup, ErrInvalidLocalIP, netip.MustParsePrefix("1.2.3.4/24"))
  1306. tc.p.LocalAddr = netip.MustParseAddr("1.2.3.8")
  1307. tc.Test(t, setup.fw)
  1308. })
  1309. t.Run("block inbound remote mismatched", func(t *testing.T) {
  1310. t.Parallel()
  1311. setup := newSetup(t, l, myPrefix)
  1312. tc := buildTestCase(setup, ErrInvalidRemoteIP, netip.MustParsePrefix("1.2.3.4/24"))
  1313. tc.p.RemoteAddr = netip.MustParseAddr("9.9.9.9")
  1314. tc.Test(t, setup.fw)
  1315. })
  1316. t.Run("Block a vpn peer packet", func(t *testing.T) {
  1317. t.Parallel()
  1318. setup := newSetup(t, l, myPrefix)
  1319. tc := buildTestCase(setup, ErrPeerRejected, netip.MustParsePrefix("2.2.2.2/24"))
  1320. tc.Test(t, setup.fw)
  1321. })
  1322. twoPrefixes := []netip.Prefix{
  1323. netip.MustParsePrefix("1.2.3.4/24"), netip.MustParsePrefix("2.2.2.2/24"),
  1324. }
  1325. t.Run("allow inbound one matching", func(t *testing.T) {
  1326. t.Parallel()
  1327. setup := newSetup(t, l, myPrefix)
  1328. tc := buildTestCase(setup, nil, twoPrefixes...)
  1329. tc.Test(t, setup.fw)
  1330. })
  1331. t.Run("block inbound multimismatch", func(t *testing.T) {
  1332. t.Parallel()
  1333. setup := newSetup(t, l, myPrefix)
  1334. tc := buildTestCase(setup, ErrInvalidRemoteIP, twoPrefixes...)
  1335. tc.p.RemoteAddr = netip.MustParseAddr("9.9.9.9")
  1336. tc.Test(t, setup.fw)
  1337. })
  1338. t.Run("allow inbound 2nd one matching", func(t *testing.T) {
  1339. t.Parallel()
  1340. setup2 := newSetup(t, l, netip.MustParsePrefix("2.2.2.1/24"))
  1341. tc := buildTestCase(setup2, nil, twoPrefixes...)
  1342. tc.p.RemoteAddr = twoPrefixes[1].Addr()
  1343. tc.Test(t, setup2.fw)
  1344. })
  1345. t.Run("allow inbound unsafe route", func(t *testing.T) {
  1346. t.Parallel()
  1347. unsafePrefix := netip.MustParsePrefix("192.168.0.0/24")
  1348. c := dummyCert{
  1349. name: "me",
  1350. networks: []netip.Prefix{myPrefix},
  1351. unsafeNetworks: []netip.Prefix{unsafePrefix},
  1352. groups: []string{"default-group"},
  1353. issuer: "signer-shasum",
  1354. }
  1355. unsafeSetup := newSetupFromCert(t, l, c)
  1356. tc := buildTestCase(unsafeSetup, nil, twoPrefixes...)
  1357. tc.p.LocalAddr = netip.MustParseAddr("192.168.0.3")
  1358. tc.err = ErrNoMatchingRule
  1359. tc.Test(t, unsafeSetup.fw) //should hit firewall and bounce off
  1360. require.NoError(t, unsafeSetup.fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"any"}, "", "", unsafePrefix.String(), "", ""))
  1361. tc.err = nil
  1362. tc.Test(t, unsafeSetup.fw) //should pass
  1363. })
  1364. }
  1365. type addRuleCall struct {
  1366. incoming bool
  1367. proto uint8
  1368. startPort int32
  1369. endPort int32
  1370. groups []string
  1371. host string
  1372. ip string
  1373. localIp string
  1374. caName string
  1375. caSha string
  1376. }
  1377. type mockFirewall struct {
  1378. lastCall addRuleCall
  1379. nextCallReturn error
  1380. }
  1381. func (mf *mockFirewall) AddRule(incoming bool, proto uint8, startPort int32, endPort int32, groups []string, host string, ip, localIp, caName string, caSha string) error {
  1382. mf.lastCall = addRuleCall{
  1383. incoming: incoming,
  1384. proto: proto,
  1385. startPort: startPort,
  1386. endPort: endPort,
  1387. groups: groups,
  1388. host: host,
  1389. ip: ip,
  1390. localIp: localIp,
  1391. caName: caName,
  1392. caSha: caSha,
  1393. }
  1394. err := mf.nextCallReturn
  1395. mf.nextCallReturn = nil
  1396. return err
  1397. }
  1398. func resetConntrack(fw *Firewall) {
  1399. fw.Conntrack.Lock()
  1400. fw.Conntrack.Conns = map[firewall.Packet]*conn{}
  1401. fw.Conntrack.Unlock()
  1402. }