table.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  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.
  29. type protoIndex struct {
  30. // hasPorts reports whether the protocol has ports; see iputil.HasPorts.
  31. hasPorts bool
  32. // byPort has a set for every port number of a protocol with ports. Port 0 is covered only by port `any`
  33. // rules, since a range starts at 1.
  34. byPort ruleSets
  35. // packet holds the rules for a packet of a protocol without ports: the port `any` rules.
  36. packet ruleSet
  37. // fragment holds the rules for a fragment of any protocol: the port `any` and port `fragment` rules.
  38. fragment ruleSet
  39. }
  40. // whichMatch returns the set of rules whose proto and port clauses allow p.
  41. func (pi *protoIndex) whichMatch(p *Packet, incoming bool) ruleSet {
  42. switch {
  43. case p.Fragment:
  44. // The ports of a fragmented packet are in its first fragment, and this is a later one.
  45. return pi.fragment
  46. case !pi.hasPorts:
  47. // The port fields are zero, or for ICMP the identifier, which is only for connection tracking.
  48. return pi.packet
  49. case incoming:
  50. return pi.byPort.at(int(p.LocalPort))
  51. default:
  52. return pi.byPort.at(int(p.RemotePort))
  53. }
  54. }
  55. // rule is a parsed rule. A packet is allowed by the rule when its port, certificate, and address clauses all pass.
  56. type rule struct {
  57. // proto is the IP protocol number the rule applies to, or ProtoAny for every protocol. ICMPv6 rules are kept
  58. // as ICMP. The protoIndex checks it, so match does not.
  59. proto uint8
  60. // startPort and endPort are the port clause. A startPort of PortAny covers every port, every packet of a
  61. // protocol without ports, and every fragment. PortFragment covers only fragments. A range covers the ports
  62. // in it, so it never applies to a protocol without ports.
  63. startPort, endPort int32
  64. // caSha and caName pass a certificate issued by either CA. When neither is set, any CA passes.
  65. caSha, caName string
  66. // remoteAny makes host, groups, and cidr irrelevant. Otherwise, the peer passes when any one of them matches.
  67. remoteAny bool
  68. host string
  69. groups []string
  70. cidr netip.Prefix // Invalid when the rule has no cidr.
  71. // localAny allows any local address. Otherwise, local must contain the address. Neither is set for a rule
  72. // without a local cidr until the Table is built and the rule gets the default; see withDefaultLocal.
  73. localAny bool
  74. local []netip.Prefix
  75. }
  76. // Match reports whether a rule for the direction p is going allows it
  77. func (r *Rules) Match(p *Packet, incoming bool, c *cert.CachedCertificate, caPool *cert.CAPool) bool {
  78. t := r.Out
  79. if incoming {
  80. t = r.In
  81. }
  82. return t.Match(p, incoming, c, caPool)
  83. }
  84. // Match reports whether a rule allows p
  85. func (t *Table) Match(p *Packet, incoming bool, c *cert.CachedCertificate, caPool *cert.CAPool) bool {
  86. proto := t.protos[p.Protocol]
  87. if proto == nil {
  88. // The protocol has no rules of its own, and there are no proto `any` rules.
  89. return false
  90. }
  91. // The index yields every rule whose proto and port clauses allow p.
  92. // The packet is allowed when the remaining clauses of any of those rules allow it as well.
  93. for id := range proto.whichMatch(p, incoming).all() {
  94. if t.rules[id].match(p, c, caPool) {
  95. return true
  96. }
  97. }
  98. return false
  99. }
  100. // match reports whether r allows p from the peer with certificate c. The port clause is not checked here; the
  101. // protoIndex has already done so.
  102. func (r *rule) match(p *Packet, c *cert.CachedCertificate, caPool *cert.CAPool) bool {
  103. return r.matchLocal(p.LocalAddr) && r.matchRemote(p.RemoteAddr, c) && r.matchCA(c, caPool)
  104. }
  105. // matchLocal reports whether r allows the local address addr.
  106. func (r *rule) matchLocal(addr netip.Addr) bool {
  107. if r.localAny {
  108. return true
  109. }
  110. for _, prefix := range r.local {
  111. if prefix.Contains(addr) {
  112. return true
  113. }
  114. }
  115. return false
  116. }
  117. // matchRemote reports whether r allows the peer with certificate c, sending from addr. The cidr is checked with
  118. // Prefix.Contains, so 0.0.0.0/0 allows any IPv4 address and ::/0 allows any IPv6 address; neither allows the
  119. // other family.
  120. func (r *rule) matchRemote(addr netip.Addr, c *cert.CachedCertificate) bool {
  121. if r.remoteAny {
  122. return true
  123. }
  124. if r.cidr.IsValid() && r.cidr.Contains(addr) {
  125. return true
  126. }
  127. if r.host != "" && r.host == c.Certificate.Name() {
  128. return true
  129. }
  130. // Every group must be present.
  131. if len(r.groups) == 0 {
  132. return false
  133. }
  134. for _, g := range r.groups {
  135. if _, ok := c.InvertedGroups[g]; !ok {
  136. return false
  137. }
  138. }
  139. return true
  140. }
  141. // matchCA reports whether c was issued by a CA that r allows.
  142. func (r *rule) matchCA(c *cert.CachedCertificate, caPool *cert.CAPool) bool {
  143. if r.caSha == "" && r.caName == "" {
  144. return true
  145. }
  146. if r.caSha != "" && r.caSha == c.Certificate.Issuer() {
  147. return true
  148. }
  149. if r.caName != "" {
  150. s, err := caPool.GetCAForCert(c.Certificate)
  151. if err != nil {
  152. return false
  153. }
  154. return r.caName == s.Certificate.Name()
  155. }
  156. return false
  157. }