Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[multi-asic] Enhanced iptable default rules #6765

Merged
merged 7 commits into from
Feb 25, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 93 additions & 49 deletions src/sonic-host-services/scripts/caclmgrd
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down Expand Up @@ -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()):
judyjoseph marked this conversation as resolved.
Show resolved Hide resolved
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")

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]))

# 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")

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]))

return fwd_snmp_traffic_from_namespace_to_host_cmds
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
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_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
Expand All @@ -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
Expand Down Expand Up @@ -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]
Expand All @@ -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":
Expand All @@ -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)

Expand All @@ -493,28 +533,33 @@ 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):
"""
Convenience wrapper which retrieves current ACL tables and rules from
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)
Expand Down Expand Up @@ -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
Expand Down