ffho_netfilter.py 4.9 KB

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