firewall.go 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174
  1. package nebula
  2. import (
  3. "crypto/sha256"
  4. "encoding/hex"
  5. "errors"
  6. "fmt"
  7. "hash/fnv"
  8. "net/netip"
  9. "reflect"
  10. "slices"
  11. "strconv"
  12. "strings"
  13. "sync"
  14. "time"
  15. "github.com/gaissmai/bart"
  16. "github.com/rcrowley/go-metrics"
  17. "github.com/sirupsen/logrus"
  18. "github.com/slackhq/nebula/cert"
  19. "github.com/slackhq/nebula/config"
  20. "github.com/slackhq/nebula/firewall"
  21. "github.com/slackhq/nebula/firewall/events"
  22. )
  23. type FirewallInterface interface {
  24. AddRule(incoming bool, proto uint8, startPort int32, endPort int32, groups []string, host string, cidr, localCidr string, caName string, caSha string) error
  25. }
  26. type conn struct {
  27. Expires time.Time // Time when this conntrack entry will expire
  28. // record why the original connection passed the firewall, so we can re-validate
  29. // after ruleset changes. Note, rulesVersion is a uint16 so that these two
  30. // fields pack for free after the uint32 above
  31. incoming bool
  32. rulesVersion uint16
  33. }
  34. // TODO: need conntrack max tracked connections handling
  35. type Firewall struct {
  36. Conntrack *FirewallConntrack
  37. InRules *FirewallTable
  38. OutRules *FirewallTable
  39. InSendReject bool
  40. OutSendReject bool
  41. //TODO: we should have many more options for TCP, an option for ICMP, and mimic the kernel a bit better
  42. // https://www.kernel.org/doc/Documentation/networking/nf_conntrack-sysctl.txt
  43. TCPTimeout time.Duration //linux: 5 days max
  44. UDPTimeout time.Duration //linux: 180s max
  45. DefaultTimeout time.Duration //linux: 600s
  46. // routableNetworks describes the vpn addresses as well as any unsafe networks issued to us in the certificate.
  47. // The vpn addresses are a full bit match while the unsafe networks only match the prefix
  48. routableNetworks *bart.Lite
  49. // assignedNetworks is a list of vpn networks assigned to us in the certificate.
  50. assignedNetworks []netip.Prefix
  51. hasUnsafeNetworks bool
  52. rules string
  53. rulesVersion uint16
  54. defaultLocalCIDRAny bool
  55. incomingMetrics firewallMetrics
  56. outgoingMetrics firewallMetrics
  57. // reporter is the optional embedder-supplied event sink. Immutable for
  58. // the lifetime of this Firewall; Control.SetFirewallEventReporter
  59. // installs it by shallow-copying the Firewall under the conntrack lock
  60. // and swapping the pointer, and reloadFirewall carries it forward.
  61. // Read unsynchronized on the data path: the preceding Firewall-pointer
  62. // read pins the field's value for the duration of that call.
  63. reporter events.Reporter
  64. l *logrus.Logger
  65. }
  66. type firewallMetrics struct {
  67. droppedLocalAddr metrics.Counter
  68. droppedRemoteAddr metrics.Counter
  69. droppedNoRule metrics.Counter
  70. }
  71. type FirewallConntrack struct {
  72. sync.Mutex
  73. Conns map[firewall.Packet]*conn
  74. TimerWheel *TimerWheel[firewall.Packet]
  75. }
  76. // FirewallTable is the entry point for a rule, the evaluation order is:
  77. // Proto AND port AND (CA SHA or CA name) AND local CIDR AND (group OR groups OR name OR remote CIDR)
  78. type FirewallTable struct {
  79. TCP firewallPort
  80. UDP firewallPort
  81. ICMP firewallPort
  82. AnyProto firewallPort
  83. }
  84. func newFirewallTable() *FirewallTable {
  85. return &FirewallTable{
  86. TCP: firewallPort{},
  87. UDP: firewallPort{},
  88. ICMP: firewallPort{},
  89. AnyProto: firewallPort{},
  90. }
  91. }
  92. type FirewallCA struct {
  93. Any *FirewallRule
  94. CANames map[string]*FirewallRule
  95. CAShas map[string]*FirewallRule
  96. }
  97. type FirewallRule struct {
  98. // Any makes Hosts, Groups, and CIDR irrelevant
  99. Any *firewallLocalCIDR
  100. Hosts map[string]*firewallLocalCIDR
  101. Groups []*firewallGroups
  102. CIDR *bart.Table[*firewallLocalCIDR]
  103. }
  104. type firewallGroups struct {
  105. Groups []string
  106. LocalCIDR *firewallLocalCIDR
  107. }
  108. // Even though ports are uint16, int32 maps are faster for lookup
  109. // Plus we can use `-1` for fragment rules
  110. type firewallPort map[int32]*FirewallCA
  111. type firewallLocalCIDR struct {
  112. Any bool
  113. LocalCIDR *bart.Lite
  114. }
  115. // NewFirewall creates a new Firewall object. A TimerWheel is created for you from the provided timeouts.
  116. // The certificate provided should be the highest version loaded in memory.
  117. func NewFirewall(l *logrus.Logger, tcpTimeout, UDPTimeout, defaultTimeout time.Duration, c cert.Certificate) *Firewall {
  118. //TODO: error on 0 duration
  119. var tmin, tmax time.Duration
  120. if tcpTimeout < UDPTimeout {
  121. tmin = tcpTimeout
  122. tmax = UDPTimeout
  123. } else {
  124. tmin = UDPTimeout
  125. tmax = tcpTimeout
  126. }
  127. if defaultTimeout < tmin {
  128. tmin = defaultTimeout
  129. } else if defaultTimeout > tmax {
  130. tmax = defaultTimeout
  131. }
  132. routableNetworks := new(bart.Lite)
  133. var assignedNetworks []netip.Prefix
  134. for _, network := range c.Networks() {
  135. nprefix := netip.PrefixFrom(network.Addr(), network.Addr().BitLen())
  136. routableNetworks.Insert(nprefix)
  137. assignedNetworks = append(assignedNetworks, network)
  138. }
  139. hasUnsafeNetworks := false
  140. for _, n := range c.UnsafeNetworks() {
  141. routableNetworks.Insert(n)
  142. hasUnsafeNetworks = true
  143. }
  144. return &Firewall{
  145. Conntrack: &FirewallConntrack{
  146. Conns: make(map[firewall.Packet]*conn),
  147. TimerWheel: NewTimerWheel[firewall.Packet](tmin, tmax),
  148. },
  149. InRules: newFirewallTable(),
  150. OutRules: newFirewallTable(),
  151. TCPTimeout: tcpTimeout,
  152. UDPTimeout: UDPTimeout,
  153. DefaultTimeout: defaultTimeout,
  154. routableNetworks: routableNetworks,
  155. assignedNetworks: assignedNetworks,
  156. hasUnsafeNetworks: hasUnsafeNetworks,
  157. l: l,
  158. incomingMetrics: firewallMetrics{
  159. droppedLocalAddr: metrics.GetOrRegisterCounter("firewall.incoming.dropped.local_addr", nil),
  160. droppedRemoteAddr: metrics.GetOrRegisterCounter("firewall.incoming.dropped.remote_addr", nil),
  161. droppedNoRule: metrics.GetOrRegisterCounter("firewall.incoming.dropped.no_rule", nil),
  162. },
  163. outgoingMetrics: firewallMetrics{
  164. droppedLocalAddr: metrics.GetOrRegisterCounter("firewall.outgoing.dropped.local_addr", nil),
  165. droppedRemoteAddr: metrics.GetOrRegisterCounter("firewall.outgoing.dropped.remote_addr", nil),
  166. droppedNoRule: metrics.GetOrRegisterCounter("firewall.outgoing.dropped.no_rule", nil),
  167. },
  168. }
  169. }
  170. func NewFirewallFromConfig(l *logrus.Logger, cs *CertState, c *config.C) (*Firewall, error) {
  171. certificate := cs.getCertificate(cert.Version2)
  172. if certificate == nil {
  173. certificate = cs.getCertificate(cert.Version1)
  174. }
  175. if certificate == nil {
  176. panic("No certificate available to reconfigure the firewall")
  177. }
  178. fw := NewFirewall(
  179. l,
  180. c.GetDuration("firewall.conntrack.tcp_timeout", time.Minute*12),
  181. c.GetDuration("firewall.conntrack.udp_timeout", time.Minute*3),
  182. c.GetDuration("firewall.conntrack.default_timeout", time.Minute*10),
  183. certificate,
  184. //TODO: max_connections
  185. )
  186. fw.defaultLocalCIDRAny = c.GetBool("firewall.default_local_cidr_any", false)
  187. inboundAction := c.GetString("firewall.inbound_action", "drop")
  188. switch inboundAction {
  189. case "reject":
  190. fw.InSendReject = true
  191. case "drop":
  192. fw.InSendReject = false
  193. default:
  194. l.WithField("action", inboundAction).Warn("invalid firewall.inbound_action, defaulting to `drop`")
  195. fw.InSendReject = false
  196. }
  197. outboundAction := c.GetString("firewall.outbound_action", "drop")
  198. switch outboundAction {
  199. case "reject":
  200. fw.OutSendReject = true
  201. case "drop":
  202. fw.OutSendReject = false
  203. default:
  204. l.WithField("action", outboundAction).Warn("invalid firewall.outbound_action, defaulting to `drop`")
  205. fw.OutSendReject = false
  206. }
  207. err := AddFirewallRulesFromConfig(l, false, c, fw)
  208. if err != nil {
  209. return nil, err
  210. }
  211. err = AddFirewallRulesFromConfig(l, true, c, fw)
  212. if err != nil {
  213. return nil, err
  214. }
  215. return fw, nil
  216. }
  217. // AddRule properly creates the in memory rule structure for a firewall table.
  218. func (f *Firewall) AddRule(incoming bool, proto uint8, startPort int32, endPort int32, groups []string, host string, cidr, localCidr, caName string, caSha string) error {
  219. var (
  220. ft *FirewallTable
  221. fp firewallPort
  222. )
  223. if incoming {
  224. ft = f.InRules
  225. } else {
  226. ft = f.OutRules
  227. }
  228. switch proto {
  229. case firewall.ProtoTCP:
  230. fp = ft.TCP
  231. case firewall.ProtoUDP:
  232. fp = ft.UDP
  233. case firewall.ProtoICMP, firewall.ProtoICMPv6:
  234. //ICMP traffic doesn't have ports, so we always coerce to "any", even if a value is provided
  235. if startPort != firewall.PortAny {
  236. f.l.WithField("startPort", startPort).Warn("ignoring port specification for ICMP firewall rule")
  237. }
  238. startPort = firewall.PortAny
  239. endPort = firewall.PortAny
  240. fp = ft.ICMP
  241. case firewall.ProtoAny:
  242. fp = ft.AnyProto
  243. default:
  244. return fmt.Errorf("unknown protocol %v", proto)
  245. }
  246. // We need this rule string because we generate a hash. Removing this will break firewall reload.
  247. ruleString := fmt.Sprintf(
  248. "incoming: %v, proto: %v, startPort: %v, endPort: %v, groups: %v, host: %v, ip: %v, localIp: %v, caName: %v, caSha: %s",
  249. incoming, proto, startPort, endPort, groups, host, cidr, localCidr, caName, caSha,
  250. )
  251. f.rules += ruleString + "\n"
  252. direction := "incoming"
  253. if !incoming {
  254. direction = "outgoing"
  255. }
  256. f.l.WithField("firewallRule", m{"direction": direction, "proto": proto, "startPort": startPort, "endPort": endPort, "groups": groups, "host": host, "cidr": cidr, "localCidr": localCidr, "caName": caName, "caSha": caSha}).
  257. Info("Firewall rule added")
  258. return fp.addRule(f, startPort, endPort, groups, host, cidr, localCidr, caName, caSha)
  259. }
  260. // GetRuleHash returns a hash representation of all inbound and outbound rules
  261. func (f *Firewall) GetRuleHash() string {
  262. sum := sha256.Sum256([]byte(f.rules))
  263. return hex.EncodeToString(sum[:])
  264. }
  265. // GetRuleHashFNV returns a uint32 FNV-1 hash representation the rules, for use as a metric value
  266. func (f *Firewall) GetRuleHashFNV() uint32 {
  267. h := fnv.New32a()
  268. h.Write([]byte(f.rules))
  269. return h.Sum32()
  270. }
  271. // GetRuleHashes returns both the sha256 and FNV-1 hashes, suitable for logging
  272. func (f *Firewall) GetRuleHashes() string {
  273. return "SHA:" + f.GetRuleHash() + ",FNV:" + strconv.FormatUint(uint64(f.GetRuleHashFNV()), 10)
  274. }
  275. func AddFirewallRulesFromConfig(l *logrus.Logger, inbound bool, c *config.C, fw FirewallInterface) error {
  276. var table string
  277. if inbound {
  278. table = "firewall.inbound"
  279. } else {
  280. table = "firewall.outbound"
  281. }
  282. r := c.Get(table)
  283. if r == nil {
  284. return nil
  285. }
  286. rs, ok := r.([]any)
  287. if !ok {
  288. return fmt.Errorf("%s failed to parse, should be an array of rules", table)
  289. }
  290. for i, t := range rs {
  291. r, err := convertRule(l, t, table, i)
  292. if err != nil {
  293. return fmt.Errorf("%s rule #%v; %s", table, i, err)
  294. }
  295. if r.Code != "" && r.Port != "" {
  296. return fmt.Errorf("%s rule #%v; only one of port or code should be provided", table, i)
  297. }
  298. if r.Host == "" && len(r.Groups) == 0 && r.Cidr == "" && r.LocalCidr == "" && r.CAName == "" && r.CASha == "" {
  299. return fmt.Errorf("%s rule #%v; at least one of host, group, cidr, local_cidr, ca_name, or ca_sha must be provided", table, i)
  300. }
  301. var sPort, errPort string
  302. if r.Code != "" {
  303. errPort = "code"
  304. sPort = r.Code
  305. } else {
  306. errPort = "port"
  307. sPort = r.Port
  308. }
  309. var proto uint8
  310. var startPort, endPort int32
  311. switch r.Proto {
  312. case "any":
  313. proto = firewall.ProtoAny
  314. startPort, endPort, err = parsePort(sPort)
  315. case "tcp":
  316. proto = firewall.ProtoTCP
  317. startPort, endPort, err = parsePort(sPort)
  318. case "udp":
  319. proto = firewall.ProtoUDP
  320. startPort, endPort, err = parsePort(sPort)
  321. case "icmp":
  322. proto = firewall.ProtoICMP
  323. startPort = firewall.PortAny
  324. endPort = firewall.PortAny
  325. if sPort != "" {
  326. l.WithField("port", sPort).Warn("ignoring port specification for ICMP firewall rule")
  327. }
  328. default:
  329. return fmt.Errorf("%s rule #%v; proto was not understood; `%s`", table, i, r.Proto)
  330. }
  331. if err != nil {
  332. return fmt.Errorf("%s rule #%v; %s %s", table, i, errPort, err)
  333. }
  334. if r.Cidr != "" && r.Cidr != "any" {
  335. _, err = netip.ParsePrefix(r.Cidr)
  336. if err != nil {
  337. return fmt.Errorf("%s rule #%v; cidr did not parse; %s", table, i, err)
  338. }
  339. }
  340. if r.LocalCidr != "" && r.LocalCidr != "any" {
  341. _, err = netip.ParsePrefix(r.LocalCidr)
  342. if err != nil {
  343. return fmt.Errorf("%s rule #%v; local_cidr did not parse; %s", table, i, err)
  344. }
  345. }
  346. if warning := r.sanity(); warning != nil {
  347. l.Warnf("%s rule #%v; %s", table, i, warning)
  348. }
  349. err = fw.AddRule(inbound, proto, startPort, endPort, r.Groups, r.Host, r.Cidr, r.LocalCidr, r.CAName, r.CASha)
  350. if err != nil {
  351. return fmt.Errorf("%s rule #%v; `%s`", table, i, err)
  352. }
  353. }
  354. return nil
  355. }
  356. var ErrUnknownNetworkType = errors.New("unknown network type")
  357. var ErrPeerRejected = errors.New("remote address is not within a network that we handle")
  358. var ErrInvalidRemoteIP = errors.New("remote address is not in remote certificate networks")
  359. var ErrInvalidLocalIP = errors.New("local address is not in list of handled local addresses")
  360. var ErrNoMatchingRule = errors.New("no matching rule in firewall table")
  361. // Drop returns an error if the packet should be dropped, explaining why. It
  362. // returns nil if the packet should not be dropped.
  363. func (f *Firewall) Drop(fp firewall.Packet, ctx firewall.PacketContext, incoming bool, h *HostInfo, caPool *cert.CAPool, localCache firewall.ConntrackCache) error {
  364. // Check if we spoke to this tuple, if we did then allow this packet
  365. if f.inConns(fp, h, caPool, localCache) {
  366. return nil
  367. }
  368. peerCert := h.ConnectionState.peerCert
  369. // Make sure remote address matches nebula certificate, and determine how to treat it
  370. if h.networks == nil {
  371. // Simple case: Certificate has one address and no unsafe networks
  372. if h.vpnAddrs[0] != fp.RemoteAddr {
  373. f.metrics(incoming).droppedRemoteAddr.Inc(1)
  374. f.reportDrop(incoming, events.DropInvalidRemoteIP, fp, ctx, peerCert)
  375. return ErrInvalidRemoteIP
  376. }
  377. } else {
  378. nwType, ok := h.networks.Lookup(fp.RemoteAddr)
  379. if !ok {
  380. f.metrics(incoming).droppedRemoteAddr.Inc(1)
  381. f.reportDrop(incoming, events.DropInvalidRemoteIP, fp, ctx, peerCert)
  382. return ErrInvalidRemoteIP
  383. }
  384. switch nwType {
  385. case NetworkTypeVPN:
  386. break // nothing special
  387. case NetworkTypeVPNPeer:
  388. f.metrics(incoming).droppedRemoteAddr.Inc(1)
  389. f.reportDrop(incoming, events.DropPeerRejected, fp, ctx, peerCert)
  390. return ErrPeerRejected // reject for now, one day this may have different FW rules
  391. case NetworkTypeUnsafe:
  392. break // nothing special, one day this may have different FW rules
  393. default:
  394. f.metrics(incoming).droppedRemoteAddr.Inc(1)
  395. f.reportDrop(incoming, events.DropUnknownNetwork, fp, ctx, peerCert)
  396. return ErrUnknownNetworkType //should never happen
  397. }
  398. }
  399. // Make sure we are supposed to be handling this local ip address
  400. if !f.routableNetworks.Contains(fp.LocalAddr) {
  401. f.metrics(incoming).droppedLocalAddr.Inc(1)
  402. f.reportDrop(incoming, events.DropInvalidLocalIP, fp, ctx, peerCert)
  403. return ErrInvalidLocalIP
  404. }
  405. table := f.OutRules
  406. if incoming {
  407. table = f.InRules
  408. }
  409. // We now know which firewall table to check against
  410. if !table.match(fp, incoming, peerCert, caPool) {
  411. f.metrics(incoming).droppedNoRule.Inc(1)
  412. f.reportDrop(incoming, events.DropNoMatchingRule, fp, ctx, peerCert)
  413. return ErrNoMatchingRule
  414. }
  415. // We always want to conntrack since it is a faster operation
  416. f.addConn(fp, ctx, incoming, peerCert)
  417. return nil
  418. }
  419. func (f *Firewall) metrics(incoming bool) firewallMetrics {
  420. if incoming {
  421. return f.incomingMetrics
  422. } else {
  423. return f.outgoingMetrics
  424. }
  425. }
  426. // Destroy cleans up any known cyclical references so the object can be freed by GC. This should be called if a new
  427. // firewall object is created
  428. func (f *Firewall) Destroy() {
  429. //TODO: clean references if/when needed
  430. }
  431. func (f *Firewall) reportDrop(incoming bool, reason events.DropReason, fp firewall.Packet, ctx firewall.PacketContext, peerCert *cert.CachedCertificate) {
  432. r := f.reporter
  433. if r == nil {
  434. return
  435. }
  436. r.ReportDrop(events.DropEvent{
  437. Incoming: incoming,
  438. Reason: reason,
  439. Packet: fp,
  440. Context: ctx,
  441. PeerCert: peerCert,
  442. RulesVersion: f.rulesVersion,
  443. })
  444. }
  445. func (f *Firewall) reportFlowCreate(incoming bool, fp firewall.Packet, ctx firewall.PacketContext, peerCert *cert.CachedCertificate) {
  446. r := f.reporter
  447. if r == nil {
  448. return
  449. }
  450. r.ReportFlowCreate(events.FlowCreateEvent{
  451. Incoming: incoming,
  452. Packet: fp,
  453. Context: ctx,
  454. PeerCert: peerCert,
  455. RulesVersion: f.rulesVersion,
  456. })
  457. }
  458. func (f *Firewall) reportFlowEvict(incoming bool, fp firewall.Packet, rulesVersion uint16, expired bool) {
  459. r := f.reporter
  460. if r == nil {
  461. return
  462. }
  463. r.ReportFlowEvict(events.FlowEvictEvent{
  464. Incoming: incoming,
  465. Packet: fp,
  466. RulesVersion: rulesVersion,
  467. Expired: expired,
  468. })
  469. }
  470. func (f *Firewall) reportRulesReload(oldVersion, newVersion uint16) {
  471. r := f.reporter
  472. if r == nil {
  473. return
  474. }
  475. r.ReportRulesReload(events.RulesReloadEvent{
  476. OldVersion: oldVersion,
  477. NewVersion: newVersion,
  478. })
  479. }
  480. func (f *Firewall) EmitStats() {
  481. conntrack := f.Conntrack
  482. conntrack.Lock()
  483. conntrackCount := len(conntrack.Conns)
  484. conntrack.Unlock()
  485. metrics.GetOrRegisterGauge("firewall.conntrack.count", nil).Update(int64(conntrackCount))
  486. metrics.GetOrRegisterGauge("firewall.rules.version", nil).Update(int64(f.rulesVersion))
  487. metrics.GetOrRegisterGauge("firewall.rules.hash", nil).Update(int64(f.GetRuleHashFNV()))
  488. }
  489. func (f *Firewall) inConns(fp firewall.Packet, h *HostInfo, caPool *cert.CAPool, localCache firewall.ConntrackCache) bool {
  490. if localCache != nil {
  491. if _, ok := localCache[fp]; ok {
  492. return true
  493. }
  494. }
  495. conntrack := f.Conntrack
  496. conntrack.Lock()
  497. // Purge every time we test
  498. ep, has := conntrack.TimerWheel.Purge()
  499. if has {
  500. f.evict(ep)
  501. }
  502. c, ok := conntrack.Conns[fp]
  503. if !ok {
  504. conntrack.Unlock()
  505. return false
  506. }
  507. if c.rulesVersion != f.rulesVersion {
  508. // This conntrack entry was for an older rule set, validate
  509. // it still passes with the current rule set
  510. table := f.OutRules
  511. if c.incoming {
  512. table = f.InRules
  513. }
  514. // We now know which firewall table to check against
  515. if !table.match(fp, c.incoming, h.ConnectionState.peerCert, caPool) {
  516. if f.l.Level >= logrus.DebugLevel {
  517. h.logger(f.l).
  518. WithField("fwPacket", fp).
  519. WithField("incoming", c.incoming).
  520. WithField("rulesVersion", f.rulesVersion).
  521. WithField("oldRulesVersion", c.rulesVersion).
  522. Debugln("dropping old conntrack entry, does not match new ruleset")
  523. }
  524. oldRulesVersion := c.rulesVersion
  525. delete(conntrack.Conns, fp)
  526. f.reportFlowEvict(c.incoming, fp, oldRulesVersion, false)
  527. conntrack.Unlock()
  528. return false
  529. }
  530. if f.l.Level >= logrus.DebugLevel {
  531. h.logger(f.l).
  532. WithField("fwPacket", fp).
  533. WithField("incoming", c.incoming).
  534. WithField("rulesVersion", f.rulesVersion).
  535. WithField("oldRulesVersion", c.rulesVersion).
  536. Debugln("keeping old conntrack entry, does match new ruleset")
  537. }
  538. c.rulesVersion = f.rulesVersion
  539. }
  540. switch fp.Protocol {
  541. case firewall.ProtoTCP:
  542. c.Expires = time.Now().Add(f.TCPTimeout)
  543. case firewall.ProtoUDP:
  544. c.Expires = time.Now().Add(f.UDPTimeout)
  545. default:
  546. c.Expires = time.Now().Add(f.DefaultTimeout)
  547. }
  548. conntrack.Unlock()
  549. if localCache != nil {
  550. localCache[fp] = struct{}{}
  551. }
  552. return true
  553. }
  554. func (f *Firewall) addConn(fp firewall.Packet, ctx firewall.PacketContext, incoming bool, peerCert *cert.CachedCertificate) {
  555. var timeout time.Duration
  556. c := &conn{}
  557. switch fp.Protocol {
  558. case firewall.ProtoTCP:
  559. timeout = f.TCPTimeout
  560. case firewall.ProtoUDP:
  561. timeout = f.UDPTimeout
  562. default:
  563. timeout = f.DefaultTimeout
  564. }
  565. conntrack := f.Conntrack
  566. conntrack.Lock()
  567. _, existing := conntrack.Conns[fp]
  568. if !existing {
  569. conntrack.TimerWheel.Advance(time.Now())
  570. conntrack.TimerWheel.Add(fp, timeout)
  571. }
  572. // Record which rulesVersion allowed this connection, so we can retest after
  573. // firewall reload
  574. c.incoming = incoming
  575. c.rulesVersion = f.rulesVersion
  576. c.Expires = time.Now().Add(timeout)
  577. conntrack.Conns[fp] = c
  578. // Report only when this represents a genuinely new flow. Fires under the
  579. // conntrack lock so FlowCreate/FlowEvict events stay ordered relative to
  580. // RulesReloadEvent, which also fires under this lock.
  581. if !existing {
  582. f.reportFlowCreate(incoming, fp, ctx, peerCert)
  583. }
  584. conntrack.Unlock()
  585. }
  586. // Evict checks if a conntrack entry has expired, if so it is removed, if not it is re-added to the wheel
  587. // Caller must own the connMutex lock!
  588. func (f *Firewall) evict(p firewall.Packet) {
  589. // Are we still tracking this conn?
  590. conntrack := f.Conntrack
  591. t, ok := conntrack.Conns[p]
  592. if !ok {
  593. return
  594. }
  595. newT := t.Expires.Sub(time.Now())
  596. // Timeout is in the future, re-add the timer
  597. if newT > 0 {
  598. conntrack.TimerWheel.Advance(time.Now())
  599. conntrack.TimerWheel.Add(p, newT)
  600. return
  601. }
  602. // This conn is done
  603. rulesVersion := t.rulesVersion
  604. incoming := t.incoming
  605. delete(conntrack.Conns, p)
  606. f.reportFlowEvict(incoming, p, rulesVersion, true)
  607. }
  608. func (ft *FirewallTable) match(p firewall.Packet, incoming bool, c *cert.CachedCertificate, caPool *cert.CAPool) bool {
  609. if ft.AnyProto.match(p, incoming, c, caPool) {
  610. return true
  611. }
  612. switch p.Protocol {
  613. case firewall.ProtoTCP:
  614. if ft.TCP.match(p, incoming, c, caPool) {
  615. return true
  616. }
  617. case firewall.ProtoUDP:
  618. if ft.UDP.match(p, incoming, c, caPool) {
  619. return true
  620. }
  621. case firewall.ProtoICMP, firewall.ProtoICMPv6:
  622. if ft.ICMP.match(p, incoming, c, caPool) {
  623. return true
  624. }
  625. }
  626. return false
  627. }
  628. func (fp firewallPort) addRule(f *Firewall, startPort int32, endPort int32, groups []string, host string, cidr, localCidr, caName string, caSha string) error {
  629. if startPort > endPort {
  630. return fmt.Errorf("start port was lower than end port")
  631. }
  632. for i := startPort; i <= endPort; i++ {
  633. if _, ok := fp[i]; !ok {
  634. fp[i] = &FirewallCA{
  635. CANames: make(map[string]*FirewallRule),
  636. CAShas: make(map[string]*FirewallRule),
  637. }
  638. }
  639. if err := fp[i].addRule(f, groups, host, cidr, localCidr, caName, caSha); err != nil {
  640. return err
  641. }
  642. }
  643. return nil
  644. }
  645. func (fp firewallPort) match(p firewall.Packet, incoming bool, c *cert.CachedCertificate, caPool *cert.CAPool) bool {
  646. // We don't have any allowed ports, bail
  647. if fp == nil {
  648. return false
  649. }
  650. // this branch is here to catch traffic from FirewallTable.Any.match and FirewallTable.ICMP.match
  651. if p.Protocol == firewall.ProtoICMP || p.Protocol == firewall.ProtoICMPv6 {
  652. // port numbers are re-used for connection tracking of ICMP,
  653. // but we don't want to actually filter on them.
  654. return fp[firewall.PortAny].match(p, c, caPool)
  655. }
  656. var port int32
  657. if p.Fragment {
  658. port = firewall.PortFragment
  659. } else if incoming {
  660. port = int32(p.LocalPort)
  661. } else {
  662. port = int32(p.RemotePort)
  663. }
  664. if fp[port].match(p, c, caPool) {
  665. return true
  666. }
  667. return fp[firewall.PortAny].match(p, c, caPool)
  668. }
  669. func (fc *FirewallCA) addRule(f *Firewall, groups []string, host string, cidr, localCidr, caName, caSha string) error {
  670. fr := func() *FirewallRule {
  671. return &FirewallRule{
  672. Hosts: make(map[string]*firewallLocalCIDR),
  673. Groups: make([]*firewallGroups, 0),
  674. CIDR: new(bart.Table[*firewallLocalCIDR]),
  675. }
  676. }
  677. if caSha == "" && caName == "" {
  678. if fc.Any == nil {
  679. fc.Any = fr()
  680. }
  681. return fc.Any.addRule(f, groups, host, cidr, localCidr)
  682. }
  683. if caSha != "" {
  684. if _, ok := fc.CAShas[caSha]; !ok {
  685. fc.CAShas[caSha] = fr()
  686. }
  687. err := fc.CAShas[caSha].addRule(f, groups, host, cidr, localCidr)
  688. if err != nil {
  689. return err
  690. }
  691. }
  692. if caName != "" {
  693. if _, ok := fc.CANames[caName]; !ok {
  694. fc.CANames[caName] = fr()
  695. }
  696. err := fc.CANames[caName].addRule(f, groups, host, cidr, localCidr)
  697. if err != nil {
  698. return err
  699. }
  700. }
  701. return nil
  702. }
  703. func (fc *FirewallCA) match(p firewall.Packet, c *cert.CachedCertificate, caPool *cert.CAPool) bool {
  704. if fc == nil {
  705. return false
  706. }
  707. if fc.Any.match(p, c) {
  708. return true
  709. }
  710. if t, ok := fc.CAShas[c.Certificate.Issuer()]; ok {
  711. if t.match(p, c) {
  712. return true
  713. }
  714. }
  715. s, err := caPool.GetCAForCert(c.Certificate)
  716. if err != nil {
  717. return false
  718. }
  719. return fc.CANames[s.Certificate.Name()].match(p, c)
  720. }
  721. func (fr *FirewallRule) addRule(f *Firewall, groups []string, host, cidr, localCidr string) error {
  722. flc := func() *firewallLocalCIDR {
  723. return &firewallLocalCIDR{
  724. LocalCIDR: new(bart.Lite),
  725. }
  726. }
  727. if fr.isAny(groups, host, cidr) {
  728. if fr.Any == nil {
  729. fr.Any = flc()
  730. }
  731. return fr.Any.addRule(f, localCidr)
  732. }
  733. if len(groups) > 0 {
  734. nlc := flc()
  735. err := nlc.addRule(f, localCidr)
  736. if err != nil {
  737. return err
  738. }
  739. fr.Groups = append(fr.Groups, &firewallGroups{
  740. Groups: groups,
  741. LocalCIDR: nlc,
  742. })
  743. }
  744. if host != "" {
  745. nlc := fr.Hosts[host]
  746. if nlc == nil {
  747. nlc = flc()
  748. }
  749. err := nlc.addRule(f, localCidr)
  750. if err != nil {
  751. return err
  752. }
  753. fr.Hosts[host] = nlc
  754. }
  755. if cidr != "" {
  756. c, err := netip.ParsePrefix(cidr)
  757. if err != nil {
  758. return err
  759. }
  760. nlc, _ := fr.CIDR.Get(c)
  761. if nlc == nil {
  762. nlc = flc()
  763. }
  764. err = nlc.addRule(f, localCidr)
  765. if err != nil {
  766. return err
  767. }
  768. fr.CIDR.Insert(c, nlc)
  769. }
  770. return nil
  771. }
  772. func (fr *FirewallRule) isAny(groups []string, host string, cidr string) bool {
  773. if len(groups) == 0 && host == "" && cidr == "" {
  774. return true
  775. }
  776. if slices.Contains(groups, "any") {
  777. return true
  778. }
  779. if host == "any" {
  780. return true
  781. }
  782. if cidr == "any" {
  783. return true
  784. }
  785. return false
  786. }
  787. func (fr *FirewallRule) match(p firewall.Packet, c *cert.CachedCertificate) bool {
  788. if fr == nil {
  789. return false
  790. }
  791. // Shortcut path for if groups, hosts, or cidr contained an `any`
  792. if fr.Any.match(p, c) {
  793. return true
  794. }
  795. // Need any of group, host, or cidr to match
  796. for _, sg := range fr.Groups {
  797. found := false
  798. for _, g := range sg.Groups {
  799. if _, ok := c.InvertedGroups[g]; !ok {
  800. found = false
  801. break
  802. }
  803. found = true
  804. }
  805. if found && sg.LocalCIDR.match(p, c) {
  806. return true
  807. }
  808. }
  809. if fr.Hosts != nil {
  810. if flc, ok := fr.Hosts[c.Certificate.Name()]; ok {
  811. if flc.match(p, c) {
  812. return true
  813. }
  814. }
  815. }
  816. for _, v := range fr.CIDR.Supernets(netip.PrefixFrom(p.RemoteAddr, p.RemoteAddr.BitLen())) {
  817. if v.match(p, c) {
  818. return true
  819. }
  820. }
  821. return false
  822. }
  823. func (flc *firewallLocalCIDR) addRule(f *Firewall, localCidr string) error {
  824. if localCidr == "any" {
  825. flc.Any = true
  826. return nil
  827. }
  828. if localCidr == "" {
  829. if !f.hasUnsafeNetworks || f.defaultLocalCIDRAny {
  830. flc.Any = true
  831. return nil
  832. }
  833. for _, network := range f.assignedNetworks {
  834. flc.LocalCIDR.Insert(network)
  835. }
  836. return nil
  837. }
  838. c, err := netip.ParsePrefix(localCidr)
  839. if err != nil {
  840. return err
  841. }
  842. flc.LocalCIDR.Insert(c)
  843. return nil
  844. }
  845. func (flc *firewallLocalCIDR) match(p firewall.Packet, c *cert.CachedCertificate) bool {
  846. if flc == nil {
  847. return false
  848. }
  849. if flc.Any {
  850. return true
  851. }
  852. return flc.LocalCIDR.Contains(p.LocalAddr)
  853. }
  854. type rule struct {
  855. Port string
  856. Code string
  857. Proto string
  858. Host string
  859. Groups []string
  860. Cidr string
  861. LocalCidr string
  862. CAName string
  863. CASha string
  864. }
  865. func convertRule(l *logrus.Logger, p any, table string, i int) (rule, error) {
  866. r := rule{}
  867. m, ok := p.(map[string]any)
  868. if !ok {
  869. return r, errors.New("could not parse rule")
  870. }
  871. toString := func(k string, m map[string]any) string {
  872. v, ok := m[k]
  873. if !ok {
  874. return ""
  875. }
  876. return fmt.Sprintf("%v", v)
  877. }
  878. r.Port = toString("port", m)
  879. r.Code = toString("code", m)
  880. r.Proto = toString("proto", m)
  881. r.Host = toString("host", m)
  882. r.Cidr = toString("cidr", m)
  883. r.LocalCidr = toString("local_cidr", m)
  884. r.CAName = toString("ca_name", m)
  885. r.CASha = toString("ca_sha", m)
  886. // Make sure group isn't an array
  887. if v, ok := m["group"].([]any); ok {
  888. if len(v) > 1 {
  889. return r, errors.New("group should contain a single value, an array with more than one entry was provided")
  890. }
  891. l.Warnf("%s rule #%v; group was an array with a single value, converting to simple value", table, i)
  892. m["group"] = v[0]
  893. }
  894. singleGroup := toString("group", m)
  895. if rg, ok := m["groups"]; ok {
  896. switch reflect.TypeOf(rg).Kind() {
  897. case reflect.Slice:
  898. v := reflect.ValueOf(rg)
  899. r.Groups = make([]string, v.Len())
  900. for i := 0; i < v.Len(); i++ {
  901. r.Groups[i] = v.Index(i).Interface().(string)
  902. }
  903. case reflect.String:
  904. r.Groups = []string{rg.(string)}
  905. default:
  906. r.Groups = []string{fmt.Sprintf("%v", rg)}
  907. }
  908. }
  909. //flatten group vs groups
  910. if singleGroup != "" {
  911. // Check if we have both groups and group provided in the rule config
  912. if len(r.Groups) > 0 {
  913. return r, fmt.Errorf("only one of group or groups should be defined, both provided")
  914. }
  915. r.Groups = []string{singleGroup}
  916. }
  917. return r, nil
  918. }
  919. // sanity returns an error if the rule would be evaluated in a way that would short-circuit a configured check on a wildcard value
  920. // rules are evaluated as "port AND proto AND (ca_sha OR ca_name) AND (host OR group OR groups OR cidr) AND local_cidr"
  921. func (r *rule) sanity() error {
  922. //port, proto, local_cidr are AND, no need to check here
  923. //ca_sha and ca_name don't have a wildcard value, no need to check here
  924. groupsEmpty := len(r.Groups) == 0
  925. hostEmpty := r.Host == ""
  926. cidrEmpty := r.Cidr == ""
  927. if (groupsEmpty && hostEmpty && cidrEmpty) == true {
  928. return nil //no content!
  929. }
  930. groupsHasAny := slices.Contains(r.Groups, "any")
  931. if groupsHasAny && len(r.Groups) > 1 {
  932. return fmt.Errorf("groups spec [%s] contains the group '\"any\". This rule will ignore the other groups specified", r.Groups)
  933. }
  934. if r.Host == "any" {
  935. if !groupsEmpty {
  936. return fmt.Errorf("groups specified as %s, but host=any will match any host, regardless of groups", r.Groups)
  937. }
  938. if !cidrEmpty {
  939. return fmt.Errorf("cidr specified as %s, but host=any will match any host, regardless of cidr", r.Cidr)
  940. }
  941. }
  942. if groupsHasAny {
  943. if !hostEmpty && r.Host != "any" {
  944. return fmt.Errorf("groups spec [%s] contains the group '\"any\". This rule will ignore the specified host %s", r.Groups, r.Host)
  945. }
  946. if !cidrEmpty {
  947. return fmt.Errorf("groups spec [%s] contains the group '\"any\". This rule will ignore the specified cidr %s", r.Groups, r.Cidr)
  948. }
  949. }
  950. if r.Code != "" {
  951. return fmt.Errorf("code specified as [%s]. Support for 'code' will be dropped in a future release, as it has never been functional", r.Code)
  952. }
  953. //todo alert on cidr-any
  954. return nil
  955. }
  956. func parsePort(s string) (int32, int32, error) {
  957. var err error
  958. const notAPort int32 = -2
  959. if s == "any" {
  960. return firewall.PortAny, firewall.PortAny, nil
  961. }
  962. if s == "fragment" {
  963. return firewall.PortFragment, firewall.PortFragment, nil
  964. }
  965. if !strings.Contains(s, `-`) {
  966. rPort, err := strconv.Atoi(s)
  967. if err != nil {
  968. return notAPort, notAPort, fmt.Errorf("was not a number; `%s`", s)
  969. }
  970. return int32(rPort), int32(rPort), nil
  971. }
  972. sPorts := strings.SplitN(s, `-`, 2)
  973. for i := range sPorts {
  974. sPorts[i] = strings.Trim(sPorts[i], " ")
  975. }
  976. if len(sPorts) != 2 || sPorts[0] == "" || sPorts[1] == "" {
  977. return notAPort, notAPort, fmt.Errorf("appears to be a range but could not be parsed; `%s`", s)
  978. }
  979. rStartPort, err := strconv.Atoi(sPorts[0])
  980. if err != nil {
  981. return notAPort, notAPort, fmt.Errorf("beginning range was not a number; `%s`", sPorts[0])
  982. }
  983. rEndPort, err := strconv.Atoi(sPorts[1])
  984. if err != nil {
  985. return notAPort, notAPort, fmt.Errorf("ending range was not a number; `%s`", sPorts[1])
  986. }
  987. startPort := int32(rStartPort)
  988. endPort := int32(rEndPort)
  989. if startPort == firewall.PortAny {
  990. endPort = firewall.PortAny
  991. }
  992. return startPort, endPort, nil
  993. }