[multi-asic] Enhanced iptable default rules (#6765)

What I did:-

For multi-asic platforms added iptable v4 rule to communicate on docker bridge ip
For multi-asic platforms extend iptable v4 rule for iptable v6 also
For multi-asic program made all internal rules applicable for all protocols (not filter based on tcp/udp). This is done to be consistent same as local host rule
For multi-asic platforms made nat rule (to forward traffic from namespace to host) generic for all protocols and also use Source IP if present for matching
This commit is contained in:
abdosi 2021-02-25 09:39:36 -08:00 committed by Qi Luo
parent a9af05d1a7
commit 82093fd669

View File

@ -63,19 +63,23 @@ class ControlPlaneAclManager(daemon_base.DaemonBase):
ACL_SERVICES = { ACL_SERVICES = {
"NTP": { "NTP": {
"ip_protocols": ["udp"], "ip_protocols": ["udp"],
"dst_ports": ["123"] "dst_ports": ["123"],
"multi_asic_ns_to_host_fwd":False
}, },
"SNMP": { "SNMP": {
"ip_protocols": ["tcp", "udp"], "ip_protocols": ["tcp", "udp"],
"dst_ports": ["161"] "dst_ports": ["161"],
"multi_asic_ns_to_host_fwd":True
}, },
"SSH": { "SSH": {
"ip_protocols": ["tcp"], "ip_protocols": ["tcp"],
"dst_ports": ["22"] "dst_ports": ["22"],
"multi_asic_ns_to_host_fwd":True
}, },
"ANY": { "ANY": {
"ip_protocols": ["any"], "ip_protocols": ["any"],
"dst_ports": ["0"] "dst_ports": ["0"],
"multi_asic_ns_to_host_fwd":False
} }
} }
@ -226,54 +230,81 @@ class ControlPlaneAclManager(daemon_base.DaemonBase):
allow_internal_docker_ip_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -A INPUT -s {} -d {} -j ACCEPT".format allow_internal_docker_ip_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -A INPUT -s {} -d {} -j ACCEPT".format
(self.namespace_docker_mgmt_ip[namespace], self.namespace_docker_mgmt_ip[namespace])) (self.namespace_docker_mgmt_ip[namespace], self.namespace_docker_mgmt_ip[namespace]))
# For namespace docker allow all tcp/udp traffic from host docker bridge to its eth0 management ip allow_internal_docker_ip_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "ip6tables -A INPUT -s {} -d {} -j ACCEPT".format
allow_internal_docker_ip_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -A INPUT -p tcp -s {} -d {} -j ACCEPT".format (self.namespace_docker_mgmt_ipv6[namespace], self.namespace_docker_mgmt_ipv6[namespace]))
allow_internal_docker_ip_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -A INPUT -s {} -d {} -j ACCEPT".format
(self.namespace_mgmt_ip, self.namespace_docker_mgmt_ip[namespace])) (self.namespace_mgmt_ip, self.namespace_docker_mgmt_ip[namespace]))
allow_internal_docker_ip_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -A INPUT -p udp -s {} -d {} -j ACCEPT".format allow_internal_docker_ip_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "ip6tables -A INPUT -s {} -d {} -j ACCEPT".format
(self.namespace_mgmt_ip, self.namespace_docker_mgmt_ip[namespace])) (self.namespace_mgmt_ipv6, self.namespace_docker_mgmt_ipv6[namespace]))
else: else:
# Also host namespace communication on docker bridge on multi-asic.
if self.namespace_docker_mgmt_ip:
allow_internal_docker_ip_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -A INPUT -s {} -d {} -j ACCEPT".format
(self.namespace_mgmt_ip, self.namespace_mgmt_ip))
if self.namespace_docker_mgmt_ipv6:
allow_internal_docker_ip_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "ip6tables -A INPUT -s {} -d {} -j ACCEPT".format
(self.namespace_mgmt_ipv6, self.namespace_mgmt_ipv6))
# In host allow all tcp/udp traffic from namespace docker eth0 management ip to host docker bridge # In host allow all tcp/udp traffic from namespace docker eth0 management ip to host docker bridge
for docker_mgmt_ip in list(self.namespace_docker_mgmt_ip.values()): for docker_mgmt_ip in list(self.namespace_docker_mgmt_ip.values()):
allow_internal_docker_ip_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -A INPUT -p tcp -s {} -d {} -j ACCEPT".format allow_internal_docker_ip_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -A INPUT -s {} -d {} -j ACCEPT".format
(docker_mgmt_ip, self.namespace_mgmt_ip)) (docker_mgmt_ip, self.namespace_mgmt_ip))
allow_internal_docker_ip_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -A INPUT -p udp -s {} -d {} -j ACCEPT".format for docker_mgmt_ipv6 in list(self.namespace_docker_mgmt_ipv6.values()):
(docker_mgmt_ip, self.namespace_mgmt_ip)) allow_internal_docker_ip_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "ip6tables -A INPUT -s {} -d {} -j ACCEPT".format
(docker_mgmt_ipv6, self.namespace_mgmt_ipv6))
return allow_internal_docker_ip_cmds return allow_internal_docker_ip_cmds
def generate_fwd_snmp_traffic_from_namespace_to_host_commands(self, namespace): def generate_fwd_traffic_from_namespace_to_host_commands(self, namespace, acl_source_ip_map):
""" """
The below SNAT and DNAT rules are added in asic namespace in multi-ASIC platforms. It helps to forward the SNMP request coming The below SNAT and DNAT rules are added in asic namespace in multi-ASIC platforms. It helps to forward request coming
in through the front panel interfaces created/present in the asic namespace to the SNMP Agent running in SNMP container in in through the front panel interfaces created/present in the asic namespace for the servie running in linux host network namespace.
linux host network namespace. The external IP addresses are NATed to the internal docker IP addresses for the SNMP Agent to respond. The external IP addresses are NATed to the internal docker IP addresses for the Host service to respond.
""" """
fwd_snmp_traffic_from_namespace_to_host_cmds = []
if namespace: if not namespace:
return []
fwd_traffic_from_namespace_to_host_cmds = []
fwd_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -t nat -X")
fwd_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -t nat -F")
fwd_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "ip6tables -t nat -X")
fwd_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "ip6tables -t nat -F")
for acl_service in self.ACL_SERVICES:
if self.ACL_SERVICES[acl_service]["multi_asic_ns_to_host_fwd"]:
# Get the Source IP Set if exists else use default source ip prefix
nat_source_ipv4_set = acl_source_ip_map[acl_service]["ipv4"] if acl_source_ip_map and acl_source_ip_map[acl_service]["ipv4"] else { "0.0.0.0/0" }
nat_source_ipv6_set = acl_source_ip_map[acl_service]["ipv6"] if acl_source_ip_map and acl_source_ip_map[acl_service]["ipv6"] else { "::/0" }
for ip_protocol in self.ACL_SERVICES[acl_service]["ip_protocols"]:
for dst_port in self.ACL_SERVICES[acl_service]["dst_ports"]:
for ipv4_src_ip in nat_source_ipv4_set:
# IPv4 rules # IPv4 rules
fwd_snmp_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -t nat -X") fwd_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] +
fwd_snmp_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -t nat -F") "iptables -t nat -A PREROUTING -p {} -s {} --dport {} -j DNAT --to-destination {}".format
(ip_protocol, ipv4_src_ip, dst_port,
fwd_snmp_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] + self.namespace_mgmt_ip))
"iptables -t nat -A PREROUTING -p udp --dport {} -j DNAT --to-destination {}".format fwd_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] +
(self.ACL_SERVICES['SNMP']['dst_ports'][0], self.namespace_mgmt_ip)) "iptables -t nat -A POSTROUTING -p {} -s {} --dport {} -j SNAT --to-source {}".format
fwd_snmp_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] + (ip_protocol, ipv4_src_ip, dst_port,
"iptables -t nat -A POSTROUTING -p udp --dport {} -j SNAT --to-source {}".format self.namespace_docker_mgmt_ip[namespace]))
(self.ACL_SERVICES['SNMP']['dst_ports'][0], self.namespace_docker_mgmt_ip[namespace])) for ipv6_src_ip in nat_source_ipv6_set:
# IPv6 rules # IPv6 rules
fwd_snmp_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "ip6tables -t nat -X") fwd_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] +
fwd_snmp_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "ip6tables -t nat -F") "ip6tables -t nat -A PREROUTING -p {} -s {} --dport {} -j DNAT --to-destination {}".format
(ip_protocol, ipv6_src_ip, dst_port,
self.namespace_mgmt_ipv6))
fwd_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] +
"ip6tables -t nat -A POSTROUTING -p {} -s {} --dport {} -j SNAT --to-source {}".format
(ip_protocol,ipv6_src_ip, dst_port,
self.namespace_docker_mgmt_ipv6[namespace]))
fwd_snmp_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] + return fwd_traffic_from_namespace_to_host_cmds
"ip6tables -t nat -A PREROUTING -p udp --dport {} -j DNAT --to-destination {}".format
(self.ACL_SERVICES['SNMP']['dst_ports'][0], self.namespace_mgmt_ipv6))
fwd_snmp_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] +
"ip6tables -t nat -A POSTROUTING -p udp --dport {} -j SNAT --to-source {}".format
(self.ACL_SERVICES['SNMP']['dst_ports'][0], self.namespace_docker_mgmt_ipv6[namespace]))
return fwd_snmp_traffic_from_namespace_to_host_cmds
def is_rule_ipv4(self, rule_props): def is_rule_ipv4(self, rule_props):
if (("SRC_IP" in rule_props and rule_props["SRC_IP"]) or if (("SRC_IP" in rule_props and rule_props["SRC_IP"]) or
@ -298,6 +329,7 @@ class ControlPlaneAclManager(daemon_base.DaemonBase):
A list of strings, each string is an iptables shell command A list of strings, each string is an iptables shell command
""" """
iptables_cmds = [] iptables_cmds = []
service_to_source_ip_map = {}
# First, add iptables commands to set default policies to accept all # First, add iptables commands to set default policies to accept all
# traffic. In case we are connected remotely, the connection will not # traffic. In case we are connected remotely, the connection will not
@ -436,7 +468,8 @@ class ControlPlaneAclManager(daemon_base.DaemonBase):
self.log_warning("Unable to determine if ACL table '{}' contains IPv4 or IPv6 rules. Skipping table..." self.log_warning("Unable to determine if ACL table '{}' contains IPv4 or IPv6 rules. Skipping table..."
.format(table_name)) .format(table_name))
continue continue
ipv4_src_ip_set = set()
ipv6_src_ip_set = set()
# For each ACL rule in this table (in descending order of priority) # For each ACL rule in this table (in descending order of priority)
for priority in sorted(iter(acl_rules.keys()), reverse=True): for priority in sorted(iter(acl_rules.keys()), reverse=True):
rule_props = acl_rules[priority] rule_props = acl_rules[priority]
@ -456,8 +489,12 @@ class ControlPlaneAclManager(daemon_base.DaemonBase):
if "SRC_IPV6" in rule_props and rule_props["SRC_IPV6"]: if "SRC_IPV6" in rule_props and rule_props["SRC_IPV6"]:
rule_cmd += " -s {}".format(rule_props["SRC_IPV6"]) rule_cmd += " -s {}".format(rule_props["SRC_IPV6"])
if rule_props["PACKET_ACTION"] == "ACCEPT":
ipv6_src_ip_set.add(rule_props["SRC_IPV6"])
elif "SRC_IP" in rule_props and rule_props["SRC_IP"]: elif "SRC_IP" in rule_props and rule_props["SRC_IP"]:
rule_cmd += " -s {}".format(rule_props["SRC_IP"]) rule_cmd += " -s {}".format(rule_props["SRC_IP"])
if rule_props["PACKET_ACTION"] == "ACCEPT":
ipv4_src_ip_set.add(rule_props["SRC_IP"])
# Destination port 0 is reserved/unused port, so, using it to apply the rule to all ports. # Destination port 0 is reserved/unused port, so, using it to apply the rule to all ports.
if dst_port != "0": if dst_port != "0":
@ -479,6 +516,9 @@ class ControlPlaneAclManager(daemon_base.DaemonBase):
iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + rule_cmd) iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + rule_cmd)
num_ctrl_plane_acl_rules += 1 num_ctrl_plane_acl_rules += 1
service_to_source_ip_map.update({ acl_service:{ "ipv4":ipv4_src_ip_set, "ipv6":ipv6_src_ip_set } })
# Add iptables commands to block ip2me traffic # Add iptables commands to block ip2me traffic
iptables_cmds += self.generate_block_ip2me_traffic_iptables_commands(namespace) iptables_cmds += self.generate_block_ip2me_traffic_iptables_commands(namespace)
@ -493,7 +533,7 @@ class ControlPlaneAclManager(daemon_base.DaemonBase):
iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -A INPUT -j DROP") iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -A INPUT -j DROP")
iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "ip6tables -A INPUT -j DROP") iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "ip6tables -A INPUT -j DROP")
return iptables_cmds return iptables_cmds, service_to_source_ip_map
def update_control_plane_acls(self, namespace): def update_control_plane_acls(self, namespace):
""" """
@ -501,20 +541,25 @@ class ControlPlaneAclManager(daemon_base.DaemonBase):
Config DB, translates control plane ACLs into a list of iptables Config DB, translates control plane ACLs into a list of iptables
commands and runs them. commands and runs them.
""" """
iptables_cmds = self.get_acl_rules_and_translate_to_iptables_commands(namespace) iptables_cmds, service_to_source_ip_map = self.get_acl_rules_and_translate_to_iptables_commands(namespace)
self.log_info("Issuing the following iptables commands:") self.log_info("Issuing the following iptables commands:")
for cmd in iptables_cmds: for cmd in iptables_cmds:
self.log_info(" " + cmd) self.log_info(" " + cmd)
self.run_commands(iptables_cmds) self.run_commands(iptables_cmds)
def update_control_plane_nat_acls(self, namespace): self.update_control_plane_nat_acls(namespace, service_to_source_ip_map)
def update_control_plane_nat_acls(self, namespace, service_to_source_ip_map):
""" """
Convenience wrapper which programs the NAT rules for allowing the Convenience wrapper for multi-asic platforms
snmp traffic coming on the front panel interface which programs the NAT rules for redirecting the
traffic coming on the front panel interface map to namespace
to the host.
""" """
# Add iptables commands to allow front panel snmp traffic # Add iptables commands to allow front panel traffic
iptables_cmds = self.generate_fwd_snmp_traffic_from_namespace_to_host_commands(namespace) iptables_cmds = self.generate_fwd_traffic_from_namespace_to_host_commands(namespace, service_to_source_ip_map)
self.log_info("Issuing the following iptables commands:") self.log_info("Issuing the following iptables commands:")
for cmd in iptables_cmds: for cmd in iptables_cmds:
self.log_info(" " + cmd) self.log_info(" " + cmd)
@ -580,7 +625,6 @@ class ControlPlaneAclManager(daemon_base.DaemonBase):
for namespace in list(self.config_db_map.keys()): for namespace in list(self.config_db_map.keys()):
# Unconditionally update control plane ACLs once at start on given namespace # Unconditionally update control plane ACLs once at start on given namespace
self.update_control_plane_acls(namespace) self.update_control_plane_acls(namespace)
self.update_control_plane_nat_acls(namespace)
# Connect to Config DB of given namespace # Connect to Config DB of given namespace
acl_db_connector = swsscommon.DBConnector("CONFIG_DB", 0, False, namespace) acl_db_connector = swsscommon.DBConnector("CONFIG_DB", 0, False, namespace)
# Subscribe to notifications when ACL tables changes # Subscribe to notifications when ACL tables changes