Tuesday, July 12, 2016

802.1x authentication on FAUCET (NFV offload of authentication).

In this post, we will discuss how to add 802.1x to any FAUCET controlled switch. This is a good example of how SDN can add functionality to a switch with just a controller upgrade - no changes to the dataplane are required.

802.1x is a standard for network authentication - whether a given device should be allowed access to the network (whether WiFi or wired).

Here's an overview of how it works with FAUCET.

  • A host, connected to the Ethernet switch, sends 802.1x authentication request packets to the switch. The switch passes them on to the controller/NFV's offload port.
  • A process, hostapd receives these packets and facilitates an authentication conversation with a RADIUS server (see here for an extremely detailed explanation).
  • Whether successful or unsuccessful, hostapd notifies another process on the same host, hostapd_cli.
  • A script, hostapd_trigger.py (see below) monitors hostapd_cli, and the FAUCET controller's log file. It correlates the MAC address of the host with the port the host is connected to on the switch, and then initiates the desired action (for example, opening a firewall port so the host can access the Internet - if successfully authenticated).



This implementation is a proof of concept of implementing the authentication entirely within the dataplane. The FAUCET controller does not participate or understand the 802.1x exchange and does not need to. Other services, like DNS and DHCP could also be run on the same host, in the same way. The trigger script can perform any action, including, modifying the FAUCET config file itself and HUPing the controller (for example, to add or remove an OpenFlow ACL to the host's port to authorize it to access the network).

For a more advanced solution including an administrative GUI, a system like PacketFence could be integrated. For example, PacketFence could also change FAUCET's config to put a problematic host in a quarantine VLAN.

There are some features being added to FAUCET to support better integration. For example, this proof of concept uses the FAUCET log file, but FAUCET will expose what it knows programmatically (specifically, what MAC addresses have been learned on what ports). There are also features to force all 802.1x, etc traffic to the offload port only, and more efficiently add or remove an ACL/VLAN from a port when requested.

hostapd takes a minimal configuration file, instructing it to listen on a port, communicate with a RADIUS server, and notify an external process of events:

interface=eth0.2001
driver=wired
logger_stdout=-1
logger_stdout_level=1
ieee8021x=1
eap_reauth_period=3600
eap_server=0
eapol_version=1
own_ip_addr=127.0.0.1
auth_server_addr=127.0.0.1
auth_server_port=1812
auth_server_shared_secret=SECRET

ctrl_interface=/var/run/hostapd

In this case it will listen on eth0.2001, which is a tagged port for FAUCET VLAN 2001. We will use a RADIUS server running on the same host as the controller (how to configure the RADIUS server can found above).

The trigger script follows. While a proof of concept it demonstrates how to correlate the host's MAC with a port and then call an external system (future versions of FAUCET will support proper programmatic access, as above).

#!/usr/bin/python

import fcntl
import os
import re
import subprocess
import time

PORTS = [5]
mac_to_port = {}
mac_authenticated = {}

faucet_log = open('/var/log/faucet/faucet.log')
fcntl.fcntl(faucet_log.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)
hostapd_cli = subprocess.Popen(
    ['/usr/local/sbin/hostapd_cli', '-p', '/var/run/hostapd'],
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
    shell=True)
fcntl.fcntl(hostapd_cli.stdout, fcntl.F_SETFL, os.O_NONBLOCK)

while True:
   log = faucet_log.readline()
   hostapi_log = ''
   try:
       hostapi_log = hostapd_cli.stdout.readline()
   except IOError:
       pass
   status_change = False
   # did we learn a MAC on a port?
   if log:
       m = re.search('src:(\S+) in_port:(\S+)', log)
       if m:
           mac = m.group(1)
           in_port = int(m.group(2))
           if in_port in PORTS:
               if mac not in mac_to_port or mac_to_port[mac] != in_port:
                   mac_to_port[mac] = in_port
                   print 'mac %s is on port %u' % (mac, in_port)
                   status_change = True
       if not status_change:
           continue
   # was a MAC authenticated or de-authenticated
   if hostapi_log:
       connected_m = re.search('AP-STA-CONNECTED (\S+)', hostapi_log)
       disconnected_m = re.search('AP-STA-DISCONNECTED (\S+)', hostapi_log)
       if connected_m:
           mac = connected_m.group(1)
           mac_authenticated[mac] = True
           print 'mac %s authenticated' % mac
           status_change = True
       elif disconnected_m:
           mac = disconnected_m.group(1)
           mac_authenticated[mac] = False
           print 'mac %s not authenticated' % mac
           status_change = True
   if status_change:
       for mac, in_port in mac_to_port.iteritems():
           print mac, in_port,
           if mac in mac_authenticated:
               print mac_authenticated[mac]
               ## ADD TRIGGER TO EXTERNAL SYSTEM HERE
           else:
               print 'unknown'
   else:
       time.sleep(0.5)
       hostapd_cli.stdin.write('\n')



No comments:

Post a Comment

Note: Only a member of this blog may post a comment.