diff --git a/docs/PacketFence_Network_Devices_Configuration_Guide.asciidoc b/docs/PacketFence_Network_Devices_Configuration_Guide.asciidoc index 2fecdd3ac627..19ab642df169 100644 --- a/docs/PacketFence_Network_Devices_Configuration_Guide.asciidoc +++ b/docs/PacketFence_Network_Devices_Configuration_Guide.asciidoc @@ -5936,10 +5936,10 @@ Finally, we need to tie the service profiles with the proper AAA configuration. Ubiquiti ~~~~~~~~ -Web authentication +Web Authentication ^^^^^^^^^^^^^^^^^^ -In order to configure web-authentication (external captive-portal) on Ubiquiti access points, you must have access to a Unifi controller and your APs must be connected to it. +In order to configure web authentication (external captive-portal) on Ubiquiti access points, you must have access to a Unifi controller and your APs must be connected to it. First, you must configure the guest policy. Go in 'Settings->Guest Control' and configure it as shown below: @@ -5972,6 +5972,64 @@ Where : * *wsPwd* is the password that is associated to the wsUser. This is configured in the 'Web Services' tab of the switch. * *controllerIp* is the IP address of your Unifi controller. This is configured in the 'Definition' tab of the switch. +VLAN Enforcement +^^^^^^^^^^^^^^^^ + +In order to configure VLAN enforcement on the Unifi controller, you need first to configure a RADIUS profile, then a secure wireless network. + +Important : You cannot reuse a VLAN ID for dynamic VLAN if it is set as a static value for another SSID on the same AP. So, if you have a SSID set to use VLAN 10, you cannot use VLAN ID 10 for RADIUS controlled VLAN users as those users will not get an IP address. + +Note that VLAN enforcement for an open SSID is not supported yet by the controller and access point. + +AAA Configuration ++++++++++++++++++ + +image::docs/images/unifi-radius.png[scaledwidth="100%",alt="Unifi Radius Profile"] + +Secure SSID ++++++++++++ + +There is a special case when you want to be able to deauthenticate a device when it is connected on the secure SSID. + +By default pmksa caching is enabled and applies even when a deauthentication request is sent to the controller, meaning you will not get a new RADIUS request afterwards. To disable this cache you will need to create a file on the controller. + +First verify the site where your access point is managed. To do this, under the administration interface of the controller, select the appropriate "Current Site" then in the URI check the value just after site. + +In this case the URL is https://192.168.1.6:8443/manage/site/4j4ee7x4/ so the site id is 4j4ee7x4. + +Next connect on the controller via SSH and go in /usr/lib/unifi/data/sites/4j4ee7x4 (replace 4j4ee7x4 with your site id) and create a file named config.properties with the following content: + + config.system_cfg.1=aaa.1.auth_cache=disabled + config.system_cfg.2=aaa.4.auth_cache=disabled + +The numbers 1 and 4 are the profile id configured on the access point. To be sure you have the correct id, you can connect on the access point via SSH or via the debug terminal and do: + + BZ.v3.9.3# cat /tmp/system.cfg | grep ssid + aaa.1.ssid=PacketFence-Secure + wireless.1.ssid=PacketFence-Secure + wireless.1.hide_ssid=false + aaa.2.ssid=PacketFence-Public + wireless.2.ssid=PacketFence-Public + wireless.2.hide_ssid=false + aaa.3.ssid=vport-802AA8863D5B + wireless.3.ssid=vport-802AA8863D5B + wireless.3.hide_ssid=true + aaa.4.ssid=PacketFence-Secure + wireless.4.ssid=PacketFence-Secure + wireless.4.hide_ssid=false + aaa.5.ssid=PacketFence-Public + wireless.5.ssid=PacketFence-Public + wireless.5.hide_ssid=false + +You can see that PacketFence-Secure appears in 1 and 4 (2 radios). + +Fore additional details, feel free to ask for support directly in the Ubiquiti forum: https://community.ubnt.com/t5/UniFi-Wireless/Feature-request-disable-pmksa-caching/m-p/2112479#M257628 + +Next you will need to create a secured profile: + +image::docs/images/unifi-secure.png[scaledwidth="100%",alt="Unifi Secure SSID"] + + Xirrus ~~~~~~ diff --git a/docs/images/unifi-open.png b/docs/images/unifi-open.png new file mode 100644 index 000000000000..907e408fe2f3 Binary files /dev/null and b/docs/images/unifi-open.png differ diff --git a/docs/images/unifi-radius.png b/docs/images/unifi-radius.png new file mode 100644 index 000000000000..19398a2b1830 Binary files /dev/null and b/docs/images/unifi-radius.png differ diff --git a/docs/images/unifi-secure.png b/docs/images/unifi-secure.png new file mode 100644 index 000000000000..e8b946a841b9 Binary files /dev/null and b/docs/images/unifi-secure.png differ diff --git a/lib/pf/Switch/AeroHIVE/AP.pm b/lib/pf/Switch/AeroHIVE/AP.pm index 23bd423f283d..4a2294eba619 100644 --- a/lib/pf/Switch/AeroHIVE/AP.pm +++ b/lib/pf/Switch/AeroHIVE/AP.pm @@ -32,6 +32,7 @@ use warnings; use pf::config qw( $WIRELESS_MAC_AUTH + $WEBAUTH_WIRELESS ); use pf::constants; use pf::locationlog; @@ -128,6 +129,7 @@ sub parseExternalPortalRequest { grant_url => $req->param('url'), status_code => '200', synchronize_locationlog => $TRUE, + connection_type => $WEBAUTH_WIRELESS, ); return \%params; diff --git a/lib/pf/Switch/Aruba.pm b/lib/pf/Switch/Aruba.pm index ee3bc7f1bfdb..db3f759605f0 100644 --- a/lib/pf/Switch/Aruba.pm +++ b/lib/pf/Switch/Aruba.pm @@ -65,6 +65,7 @@ use pf::constants; use pf::config qw( $MAC $SSID + $WEBAUTH_WIRELESS ); use pf::Switch::constants; use pf::util; @@ -558,7 +559,8 @@ sub parseExternalPortalRequest { client_ip => $req->param('ip'), ssid => $req->param('essid'), redirect_url => $req->param('url'), - synchronize_locationlog => $FALSE, + synchronize_locationlog => $FALSE, + connection_type => $WEBAUTH_WIRELESS, ); return \%params; diff --git a/lib/pf/Switch/Cisco/Catalyst_2960.pm b/lib/pf/Switch/Cisco/Catalyst_2960.pm index a4b76e58a45a..b05e83abb48c 100644 --- a/lib/pf/Switch/Cisco/Catalyst_2960.pm +++ b/lib/pf/Switch/Cisco/Catalyst_2960.pm @@ -117,6 +117,7 @@ use pf::constants; use pf::config qw( $WIRED_802_1X $WIRED_MAC_AUTH + $WEBAUTH_WIRED, ); use pf::Switch::constants; use pf::util; @@ -657,7 +658,8 @@ sub parseExternalPortalRequest { client_ip => $client_ip, redirect_url => $redirect_url, synchronize_locationlog => $FALSE, - ); + connection_type => $WEBAUTH_WIRED, +); return \%params; } diff --git a/lib/pf/Switch/Cisco/WLC.pm b/lib/pf/Switch/Cisco/WLC.pm index e4df9c7fca0b..45de5e6eb109 100644 --- a/lib/pf/Switch/Cisco/WLC.pm +++ b/lib/pf/Switch/Cisco/WLC.pm @@ -111,6 +111,7 @@ use pf::constants; use pf::config qw( $MAC $SSID + $WEBAUTH_WIRELESS ); use pf::web::util; use pf::util; @@ -634,6 +635,7 @@ sub parseExternalPortalRequest { client_ip => $client_ip, redirect_url => $redirect_url, synchronize_locationlog => $FALSE, + connection_type => $WEBAUTH_WIRELESS, ); return \%params; diff --git a/lib/pf/Switch/CoovaChilli.pm b/lib/pf/Switch/CoovaChilli.pm index a923f189cc0c..014f5d933102 100644 --- a/lib/pf/Switch/CoovaChilli.pm +++ b/lib/pf/Switch/CoovaChilli.pm @@ -23,6 +23,7 @@ use base ('pf::Switch'); use pf::config qw( $WIRELESS_MAC_AUTH + $WEBAUTH_WIRELESS ); use pf::constants; use pf::node; @@ -64,6 +65,7 @@ sub parseExternalPortalRequest { redirect_url => $req->param('userurl'), status_code => $req->param('res'), synchronize_locationlog => $TRUE, + connection_type => $WEBAUTH_WIRELESS, ); return \%params; diff --git a/lib/pf/Switch/Fortinet/FortiGate.pm b/lib/pf/Switch/Fortinet/FortiGate.pm index bfab29bef753..5f6e115e712a 100644 --- a/lib/pf/Switch/Fortinet/FortiGate.pm +++ b/lib/pf/Switch/Fortinet/FortiGate.pm @@ -31,6 +31,7 @@ use HTTP::Request::Common; use pf::log; use pf::constants; use pf::accounting qw(node_accounting_dynauth_attr); +use pf::config qw ($WEBAUTH_WIRELESS); use base ('pf::Switch::Fortinet'); @@ -78,6 +79,7 @@ sub parseExternalPortalRequest { grant_url => $req->param('post'), status_code => '200', synchronize_locationlog => $TRUE, + connection_type => $WEBAUTH_WIRELESS, ); return \%params; diff --git a/lib/pf/Switch/Meraki/MR.pm b/lib/pf/Switch/Meraki/MR.pm index a3e5c314f5e4..ac677a435db8 100644 --- a/lib/pf/Switch/Meraki/MR.pm +++ b/lib/pf/Switch/Meraki/MR.pm @@ -32,7 +32,7 @@ use warnings; use base ('pf::Switch'); use pf::constants; -use pf::config qw($WIRELESS_MAC_AUTH); +use pf::config qw($WIRELESS_MAC_AUTH $WEBAUTH_WIRELESS); use pf::util; use pf::node; @@ -82,6 +82,7 @@ sub parseExternalPortalRequest { redirect_url => $req->param('continue_url'), status_code => '200', synchronize_locationlog => $TRUE, + connection_type => $WEBAUTH_WIRELESS, ); return \%params; diff --git a/lib/pf/Switch/Mikrotik.pm b/lib/pf/Switch/Mikrotik.pm index fed131906271..d35018a20d06 100644 --- a/lib/pf/Switch/Mikrotik.pm +++ b/lib/pf/Switch/Mikrotik.pm @@ -28,6 +28,7 @@ use pf::config qw( $MAC $SSID $WIRELESS_MAC_AUTH + $WEBAUTH_WIRELESS ); sub description { 'Mikrotik' } @@ -72,6 +73,7 @@ sub parseExternalPortalRequest { client_ip => $req->param('ip'), status_code => '200', synchronize_locationlog => $TRUE, + connection_type => $WEBAUTH_WIRELESS, ); return \%params; diff --git a/lib/pf/Switch/Ruckus.pm b/lib/pf/Switch/Ruckus.pm index ab08fa132147..68d679597ea4 100644 --- a/lib/pf/Switch/Ruckus.pm +++ b/lib/pf/Switch/Ruckus.pm @@ -45,6 +45,7 @@ use pf::constants; use pf::config qw( $MAC $SSID + $WEBAUTH_WIRELESS ); use pf::util; @@ -188,6 +189,7 @@ sub parseExternalPortalRequest { ssid => $req->param('ssid'), redirect_url => $req->param('url'), synchronize_locationlog => $FALSE, + connection_type => $WEBAUTH_WIRELESS, ); return \%params; diff --git a/lib/pf/Switch/Ruckus/SmartZone.pm b/lib/pf/Switch/Ruckus/SmartZone.pm index 8e6dde30c3ce..bb922f6588d0 100644 --- a/lib/pf/Switch/Ruckus/SmartZone.pm +++ b/lib/pf/Switch/Ruckus/SmartZone.pm @@ -23,6 +23,7 @@ use pf::node; use pf::violation; use pf::ip4log; use JSON::MaybeXS qw(encode_json); +use pf::config qw ($WEBAUTH_WIRELESS); sub description { 'Ruckus SmartZone Wireless Controllers' } sub supportsWebFormRegistration { return $FALSE; } @@ -53,6 +54,7 @@ sub parseExternalPortalRequest { redirect_url => $req->param('url'), switch_id => $req->param('nbiIP'), synchronize_locationlog => $TRUE, + connection_type => $WEBAUTH_WIRELESS, ); return \%params; diff --git a/lib/pf/Switch/Ubiquiti/Unifi.pm b/lib/pf/Switch/Ubiquiti/Unifi.pm index a971d864fbcb..04e36878a6b8 100644 --- a/lib/pf/Switch/Ubiquiti/Unifi.pm +++ b/lib/pf/Switch/Ubiquiti/Unifi.pm @@ -28,6 +28,13 @@ use pf::file_paths qw($var_dir); use pf::constants; use pf::util; use pf::node; +use pf::config qw( + %connection_type_to_str + $MAC + $SSID + $WEBAUTH_WIRELESS +); +use JSON::MaybeXS; # The port to reach the Unifi controller API our $UNIFI_API_PORT = "8443"; @@ -41,6 +48,10 @@ sub description { 'Unifi Controller' } # CAPABILITIES # access technology supported sub supportsExternalPortal { return $TRUE; } +sub supportsWirelessDot1x { return $TRUE; } +sub supportsWirelessMacAuth { return $TRUE; } +# inline capabilities +sub inlineCapabilities { return ($MAC,$SSID); } =head2 synchronize_locationlog @@ -80,6 +91,7 @@ sub parseExternalPortalRequest { redirect_url => $req->param('url'), status_code => '200', synchronize_locationlog => $TRUE, + connection_type => $WEBAUTH_WIRELESS, ); return \%params; @@ -122,8 +134,12 @@ sub _deauthenticateMacWithHTTP { my $username = $self->{_wsUser}; my $password = $self->{_wsPwd}; + my $site = 'default'; + my $command = ($node_info->{status} eq $STATUS_UNREGISTERED || violation_count_reevaluate_access($mac)) ? "unauthorize-guest" : "authorize-guest"; + $command = "kick-sta" if ($node_info->{last_connection_type} ne $connection_type_to_str{$WEBAUTH_WIRELESS}); + my $ua = LWP::UserAgent->new(); $ua->cookie_jar({ file => "$var_dir/run/.ubiquiti.cookies.txt" }); $ua->ssl_opts(verify_hostname => 0); @@ -139,8 +155,22 @@ sub _deauthenticateMacWithHTTP { return; } - $response = $ua->post("$base_url/api/s/default/cmd/stamgr", Content => '{"cmd":"'.$command.'", "mac":"'.$mac.'"}'); - + $response = $ua->get("$base_url/api/self/sites"); + + unless($response->is_success) { + $logger->error("Can't have the site list from the Unifi controller: ".$response->status_line); + return; + } + + my $json_data = decode_json($response->decoded_content()); + + foreach my $entry (@{$json_data->{'data'}}) { + $response = $ua->post("$base_url/api/s/$entry->{'name'}/cmd/stamgr", Content => '{"cmd":"'.$command.'", "mac":"'.$mac.'"}'); + if ($response->is_success) { + $logger->info("Deauth on site: $entry->{'desc'}"); + } + } + unless($response->is_success) { $logger->error("Can't send request on the Unifi controller: ".$response->status_line); return; diff --git a/lib/pf/Switch/Xirrus.pm b/lib/pf/Switch/Xirrus.pm index 735154404706..01e79acba9ad 100644 --- a/lib/pf/Switch/Xirrus.pm +++ b/lib/pf/Switch/Xirrus.pm @@ -40,6 +40,7 @@ use pf::config qw( $MAC $SSID $WIRELESS_MAC_AUTH + $WEBAUTH_WIRELESS ); use pf::constants; use pf::node; @@ -399,6 +400,7 @@ sub parseExternalPortalRequest { redirect_url => $req->param('userurl'), status_code => '200', synchronize_locationlog => $TRUE, + connection_type => $WEBAUTH_WIRELESS, ); return \%params; diff --git a/lib/pf/config.pm b/lib/pf/config.pm index 8a5b9bf74db1..6f9146fecb31 100644 --- a/lib/pf/config.pm +++ b/lib/pf/config.pm @@ -166,7 +166,7 @@ BEGIN { $ACCOUNTING_POLICY_TIME $ACCOUNTING_POLICY_BANDWIDTH $WIPS_VID $thread $fqdn $reverse_fqdn $IF_INTERNAL $IF_ENFORCEMENT_VLAN $IF_ENFORCEMENT_INLINE $IF_ENFORCEMENT_DNS - $WIRELESS_802_1X $WIRELESS_MAC_AUTH $WIRED_802_1X $WIRED_MAC_AUTH $WIRED_SNMP_TRAPS $UNKNOWN $INLINE + $WIRELESS_802_1X $WIRELESS_MAC_AUTH $WIRED_802_1X $WIRED_MAC_AUTH $WIRED_SNMP_TRAPS $UNKNOWN $INLINE $WEBAUTH $WEBAUTH_WIRED $WEBAUTH_WIRELESS $NET_TYPE_INLINE $NET_TYPE_INLINE_L2 $NET_TYPE_INLINE_L3 $WIRELESS $WIRED $EAP $WEB_ADMIN_NONE $WEB_ADMIN_ALL @@ -321,17 +321,28 @@ Readonly our $IF_INTERNAL => 'internal'; # Interface enforcement techniques # connection type constants -Readonly our $WIRELESS_802_1X => 0b110000001; -Readonly our $WIRELESS_MAC_AUTH => 0b100000010; -Readonly our $WIRED_802_1X => 0b011000100; -Readonly our $WIRED_MAC_AUTH => 0b001001000; -Readonly our $WIRED_SNMP_TRAPS => 0b001010000; -Readonly our $INLINE => 0b000100000; -Readonly our $UNKNOWN => 0b000000000; +# 1 : Wireless +# 2 : Eap +# 3 : Wired +# 4 : Inline +# 5 : SNMP +# 6 : WebAuth + +Readonly our $WIRELESS_802_1X => 0b1100000000; +Readonly our $WIRELESS_MAC_AUTH => 0b1000000001; +Readonly our $WIRED_802_1X => 0b0110000010; +Readonly our $WIRED_MAC_AUTH => 0b0010000011; +Readonly our $WIRED_SNMP_TRAPS => 0b0010100100; +Readonly our $INLINE => 0b0001000101; +Readonly our $UNKNOWN => 0b0000000000; +Readonly our $WEBAUTH_WIRELESS => 0b1000010111; +Readonly our $WEBAUTH_WIRED => 0b0010011000; + # masks to be used on connection types -Readonly our $WIRELESS => 0b100000000; -Readonly our $WIRED => 0b001000000; -Readonly our $EAP => 0b010000000; +Readonly our $WIRELESS => 0b1000000000; +Readonly our $WIRED => 0b0010000000; +Readonly our $EAP => 0b0100000000; +Readonly our $WEBAUTH => 0b0000010000; # Catalyst-based access level constants Readonly::Scalar our $ADMIN_USERNAME => 'admin'; @@ -348,13 +359,15 @@ Readonly our $WEB_ADMIN_ALL => 4294967295; 'SNMP-Traps' => $WIRED_SNMP_TRAPS, 'Inline' => $INLINE, 'WIRED_MAC_AUTH' => $WIRED_MAC_AUTH, + 'Ethernet-Web-Auth' => $WEBAUTH_WIRED, + 'Wireless-Web-Auth' => $WEBAUTH_WIRELESS, ); %connection_group = ( 'Wireless' => $WIRELESS, 'Ethernet' => $WIRED, 'EAP' => $EAP, + 'Web-Auth' => $WEBAUTH, ); - # Their string equivalent for database storage %connection_type_to_str = ( $WIRELESS_802_1X => 'Wireless-802.11-EAP', @@ -364,11 +377,14 @@ Readonly our $WEB_ADMIN_ALL => 4294967295; $WIRED_SNMP_TRAPS => 'SNMP-Traps', $INLINE => 'Inline', $UNKNOWN => '', + $WEBAUTH_WIRELESS => 'Wireless-Web-Auth', + $WEBAUTH_WIRED => 'Ethernet-Web-Auth', ); %connection_group_to_str = ( $WIRELESS => 'Wireless', $WIRED => 'Ethernet', $EAP => 'EAP', + $WEBAUTH => 'Web-Auth', ); # String to constant hash @@ -382,6 +398,8 @@ Readonly our $WEB_ADMIN_ALL => 4294967295; $WIRED_SNMP_TRAPS => 'Wired SNMP', $INLINE => 'Inline', $UNKNOWN => 'Unknown', + $WEBAUTH_WIRELESS => 'Wifi Web Auth', + $WEBAUTH_WIRED => 'Wired Web Auth', ); %connection_type_explained_to_str = map { $connection_type_explained{$_} => $connection_type_to_str{$_} } keys %connection_type_explained; diff --git a/lib/pf/enforcement.pm b/lib/pf/enforcement.pm index 3ee5c61a2ca5..45c8ae58b82e 100644 --- a/lib/pf/enforcement.pm +++ b/lib/pf/enforcement.pm @@ -43,6 +43,7 @@ use pf::config qw( %connection_type_explained $WIRED $WIRELESS + $WEBAUTH ); use pf::inline::custom $INLINE_API_LEVEL; use pf::iptables; @@ -169,7 +170,7 @@ sub _vlan_reevaluation { $client->notify( 'ReAssignVlan', %data ); } } - elsif ( ( $conn_type & $WIRELESS ) == $WIRELESS ) { + elsif ( ( ( $conn_type & $WIRELESS ) == $WIRELESS ) || ( ( $conn_type & $WEBAUTH ) == $WEBAUTH ) ) { $logger->debug("Calling API with desAssociate request on switch (".$switch_id.")"); if ($cluster_deauth) { $client->notify( 'desAssociate_in_queue', %data ); diff --git a/lib/pf/web/externalportal.pm b/lib/pf/web/externalportal.pm index 6a021d760db8..8e93b941af56 100644 --- a/lib/pf/web/externalportal.pm +++ b/lib/pf/web/externalportal.pm @@ -24,7 +24,7 @@ use Hash::Merge qw(merge); use UNIVERSAL::require; use pf::config qw( - $WIRELESS_MAC_AUTH + $WEBAUTH ); use pf::ip4log; use pf::log; @@ -131,6 +131,7 @@ sub handle { grant_url => undef, # Grant URL status_code => undef, # Status code synchronize_locationlog => undef, # Should we synchronize locationlog + connection_type => undef, # Set the connection_type ); my $switch_params = $switch_type->parseExternalPortalRequest($r, $req); @@ -164,7 +165,7 @@ sub handle { pf::ip4log::open($params{'client_ip'}, $params{'client_mac'}, 3600); # Updating locationlog if required - $switch->synchronize_locationlog("0", "0", $params{'client_mac'}, 0, $WIRELESS_MAC_AUTH, undef, $params{'client_mac'}, $params{'ssid'}) if ( $params{'synchronize_locationlog'} ); + $switch->synchronize_locationlog("0", "0", $params{'client_mac'}, 0, $params{'connection_type'}, undef, $params{'client_mac'}, $params{'ssid'}) if ( $params{'synchronize_locationlog'} ); my $portalSession = $self->_setup_session($req, $params{'client_mac'}, $params{'client_ip'}, $params{'redirect_url'}, $params{'grant_url'});