ffho_netfilter.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. #
  2. # FFHO netfilter helper functions
  3. #
  4. import ipaddress
  5. import re
  6. import ffho_net
  7. # Prepare regex to match VLAN intefaces / extract IDs
  8. vlan_re = re.compile (r'^vlan(\d+)$')
  9. def generate_service_rules (services, acls, af):
  10. rules = []
  11. for srv in services:
  12. rule = ""
  13. comment = srv['descr']
  14. acl_comment = ""
  15. src_prefixes = []
  16. # If there are no DST IPs set at all or DST IPs for this AF set, we have a rule to build,
  17. # if this is NOT the case, there is no rule for this AF to generate, carry on.
  18. if not ((not srv['ips']['4'] and not srv['ips']['6']) or srv['ips'][str(af)]):
  19. continue
  20. # Is/are IP(s) set for this service?
  21. if srv['ips'][str(af)]:
  22. rule += "ip" if af == 4 else "ip6"
  23. dst_ips = srv['ips'][str(af)]
  24. if len (dst_ips) == 1:
  25. rule += " daddr %s " % dst_ips[0]
  26. else:
  27. rule += " daddr { %s } " % ", ".join (dst_ips)
  28. # ACLs defined for this service?
  29. if srv['acl']:
  30. srv_acl = sorted (srv['acl'])
  31. for ace in srv_acl:
  32. ace_pfx = (acls[ace][af])
  33. # Many entries
  34. if type (ace_pfx) == list:
  35. src_prefixes.extend (ace_pfx)
  36. else:
  37. src_prefixes.append (ace_pfx)
  38. acl_comment = "acl: %s" % ", ".join (srv_acl)
  39. # Additional prefixes defined for this service?
  40. if srv['additional_prefixes']:
  41. add_pfx = []
  42. # Additional prefixes are given as a space separated list
  43. for entry in srv['additional_prefixes'].split ():
  44. # Strip commas and spaces, just in case
  45. pfx_str = entry.strip (" ,")
  46. pfx_obj = ipaddress.ip_network (pfx_str)
  47. # We only care for additional pfx for this AF
  48. if pfx_obj.version != af:
  49. continue
  50. add_pfx.append (pfx_str)
  51. if add_pfx:
  52. src_prefixes.extend (add_pfx)
  53. if acl_comment:
  54. acl_comment += ", "
  55. acl_comment += "additional pfx"
  56. # Combine ACL + additional prefixes (if any)
  57. if src_prefixes:
  58. rule += "ip" if af == 4 else "ip6"
  59. if len (src_prefixes) > 1:
  60. rule += " saddr { %s } " % ", ".join (src_prefixes)
  61. else:
  62. rule += " saddr %s " % src_prefixes[0]
  63. if acl_comment:
  64. comment += " (%s)" % acl_comment
  65. # Multiple ports?
  66. if len (srv['ports']) > 1:
  67. ports = "{ %s }" % ", ".join (map (str, srv['ports']))
  68. else:
  69. ports = srv['ports'][0]
  70. rule += "%s dport %s counter accept comment \"%s\"" % (srv['proto'], ports, comment)
  71. rules.append (rule)
  72. return rules
  73. def generate_forward_policy (policy, roles, config_context):
  74. fp = {
  75. # Get default policy for packets to be forwarded
  76. 'policy' : 'drop',
  77. 'policy_reason' : 'default',
  78. 'rules': {
  79. 4 : [],
  80. 6 : [],
  81. },
  82. }
  83. if 'forward_default_policy' in policy:
  84. fp['policy'] = policy['forward_default_policy']
  85. fp['policy_reason'] = 'forward_default_policy'
  86. # Does any local role warrants for forwarding packets?
  87. accept_roles = [role for role in policy.get ('forward_accept_roles', []) if role in roles]
  88. if accept_roles:
  89. fp['policy'] = 'accept'
  90. fp['policy_reason'] = "roles: " + ",".join (accept_roles)
  91. try:
  92. cust_rules = config_context['filter']['forward']
  93. for af in [ 4, 6 ]:
  94. if af not in cust_rules:
  95. continue
  96. if type (cust_rules[af]) != list:
  97. raise ValueError ("nftables:filter:forward:%d in config context expected to be a list!" % af)
  98. fp['rules'][af] = cust_rules[af]
  99. except KeyError:
  100. pass
  101. return fp
  102. def generate_nat_policy (roles, config_context):
  103. np = {
  104. 4 : {},
  105. 6 : {},
  106. }
  107. # Any custom rules?
  108. cc_nat = config_context.get ('nat')
  109. if cc_nat:
  110. for chain in ['output', 'prerouting', 'postrouting']:
  111. if chain not in cc_nat:
  112. continue
  113. for af in [ 4, 6 ]:
  114. if str (af) in cc_nat[chain]:
  115. np[af][chain] = cc_nat[chain][str (af)]
  116. return np
  117. def _active_urpf (iface, iface_config):
  118. # Ignore loopback
  119. if iface == "lo":
  120. return False
  121. # Forcefully enable uRPF via tags on Netbox interface?
  122. if 'urpf_enable' in iface_config.get ('tags', []):
  123. return True
  124. # No uRPF on infra VPNs
  125. for vpn_prefix in ["gre_", "ovpn-", "wg-"]:
  126. if iface.startswith (vpn_prefix):
  127. return False
  128. # No address, no uRPF
  129. if not iface_config.get ('prefixes'):
  130. return False
  131. # Interface in vrf_external connect to the Internet
  132. if iface_config.get ('vrf') in ['vrf_external']:
  133. return False
  134. # Ignore interfaces by VLAN
  135. match = vlan_re.search (iface)
  136. if match:
  137. vid = int (match.group (1))
  138. # Magic
  139. if 900 <= vid <= 999:
  140. return False
  141. # Wired infrastructure stuff
  142. if 1000 <= vid <= 1499:
  143. return False
  144. # Wireless infrastructure stuff
  145. if 2000 <= vid <= 2299:
  146. return False
  147. return True
  148. def generate_urpf_policy (interfaces):
  149. urpf = {}
  150. for iface in sorted (interfaces.keys ()):
  151. iface_config = interfaces[iface]
  152. if not _active_urpf (iface, iface_config):
  153. continue
  154. # Ok this seems to be and edge interface
  155. urpf[iface] = {
  156. 'iface' : iface,
  157. 'desc' : iface_config.get ('desc', ''),
  158. 4 : [],
  159. 6 : [],
  160. }
  161. # Gather configure prefixes
  162. for address in iface_config.get ('prefixes'):
  163. pfx = ipaddress.ip_network (address, strict = False)
  164. urpf[iface][pfx.version].append ("%s/%s" % (pfx.network_address, pfx.prefixlen))
  165. sorted_urpf = []
  166. for iface in ffho_net.get_interface_list (urpf):
  167. sorted_urpf.append (urpf[iface])
  168. return sorted_urpf
  169. #
  170. # Check if at least one of the node roles are supposed to run DHCP
  171. def allow_dhcp (fw_policy, roles):
  172. for dhcp_role in fw_policy.get ('dhcp_roles', []):
  173. if dhcp_role in roles:
  174. return True
  175. return False