-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add caclmgrd and related files to translate and install control plane…
… ACL rules (#1240)
- Loading branch information
Showing
9 changed files
with
294 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
[Service] | ||
ExecStart= | ||
ExecStart=/usr/bin/docker daemon -H fd:// --storage-driver=aufs --bip=240.127.1.1/24 | ||
ExecStart=/usr/bin/docker daemon -H fd:// --storage-driver=aufs --bip=240.127.1.1/24 --iptables=false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,251 @@ | ||
#!/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 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() | ||
|
||
|
||
# ============================== 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, 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 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") | ||
|
||
# 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) | ||
|
||
# Walk the ACL tables | ||
for (table_name, table_data) in self._tables_db_info.iteritems(): | ||
# Ignore non-control-plane ACL tables | ||
if table_data["type"] != self.ACL_TABLE_TYPE_CTRLPLANE: | ||
continue | ||
|
||
acl_service = table_data["service"] | ||
|
||
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: | ||
acl_rules[rule_props["PRIORITY"]] = rule_props | ||
|
||
# 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 | ||
|
||
# If the rule contains an IP protocol, we will use it. | ||
# Otherwise, we will apply the rule to the default | ||
# protocol(s) for this ACL service | ||
if "IP_PROTOCOL" in rule_props: | ||
ip_protocols = [rule_props["IP_PROTOCOL"]] | ||
|
||
for ip_protocol in ip_protocols: | ||
for dst_port in dst_ports: | ||
rule_cmd = "iptables -A INPUT -p {}".format(ip_protocol) | ||
|
||
if "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, append them | ||
if "TCP_FLAGS" in rule_props and rule_props["TCP_FLAGS"]: | ||
tcp_flags = int(rule_props["TCP_FLAGS"], 16) | ||
|
||
if tcp_flags > 0: | ||
rule_cmd += " --tcp-flags " | ||
|
||
if tcp_flags & 0x01: | ||
rule_cmd += "FIN," | ||
if tcp_flags & 0x02: | ||
rule_cmd += "SYN," | ||
if tcp_flags & 0x04: | ||
rule_cmd += "RST," | ||
if tcp_flags & 0x08: | ||
rule_cmd += "PSH," | ||
if tcp_flags & 0x10: | ||
rule_cmd += "ACK," | ||
if tcp_flags & 0x20: | ||
rule_cmd += "URG," | ||
if tcp_flags & 0x40: | ||
rule_cmd += "ECE," | ||
if tcp_flags & 0x80: | ||
rule_cmd += "CWR," | ||
|
||
# Delete the trailing comma | ||
rule_cmd = rule_cmd[:-1] | ||
|
||
# Append the packet action as the jump target | ||
rule_cmd += " -j {}".format(rule_props["PACKET_ACTION"]) | ||
|
||
iptables_cmds.append(rule_cmd) | ||
|
||
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
#!/usr/bin/env bash | ||
|
||
# Only start control plance ACL manager daemon if not an Arista platform. | ||
# Arista devices will use their own service ACL manager daemon(s) instead. | ||
if [ "$(sonic-cfggen -v "platform" | grep -c "arista")" -gt 0 ]; then | ||
echo "Not starting caclmgrd - unsupported platform" | ||
exit 0 | ||
fi | ||
|
||
exec /usr/bin/caclmgrd |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
[Unit] | ||
Description=Control Plane ACL configuration daemon | ||
Requires=database.service | ||
After=database.service | ||
|
||
[Service] | ||
Type=oneshot | ||
ExecStart=/usr/bin/caclmgrd-start.sh | ||
|
||
[Install] | ||
WantedBy=multi-user.target |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Submodule sonic-utilities
updated
from 5ad848 to 6823ce