firewall_test.go 55 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524
  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. func TestNewFirewallFromConfig(t *testing.T) {
  902. l := test.NewLogger()
  903. // Test a bad rule definition
  904. c := &dummyCert{}
  905. cs, err := newCertState(cert.Version2, nil, c, false, cert.Curve_CURVE25519, nil, "aes")
  906. require.NoError(t, err)
  907. conf := config.NewC(test.NewLogger())
  908. conf.Settings["firewall"] = map[string]any{"outbound": "asdf"}
  909. _, err = NewFirewallFromConfig(l, cs, conf)
  910. require.EqualError(t, err, "firewall.outbound failed to parse, should be an array of rules")
  911. // Test both port and code
  912. conf = config.NewC(test.NewLogger())
  913. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "code": "2"}}}
  914. _, err = NewFirewallFromConfig(l, cs, conf)
  915. require.EqualError(t, err, "firewall.outbound rule #0; only one of port or code should be provided")
  916. // Test missing host, group, cidr, ca_name and ca_sha
  917. conf = config.NewC(test.NewLogger())
  918. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{}}}
  919. _, err = NewFirewallFromConfig(l, cs, conf)
  920. 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")
  921. // Test code/port error
  922. conf = config.NewC(test.NewLogger())
  923. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"code": "a", "host": "testh", "proto": "any"}}}
  924. _, err = NewFirewallFromConfig(l, cs, conf)
  925. require.EqualError(t, err, "firewall.outbound rule #0; code was not a number; `a`")
  926. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "a", "host": "testh", "proto": "any"}}}
  927. _, err = NewFirewallFromConfig(l, cs, conf)
  928. require.EqualError(t, err, "firewall.outbound rule #0; port was not a number; `a`")
  929. // Test proto error
  930. conf = config.NewC(test.NewLogger())
  931. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"code": "1", "host": "testh"}}}
  932. _, err = NewFirewallFromConfig(l, cs, conf)
  933. require.EqualError(t, err, "firewall.outbound rule #0; proto was not understood; ``")
  934. // Test cidr parse error
  935. conf = config.NewC(test.NewLogger())
  936. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"code": "1", "cidr": "testh", "proto": "any"}}}
  937. _, err = NewFirewallFromConfig(l, cs, conf)
  938. require.EqualError(t, err, "firewall.outbound rule #0; cidr did not parse; netip.ParsePrefix(\"testh\"): no '/'")
  939. // Test local_cidr parse error
  940. conf = config.NewC(test.NewLogger())
  941. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"code": "1", "local_cidr": "testh", "proto": "any"}}}
  942. _, err = NewFirewallFromConfig(l, cs, conf)
  943. require.EqualError(t, err, "firewall.outbound rule #0; local_cidr did not parse; netip.ParsePrefix(\"testh\"): no '/'")
  944. // Test both group and groups
  945. conf = config.NewC(test.NewLogger())
  946. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "group": "a", "groups": []string{"b", "c"}}}}
  947. _, err = NewFirewallFromConfig(l, cs, conf)
  948. require.EqualError(t, err, "firewall.inbound rule #0; only one of group or groups should be defined, both provided")
  949. }
  950. func TestAddFirewallRulesFromConfig(t *testing.T) {
  951. l := test.NewLogger()
  952. // Test adding tcp rule
  953. conf := config.NewC(test.NewLogger())
  954. mf := &mockFirewall{}
  955. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": "tcp", "host": "a"}}}
  956. require.NoError(t, AddFirewallRulesFromConfig(l, false, conf, mf))
  957. assert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoTCP, startPort: 1, endPort: 1, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
  958. // Test adding udp rule
  959. conf = config.NewC(test.NewLogger())
  960. mf = &mockFirewall{}
  961. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": "udp", "host": "a"}}}
  962. require.NoError(t, AddFirewallRulesFromConfig(l, false, conf, mf))
  963. assert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoUDP, startPort: 1, endPort: 1, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
  964. // Test adding icmp rule
  965. conf = config.NewC(test.NewLogger())
  966. mf = &mockFirewall{}
  967. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": "icmp", "host": "a"}}}
  968. require.NoError(t, AddFirewallRulesFromConfig(l, false, conf, mf))
  969. assert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoICMP, startPort: firewall.PortAny, endPort: firewall.PortAny, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
  970. // Test adding icmp rule no port
  971. conf = config.NewC(test.NewLogger())
  972. mf = &mockFirewall{}
  973. conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"proto": "icmp", "host": "a"}}}
  974. require.NoError(t, AddFirewallRulesFromConfig(l, false, conf, mf))
  975. assert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoICMP, startPort: firewall.PortAny, endPort: firewall.PortAny, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
  976. // Test adding any rule
  977. conf = config.NewC(test.NewLogger())
  978. mf = &mockFirewall{}
  979. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "host": "a"}}}
  980. require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
  981. assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
  982. // Test adding rule with cidr
  983. cidr := netip.MustParsePrefix("10.0.0.0/8")
  984. conf = config.NewC(test.NewLogger())
  985. mf = &mockFirewall{}
  986. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": cidr.String()}}}
  987. require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
  988. assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: cidr.String(), localIp: ""}, mf.lastCall)
  989. // Test adding rule with local_cidr
  990. conf = config.NewC(test.NewLogger())
  991. mf = &mockFirewall{}
  992. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": cidr.String()}}}
  993. require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
  994. assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "", localIp: cidr.String()}, mf.lastCall)
  995. // Test adding rule with cidr ipv6
  996. cidr6 := netip.MustParsePrefix("fd00::/8")
  997. conf = config.NewC(test.NewLogger())
  998. mf = &mockFirewall{}
  999. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": cidr6.String()}}}
  1000. require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
  1001. assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: cidr6.String(), localIp: ""}, mf.lastCall)
  1002. // Test adding rule with any cidr
  1003. conf = config.NewC(test.NewLogger())
  1004. mf = &mockFirewall{}
  1005. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": "any"}}}
  1006. require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
  1007. assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "any", localIp: ""}, mf.lastCall)
  1008. // Test adding rule with junk cidr
  1009. conf = config.NewC(test.NewLogger())
  1010. mf = &mockFirewall{}
  1011. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": "junk/junk"}}}
  1012. 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")
  1013. // Test adding rule with local_cidr ipv6
  1014. conf = config.NewC(test.NewLogger())
  1015. mf = &mockFirewall{}
  1016. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": cidr6.String()}}}
  1017. require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
  1018. assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "", localIp: cidr6.String()}, mf.lastCall)
  1019. // Test adding rule with any local_cidr
  1020. conf = config.NewC(test.NewLogger())
  1021. mf = &mockFirewall{}
  1022. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": "any"}}}
  1023. require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
  1024. assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, localIp: "any"}, mf.lastCall)
  1025. // Test adding rule with junk local_cidr
  1026. conf = config.NewC(test.NewLogger())
  1027. mf = &mockFirewall{}
  1028. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": "junk/junk"}}}
  1029. 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")
  1030. // Test adding rule with ca_sha
  1031. conf = config.NewC(test.NewLogger())
  1032. mf = &mockFirewall{}
  1033. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "ca_sha": "12312313123"}}}
  1034. require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
  1035. assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "", localIp: "", caSha: "12312313123"}, mf.lastCall)
  1036. // Test adding rule with ca_name
  1037. conf = config.NewC(test.NewLogger())
  1038. mf = &mockFirewall{}
  1039. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "ca_name": "root01"}}}
  1040. require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
  1041. assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "", localIp: "", caName: "root01"}, mf.lastCall)
  1042. // Test single group
  1043. conf = config.NewC(test.NewLogger())
  1044. mf = &mockFirewall{}
  1045. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "group": "a"}}}
  1046. require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
  1047. assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: []string{"a"}, ip: "", localIp: ""}, mf.lastCall)
  1048. // Test single groups
  1049. conf = config.NewC(test.NewLogger())
  1050. mf = &mockFirewall{}
  1051. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "groups": "a"}}}
  1052. require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
  1053. assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: []string{"a"}, ip: "", localIp: ""}, mf.lastCall)
  1054. // Test multiple AND groups
  1055. conf = config.NewC(test.NewLogger())
  1056. mf = &mockFirewall{}
  1057. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "groups": []string{"a", "b"}}}}
  1058. require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
  1059. assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: []string{"a", "b"}, ip: "", localIp: ""}, mf.lastCall)
  1060. // Test Add error
  1061. conf = config.NewC(test.NewLogger())
  1062. mf = &mockFirewall{}
  1063. mf.nextCallReturn = errors.New("test error")
  1064. conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "host": "a"}}}
  1065. require.EqualError(t, AddFirewallRulesFromConfig(l, true, conf, mf), "firewall.inbound rule #0; `test error`")
  1066. }
  1067. func TestFirewall_convertRule(t *testing.T) {
  1068. ob := &bytes.Buffer{}
  1069. l := test.NewLoggerWithOutput(ob)
  1070. // Ensure group array of 1 is converted and a warning is printed
  1071. c := map[string]any{
  1072. "group": []any{"group1"},
  1073. }
  1074. r, err := convertRule(l, c, "test", 1)
  1075. assert.Contains(t, ob.String(), "group was an array with a single value, converting to simple value")
  1076. assert.Contains(t, ob.String(), "table=test")
  1077. assert.Contains(t, ob.String(), "rule=1")
  1078. require.NoError(t, err)
  1079. assert.Equal(t, []string{"group1"}, r.Groups)
  1080. // Ensure group array of > 1 is errord
  1081. ob.Reset()
  1082. c = map[string]any{
  1083. "group": []any{"group1", "group2"},
  1084. }
  1085. r, err = convertRule(l, c, "test", 1)
  1086. assert.Empty(t, ob.String())
  1087. require.Error(t, err, "group should contain a single value, an array with more than one entry was provided")
  1088. // Make sure a well formed group is alright
  1089. ob.Reset()
  1090. c = map[string]any{
  1091. "group": "group1",
  1092. }
  1093. r, err = convertRule(l, c, "test", 1)
  1094. require.NoError(t, err)
  1095. assert.Equal(t, []string{"group1"}, r.Groups)
  1096. }
  1097. func TestFirewall_convertRuleSanity(t *testing.T) {
  1098. ob := &bytes.Buffer{}
  1099. l := test.NewLoggerWithOutput(ob)
  1100. noWarningPlease := []map[string]any{
  1101. {"group": "group1"},
  1102. {"groups": []any{"group2"}},
  1103. {"host": "bob"},
  1104. {"cidr": "1.1.1.1/1"},
  1105. {"groups": []any{"group2"}, "host": "bob"},
  1106. {"cidr": "1.1.1.1/1", "host": "bob"},
  1107. {"groups": []any{"group2"}, "cidr": "1.1.1.1/1"},
  1108. {"groups": []any{"group2"}, "cidr": "1.1.1.1/1", "host": "bob"},
  1109. }
  1110. for _, c := range noWarningPlease {
  1111. r, err := convertRule(l, c, "test", 1)
  1112. require.NoError(t, err)
  1113. require.NoError(t, r.sanity(), "should not generate a sanity warning, %+v", c)
  1114. }
  1115. yesWarningPlease := []map[string]any{
  1116. {"group": "group1"},
  1117. {"groups": []any{"group2"}},
  1118. {"cidr": "1.1.1.1/1"},
  1119. {"groups": []any{"group2"}, "host": "bob"},
  1120. {"cidr": "1.1.1.1/1", "host": "bob"},
  1121. {"groups": []any{"group2"}, "cidr": "1.1.1.1/1"},
  1122. {"groups": []any{"group2"}, "cidr": "1.1.1.1/1", "host": "bob"},
  1123. }
  1124. for _, c := range yesWarningPlease {
  1125. c["host"] = "any"
  1126. r, err := convertRule(l, c, "test", 1)
  1127. require.NoError(t, err)
  1128. err = r.sanity()
  1129. require.Error(t, err, "I wanted a warning: %+v", c)
  1130. }
  1131. //reset the list
  1132. yesWarningPlease = []map[string]any{
  1133. {"group": "group1"},
  1134. {"groups": []any{"group2"}},
  1135. {"cidr": "1.1.1.1/1"},
  1136. {"groups": []any{"group2"}, "host": "bob"},
  1137. {"cidr": "1.1.1.1/1", "host": "bob"},
  1138. {"groups": []any{"group2"}, "cidr": "1.1.1.1/1"},
  1139. {"groups": []any{"group2"}, "cidr": "1.1.1.1/1", "host": "bob"},
  1140. }
  1141. for _, c := range yesWarningPlease {
  1142. r, err := convertRule(l, c, "test", 1)
  1143. require.NoError(t, err)
  1144. r.Groups = append(r.Groups, "any")
  1145. err = r.sanity()
  1146. require.Error(t, err, "I wanted a warning: %+v", c)
  1147. }
  1148. }
  1149. type testcase struct {
  1150. h *HostInfo
  1151. p firewall.Packet
  1152. c cert.Certificate
  1153. err error
  1154. }
  1155. func (c *testcase) Test(t *testing.T, fw *Firewall) {
  1156. t.Helper()
  1157. cp := cert.NewCAPool()
  1158. resetConntrack(fw)
  1159. err := fw.Drop(c.p, true, c.h, cp, nil)
  1160. if c.err == nil {
  1161. require.NoError(t, err, "failed to not drop remote address %s", c.p.RemoteAddr)
  1162. } else {
  1163. require.ErrorIs(t, c.err, err, "failed to drop remote address %s", c.p.RemoteAddr)
  1164. }
  1165. }
  1166. func buildTestCase(setup testsetup, err error, theirPrefixes ...netip.Prefix) testcase {
  1167. c1 := dummyCert{
  1168. name: "host1",
  1169. networks: theirPrefixes,
  1170. groups: []string{"default-group"},
  1171. issuer: "signer-shasum",
  1172. }
  1173. h := HostInfo{
  1174. ConnectionState: &ConnectionState{
  1175. peerCert: &cert.CachedCertificate{
  1176. Certificate: &c1,
  1177. InvertedGroups: map[string]struct{}{"default-group": {}},
  1178. },
  1179. },
  1180. vpnAddrs: make([]netip.Addr, len(theirPrefixes)),
  1181. }
  1182. for i := range theirPrefixes {
  1183. h.vpnAddrs[i] = theirPrefixes[i].Addr()
  1184. }
  1185. h.buildNetworks(setup.myVpnNetworksTable, &c1)
  1186. p := firewall.Packet{
  1187. LocalAddr: setup.c.Networks()[0].Addr(), //todo?
  1188. RemoteAddr: theirPrefixes[0].Addr(),
  1189. LocalPort: 10,
  1190. RemotePort: 90,
  1191. Protocol: firewall.ProtoUDP,
  1192. Fragment: false,
  1193. }
  1194. return testcase{
  1195. h: &h,
  1196. p: p,
  1197. c: &c1,
  1198. err: err,
  1199. }
  1200. }
  1201. type testsetup struct {
  1202. c dummyCert
  1203. myVpnNetworksTable *bart.Lite
  1204. fw *Firewall
  1205. }
  1206. func newSetup(t *testing.T, l *slog.Logger, myPrefixes ...netip.Prefix) testsetup {
  1207. c := dummyCert{
  1208. name: "me",
  1209. networks: myPrefixes,
  1210. groups: []string{"default-group"},
  1211. issuer: "signer-shasum",
  1212. }
  1213. return newSetupFromCert(t, l, c)
  1214. }
  1215. func newSetupFromCert(t *testing.T, l *slog.Logger, c dummyCert) testsetup {
  1216. myVpnNetworksTable := new(bart.Lite)
  1217. for _, prefix := range c.Networks() {
  1218. myVpnNetworksTable.Insert(prefix)
  1219. }
  1220. fw := NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
  1221. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"any"}, "", "", "", "", ""))
  1222. return testsetup{
  1223. c: c,
  1224. fw: fw,
  1225. myVpnNetworksTable: myVpnNetworksTable,
  1226. }
  1227. }
  1228. func TestFirewall_Drop_EnforceIPMatch(t *testing.T) {
  1229. t.Parallel()
  1230. ob := &bytes.Buffer{}
  1231. l := test.NewLoggerWithOutput(ob)
  1232. myPrefix := netip.MustParsePrefix("1.1.1.1/8")
  1233. // for now, it's okay that these are all "incoming", the logic this test tries to check doesn't care about in/out
  1234. t.Run("allow inbound all matching", func(t *testing.T) {
  1235. t.Parallel()
  1236. setup := newSetup(t, l, myPrefix)
  1237. tc := buildTestCase(setup, nil, netip.MustParsePrefix("1.2.3.4/24"))
  1238. tc.Test(t, setup.fw)
  1239. })
  1240. t.Run("allow inbound local matching", func(t *testing.T) {
  1241. t.Parallel()
  1242. setup := newSetup(t, l, myPrefix)
  1243. tc := buildTestCase(setup, ErrInvalidLocalIP, netip.MustParsePrefix("1.2.3.4/24"))
  1244. tc.p.LocalAddr = netip.MustParseAddr("1.2.3.8")
  1245. tc.Test(t, setup.fw)
  1246. })
  1247. t.Run("block inbound remote mismatched", func(t *testing.T) {
  1248. t.Parallel()
  1249. setup := newSetup(t, l, myPrefix)
  1250. tc := buildTestCase(setup, ErrInvalidRemoteIP, netip.MustParsePrefix("1.2.3.4/24"))
  1251. tc.p.RemoteAddr = netip.MustParseAddr("9.9.9.9")
  1252. tc.Test(t, setup.fw)
  1253. })
  1254. t.Run("Block a vpn peer packet", func(t *testing.T) {
  1255. t.Parallel()
  1256. setup := newSetup(t, l, myPrefix)
  1257. tc := buildTestCase(setup, ErrPeerRejected, netip.MustParsePrefix("2.2.2.2/24"))
  1258. tc.Test(t, setup.fw)
  1259. })
  1260. twoPrefixes := []netip.Prefix{
  1261. netip.MustParsePrefix("1.2.3.4/24"), netip.MustParsePrefix("2.2.2.2/24"),
  1262. }
  1263. t.Run("allow inbound one matching", func(t *testing.T) {
  1264. t.Parallel()
  1265. setup := newSetup(t, l, myPrefix)
  1266. tc := buildTestCase(setup, nil, twoPrefixes...)
  1267. tc.Test(t, setup.fw)
  1268. })
  1269. t.Run("block inbound multimismatch", func(t *testing.T) {
  1270. t.Parallel()
  1271. setup := newSetup(t, l, myPrefix)
  1272. tc := buildTestCase(setup, ErrInvalidRemoteIP, twoPrefixes...)
  1273. tc.p.RemoteAddr = netip.MustParseAddr("9.9.9.9")
  1274. tc.Test(t, setup.fw)
  1275. })
  1276. t.Run("allow inbound 2nd one matching", func(t *testing.T) {
  1277. t.Parallel()
  1278. setup2 := newSetup(t, l, netip.MustParsePrefix("2.2.2.1/24"))
  1279. tc := buildTestCase(setup2, nil, twoPrefixes...)
  1280. tc.p.RemoteAddr = twoPrefixes[1].Addr()
  1281. tc.Test(t, setup2.fw)
  1282. })
  1283. t.Run("allow inbound unsafe route", func(t *testing.T) {
  1284. t.Parallel()
  1285. unsafePrefix := netip.MustParsePrefix("192.168.0.0/24")
  1286. c := dummyCert{
  1287. name: "me",
  1288. networks: []netip.Prefix{myPrefix},
  1289. unsafeNetworks: []netip.Prefix{unsafePrefix},
  1290. groups: []string{"default-group"},
  1291. issuer: "signer-shasum",
  1292. }
  1293. unsafeSetup := newSetupFromCert(t, l, c)
  1294. tc := buildTestCase(unsafeSetup, nil, twoPrefixes...)
  1295. tc.p.LocalAddr = netip.MustParseAddr("192.168.0.3")
  1296. tc.err = ErrNoMatchingRule
  1297. tc.Test(t, unsafeSetup.fw) //should hit firewall and bounce off
  1298. require.NoError(t, unsafeSetup.fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"any"}, "", "", unsafePrefix.String(), "", ""))
  1299. tc.err = nil
  1300. tc.Test(t, unsafeSetup.fw) //should pass
  1301. })
  1302. }
  1303. type addRuleCall struct {
  1304. incoming bool
  1305. proto uint8
  1306. startPort int32
  1307. endPort int32
  1308. groups []string
  1309. host string
  1310. ip string
  1311. localIp string
  1312. caName string
  1313. caSha string
  1314. }
  1315. type mockFirewall struct {
  1316. lastCall addRuleCall
  1317. nextCallReturn error
  1318. }
  1319. func (mf *mockFirewall) AddRule(incoming bool, proto uint8, startPort int32, endPort int32, groups []string, host string, ip, localIp, caName string, caSha string) error {
  1320. mf.lastCall = addRuleCall{
  1321. incoming: incoming,
  1322. proto: proto,
  1323. startPort: startPort,
  1324. endPort: endPort,
  1325. groups: groups,
  1326. host: host,
  1327. ip: ip,
  1328. localIp: localIp,
  1329. caName: caName,
  1330. caSha: caSha,
  1331. }
  1332. err := mf.nextCallReturn
  1333. mf.nextCallReturn = nil
  1334. return err
  1335. }
  1336. func resetConntrack(fw *Firewall) {
  1337. fw.Conntrack.Lock()
  1338. fw.Conntrack.Conns = map[firewall.Packet]*conn{}
  1339. fw.Conntrack.Unlock()
  1340. }