Rework the RADIUS base script.

- This fixes BIT-1769 by logging all requests even in the absence of a
   reply.  The way that request and replying matching were being handled
   was restructured to mostly ignore the transaction ids because they
   aren't that helpful for network monitoring and it makes the script
   structure more complicated.
 - Add `framed_addr` field to the radius log to indicate if the radius
   server is hinting at an address for the client.
 - Add `ttl` field to indicate how quickly the radius server is replying
   to the network access server.
 - Fix a bunch of indentation inconsistencies.
This commit is contained in:
Seth Hall 2017-02-20 00:07:14 -05:00
parent f935675d82
commit 623ebea655
5 changed files with 113 additions and 70 deletions

View file

@ -10,52 +10,51 @@ export {
type Info: record {
## Timestamp for when the event happened.
ts : time &log;
ts : time &log;
## Unique ID for the connection.
uid : string &log;
uid : string &log;
## The connection's 4-tuple of endpoint addresses/ports.
id : conn_id &log;
id : conn_id &log;
## The username, if present.
username : string &log &optional;
username : string &log &optional;
## MAC address, if present.
mac : string &log &optional;
## Remote IP address, if present.
remote_ip : addr &log &optional;
mac : string &log &optional;
## The address given to the network access server, if
## present. This is only a hint from the RADIUS server
## and the network access server is not required to honor
## the address.
framed_addr : addr &log &optional;
## Remote IP address, if present. This is collected
## from the Tunnel-Client-Endpoint attribute.
remote_ip : addr &log &optional;
## Connect info, if present.
connect_info : string &log &optional;
connect_info : string &log &optional;
## Reply message from the server challenge. This is
## frequently shown to the user authenticating.
reply_msg : string &log &optional;
## Successful or failed authentication.
result : string &log &optional;
## Whether this has already been logged and can be ignored.
logged : bool &optional;
result : string &log &optional;
## The duration between the first request and
## either the "Access-Accept" message or an error.
## If the field is empty, it means that either
## the request or response was not seen.
ttl : interval &log &optional;
## Whether this has already been logged and can be ignored.
logged : bool &default=F;
};
## The amount of time we wait for an authentication response before
## expiring it.
const expiration_interval = 10secs &redef;
## Logs an authentication attempt if we didn't see a response in time.
##
## t: A table of Info records.
##
## idx: The index of the connection$radius table corresponding to the
## radius authentication about to expire.
##
## Returns: 0secs, which when this function is used as an
## :bro:attr:`&expire_func`, indicates to remove the element at
## *idx* immediately.
global expire: function(t: table[count] of Info, idx: count): interval;
## Event that can be handled to access the RADIUS record as it is sent on
## to the loggin framework.
## to the logging framework.
global log_radius: event(rec: Info);
}
redef record connection += {
radius: table[count] of Info &optional &write_expire=expiration_interval &expire_func=expire;
radius: Info &optional;
};
const ports = { 1812/udp };
redef likely_server_ports += { ports };
event bro_init() &priority=5
{
@ -63,64 +62,86 @@ event bro_init() &priority=5
Analyzer::register_for_ports(Analyzer::ANALYZER_RADIUS, ports);
}
event radius_message(c: connection, result: RADIUS::Message)
event radius_message(c: connection, result: RADIUS::Message) &priority=5
{
local info: Info;
if ( c?$radius && result$trans_id in c$radius )
info = c$radius[result$trans_id];
else
if ( ! c?$radius )
{
c$radius = table();
info$ts = network_time();
info$uid = c$uid;
info$id = c$id;
c$radius = Info($ts = network_time(),
$uid = c$uid,
$id = c$id);
}
switch ( RADIUS::msg_types[result$code] ) {
switch ( RADIUS::msg_types[result$code] )
{
case "Access-Request":
if ( result?$attributes ) {
if ( result?$attributes )
{
# User-Name
if ( ! info?$username && 1 in result$attributes )
info$username = result$attributes[1][0];
if ( ! c$radius?$username && 1 in result$attributes )
c$radius$username = result$attributes[1][0];
# Calling-Station-Id (we expect this to be a MAC)
if ( ! info?$mac && 31 in result$attributes )
info$mac = normalize_mac(result$attributes[31][0]);
if ( ! c$radius?$mac && 31 in result$attributes )
c$radius$mac = normalize_mac(result$attributes[31][0]);
# Tunnel-Client-EndPoint (useful for VPNs)
if ( ! info?$remote_ip && 66 in result$attributes )
info$remote_ip = to_addr(result$attributes[66][0]);
if ( ! c$radius?$remote_ip && 66 in result$attributes )
c$radius$remote_ip = to_addr(result$attributes[66][0]);
# Connect-Info
if ( ! info?$connect_info && 77 in result$attributes )
info$connect_info = result$attributes[77][0];
}
if ( ! c$radius?$connect_info && 77 in result$attributes )
c$radius$connect_info = result$attributes[77][0];
}
break;
case "Access-Challenge":
if ( result?$attributes )
{
# Framed-IP-Address
if ( ! c$radius?$framed_addr && 8 in result$attributes )
c$radius$framed_addr = raw_bytes_to_v4_addr(result$attributes[8][0]);
if ( ! c$radius?$reply_msg && 18 in result$attributes )
c$radius$reply_msg = result$attributes[18][0];
}
break;
case "Access-Accept":
info$result = "success";
c$radius$result = "success";
break;
case "Access-Reject":
info$result = "failed";
c$radius$result = "failed";
break;
}
if ( info?$result && ! info?$logged )
{
info$logged = T;
Log::write(RADIUS::LOG, info);
# TODO: Support RADIUS accounting. (add port 1813/udp above too)
#case "Accounting-Request":
# break;
#
#case "Accounting-Response":
# break;
}
c$radius[result$trans_id] = info;
}
event radius_message(c: connection, result: RADIUS::Message) &priority=-5
{
if ( c$radius?$result )
{
local ttl = network_time() - c$radius$ts;
if ( ttl != 0secs )
c$radius$ttl = ttl;
function expire(t: table[count] of Info, idx: count): interval
{
t[idx]$result = "unknown";
Log::write(RADIUS::LOG, t[idx]);
return 0secs;
}
Log::write(RADIUS::LOG, c$radius);
delete c$radius;
}
}
event connection_state_remove(c: connection) &priority=-5
{
if ( c?$radius && ! c$radius$logged )
{
c$radius$result = "unknown";
Log::write(RADIUS::LOG, c$radius);
}
}