7b8037770d
I found that with IPv4Network types, calling list(ip_ntwrk.hosts()) is reliable. However, when doing the same with an IPv6Network, I found that the conversion to a list can hang indefinitely. This appears to me to be a bug in the ipaddress.IPv6Network implementation. However, I could not find any other reports on the web. This patch changes the behavior to call next() on the ip_ntwrk.hosts() generator instead, which returns the IP address of the first host.
468 lines
22 KiB
Python
Executable File
468 lines
22 KiB
Python
Executable File
#!/usr/bin/env python
|
|
#
|
|
# caclmgrd
|
|
#
|
|
# Control plane ACL manager daemon for SONiC
|
|
#
|
|
# Upon starting, this daemon reads control plane ACL tables and rules from
|
|
# Config DB, converts the rules into iptables rules and installs the iptables
|
|
# rules. The daemon then indefintely listens for notifications from Config DB
|
|
# and updates iptables rules if control plane ACL configuration has changed.
|
|
#
|
|
|
|
try:
|
|
import ipaddress
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import syslog
|
|
from swsssdk import ConfigDBConnector
|
|
except ImportError as err:
|
|
raise ImportError("%s - required module not found" % str(err))
|
|
|
|
VERSION = "1.0"
|
|
|
|
SYSLOG_IDENTIFIER = "caclmgrd"
|
|
|
|
|
|
# ========================== Syslog wrappers ==========================
|
|
|
|
def log_info(msg):
|
|
syslog.openlog(SYSLOG_IDENTIFIER)
|
|
syslog.syslog(syslog.LOG_INFO, msg)
|
|
syslog.closelog()
|
|
|
|
|
|
def log_warning(msg):
|
|
syslog.openlog(SYSLOG_IDENTIFIER)
|
|
syslog.syslog(syslog.LOG_WARNING, msg)
|
|
syslog.closelog()
|
|
|
|
|
|
def log_error(msg):
|
|
syslog.openlog(SYSLOG_IDENTIFIER)
|
|
syslog.syslog(syslog.LOG_ERR, msg)
|
|
syslog.closelog()
|
|
|
|
# ========================== Helper Functions =========================
|
|
|
|
def _ip_prefix_in_key(key):
|
|
"""
|
|
Function to check if IP prefix is present in a Redis database key.
|
|
If it is present, then the key will be a tuple. Otherwise, the
|
|
key will be a string.
|
|
"""
|
|
return (isinstance(key, tuple))
|
|
|
|
# ============================== Classes ==============================
|
|
|
|
class ControlPlaneAclManager(object):
|
|
"""
|
|
Class which reads control plane ACL tables and rules from Config DB,
|
|
translates them into equivalent iptables commands and runs those
|
|
commands in order to apply the control plane ACLs.
|
|
|
|
Attributes:
|
|
config_db: Handle to Config Redis database via SwSS SDK
|
|
"""
|
|
ACL_TABLE = "ACL_TABLE"
|
|
ACL_RULE = "ACL_RULE"
|
|
|
|
ACL_TABLE_TYPE_CTRLPLANE = "CTRLPLANE"
|
|
|
|
# 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"]
|
|
}
|
|
}
|
|
|
|
def __init__(self):
|
|
# Open a handle to the Config database
|
|
self.config_db = ConfigDBConnector()
|
|
self.config_db.connect()
|
|
|
|
def run_commands(self, commands):
|
|
"""
|
|
Given a list of shell commands, run them in order
|
|
|
|
Args:
|
|
commands: List of strings, each string is a shell command
|
|
"""
|
|
for cmd in commands:
|
|
proc = subprocess.Popen(cmd, shell=True)
|
|
|
|
(stdout, stderr) = proc.communicate()
|
|
|
|
if proc.returncode != 0:
|
|
log_error("Error running command '{}'".format(cmd))
|
|
|
|
def parse_int_to_tcp_flags(self, hex_value):
|
|
tcp_flags_str = ""
|
|
if hex_value & 0x01:
|
|
tcp_flags_str += "FIN,"
|
|
if hex_value & 0x02:
|
|
tcp_flags_str += "SYN,"
|
|
if hex_value & 0x04:
|
|
tcp_flags_str += "RST,"
|
|
if hex_value & 0x08:
|
|
tcp_flags_str += "PSH,"
|
|
if hex_value & 0x10:
|
|
tcp_flags_str += "ACK,"
|
|
if hex_value & 0x20:
|
|
tcp_flags_str += "URG,"
|
|
# iptables doesn't handle the flags below now. It has some special keys for it:
|
|
# --ecn-tcp-cwr This matches if the TCP ECN CWR (Congestion Window Received) bit is set.
|
|
# --ecn-tcp-ece This matches if the TCP ECN ECE (ECN Echo) bit is set.
|
|
# if hex_value & 0x40:
|
|
# tcp_flags_str += "ECE,"
|
|
# if hex_value & 0x80:
|
|
# tcp_flags_str += "CWR,"
|
|
|
|
# Delete the trailing comma
|
|
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 key, _ in loopback_iface_table.iteritems():
|
|
if not _ip_prefix_in_key(key):
|
|
continue
|
|
iface_name, iface_cidr = key
|
|
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 key, _ in mgmt_iface_table.iteritems():
|
|
if not _ip_prefix_in_key(key):
|
|
continue
|
|
iface_name, iface_cidr = key
|
|
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 key, _ in vlan_iface_table.iteritems():
|
|
if not _ip_prefix_in_key(key):
|
|
continue
|
|
iface_name, iface_cidr = key
|
|
ip_ntwrk = ipaddress.ip_network(iface_cidr, strict=False)
|
|
first_host = next(ip_ntwrk.hosts())
|
|
if isinstance(ip_ntwrk, ipaddress.IPv4Network):
|
|
block_ip2me_cmds.append("iptables -A INPUT -d {}/{} -j DROP".format(first_host, ip_ntwrk.max_prefixlen))
|
|
elif isinstance(ip_ntwrk, ipaddress.IPv6Network):
|
|
block_ip2me_cmds.append("ip6tables -A INPUT -d {}/{} -j DROP".format(first_host, 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 key, _ in portchannel_iface_table.iteritems():
|
|
if not _ip_prefix_in_key(key):
|
|
continue
|
|
iface_name, iface_cidr = key
|
|
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 key, _ in iface_table.iteritems():
|
|
if not _ip_prefix_in_key(key):
|
|
continue
|
|
iface_name, iface_cidr = key
|
|
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
|
|
control plane ACLs into a list of iptables commands that can be run
|
|
in order to install ACL rules.
|
|
|
|
Returns:
|
|
A list of strings, each string is an iptables shell command
|
|
|
|
"""
|
|
iptables_cmds = []
|
|
|
|
# First, add iptables commands to set default policies to accept all
|
|
# traffic. In case we are connected remotely, the connection will not
|
|
# drop when we flush the current rules
|
|
iptables_cmds.append("iptables -P INPUT ACCEPT")
|
|
iptables_cmds.append("iptables -P FORWARD ACCEPT")
|
|
iptables_cmds.append("iptables -P OUTPUT ACCEPT")
|
|
|
|
# Add iptables command to flush the current rules
|
|
iptables_cmds.append("iptables -F")
|
|
|
|
# Add iptables command to delete all non-default chains
|
|
iptables_cmds.append("iptables -X")
|
|
|
|
# Add same set of commands for ip6tables
|
|
iptables_cmds.append("ip6tables -P INPUT ACCEPT")
|
|
iptables_cmds.append("ip6tables -P FORWARD ACCEPT")
|
|
iptables_cmds.append("ip6tables -P OUTPUT ACCEPT")
|
|
iptables_cmds.append("ip6tables -F")
|
|
iptables_cmds.append("ip6tables -X")
|
|
|
|
# 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")
|
|
iptables_cmds.append("iptables -A INPUT -p icmp --icmp-type destination-unreachable -j ACCEPT")
|
|
iptables_cmds.append("iptables -A INPUT -p icmp --icmp-type time-exceeded -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")
|
|
iptables_cmds.append("ip6tables -A INPUT -p icmpv6 --icmpv6-type destination-unreachable -j ACCEPT")
|
|
iptables_cmds.append("ip6tables -A INPUT -p icmpv6 --icmpv6-type time-exceeded -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():
|
|
|
|
table_ip_version = None
|
|
|
|
# Ignore non-control-plane ACL tables
|
|
if table_data["type"] != self.ACL_TABLE_TYPE_CTRLPLANE:
|
|
continue
|
|
|
|
acl_services = table_data["services"]
|
|
|
|
for acl_service in acl_services:
|
|
if acl_service not in self.ACL_SERVICES:
|
|
log_warning("Ignoring control plane ACL '{}' with unrecognized service '{}'"
|
|
.format(table_name, acl_service))
|
|
continue
|
|
|
|
log_info("Translating ACL rules for control plane ACL '{}' (service: '{}')"
|
|
.format(table_name, acl_service))
|
|
|
|
# Obtain default IP protocol(s) and destination port(s) for this service
|
|
ip_protocols = self.ACL_SERVICES[acl_service]["ip_protocols"]
|
|
dst_ports = self.ACL_SERVICES[acl_service]["dst_ports"]
|
|
|
|
acl_rules = {}
|
|
|
|
for ((rule_table_name, rule_id), rule_props) in self._rules_db_info.iteritems():
|
|
if rule_table_name == table_name:
|
|
if not rule_props:
|
|
log_warning("rule_props for rule_id {} empty or null!".format(rule_id))
|
|
continue
|
|
|
|
try:
|
|
acl_rules[rule_props["PRIORITY"]] = rule_props
|
|
except KeyError:
|
|
log_error("rule_props for rule_id {} does not have key 'PRIORITY'!".format(rule_id))
|
|
continue
|
|
|
|
# If we haven't determined the IP version for this ACL table yet,
|
|
# try to do it now. We attempt to determine heuristically based on
|
|
# whether the src or dst IP of this rule is an IPv4 or IPv6 address.
|
|
if not table_ip_version:
|
|
if (("SRC_IPV6" in rule_props and rule_props["SRC_IPV6"]) or
|
|
("DST_IPV6" in rule_props and rule_props["DST_IPV6"])):
|
|
table_ip_version = 6
|
|
elif (("SRC_IP" in rule_props and rule_props["SRC_IP"]) or
|
|
("DST_IP" in rule_props and rule_props["DST_IP"])):
|
|
table_ip_version = 4
|
|
|
|
# If we were unable to determine whether this ACL table contains
|
|
# IPv4 or IPv6 rules, log a message and skip processing this table.
|
|
if not table_ip_version:
|
|
log_warning("Unable to determine if ACL table '{}' contains IPv4 or IPv6 rules. Skipping table..."
|
|
.format(table_name))
|
|
continue
|
|
|
|
# For each ACL rule in this table (in descending order of priority)
|
|
for priority in sorted(acl_rules.iterkeys(), reverse=True):
|
|
rule_props = acl_rules[priority]
|
|
|
|
if "PACKET_ACTION" not in rule_props:
|
|
log_error("ACL rule does not contain PACKET_ACTION property")
|
|
continue
|
|
|
|
# Apply the rule to the default protocol(s) for this ACL service
|
|
for ip_protocol in ip_protocols:
|
|
for dst_port in dst_ports:
|
|
rule_cmd = "ip6tables" if table_ip_version == 6 else "iptables"
|
|
rule_cmd += " -A INPUT -p {}".format(ip_protocol)
|
|
|
|
if "SRC_IPV6" in rule_props and rule_props["SRC_IPV6"]:
|
|
rule_cmd += " -s {}".format(rule_props["SRC_IPV6"])
|
|
elif "SRC_IP" in rule_props and rule_props["SRC_IP"]:
|
|
rule_cmd += " -s {}".format(rule_props["SRC_IP"])
|
|
|
|
rule_cmd += " --dport {}".format(dst_port)
|
|
|
|
# If there are TCP flags present and ip protocol is TCP, append them
|
|
if ip_protocol == "tcp" and "TCP_FLAGS" in rule_props and rule_props["TCP_FLAGS"]:
|
|
tcp_flags, tcp_flags_mask = rule_props["TCP_FLAGS"].split("/")
|
|
|
|
tcp_flags = int(tcp_flags, 16)
|
|
tcp_flags_mask = int(tcp_flags_mask, 16)
|
|
|
|
if tcp_flags_mask > 0:
|
|
rule_cmd += " --tcp-flags {mask} {flags}".format(mask = self.parse_int_to_tcp_flags(tcp_flags_mask), flags = self.parse_int_to_tcp_flags(tcp_flags))
|
|
|
|
# Append the packet action as the jump target
|
|
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
|
|
|
|
def update_control_plane_acls(self):
|
|
"""
|
|
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()
|
|
|
|
log_info("Issuing the following iptables commands:")
|
|
for cmd in iptables_cmds:
|
|
log_info(" " + cmd)
|
|
|
|
self.run_commands(iptables_cmds)
|
|
|
|
def notification_handler(self, key, data):
|
|
log_info("ACL configuration changed. Updating iptables rules for control plane ACLs...")
|
|
self.update_control_plane_acls()
|
|
|
|
def run(self):
|
|
# Unconditionally update control plane ACLs once at start
|
|
self.update_control_plane_acls()
|
|
|
|
# Subscribe to notifications when ACL tables or rules change
|
|
self.config_db.subscribe(self.ACL_TABLE,
|
|
lambda table, key, data: self.notification_handler(key, data))
|
|
self.config_db.subscribe(self.ACL_RULE,
|
|
lambda table, key, data: self.notification_handler(key, data))
|
|
|
|
# Indefinitely listen for Config DB notifications
|
|
self.config_db.listen()
|
|
|
|
|
|
# ============================= Functions =============================
|
|
|
|
def main():
|
|
log_info("Starting up...")
|
|
|
|
if not os.geteuid() == 0:
|
|
log_error("Must be root to run this daemon")
|
|
print "Error: Must be root to run this daemon"
|
|
sys.exit(1)
|
|
|
|
# Instantiate a ControlPlaneAclManager object
|
|
caclmgr = ControlPlaneAclManager()
|
|
caclmgr.run()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|