Skip to content

Commit

Permalink
Event-driver MUD ACL generator
Browse files Browse the repository at this point in the history
  • Loading branch information
grafnu committed Aug 1, 2018
1 parent 6f23132 commit 270c14e
Show file tree
Hide file tree
Showing 23 changed files with 482 additions and 114 deletions.
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
18 changes: 11 additions & 7 deletions bin/mudacl
Original file line number Diff line number Diff line change
Expand Up @@ -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
9 changes: 7 additions & 2 deletions bin/test_daq
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
115 changes: 98 additions & 17 deletions daq/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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')
Expand All @@ -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
Expand Down Expand Up @@ -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]
79 changes: 46 additions & 33 deletions docs/mudacl.md
Original file line number Diff line number Diff line change
@@ -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).

* <b>Switch Topology</b>: Specified by `inst/faucet.yaml` (shouldn't need to change).
* <b>Device Topology</b>: Specified in `local/devices.json` (copied into place by `bin/mudacl`).
* <b>Device Types</b>: Specified in `local/types.json` (copied into place by `bin/mudacl`).
* <b>MUD Files</b>: 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
<pre>
<b>~/daq$</b> 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"
<b>~/daq$</b> sudo rm -rf inst/port_acls inst/device_types.json
<b>~/daq$</b> sudo cmd/exrun -s > daq_open.log 2>&1
<b>~/daq$</b> fgrep template daq_open.log
<b>~/daq$</b> 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
<b>~/daq$</b> 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
<b>~/daq$</b> ls -l inst/device_types.json
-rw-r--r-- 1 username primarygroup 140 Jul 31 15:38 inst/device_types.json
<b>~/daq$</b> sudo cmd/exrun -s > daq_mud.log 2>&1
<b>~/daq$</b> fgrep template daq_mud.log
INFO:network:Processing acl template for 9a:02:57:1e:8f:01/lightbulb
<b>~/daq$</b> cat inst/run-port-01/nodes/nmap01/tmp/open.txt
<b>~/daq$</b>
</pre>
2 changes: 1 addition & 1 deletion misc/FAUCET_VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
fd56eb481546dd955edb40999a7597bac8826a15
0bca70c08c067ce3f965674807b4638ac6aa1b9a
7 changes: 7 additions & 0 deletions misc/device_types.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"macAddrs": {
"9a:02:57:1e:8f:01": {
"type": "lightbulb"
}
}
}
8 changes: 8 additions & 0 deletions mud_files/default.json
Original file line number Diff line number Diff line change
@@ -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
}
}
Loading

0 comments on commit 270c14e

Please sign in to comment.