diff --git a/.travis.yml b/.travis.yml index daebe0a4d3..de131c80a2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,10 +22,11 @@ env: global: - DOCKER_STARTUP_TIMEOUT_MS=60000 matrix: - - DAQ_CODECOV=y - DAQ_MODE=in DAQ_BUILD=in DAQ_PUSH=daqf/runner - DAQ_MODE=in DAQ_BUILD=no + - DAQ_CODECOV=y - DAQ_DEVICES=1 DAQ_FAIL=telnet + - DAQ_DEVICES=1 DAQ_FAIL=!telnet DAQ_MUD=true - DAQ_DEVICES=3 - DAQ_SWITCH=ext branches: diff --git a/bin/mudacl b/bin/mudacl index 23d836410f..20224d522a 100755 --- a/bin/mudacl +++ b/bin/mudacl @@ -5,17 +5,21 @@ ROOT=$(dirname $0)/.. +DEVICE_TYPES_FILE=device_types.json + cd $ROOT +test -d inst/ || mkdir inst touch inst/ || sudo chown $USER inst -if [ ! -f local/devices.json ]; then - cp mudacl/site/test/devices.json local/devices.json - cp mudacl/site/test/types.json local/types.json -fi +test -f local/$DEVICE_TYPES_FILE || cp misc/$DEVICE_TYPES_FILE local/ +cp -f local/$DEVICE_TYPES_FILE inst/$DEVICE_TYPES_FILE -if [ ! -d inst/port_acls ]; then - mkdir inst/port_acls -fi +rm -rf inst/acl_templates +mkdir inst/acl_templates +rm -rf inst/port_acls +mkdir inst/port_acls mudacl/bin/run.sh + +ls -l inst/acl_templates inst/port_acls inst/$DEVICE_TYPES_FILE diff --git a/bin/test_daq b/bin/test_daq index fcfb4e0f06..efb6179261 100755 --- a/bin/test_daq +++ b/bin/test_daq @@ -21,6 +21,10 @@ if [ -d faucet ]; then echo fi +if [ -n "$DAQ_MUD" ]; then + bin/mudacl +fi + if [ "$DAQ_SWITCH" == "ext" ]; then echo ext_dpid=0x1aeb960541 >> local/system.conf bin/external_ovs @@ -29,12 +33,13 @@ if [ "$DAQ_SWITCH" == "ext" ]; then fi fi +fail_mode=${DAQ_FAIL#!} if [ -n "$DAQ_DEVICES" ]; then count=1 daq_intf= while [ $count -le $DAQ_DEVICES ]; do daq_intf+=,faux-$count - cmd/faux $count $DAQ_FAIL + cmd/faux $count $fail_mode count=$(($count+1)) done echo daq_intf=${daq_intf#,} >> local/system.conf @@ -55,7 +60,7 @@ else sudo cmd/exrun -s || failed=true fi -if [ -n "$DAQ_FAIL" ]; then +if [ -n "$DAQ_FAIL" -a "$DAQ_FAIL" == "$fail_mode" ]; then if [ -z "$failed" ]; then echo Test did not fail as expected with $DAQ_FAIL. false diff --git a/daq/network.py b/daq/network.py index 0d30c136bf..c03287fbfe 100644 --- a/daq/network.py +++ b/daq/network.py @@ -35,19 +35,37 @@ class TestNetwork(object): """Test network manager""" DP_ACL_FILE_FORMAT = "inst/dp_%s_port_acls.yaml" + PORT_ACL_NAME_FORMAT = "dp_%s_port_%d_acl" + PORT_ACL_FILE_FORMAT = "inst/port_acls/dp_%s_port_%d_acl.yaml" + TEMPLATE_FILE_FORMAT = "inst/acl_templates/template_%s_acl.yaml" + RULES_KEY_FORMAT = "@acl:template_%s_acl" + DEVICE_TYPES_FILE = "inst/device_types.json" OVS_CLS = mininet_node.OVSSwitch + MAC_PLACEHOLDER = "@src_mac:" + DNS_PLACEHOLDER = "@dns:" + def __init__(self, config): self.config = config self.net = None self.pri = None self.sec = None + self.sec_name = 'sec' self.sec_dpid = None self.sec_port = None self.ext_intf_name = None self.switch_links = {} self.device_intfs = None - self.mac_map = {} + self._mac_map = {} + self._device_types = self._load_file(self.DEVICE_TYPES_FILE) + + def _load_file(self, filename): + if not os.path.isfile(filename): + LOGGER.debug("File %s does not exist, skipping.", filename) + return None + LOGGER.debug("Loading file %s", filename) + with open(filename) as stream: + return yaml.safe_load(stream) # pylint: disable=too-many-arguments def add_host(self, name, cls=DAQHost, ip_addr=None, env_vars=None, vol_maps=None, @@ -185,7 +203,7 @@ def initialize(self): self.sec.addIntf(device_intf, port=device_intf.port) self._switch_attach(self.sec, device_intf) - self._generate_mac_map() + self._generate_acls() LOGGER.info("Starting faucet...") output = self.pri.cmd('cmd/faucet && echo SUCCESS') @@ -196,39 +214,43 @@ def initialize(self): def direct_port_traffic(self, target_mac, port_no): """Direct traffic from a given mac to specified port set""" if port_no is None: - del self.mac_map[target_mac] + del self._mac_map[target_mac] else: - self.mac_map[target_mac] = port_no - self._generate_mac_map() + self._mac_map[target_mac] = port_no + self._generate_acls(port=port_no) - def _generate_mac_map(self): + def _generate_acls(self, port=None): + self._generate_pri_acls() + self._generate_port_acls(port=port) + + def _generate_pri_acls(self): switch_name = self.pri.name incoming_acl = [] portset_acl = [] - for target_mac in self.mac_map: - port_set = self.mac_map[target_mac] + for target_mac in self._mac_map: + port_set = self._mac_map[target_mac] ports = range(port_set * 10, port_set*10+4) - self._add_acl_rule(incoming_acl, src_mac=target_mac, in_vlan=10, ports=ports) - self._add_acl_rule(portset_acl, dst_mac=target_mac, out_vlan=10, port=1) + self._add_acl_pri_rule(incoming_acl, src_mac=target_mac, in_vlan=10, ports=ports) + self._add_acl_pri_rule(portset_acl, dst_mac=target_mac, out_vlan=10, port=1) - self._add_acl_rule(portset_acl, allow=1) + self._add_acl_pri_rule(portset_acl, allow=1) acls = {} acls["dp_%s_incoming_acl" % switch_name] = incoming_acl acls["dp_%s_portset_acl" % switch_name] = portset_acl - mac_map = {} - mac_map["acls"] = acls + pri_acls = {} + pri_acls["acls"] = acls filename = self.DP_ACL_FILE_FORMAT % switch_name - LOGGER.debug("Writing updated mac map to %s", filename) + LOGGER.debug("Writing updated pri acls to %s", filename) with open(filename, "w+") as output_stream: - yaml.safe_dump(mac_map, stream=output_stream) + yaml.safe_dump(pri_acls, stream=output_stream) - def _add_acl_rule(self, acl, src_mac=None, dst_mac=None, in_vlan=None, out_vlan=None, - port=None, ports=None, allow=None): + def _add_acl_pri_rule(self, acl, src_mac=None, dst_mac=None, in_vlan=None, out_vlan=None, + port=None, ports=None, allow=None): output = {} if port: output["port"] = port @@ -258,3 +280,62 @@ def _add_acl_rule(self, acl, src_mac=None, dst_mac=None, in_vlan=None, out_vlan= rule["rule"] = subrule acl.append(rule) + + def _generate_port_acls(self, port=None): + if port: + self._generate_port_acl(port=port) + else: + for port in range(0, self.sec_port): + self._generate_port_acl(port=port) + + def _generate_port_acl(self, port=None): + has_mapping = False + rules = [] + if self._device_types: + for target_mac in self._mac_map: + if self._mac_map[target_mac] == port: + self._add_acl_port_rules(rules, target_mac=target_mac) + has_mapping = True + + filename = self.PORT_ACL_FILE_FORMAT % (self.sec_name, port) + if has_mapping: + self._append_acl_template(rules, 'baseline') + LOGGER.debug("Writing port acl file %s", filename) + self._write_port_acl(port, rules, filename) + elif os.path.isfile(filename): + LOGGER.debug("Removing unused port acl file %s", filename) + os.remove(filename) + + def _write_port_acl(self, port, rules, filename): + acl_name = self.PORT_ACL_NAME_FORMAT % (self.sec_name, port) + acls = {} + acls[acl_name] = rules + port_acl = {} + port_acl['acls'] = acls + with open(filename, "w+") as output_stream: + yaml.safe_dump(port_acl, stream=output_stream) + + def _add_acl_port_rules(self, rules, target_mac): + mac_map = self._device_types['macAddrs'] + device_type = mac_map[target_mac]['type'] if target_mac in mac_map else 'default' + LOGGER.info("Processing acl template for %s/%s", target_mac, device_type) + self._append_acl_template(rules, device_type, target_mac) + + def _append_acl_template(self, rules, template, target_mac=None): + filename = self.TEMPLATE_FILE_FORMAT % template + template_key = self.RULES_KEY_FORMAT % template + template_acl = self._load_file(filename) + for acl in template_acl['acls'][template_key]: + new_rule = acl['rule'] + self._resolve_template_field(new_rule, 'dl_src', target_mac=target_mac) + self._resolve_template_field(new_rule, 'nw_dst') + rules.append(acl) + + def _resolve_template_field(self, rule, field, target_mac=None): + if field not in rule: + return + placeholder = rule[field] + if placeholder.startswith(self.MAC_PLACEHOLDER): + rule[field] = target_mac + elif placeholder.startswith(self.DNS_PLACEHOLDER): + del rule[field] diff --git a/docs/mudacl.md b/docs/mudacl.md index 853915ade9..04b785b696 100644 --- a/docs/mudacl.md +++ b/docs/mudacl.md @@ -1,52 +1,65 @@ ## MUD ACL generator prototype -The MUD ACL generator prototoype combines MUD files with a currently static set of system configuration -files to produce FAUCET-compatible ACLs. This feature is in the early stages, so some assembly is +The MUD ACL generator prototoype combines +[IETF MUD files](https://datatracker.ietf.org/doc/draft-ietf-opsawg-mud/) +with the dynamic state of the system to produce FAUCET-compatible network ACLs. +This feature is in the early stages, so some assembly is required. Specifically, as a development feature, the system will need to be setup for running in development mode (see [build documentation](build.md)). Everything here is subject to change. Initially, this only provides a limited subset of policy enforcement; additional restrictions will manifest as the system improves. -The basic combinator application (invoked with `bin/mudacl`) combines the following pieces of information: +The basic combinator application (invoked with `bin/mudacl`) combines the system configuration +(`inst/faucet.yaml`) with a set of pre-defined MUD files (`mud_files/`). The script will output +compiled results into the `inst/acl_templates/` directory (and also does some other basic +system setup tasks). -* Switch Topology: Specified by `inst/faucet.yaml` (shouldn't need to change). -* Device Topology: Specified in `local/devices.json` (copied into place by `bin/mudacl`). -* Device Types: Specified in `local/types.json` (copied into place by `bin/mudacl`). -* MUD Files: Specified in `mud_files/`. - -The `bin/mudacl` script will output compiled results into `inst/port_acls/`, where they are then -included by the FAUCET runtime (see include directives in `inst/faucet.yaml`). +If there is a device type configuration file (`inst/device_types.json`), the runtime system +will monitor for referenced devices and apply the resolved ACL files appropriately. The following steps show how it all works for a test against the internal faux device. Just requires a simple edit to the default `system.conf` file to enable some deviant device behavior. First it runs DAQ without MUD, showing the exposed telnet port, and then again with MUD enforcement which should not allow telnet. -``` -~/daq$ diff misc/system.conf local/system.conf +
+~/daq$ diff local/system.conf misc/system.conf +7c7 +< daq_intf=faux-1! +--- +> daq_intf=faux! 13c13 -< #faux_args="telnet" +< faux_args="telnet" --- -> faux_args="telnet" -~/daq$ sudo rm -rf inst/port_acls inst/faucet.log -~/daq$ sudo cmd/exrun -s > daq_open.log 2>&1 -~/daq$ fgrep port_1_acl inst/faucet.log -Jul 24 22:19:30 faucet.config WARNING not a regular file or does not exist: /etc/faucet/port_acls/dp_sec_port_1_acl.yaml -Jul 24 22:19:30 faucet.config WARNING skipping optional include file: /etc/faucet/port_acls/dp_sec_port_1_acl.yaml -~/daq$ cat inst/run-port-01/nodes/nmap01/tmp/open.txt +> #faux_args="telnet" +~/daq$ sudo rm -rf inst/port_acls inst/device_types.json +~/daq$ sudo cmd/exrun -s > daq_open.log 2>&1 +~/daq$ fgrep template daq_open.log +~/daq$ cat inst/run-port-01/nodes/nmap01/tmp/open.txt 23/open/tcp//telnet/// -~/daq$ bin/mudacl -touch: setting times of 'inst/': Permission denied -BUILD SUCCESSFUL in 1s +~/daq$ bin/mudacl + +BUILD SUCCESSFUL in 0s 2 actionable tasks: 2 up-to-date + Executing mudacl generator... -Writing output files to /home/username/daq/inst/port_acls -total 8 --rw-rw-r-- 1 username username 939 Jul 24 22:21 dp_sec_port_1_acl.yaml --rw-rw-r-- 1 username username 518 Jul 24 22:21 dp_sec_port_2_acl.yaml -~/daq$ sudo rm inst/faucet.log -~/daq$ sudo cmd/exrun -s > daq_mud.log 2>&1 -~/daq$ fgrep port_1_acl inst/faucet.log -~/daq$ cat inst/run-port-01/nodes/nmap01/tmp/open.txt -~/daq$ -``` +Writing output files to /home/username/daq/inst/acl_templates +-rw-r--r-- 1 username primarygroup 140 Jul 31 15:38 inst/device_types.json + +inst/acl_templates: +total 16 +-rw-r--r-- 1 username primarygroup 408 Jul 31 15:38 template_baseline_acl.yaml +-rw-r--r-- 1 username primarygroup 44 Jul 31 15:38 template_default_acl.yaml +-rw-r--r-- 1 username primarygroup 465 Jul 31 15:38 template_lightbulb_acl.yaml +-rw-r--r-- 1 username primarygroup 217 Jul 31 15:38 template_telnet_acl.yaml + +inst/port_acls: +total 0 +~/daq$ ls -l inst/device_types.json +-rw-r--r-- 1 username primarygroup 140 Jul 31 15:38 inst/device_types.json +~/daq$ sudo cmd/exrun -s > daq_mud.log 2>&1 +~/daq$ fgrep template daq_mud.log +INFO:network:Processing acl template for 9a:02:57:1e:8f:01/lightbulb +~/daq$ cat inst/run-port-01/nodes/nmap01/tmp/open.txt +~/daq$ +diff --git a/misc/FAUCET_VERSION b/misc/FAUCET_VERSION index a75d2e3c5c..9968fcb3e5 100644 --- a/misc/FAUCET_VERSION +++ b/misc/FAUCET_VERSION @@ -1 +1 @@ -fd56eb481546dd955edb40999a7597bac8826a15 +0bca70c08c067ce3f965674807b4638ac6aa1b9a diff --git a/misc/device_types.json b/misc/device_types.json new file mode 100644 index 0000000000..d8c0534098 --- /dev/null +++ b/misc/device_types.json @@ -0,0 +1,7 @@ +{ + "macAddrs": { + "9a:02:57:1e:8f:01": { + "type": "lightbulb" + } + } +} diff --git a/mud_files/default.json b/mud_files/default.json new file mode 100644 index 0000000000..f31df09875 --- /dev/null +++ b/mud_files/default.json @@ -0,0 +1,8 @@ +{ + "ietf-mud:mud": { + "mud-version": 1, + "last-update": "2018-03-02T11:20:51+01:00", + "cache-validity": 48, + "is-supported": true + } +} diff --git a/mud_files/telnet.json b/mud_files/telnet.json new file mode 100644 index 0000000000..4fbce12dbf --- /dev/null +++ b/mud_files/telnet.json @@ -0,0 +1,91 @@ + +{ + "ietf-mud:mud": { + "mud-version": 1, + "mud-url": "https://example.com/telnet-server", + "last-update": "2018-07-31T21:41:19+02:00", + "cache-validity": 48, + "is-supported": true, + "systeminfo": "Test telnet server", + "from-device-policy": { + "access-lists": { + "access-list": [ + { + "name": "mud-92939-v4fr" + } + ] + } + }, + "to-device-policy": { + "access-lists": { + "access-list": [ + { + "name": "mud-92939-v4to" + } + ] + } + } + }, + "ietf-access-control-list:access-lists": { + "acl": [ + { + "name": "mud-92939-v4to", + "type": "ipv4-acl-type", + "aces": { + "ace": [ + { + "name": "ent0-todev", + "matches": { + "ietf-mud:mud": { + "controller": "http://telnet-host.com" + }, + "ipv4": { + "protocol": 6 + }, + "tcp": { + "ietf-mud:direction-initiated": "to-device", + "destination-port": { + "operator": "eq", + "port": 23 + } + } + }, + "actions": { + "forwarding": "accept" + } + } + ] + } + }, + { + "name": "mud-92939-v4fr", + "type": "ipv4-acl-type", + "aces": { + "ace": [ + { + "name": "ent0-frdev", + "matches": { + "ietf-mud:mud": { + "controller": "http://telnet-host.com" + }, + "ipv4": { + "protocol": 6 + }, + "tcp": { + "ietf-mud:direction-initiated": "to-device", + "source-port": { + "operator": "eq", + "port": 23 + } + } + }, + "actions": { + "forwarding": "accept" + } + } + ] + } + } + ] + } +} diff --git a/mudacl/bin/run.sh b/mudacl/bin/run.sh index 9515909b12..97a62deceb 100755 --- a/mudacl/bin/run.sh +++ b/mudacl/bin/run.sh @@ -8,20 +8,12 @@ SWITCH_TOPOLOGY=inst/faucet.yaml # Set of available mud files. MUD_DIR=mud_files/ -# Device topology specific to local setup. -DEVICE_TOPOLOGY=local/devices.json - -# Device type mapping, specific to local setup. -DEVICE_TYPES=local/types.json - # Output directory for runtime ACLs. -OUTPUT_DIR=inst/port_acls/ +TEMPLATE_DIR=inst/acl_templates (cd $ROOT; ./gradlew shadow) echo echo Executing mudacl generator... -java -jar $ROOT/build/libs/mudacl-1.0-SNAPSHOT-all.jar $SWITCH_TOPOLOGY $DEVICE_TOPOLOGY $DEVICE_TYPES $MUD_DIR $OUTPUT_DIR - -ls -l $OUTPUT_DIR +java -jar $ROOT/build/libs/mudacl-1.0-SNAPSHOT-all.jar $SWITCH_TOPOLOGY $MUD_DIR $TEMPLATE_DIR diff --git a/mudacl/site/test/port_acls/dp_sec_port_3_acl.yaml b/mudacl/site/test/acl_templates/template_baseline_acl.yaml similarity index 69% rename from mudacl/site/test/port_acls/dp_sec_port_3_acl.yaml rename to mudacl/site/test/acl_templates/template_baseline_acl.yaml index 201581f236..df714a6e11 100644 --- a/mudacl/site/test/port_acls/dp_sec_port_3_acl.yaml +++ b/mudacl/site/test/acl_templates/template_baseline_acl.yaml @@ -1,11 +1,12 @@ --- acls: - dp_sec_port_3_acl: + '@acl:template_baseline_acl': - rule: - description: "Block 9a:02:57:1e:8f:02" - dl_src: "9a:02:57:1e:8f:02" + description: "ICMP Allow" + dl_type: "0x0800" + nw_proto: 1 actions: - allow: 0 + allow: 1 - rule: description: "ARP Allow" dl_type: "0x0806" diff --git a/mudacl/site/test/acl_templates/template_default_acl.yaml b/mudacl/site/test/acl_templates/template_default_acl.yaml new file mode 100644 index 0000000000..db6438f045 --- /dev/null +++ b/mudacl/site/test/acl_templates/template_default_acl.yaml @@ -0,0 +1,3 @@ +--- +acls: + '@acl:template_default_acl': [] diff --git a/mudacl/site/test/acl_templates/template_lightbulb_acl.yaml b/mudacl/site/test/acl_templates/template_lightbulb_acl.yaml new file mode 100644 index 0000000000..78bfcd398c --- /dev/null +++ b/mudacl/site/test/acl_templates/template_lightbulb_acl.yaml @@ -0,0 +1,21 @@ +--- +acls: + '@acl:template_lightbulb_acl': + - rule: + description: "MUD lightbulb cl0-frdev" + dl_type: "0x0800" + dl_src: "@src_mac:lightbulb" + nw_proto: 6 + nw_dst: "@dns:test.com" + tcp_dst: 443 + actions: + allow: 1 + - rule: + description: "MUD lightbulb bacnet-from" + dl_type: "0x0800" + dl_src: "@src_mac:lightbulb" + nw_proto: 17 + udp_src: 47808 + udp_dst: 47808 + actions: + allow: 1 diff --git a/mudacl/site/test/acl_templates/template_telnet_acl.yaml b/mudacl/site/test/acl_templates/template_telnet_acl.yaml new file mode 100644 index 0000000000..2bdafd64b6 --- /dev/null +++ b/mudacl/site/test/acl_templates/template_telnet_acl.yaml @@ -0,0 +1,11 @@ +--- +acls: + '@acl:template_telnet_acl': + - rule: + description: "MUD telnet ent0-frdev" + dl_type: "0x0800" + dl_src: "@src_mac:telnet" + nw_proto: 6 + tcp_src: 23 + actions: + allow: 1 diff --git a/mudacl/site/test/faucet.yaml b/mudacl/site/test/faucet.yaml index 50e71247d9..95ae5af916 100644 --- a/mudacl/site/test/faucet.yaml +++ b/mudacl/site/test/faucet.yaml @@ -1,5 +1,6 @@ -include: +include-optional: - port_acls/dp_sec_port_1_acl.yaml + - port_acls/dp_sec_port_2_acl.yaml dps: pri: dp_id: 1 @@ -38,7 +39,6 @@ dps: acl_in: dp_sec_port_2_acl 3: native_vlan: 30 - acl_in: dp_sec_port_3_acl 4: native_vlan: 40 5: @@ -69,3 +69,8 @@ acls: description: Default deny rule actions: allow: 0 + dp_sec_port_2_acl: + - rule: + description: Default deny rule + actions: + allow: 0 diff --git a/mudacl/src/main/java/com/google/daq/orchestrator/mudacl/AclHelper.java b/mudacl/src/main/java/com/google/daq/orchestrator/mudacl/AclHelper.java index 3010e682d2..872612a648 100644 --- a/mudacl/src/main/java/com/google/daq/orchestrator/mudacl/AclHelper.java +++ b/mudacl/src/main/java/com/google/daq/orchestrator/mudacl/AclHelper.java @@ -12,7 +12,7 @@ public final class AclHelper { public static final int IP_PROTO_TCP = 6; public static final int IP_PROTO_UDP = 17; - public static void finalizeAcl(Acl acl) { + public static void addBaselineRules(Acl acl) { acl.add(makeIcmpRule()); acl.add(makeArpRule()); acl.add(makeDhcpRule()); diff --git a/mudacl/src/main/java/com/google/daq/orchestrator/mudacl/AclProvider.java b/mudacl/src/main/java/com/google/daq/orchestrator/mudacl/AclProvider.java index 8f02efb4ec..693d29ceba 100644 --- a/mudacl/src/main/java/com/google/daq/orchestrator/mudacl/AclProvider.java +++ b/mudacl/src/main/java/com/google/daq/orchestrator/mudacl/AclProvider.java @@ -3,7 +3,10 @@ import com.google.daq.orchestrator.mudacl.DeviceTopology.MacIdentifier; import com.google.daq.orchestrator.mudacl.DeviceTypes.DeviceClassifier; import com.google.daq.orchestrator.mudacl.SwitchTopology.Acl; +import java.util.List; public interface AclProvider { Acl makeEdgeAcl(MacIdentifier macAddress, DeviceClassifier device); + + List