Open Checks
Daniel Lindeman
CIS 654 - W2017
Concept
- Configuration Time SDN Checking
- "Security Expert" in a box
Problem(s)
- SDN Configuration is difficult
- Many inexperienced people
- All choices left up to the Admin
- Order matters
- 3 + 2 != 2 + 3
- Potential insane defaults
- SSL is not default
- Opt-in security
Firewall Proof of Concept
// A small network
f1: firewall
h2: server
h1: client
h3: client
Routes:
h1 <---> f1 <---> h2
h3 <---> f1 <-/-> h2
Security Checking Options
- Wait for hackers (bad)
- Hire a security firm auditor (expensive)
- Manual Test (slow)
- Automated Test (best option)
Common thread?
There is a better way
Find it before you write it

Vendor Solutions Exist
- Paid for
- Cisco/VMWare
- None for OpenFlow
Just like software bugs
- Customer finds it: Really bad
- Tester Finds it: Fine
- Developer Tests Find it: Okay
- IDE finds it: Best
Catchable Problems
Define f1 as a Firewall
Is there a route through f1 for HTTP traffic?
Is there a rule to block anything else?
Is there a source IP whitelist?
Is there a destination IP whitelist?
Is handling port 80 the firewall's only job?
mininet> h2 python -m SimpleHTTPServer 80 &
mininet> h1 wget -O - h2
mininet> h3 wget -O - h2
We know all of this before starting the network!
How do we find out?
- Dump Flow tables or other artifacts
- Introduce "Type System" to SDNs
- Type - Check - Interface
- Check an element against its "Type"
- By running tests on instantiation
Given
"If you see h1 and h2 talking on port 80, cool.
Otherwise, drop it."

tables = [
{'dl_src':'00:00:00:00:00:01', 'dl_dst':'00:00:00:00:00:02', 'tp_dst': 80, 'actions':'NORMAL'},
{'dl_src':'00:00:00:00:00:02', 'dl_dst':'00:00:00:00:00:01', 'tp_dst': 80, 'actions':'NORMAL'},
{'actions':'ANY'}
]

class FirewallCheck(object):
def __init__(self, tables):
self.tables = tables
self.src_ips = [ table['dl_src'] for table in tables if 'dl_src' in table]
self.dst_ips = [ table['dl_dst'] for table in tables if 'dl_dst' in table]
self.tp_dsts = set([ table['tp_dst'] for table in tables if 'tp_dst' in table])
self.run_all_checks()
def run_all_checks(self):
self.specify_source_ips()
self.specify_destination_ip()
self.specify_destination_port()
self.block_by_default()
self.allow_specific_traffic()
def specify_source_ips(self):
raise NotImplementedError
def specify_destination_ip(self):
raise NotImplementedError
def specify_destination_port(self):
raise NotImplementedError
def block_by_default(self):
raise NotImplementedError
def allow_specific_traffic(self):
raise NotImplementedError
class HttpFirewallCheck(FirewallCheck):
def __init__(self, table):
super().__init__(table)
def block_by_default(self):
# assert that there is a catch-all block to drop anything else
for table in tables:
if 'dl_src' not in table:
assert(table['actions'] == 'ANY')
print("block_by_default check passed")
def allow_specific_traffic(self):
# assert that there exists an entry in tables that will allow port 80 traffic
for table in tables:
if ('tp_dst' in table and table['tp_dst'] == 80):
assert(table['dl_dst'])
assert(table['dl_src'])
assert(table['actions'] == 'NORMAL')
print("allow_specific_traffic check passed")
def specify_source_ips(self):
# assert that source IPs have been specified in the tables
assert(self.src_ips)
print("specify_soure_ips check passed")
def specify_destination_ip(self):
# assert that destination IPs have been specified in the tables
assert(self.dst_ips)
print("specify_destination_ip check passed")
def specify_destination_port(self):
# assert that port 80 is the only port with a NORMAL entry
assert(set([80]) == self.tp_dsts)
assert(len(self.tp_dsts) == 1)
for table in tables:
if 'dl_src' in table and 'dl_dst' in table and 'tp_dst' in table and table['tp_dst'] == 80 and 'actions' in table:
assert(table['actions'] == "NORMAL")
print("specify_destination_port check passed")
Running:
daniellindeman@Daniels-MBP:~/Desktop
] python firewall_check.py
specify_soure_ips check passed
specify_destination_ip check passed
specify_destination_port check passed
block_by_default check passed
allow_specific_traffic check passed
Running with bad config:
tables = [
{'dl_src':'00:00:00:00:00:01', 'dl_dst':'00:00:00:00:00:02', 'tp_dst': 80, 'actions':'NORMAL'},
{'dl_src':'00:00:00:00:00:02', 'dl_dst':'00:00:00:00:00:01', 'tp_dst': 90, 'actions':'NORMAL'},
{'actions':'ANY'}]
daniellindeman@Daniels-MBP:~/Desktop
] python firewall_check.py
specify_soure_ips check passed
specify_destination_ip check passed
Traceback (most recent call last):
File "firewall_check.py", line 78, in <module>
f1 = HttpFirewallCheck(tables)
File "firewall_check.py", line 37, in __init__
super().__init__(table)
File "firewall_check.py", line 9, in __init__
self.run_all_checks()
File "firewall_check.py", line 14, in run_all_checks
self.specify_destination_port()
File "firewall_check.py", line 67, in specify_destination_port
assert(set([80]) == self.tp_dsts)
AssertionError
What good is it?
- Exception raising == awareness raising
- If a test is not implemented -> you know
- A "contract" of security
- A starting point
Limitations
- There are many ways to configure SDNs
- ways of thinking about programming
- not in vs in
- ways of thinking about programming
- Errors without breadcrumbs
- Runtime changes
- Things not left in artifacts like Flow Tables
Future Work
- Expand Definitions
- Have more than one
- Explore the static space more completely
- Composable Checks
- Create a Static Typing Framework for OpenFlow
- Create a Linter/Static Analysis Tool
References
- https://support.rackspace.com/how-to/best-practices-for-firewall-rules-configuration/
Open Checks
By dlindema
Open Checks
- 324