Skip to content

Commit

Permalink
[caclmgrd] Add some default ACCEPT rules and lastly drop all incoming…
Browse files Browse the repository at this point in the history
… packets (#4412)

Modified caclmgrd behavior to enhance control plane security as follows:

Upon starting or receiving notification of ACL table/rule changes in Config DB:
1. Add iptables/ip6tables commands to allow all incoming packets from established TCP sessions or new TCP sessions which are related to established TCP sessions
2. Add iptables/ip6tables commands to allow bidirectional ICMPv4 ping and traceroute
3. Add iptables/ip6tables commands to allow bidirectional ICMPv6 ping and traceroute
4. Add iptables/ip6tables commands to allow all incoming Neighbor Discovery Protocol (NDP) NS/NA/RS/RA messages
5. Add iptables/ip6tables commands to allow all incoming IPv4 DHCP packets
6. Add iptables/ip6tables commands to allow all incoming IPv6 DHCP packets
7. Add iptables/ip6tables commands to allow all incoming BGP traffic
8. Add iptables/ip6tables commands for all ACL rules for recognized services (currently SSH, SNMP, NTP)
9. For all services which we did not find configured ACL rules, add iptables/ip6tables commands to allow all incoming packets for those services (allows the device to accept SSH connections before the device is configured)
10. Add iptables rules to drop all packets destined for loopback interface IP addresses
11. Add iptables rules to drop all packets destined for management interface IP addresses
12. Add iptables rules to drop all packets destined for point-to-point interface IP addresses
13. Add iptables rules to drop all packets destined for our VLAN interface gateway IP addresses
14. Add iptables/ip6tables commands to allow all incoming packets with TTL of 0 or 1 (This allows the device to respond to tools like tcptraceroute)
15. If we found control plane ACLs in the configuration and applied them, we lastly add iptables/ip6tables commands to drop all other incoming packets
  • Loading branch information
jleveque authored and yxieca committed Jun 9, 2020
1 parent 9f8d691 commit 3ee9c5d
Showing 1 changed file with 145 additions and 7 deletions.
152 changes: 145 additions & 7 deletions files/image_config/caclmgrd/caclmgrd
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#

try:
import ipaddr as ipaddress
import ipaddress
import os
import subprocess
import sys
Expand Down Expand Up @@ -61,12 +61,21 @@ class ControlPlaneAclManager(object):

ACL_TABLE_TYPE_CTRLPLANE = "CTRLPLANE"

# To specify a port range, use iptables format: separate start and end
# ports with a colon, e.g., "1000:2000"
# To specify a port range instead of a single port, use iptables format:
# separate start and end ports with a colon, e.g., "1000:2000"
ACL_SERVICES = {
"NTP": {"ip_protocols": ["udp"], "dst_ports": ["123"]},
"SNMP": {"ip_protocols": ["tcp", "udp"], "dst_ports": ["161"]},
"SSH": {"ip_protocols": ["tcp"], "dst_ports": ["22"]}
"NTP": {
"ip_protocols": ["udp"],
"dst_ports": ["123"]
},
"SNMP": {
"ip_protocols": ["tcp", "udp"],
"dst_ports": ["161"]
},
"SSH": {
"ip_protocols": ["tcp"],
"dst_ports": ["22"]
}
}

def __init__(self):
Expand Down Expand Up @@ -115,6 +124,78 @@ class ControlPlaneAclManager(object):
tcp_flags_str = tcp_flags_str[:-1]
return tcp_flags_str

def generate_block_ip2me_traffic_iptables_commands(self):
LOOPBACK_INTERFACE_TABLE_NAME = "LOOPBACK_INTERFACE"
MGMT_INTERFACE_TABLE_NAME = "MGMT_INTERFACE"
VLAN_INTERFACE_TABLE_NAME = "VLAN_INTERFACE"
PORTCHANNEL_INTERFACE_TABLE_NAME = "PORTCHANNEL_INTERFACE"
INTERFACE_TABLE_NAME = "INTERFACE"

block_ip2me_cmds = []

# Add iptables rules to drop all packets destined for loopback interface IP addresses
loopback_iface_table = self.config_db.get_table(LOOPBACK_INTERFACE_TABLE_NAME)
if loopback_iface_table:
for ((iface_name, iface_cidr), _) in loopback_iface_table.iteritems():
ip_ntwrk = ipaddress.ip_network(iface_cidr, strict=False)
if isinstance(ip_ntwrk, ipaddress.IPv4Network):
block_ip2me_cmds.append("iptables -A INPUT -d {}/{} -j DROP".format(ip_ntwrk.network_address, ip_ntwrk.max_prefixlen))
elif isinstance(ip_ntwrk, ipaddress.IPv6Network):
block_ip2me_cmds.append("ip6tables -A INPUT -d {}/{} -j DROP".format(ip_ntwrk.network_address, ip_ntwrk.max_prefixlen))
else:
log_warning("Unrecognized IP address type on interface '{}': {}".format(iface_name, ip_ntwrk))

# Add iptables rules to drop all packets destined for management interface IP addresses
mgmt_iface_table = self.config_db.get_table(MGMT_INTERFACE_TABLE_NAME)
if mgmt_iface_table:
for ((iface_name, iface_cidr), _) in mgmt_iface_table.iteritems():
ip_ntwrk = ipaddress.ip_network(iface_cidr, strict=False)
if isinstance(ip_ntwrk, ipaddress.IPv4Network):
block_ip2me_cmds.append("iptables -A INPUT -d {}/{} -j DROP".format(ip_ntwrk.network_address, ip_ntwrk.max_prefixlen))
elif isinstance(ip_ntwrk, ipaddress.IPv6Network):
block_ip2me_cmds.append("ip6tables -A INPUT -d {}/{} -j DROP".format(ip_ntwrk.network_address, ip_ntwrk.max_prefixlen))
else:
log_warning("Unrecognized IP address type on interface '{}': {}".format(iface_name, ip_ntwrk))

# Add iptables rules to drop all packets destined for our VLAN interface gateway IP addresses
vlan_iface_table = self.config_db.get_table(VLAN_INTERFACE_TABLE_NAME)
if vlan_iface_table:
for ((iface_name, iface_cidr), _) in vlan_iface_table.iteritems():
ip_ntwrk = ipaddress.ip_network(iface_cidr, strict=False)
if isinstance(ip_ntwrk, ipaddress.IPv4Network):
block_ip2me_cmds.append("iptables -A INPUT -d {}/{} -j DROP".format(list(ip_ntwrk.hosts())[0], ip_ntwrk.max_prefixlen))
elif isinstance(ip_ntwrk, ipaddress.IPv6Network):
block_ip2me_cmds.append("ip6tables -A INPUT -d {}/{} -j DROP".format(list(ip_ntwrk.hosts())[0], ip_ntwrk.max_prefixlen))
else:
log_warning("Unrecognized IP address type on interface '{}': {}".format(iface_name, ip_ntwrk))

# Add iptables rules to drop all packets destined for point-to-point interface IP addresses
# (All portchannel interfaces and configured front-panel interfaces)
portchannel_iface_table = self.config_db.get_table(PORTCHANNEL_INTERFACE_TABLE_NAME)
if portchannel_iface_table:
for ((iface_name, iface_cidr), _) in portchannel_iface_table.iteritems():
ip_ntwrk = ipaddress.ip_network(iface_cidr, strict=False)
if isinstance(ip_ntwrk, ipaddress.IPv4Network):
block_ip2me_cmds.append("iptables -A INPUT -d {}/{} -j DROP".format(ip_ntwrk.network_address, ip_ntwrk.max_prefixlen))
elif isinstance(ip_ntwrk, ipaddress.IPv6Network):
block_ip2me_cmds.append("ip6tables -A INPUT -d {}/{} -j DROP".format(ip_ntwrk.network_address, ip_ntwrk.max_prefixlen))
else:
log_warning("Unrecognized IP address type on interface '{}': {}".format(iface_name, ip_ntwrk))

iface_table = self.config_db.get_table(INTERFACE_TABLE_NAME)
if iface_table:
for ((iface_name, iface_cidr), _) in iface_table.iteritems():
ip_ntwrk = ipaddress.ip_network(iface_cidr, strict=False)
if isinstance(ip_ntwrk, ipaddress.IPv4Network):
block_ip2me_cmds.append("iptables -A INPUT -d {}/{} -j DROP".format(ip_ntwrk.network_address, ip_ntwrk.max_prefixlen))
elif isinstance(ip_ntwrk, ipaddress.IPv6Network):
block_ip2me_cmds.append("ip6tables -A INPUT -d {}/{} -j DROP".format(ip_ntwrk.network_address, ip_ntwrk.max_prefixlen))
else:
log_warning("Unrecognized IP address type on interface '{}': {}".format(iface_name, ip_ntwrk))

return block_ip2me_cmds


def get_acl_rules_and_translate_to_iptables_commands(self):
"""
Retrieves current ACL tables and rules from Config DB, translates
Expand Down Expand Up @@ -147,14 +228,54 @@ class ControlPlaneAclManager(object):
iptables_cmds.append("ip6tables -F")
iptables_cmds.append("ip6tables -X")

# Add iptables commands to allow all IPv4 and IPv6 traffic from localhost
# Add iptables/ip6tables commands to allow all traffic from localhost
iptables_cmds.append("iptables -A INPUT -s 127.0.0.1 -i lo -j ACCEPT")
iptables_cmds.append("ip6tables -A INPUT -s ::1 -i lo -j ACCEPT")

# Add iptables/ip6tables commands to allow all incoming packets from established
# TCP sessions or new TCP sessions which are related to established TCP sessions
iptables_cmds.append("iptables -A INPUT -p tcp -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT")
iptables_cmds.append("ip6tables -A INPUT -p tcp -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT")

# Add iptables/ip6tables commands to allow bidirectional ICMPv4 ping and traceroute
# TODO: Support processing ICMPv4 service ACL rules, and remove this blanket acceptance
iptables_cmds.append("iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT")
iptables_cmds.append("iptables -A INPUT -p icmp --icmp-type echo-reply -j ACCEPT")

# Add iptables/ip6tables commands to allow bidirectional ICMPv6 ping and traceroute
# TODO: Support processing ICMPv6 service ACL rules, and remove this blanket acceptance
iptables_cmds.append("ip6tables -A INPUT -p icmpv6 --icmpv6-type echo-request -j ACCEPT")
iptables_cmds.append("ip6tables -A INPUT -p icmpv6 --icmpv6-type echo-reply -j ACCEPT")

# Add iptables/ip6tables commands to allow all incoming Neighbor Discovery Protocol (NDP) NS/NA/RS/RA messages
# TODO: Support processing NDP service ACL rules, and remove this blanket acceptance
iptables_cmds.append("ip6tables -A INPUT -p icmpv6 --icmpv6-type neighbor-solicitation -j ACCEPT")
iptables_cmds.append("ip6tables -A INPUT -p icmpv6 --icmpv6-type neighbor-advertisement -j ACCEPT")
iptables_cmds.append("ip6tables -A INPUT -p icmpv6 --icmpv6-type router-solicitation -j ACCEPT")
iptables_cmds.append("ip6tables -A INPUT -p icmpv6 --icmpv6-type router-advertisement -j ACCEPT")

# Add iptables/ip6tables commands to allow all incoming IPv4 DHCP packets
iptables_cmds.append("iptables -A INPUT -p udp --dport 67:68 --sport 67:68 -j ACCEPT")
iptables_cmds.append("ip6tables -A INPUT -p udp --dport 67:68 --sport 67:68 -j ACCEPT")

# Add iptables/ip6tables commands to allow all incoming IPv6 DHCP packets
iptables_cmds.append("iptables -A INPUT -p udp --dport 546:547 --sport 546:547 -j ACCEPT")
iptables_cmds.append("ip6tables -A INPUT -p udp --dport 546:547 --sport 546:547 -j ACCEPT")

# Add iptables/ip6tables commands to allow all incoming BGP traffic
# TODO: Determine BGP ACLs based on configured device sessions, and remove this blanket acceptance
iptables_cmds.append("iptables -A INPUT -p tcp --dport 179 -j ACCEPT")
iptables_cmds.append("iptables -A INPUT -p tcp --sport 179 -j ACCEPT")
iptables_cmds.append("ip6tables -A INPUT -p tcp --dport 179 -j ACCEPT")
iptables_cmds.append("ip6tables -A INPUT -p tcp --sport 179 -j ACCEPT")


# Get current ACL tables and rules from Config DB
self._tables_db_info = self.config_db.get_table(self.ACL_TABLE)
self._rules_db_info = self.config_db.get_table(self.ACL_RULE)

num_ctrl_plane_acl_rules = 0

# Walk the ACL tables
for (table_name, table_data) in self._tables_db_info.iteritems():

Expand Down Expand Up @@ -246,6 +367,23 @@ class ControlPlaneAclManager(object):
rule_cmd += " -j {}".format(rule_props["PACKET_ACTION"])

iptables_cmds.append(rule_cmd)
num_ctrl_plane_acl_rules += 1

# Add iptables commands to block ip2me traffic
iptables_cmds += self.generate_block_ip2me_traffic_iptables_commands()

# Add iptables/ip6tables commands to allow all incoming packets with TTL of 0 or 1
# This allows the device to respond to tools like tcptraceroute
iptables_cmds.append("iptables -A INPUT -m ttl --ttl-lt 2 -j ACCEPT")
iptables_cmds.append("ip6tables -A INPUT -p tcp -m hl --hl-lt 2 -j ACCEPT")

# Finally, if the device has control plane ACLs configured,
# add iptables/ip6tables commands to drop all other incoming packets
if num_ctrl_plane_acl_rules > 0:
iptables_cmds.append("iptables -A INPUT -j DROP")
iptables_cmds.append("iptables -A FORWARD -j DROP")
iptables_cmds.append("ip6tables -A INPUT -j DROP")
iptables_cmds.append("ip6tables -A FORWARD -j DROP")

return iptables_cmds

Expand Down

0 comments on commit 3ee9c5d

Please sign in to comment.