table.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. package firewall
  2. import (
  3. "net/netip"
  4. "github.com/slackhq/nebula/cert"
  5. )
  6. // Rules are the Tables built for each direction, with hashes of the rules they were built from
  7. type Rules struct {
  8. In, Out *Table
  9. // Hash is a sha256 of the rules, HashFNV is an FNV-1a of them for use as a metric value.
  10. // Rule sets with the same rules have the same hashes.
  11. Hash string
  12. HashFNV uint32
  13. }
  14. // Table holds the rules for one direction. A packet is allowed when every clause of a single rule allows it:
  15. // proto AND port AND (CA SHA or CA name) AND local CIDR AND (group OR groups OR name OR remote CIDR).
  16. // A Table does not change once built; see RulesBuilder.
  17. type Table struct {
  18. // rules holds every rule for the direction, in the order they were added. A rule's index in this slice is
  19. // its id in the ruleSets.
  20. rules []rule
  21. // protos holds the index for each IP protocol number, so a packet is checked against a single index.
  22. // A protocol with rules of its own has an index that also includes every proto `any` rule. The remaining
  23. // protocols share an index of only the proto `any` rules: one for protocols with ports and one for
  24. // protocols without; see hasPorts. An entry is nil when the protocol has no rules at all.
  25. // ICMP and ICMPv6 share one index.
  26. protos [256]*protoIndex
  27. }
  28. // protoIndex holds the rules that can apply to one protocol, in a set for each kind of packet. Rule ids are
  29. // indexes into the direction's rules, so every set is sized by the direction's rule count, not the protocol's:
  30. // byPort costs 64 KB per 8 rules in the direction.
  31. type protoIndex struct {
  32. // hasPorts reports whether the protocol has ports; see iputil.HasPorts.
  33. hasPorts bool
  34. // byPort has a set for every port number of a protocol with ports. Port 0 is covered only by port `any`
  35. // rules, since a range starts at 1.
  36. byPort ruleSets
  37. // packet holds the rules for a packet of a protocol without ports: the port `any` rules.
  38. packet ruleSet
  39. // fragment holds the rules for a fragment of any protocol: the port `any` and port `fragment` rules.
  40. fragment ruleSet
  41. }
  42. // whichMatch returns the set of rules whose proto and port clauses allow p.
  43. func (pi *protoIndex) whichMatch(p *Packet, incoming bool) ruleSet {
  44. switch {
  45. case p.Fragment:
  46. // The ports of a fragmented packet are in its first fragment, and this is a later one.
  47. return pi.fragment
  48. case !pi.hasPorts:
  49. // The port fields are zero, or for ICMP the identifier, which is only for connection tracking.
  50. return pi.packet
  51. case incoming:
  52. return pi.byPort.at(int(p.LocalPort))
  53. default:
  54. return pi.byPort.at(int(p.RemotePort))
  55. }
  56. }
  57. // rule is a parsed rule. A packet is allowed by the rule when its port, certificate, and address clauses all pass.
  58. type rule struct {
  59. // proto is the IP protocol number the rule applies to, or ProtoAny for every protocol. ICMPv6 rules are kept
  60. // as ICMP. The protoIndex checks it, so match does not.
  61. proto uint8
  62. // startPort and endPort are the port clause. A startPort of PortAny covers every port, every packet of a
  63. // protocol without ports, and every fragment. PortFragment covers only fragments. A range covers the ports
  64. // in it, so it never applies to a protocol without ports.
  65. startPort, endPort int32
  66. // caSha and caName pass a certificate issued by either CA. When neither is set, any CA passes.
  67. caSha, caName string
  68. // remoteAny makes host, groups, and cidr irrelevant. Otherwise, the peer passes when any one of them matches.
  69. remoteAny bool
  70. host string
  71. groups []string
  72. cidr netip.Prefix // Invalid when the rule has no cidr.
  73. // localAny allows any local address. Otherwise, local must contain the address. Neither is set for a rule
  74. // without a local cidr until the Table is built and the rule gets the default; see withDefaultLocal.
  75. localAny bool
  76. local []netip.Prefix
  77. }
  78. // Match reports whether a rule for the direction p is going allows it
  79. func (r *Rules) Match(p *Packet, incoming bool, c *cert.CachedCertificate, caPool *cert.CAPool) bool {
  80. t := r.Out
  81. if incoming {
  82. t = r.In
  83. }
  84. return t.Match(p, incoming, c, caPool)
  85. }
  86. // Match reports whether a rule allows p
  87. func (t *Table) Match(p *Packet, incoming bool, c *cert.CachedCertificate, caPool *cert.CAPool) bool {
  88. proto := t.protos[p.Protocol]
  89. if proto == nil {
  90. // The protocol has no rules of its own, and there are no proto `any` rules.
  91. return false
  92. }
  93. // The index yields every rule whose proto and port clauses allow p.
  94. // The packet is allowed when the remaining clauses of any of those rules allow it as well.
  95. for id := range proto.whichMatch(p, incoming).all() {
  96. if t.rules[id].match(p, c, caPool) {
  97. return true
  98. }
  99. }
  100. return false
  101. }
  102. // match reports whether r allows p from the peer with certificate c. The port clause is not checked here; the
  103. // protoIndex has already done so.
  104. func (r *rule) match(p *Packet, c *cert.CachedCertificate, caPool *cert.CAPool) bool {
  105. return r.matchLocal(p.LocalAddr) && r.matchRemote(p.RemoteAddr, c) && r.matchCA(c, caPool)
  106. }
  107. // matchLocal reports whether r allows the local address addr.
  108. func (r *rule) matchLocal(addr netip.Addr) bool {
  109. if r.localAny {
  110. return true
  111. }
  112. for _, prefix := range r.local {
  113. if prefix.Contains(addr) {
  114. return true
  115. }
  116. }
  117. return false
  118. }
  119. // matchRemote reports whether r allows the peer with certificate c, sending from addr. The cidr is checked with
  120. // Prefix.Contains, so 0.0.0.0/0 allows any IPv4 address and ::/0 allows any IPv6 address; neither allows the
  121. // other family.
  122. func (r *rule) matchRemote(addr netip.Addr, c *cert.CachedCertificate) bool {
  123. if r.remoteAny {
  124. return true
  125. }
  126. if r.cidr.IsValid() && r.cidr.Contains(addr) {
  127. return true
  128. }
  129. if r.host != "" && r.host == c.Certificate.Name() {
  130. return true
  131. }
  132. // Every group must be present.
  133. if len(r.groups) == 0 {
  134. return false
  135. }
  136. for _, g := range r.groups {
  137. if _, ok := c.InvertedGroups[g]; !ok {
  138. return false
  139. }
  140. }
  141. return true
  142. }
  143. // matchCA reports whether c was issued by a CA that r allows.
  144. func (r *rule) matchCA(c *cert.CachedCertificate, caPool *cert.CAPool) bool {
  145. if r.caSha == "" && r.caName == "" {
  146. return true
  147. }
  148. if r.caSha != "" && r.caSha == c.Certificate.Issuer() {
  149. return true
  150. }
  151. if r.caName != "" {
  152. s, err := caPool.GetCAForCert(c.Certificate)
  153. if err != nil {
  154. return false
  155. }
  156. return r.caName == s.Certificate.Name()
  157. }
  158. return false
  159. }