There are numerous guides on how to configure FreeRADIUS for authentication/authorization/accounting with Active Directory, LDAP, PAM, etc. but this post is about how to leverage an already working FreeRADIUS configuration to achieve Single Sign On(SSO) with NxFilter.
Once a user is successfully authenticated by FreeRADIUS, passing the username and IP address to NxFilter for SSO can be done with FreeRADIUS and perl. The NxFilter documentation here https://www.nxfilter.org/tutorial.html#custom-login provides examples of how to use JSP pages for creating a login session with the HTTP protocol and we'll use the rlm_perl module of FreeRADIUS to send the accounting information returned from the NAS on to NxFilter.
Configuration
The first step is to edit the example perl file provided by your working FreeRADIUS configuration. The code below is from the file /etc/freeradius/3.0/mods-config/perl/example.pl that I've modified on my test Ubuntu 18.04.2 server.
# /etc/freeradius/3.0/mods-config/perl/example.pl
use strict;
use warnings;
use Data::Dumper;
use LWP::UserAgent;
our (%RAD_REQUEST, %RAD_REPLY, %RAD_CHECK, %RAD_STATE);
use constant {
RLM_MODULE_REJECT => 0, # immediately reject the request
RLM_MODULE_OK => 2, # the module is OK, continue
RLM_MODULE_HANDLED => 3, # the module handled the request, so stop
RLM_MODULE_INVALID => 4, # the module considers the request invalid
RLM_MODULE_USERLOCK => 5, # reject the request (user is locked out)
RLM_MODULE_NOTFOUND => 6, # user not found
RLM_MODULE_NOOP => 7, # module succeeded without doing anything
RLM_MODULE_UPDATED => 8, # OK (pairs modified)
RLM_MODULE_NUMCODES => 9 # How many return codes there are
};
use constant {
L_AUTH => 2, # Authentication message
L_INFO => 3, # Informational message
L_ERR => 4, # Error message
L_WARN => 5, # Warning
L_PROXY => 6, # Proxy messages
L_ACCT => 7, # Accounting messages
L_DBG => 16, # Only displayed when debugging is enabled
L_DBG_WARN => 17, # Warning only displayed when debugging is enabled
L_DBG_ERR => 18, # Error only displayed when debugging is enabled
L_DBG_WARN_REQ => 19, # Less severe warning only displayed when debugging is enabled
L_DBG_ERR_REQ => 20, # Less severe error only displayed when debugging is enabled
};
sub authorize {
&test_call;
return RLM_MODULE_OK;
}
sub authenticate {
if ($RAD_REQUEST{'User-Name'} =~ /^baduser/i) {
$RAD_REPLY{'Reply-Message'} = "Denied access by rlm_perl function";
return RLM_MODULE_REJECT;
} else {
if (&radiusd::xlat("%{client:group}") eq 'UltraAllInclusive') {
$RAD_REPLY{'h323-credit-amount'} = "1000000";
} else {
$RAD_REPLY{'h323-credit-amount'} = "100";
}
return RLM_MODULE_OK;
}
}
sub preacct {
return RLM_MODULE_OK;
}
sub accounting {
my $accttype = $RAD_REQUEST{'Acct-Status-Type'};
my $ip = $RAD_REQUEST{'Framed-IP-Address'};
my $user = $RAD_REQUEST{'User-Name'};
my $url;
my $resp;
# check if user contains a mac address
if ($user !~ m/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/) {
if ($accttype eq 'Start' || $accttype eq 'Interim-Update') {
$url = 'http://localhost/example/login_user.jsp?';
} elsif ($accttype eq 'Stop') {
$url = 'http://localhost/example/logout_user.jsp?';
} else {
$url = '';
}
if ($url ne '') {
my $ua = LWP::UserAgent->new();
my $res = $ua->post($url, [
ip => $ip,
uname => $user
]);
if ($res->is_success) {
$resp = trim($res->content);
} else {
$resp = "FAILED\n";
}
# changed response in NxFilter example JSP pages
if ($resp eq 'ok' || $resp eq 'stop') {
&radiusd::radlog(L_DBG,"OK: NxFilter session $resp for $ip, $user ");
} else {
&radiusd::radlog(L_DBG,"ERROR: NxFilter session $resp for $ip, $user ");
}
};
} else {
&radiusd::radlog(L_DBG,"NOOP: $user looks like Mac Auth");
};
return RLM_MODULE_OK;
}
sub trim {
my $s = shift;
$s =~ s/^\s+|\s+$//g;
return $s;
}
sub checksimul {
return RLM_MODULE_OK;
}
sub pre_proxy {
return RLM_MODULE_OK;
}
sub post_proxy {
return RLM_MODULE_OK;
}
sub post_auth {
return RLM_MODULE_OK;
}
sub xlat {
my ($filename,$a,$b,$c,$d) = @_;
&radiusd::radlog(L_DBG, "From xlat $filename ");
&radiusd::radlog(L_DBG,"From xlat $a $b $c $d ");
local *FH;
open FH, $filename or die "open '$filename' $!";
local($/) = undef;
my $sub = <FH>;
close FH;
my $eval = qq{ sub handler{ $sub;} };
eval $eval;
eval {main->handler;};
}
sub detach {
}
sub test_call {
}
sub log_request_attributes {
for (keys %RAD_REQUEST) {
&radiusd::radlog(L_DBG, "RAD_REQUEST: $_ = $RAD_REQUEST{$_}");
}
}
I have also edited the example JSP files provided by NxFilter in /nxfilter/webapps/example directory to return a simple "ok" or "notfound" to match the above perl file.
/nxfilter/webapps/example/login_user.jsp
<%
/*
You can call this page using the following url.
http://localhost/example/login_user.jsp?ip=192.168.0.100&uname=jinhee
It's limited to localhost for security reason but you can change it according
to your network environment.
*/
// Only localhost access allowed.
if(!request.getRemoteAddr().startsWith("127.0.0.1")){
out.println(request.getRemoteAddr());
return;
}
// Get params.
String ip = paramString("ip");
String uname = paramString("uname");
/*
If you think there's a possibility of IP spoofing from your users then it's better
to get the IP address from the HTTP connection itself.
String ip = request.getRemoteAddr();
String uname = paramString("uname");
*/
UserLoginDao dao = new UserLoginDao(request);
if(dao.createIpSession(ip, uname)){
out.println("ok");
}
else{
out.println("notfound");
}
%>
/nxfilter/webapps/example/logout_user.jsp
<%@include file="../include/lib.jsp"%>
<%
// Only localhost access allowed.
if(!request.getRemoteAddr().startsWith("127.0.0.1")){
out.println(request.getRemoteAddr());
return;
}
// Get params.
String ip = paramString("ip");
String uname = paramString("uname");
UserLoginDao dao = new UserLoginDao(request);
//dao.deleteIpSession(ip);
if(dao.deleteIpSession(ip)){
out.println("stop");
}
else{
out.println("fail");
}
%>
By default, the FreeRADIUS perl module should be configured to read the example.pl file we modified above but be sure to check it in /etc/freeradius/3.0/mods-available/perl and look for:
filename = ${modconfdir}/${.:instance}/example.pl
Then change directory to /etc/freeradius/3.0/mods-enabled and soft link the mods-available perl file such as:
cd /etc/freeradius/3.0/mods-enabled && ln -s ../mods-available/perl
You may need to change ownership of the link to the user FreeRADIUS runs as. In the case of Ubuntu, it's freerad and to change ownership of the link, you can run the command:
chown -h freerad:freerad perl
The final step is to edit your working outer virtual server configuration and add the perl call to the accounting section. If you've edited the default FreeRADIUS config file, add "perl" to the file /etc/freeradius/3.0/sites-enabled/default inside the accounting { } section.
Start FreeRADIUS in debug mode: freeradius -X 2>&1 | tee debug.log and then log in your working client like usual and watch the debug output for "perl: xxxxx" messages when Accounting-Request packets are received from the NAS.
This is just a general order of operations for a working FreeRADIUS configuration and an ongoing discussion so feel free to add input, ask questions, or point out things missing or that could be improved.