[caclmgrd] Add some default ACCEPT rules and lastly drop all incoming 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
This commit is contained in:
Joe LeVeque 2020-05-11 12:36:47 -07:00 committed by GitHub
parent c95db04f12
commit 5e8e0d76fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -11,7 +11,7 @@
# #
try: try:
import ipaddr as ipaddress import ipaddress
import os import os
import subprocess import subprocess
import sys import sys
@ -61,12 +61,21 @@ class ControlPlaneAclManager(object):
ACL_TABLE_TYPE_CTRLPLANE = "CTRLPLANE" ACL_TABLE_TYPE_CTRLPLANE = "CTRLPLANE"
# To specify a port range, use iptables format: separate start and end # To specify a port range instead of a single port, use iptables format:
# ports with a colon, e.g., "1000:2000" # separate start and end ports with a colon, e.g., "1000:2000"
ACL_SERVICES = { ACL_SERVICES = {
"NTP": {"ip_protocols": ["udp"], "dst_ports": ["123"]}, "NTP": {
"SNMP": {"ip_protocols": ["tcp", "udp"], "dst_ports": ["161"]}, "ip_protocols": ["udp"],
"SSH": {"ip_protocols": ["tcp"], "dst_ports": ["22"]} "dst_ports": ["123"]
},
"SNMP": {
"ip_protocols": ["tcp", "udp"],
"dst_ports": ["161"]
},
"SSH": {
"ip_protocols": ["tcp"],
"dst_ports": ["22"]
}
} }
def __init__(self): def __init__(self):
@ -115,6 +124,78 @@ class ControlPlaneAclManager(object):
tcp_flags_str = tcp_flags_str[:-1] tcp_flags_str = tcp_flags_str[:-1]
return tcp_flags_str 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): def get_acl_rules_and_translate_to_iptables_commands(self):
""" """
Retrieves current ACL tables and rules from Config DB, translates Retrieves current ACL tables and rules from Config DB, translates
@ -147,14 +228,54 @@ class ControlPlaneAclManager(object):
iptables_cmds.append("ip6tables -F") iptables_cmds.append("ip6tables -F")
iptables_cmds.append("ip6tables -X") 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("iptables -A INPUT -s 127.0.0.1 -i lo -j ACCEPT")
iptables_cmds.append("ip6tables -A INPUT -s ::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 # Get current ACL tables and rules from Config DB
self._tables_db_info = self.config_db.get_table(self.ACL_TABLE) self._tables_db_info = self.config_db.get_table(self.ACL_TABLE)
self._rules_db_info = self.config_db.get_table(self.ACL_RULE) self._rules_db_info = self.config_db.get_table(self.ACL_RULE)
num_ctrl_plane_acl_rules = 0
# Walk the ACL tables # Walk the ACL tables
for (table_name, table_data) in self._tables_db_info.iteritems(): for (table_name, table_data) in self._tables_db_info.iteritems():
@ -246,6 +367,23 @@ class ControlPlaneAclManager(object):
rule_cmd += " -j {}".format(rule_props["PACKET_ACTION"]) rule_cmd += " -j {}".format(rule_props["PACKET_ACTION"])
iptables_cmds.append(rule_cmd) 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 return iptables_cmds