From 82093fd669876e14ba2dd1c8a474be10784ac784 Mon Sep 17 00:00:00 2001 From: abdosi <58047199+abdosi@users.noreply.github.com> Date: Thu, 25 Feb 2021 09:39:36 -0800 Subject: [PATCH] [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 --- src/sonic-host-services/scripts/caclmgrd | 134 +++++++++++++++-------- 1 file changed, 89 insertions(+), 45 deletions(-) diff --git a/src/sonic-host-services/scripts/caclmgrd b/src/sonic-host-services/scripts/caclmgrd index db2eeeba5c..f1d2a41abd 100755 --- a/src/sonic-host-services/scripts/caclmgrd +++ b/src/sonic-host-services/scripts/caclmgrd @@ -63,19 +63,23 @@ class ControlPlaneAclManager(daemon_base.DaemonBase): ACL_SERVICES = { "NTP": { "ip_protocols": ["udp"], - "dst_ports": ["123"] + "dst_ports": ["123"], + "multi_asic_ns_to_host_fwd":False }, "SNMP": { "ip_protocols": ["tcp", "udp"], - "dst_ports": ["161"] + "dst_ports": ["161"], + "multi_asic_ns_to_host_fwd":True }, "SSH": { "ip_protocols": ["tcp"], - "dst_ports": ["22"] + "dst_ports": ["22"], + "multi_asic_ns_to_host_fwd":True }, "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 (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] + "iptables -A INPUT -p tcp -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_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])) - allow_internal_docker_ip_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -A INPUT -p udp -s {} -d {} -j ACCEPT".format - (self.namespace_mgmt_ip, self.namespace_docker_mgmt_ip[namespace])) + 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_docker_mgmt_ipv6[namespace])) + 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 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)) - allow_internal_docker_ip_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -A INPUT -p udp -s {} -d {} -j ACCEPT".format - (docker_mgmt_ip, self.namespace_mgmt_ip)) + for docker_mgmt_ipv6 in list(self.namespace_docker_mgmt_ipv6.values()): + 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 - 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 - in through the front panel interfaces created/present in the asic namespace to the SNMP Agent running in SNMP container in - linux host network namespace. The external IP addresses are NATed to the internal docker IP addresses for the SNMP Agent to respond. + 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 for the servie running in linux host network namespace. + 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: - # IPv4 rules - fwd_snmp_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -t nat -X") - fwd_snmp_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -t nat -F") + if not namespace: + return [] - fwd_snmp_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] + - "iptables -t nat -A PREROUTING -p udp --dport {} -j DNAT --to-destination {}".format - (self.ACL_SERVICES['SNMP']['dst_ports'][0], self.namespace_mgmt_ip)) - fwd_snmp_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] + - "iptables -t nat -A POSTROUTING -p udp --dport {} -j SNAT --to-source {}".format - (self.ACL_SERVICES['SNMP']['dst_ports'][0], self.namespace_docker_mgmt_ip[namespace])) + 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") - # IPv6 rules - fwd_snmp_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "ip6tables -t nat -X") - fwd_snmp_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" } - fwd_snmp_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] + - "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])) + 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 + fwd_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] + + "iptables -t nat -A PREROUTING -p {} -s {} --dport {} -j DNAT --to-destination {}".format + (ip_protocol, ipv4_src_ip, dst_port, + self.namespace_mgmt_ip)) + fwd_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] + + "iptables -t nat -A POSTROUTING -p {} -s {} --dport {} -j SNAT --to-source {}".format + (ip_protocol, ipv4_src_ip, dst_port, + self.namespace_docker_mgmt_ip[namespace])) + for ipv6_src_ip in nat_source_ipv6_set: + # IPv6 rules + fwd_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] + + "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])) - return fwd_snmp_traffic_from_namespace_to_host_cmds + return fwd_traffic_from_namespace_to_host_cmds def is_rule_ipv4(self, rule_props): 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 """ iptables_cmds = [] + service_to_source_ip_map = {} # First, add iptables commands to set default policies to accept all # 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..." .format(table_name)) continue - + ipv4_src_ip_set = set() + ipv6_src_ip_set = set() # For each ACL rule in this table (in descending order of priority) for priority in sorted(iter(acl_rules.keys()), reverse=True): 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"]: 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"]: 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. if dst_port != "0": @@ -479,6 +516,9 @@ class ControlPlaneAclManager(daemon_base.DaemonBase): iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + rule_cmd) 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 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] + "ip6tables -A INPUT -j DROP") - return iptables_cmds + return iptables_cmds, service_to_source_ip_map 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 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:") for cmd in iptables_cmds: self.log_info(" " + cmd) 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 - snmp traffic coming on the front panel interface + Convenience wrapper for multi-asic platforms + 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 - iptables_cmds = self.generate_fwd_snmp_traffic_from_namespace_to_host_commands(namespace) + # Add iptables commands to allow front panel traffic + 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:") for cmd in iptables_cmds: self.log_info(" " + cmd) @@ -580,7 +625,6 @@ class ControlPlaneAclManager(daemon_base.DaemonBase): for namespace in list(self.config_db_map.keys()): # Unconditionally update control plane ACLs once at start on given namespace self.update_control_plane_acls(namespace) - self.update_control_plane_nat_acls(namespace) # Connect to Config DB of given namespace acl_db_connector = swsscommon.DBConnector("CONFIG_DB", 0, False, namespace) # Subscribe to notifications when ACL tables changes