Newer
Older
Tomáš Plesník
committed
# Warden.pm
# Copyright (C) 2011-2013 Cesnet z.s.p.o
Tomáš Plesník
committed
# Use of this source is governed by a BSD-style license, see LICENSE file.
use DBI;
use DBD::mysql;
use Sys::Syslog qw(:DEFAULT setlogsock);
Sys::Syslog::setlogsock('unix');
use Net::CIDR::Lite;
use DateTime;
use MIME::Base64;
use Crypt::X509;
Tomáš Plesník
committed
use SOAP::Lite;
Tomáš Plesník
committed
use Carp;
use File::Basename;
my $basedir = "/opt/warden-server/";
use lib $basedir . "lib";
Tomáš Plesník
committed
use WardenCommon;
################################################################################
# VARIABLES
################################################################################
my $etc = $basedir . "etc";
our $FILENAME = File::Basename::basename($0);
Tomáš Plesník
committed
Tomáš Plesník
committed
################################################################################
Tomáš Plesník
committed
# READING OF CONFIGURATION VARIABLES
################################################################################
Tomáš Plesník
committed
my $conf_file = "$etc/warden-server.conf";
Tomáš Plesník
committed
WardenCommon::loadConf($conf_file);
Tomáš Plesník
committed
Tomáš Plesník
committed
################################################################################
# DB CONNECT
################################################################################
Tomáš Plesník
committed
our $DBH = DBI->connect("DBI:mysql:database=$WardenCommon::DB_NAME;host=$WardenCommon::DB_HOST", $WardenCommon::DB_USER, $WardenCommon::DB_PASS, {RaiseError => 1, mysql_auto_reconnect => 1})
|| die "Could not connect to database: $DBI::errstr";
Tomáš Plesník
committed
################################################################################
# FUNCTIONS
################################################################################
#-------------------------------------------------------------------------------
# sendMsg - wrapper for more complex WardenCommon::sendMsg function
#-------------------------------------------------------------------------------
Tomáš Plesník
committed
sub sendMsg
my $severity = shift;
my $syslog_msg = shift;
my $soap_msg = shift;
Tomáš Plesník
committed
Tomáš Plesník
committed
WardenCommon::sendMsg($WardenCommon::SYSLOG, $WardenCommon::SYSLOG_VERBOSE, $WardenCommon::SYSLOG_FACILITY, $severity,
$syslog_msg, $soap_msg, $FILENAME);
}
#-------------------------------------------------------------------------------
Tomáš Plesník
committed
# getAltNames - parse Alternate names from SSL certifiate
#-------------------------------------------------------------------------------
Tomáš Plesník
committed
sub getAltNames
Tomáš Plesník
committed
my $cn = $ENV{'SSL_CLIENT_S_DN_CN'};
Tomáš Plesník
committed
push(@an_array, $DBH->quote($cn));
my @a = split("\n", $ENV{'SSL_CLIENT_CERT'});
pop @a;
shift @a;
my $der = decode_base64(join("", @a));
my $decoded= Crypt::X509->new(cert => $der);
foreach my $tmp (@{$decoded->SubjectAltName}) {
Tomáš Plesník
committed
if($tmp =~ s/dNSName=//){
push(@an_array, $DBH->quote($tmp));
}
}
my $alt_names = join(',', @an_array);
return $alt_names;
#-------------------------------------------------------------------------------
# authorizeClient - authorize client by CN,AN and source IP range
#-------------------------------------------------------------------------------
sub authorizeClient
{
Tomáš Plesník
committed
my ($alt_names, $ip, $service_type, $client_type, $function_name) = @_;
my $sth;
# obtain cidr based on rigth common name and alternate names, service and client_type
if($function_name eq 'saveNewEvent') {
Tomáš Plesník
committed
$sth = $DBH->prepare("SELECT client_id, ip_net_client, receive_own_events FROM clients WHERE hostname IN ($alt_names) AND service = ? AND client_type = ? ORDER BY SUBSTRING_INDEX(ip_net_client,'/', -1) DESC;");
Tomáš Plesník
committed
} elsif($function_name eq 'getNewEvents') {
Tomáš Plesník
committed
$sth = $DBH->prepare("SELECT client_id, ip_net_client, receive_own_events FROM clients WHERE hostname IN ($alt_names) AND (type = ? OR type = '_any_') AND client_type = ? ORDER BY SUBSTRING_INDEX(ip_net_client,'/', -1) DESC;");
Tomáš Plesník
committed
} elsif($function_name eq 'getClientInfo') {
Tomáš Plesník
committed
$sth = $DBH->prepare("SELECT client_id, ip_net_client, receive_own_events FROM clients WHERE hostname IN ($alt_names) ORDER BY SUBSTRING_INDEX(ip_net_client,'/', -1) DESC;");
Tomáš Plesník
committed
} elsif($function_name eq 'getLastId') {
Tomáš Plesník
committed
$sth = $DBH->prepare("SELECT client_id, ip_net_client, receive_own_events FROM clients WHERE hostname IN ($alt_names) AND client_type = 'r' ORDER BY SUBSTRING_INDEX(ip_net_client,'/', -1) DESC;");
Tomáš Plesník
committed
}
# check db handler
Tomáš Plesník
committed
if (!defined $sth) {
sendMsg("err",
"Cannot prepare authorization statement in $function_name: $DBH->errstr",
"Internal 'prepare' server error");
Tomáš Plesník
committed
}
Tomáš Plesník
committed
Tomáš Plesník
committed
# execute query for two or none params functions
if ($function_name eq 'saveNewEvent' || $function_name eq 'getNewEvents') {
$sth->execute($service_type, $client_type);
} else {
$sth->execute;
}
# obtain registration info about clients
Tomáš Plesník
committed
my ($client_id, $ip_net_client, $receive_own, $ip_net_client_list);
Tomáš Plesník
committed
my $correct_ip_source = 0;
my %ret;
Tomáš Plesník
committed
while(($client_id, $ip_net_client, $receive_own) = $sth->fetchrow()) {
my $ip_net_client_list = Net::CIDR::Lite->new->add($ip_net_client);
Tomáš Plesník
committed
$ret{'client_id'} = $client_id;
Tomáš Plesník
committed
$ret{'receive_own'} = $receive_own;
Tomáš Plesník
committed
if ($ip_net_client_list->bin_find($ip)) {
Tomáš Plesník
committed
$correct_ip_source = 1;
last;
Tomáš Plesník
committed
}
Tomáš Plesník
committed
# check if client is registered
if ($sth->rows == 0) {
Tomáš Plesník
committed
sendMsg("err",
"Unauthorized access to function '$function_name' from [IP: '$ip'; CN(AN): $alt_names; Client_type: '$client_type'; Service/Type: '$service_type'] - client is not registered at Warden server '$ENV{'SERVER_NAME'}'",
"Access denied - client is not registered at Warden server '$ENV{'SERVER_NAME'}'");
Tomáš Plesník
committed
return undef;
}
# check if client has IP from registered CIDR
if (!$correct_ip_source) {
Tomáš Plesník
committed
sendMsg ("err",
Tomáš Plesník
committed
"Unauthorized access to function '$function_name' from [IP: '$ip'; CN(AN): $alt_names; Client_type: '$client_type'; Service/Type: '$service_type'] - access to Warden server '$ENV{'SERVER_NAME'}' from another subnet than '$ip_net_client'",
"Access denied - access to Warden server '$ENV{'SERVER_NAME'}' from unauthorized subnet '$ip_net_client'");
Tomáš Plesník
committed
return undef;
}
return %ret;
} # END of authorizeClient
Tomáš Plesník
committed
################################################################################
# SOAP Functions
################################################################################
#-----------------------------------------------------------------------------
# saveNewEvent - save new received event into database
#-----------------------------------------------------------------------------
sub saveNewEvent
{
my ($class, $data) = @_;
Tomáš Plesník
committed
my $sth;
Tomáš Plesník
committed
# client network information
my $cn = $ENV{'SSL_CLIENT_S_DN_CN'};
my $alt_names = getAltNames(undef);
my $ip = $ENV{'REMOTE_ADDR'};
my $function_name = 'saveNewEvent';
my $client_type = 's'; # incoming client MUST be sender
my $valid = 't'; # registered sender has valid events
my $received = DateTime->now; # time of event delivery (UTC)
Tomáš Plesník
committed
# parse object (event) parameters
my $service = $data->{'SERVICE'};
my $detected = $data->{'DETECTED'};
Tomáš Plesník
committed
my $type = $data->{'TYPE'};
my $source_type = $data->{'SOURCE_TYPE'};
my $source = $data->{'SOURCE'};
my $target_proto = $data->{'TARGET_PROTO'};
my $target_port = $data->{'TARGET_PORT'};
my $attack_scale = $data->{'ATTACK_SCALE'};
Tomáš Plesník
committed
my $note = $data->{'NOTE'};
my $priority = $data->{'PRIORITY'};
my $timeout = $data->{'TIMEOUT'};
my %client = authorizeClient($alt_names, $ip, $service, $client_type, $function_name);
if (defined %client) {
sendMsg("debug",
"Incoming event: [service: '$service', detected: '$detected', type: '$type', source_type: '$source_type', source: '$source', target_proto: '$target_proto', target_port: '$target_port', attack_scale: '$attack_scale', note: '$note', priority: '$priority', timeout: '$timeout']",
undef);
Tomáš Plesník
committed
if (%WardenCommon::VALID_STRINGS) { # check if hash is not empty - use VALIDATION HASH
if (!(exists $WardenCommon::VALID_STRINGS{'type'} && grep $type eq $_, @{$WardenCommon::VALID_STRINGS{'type'}})) {
sendMsg("err",
"Unknown event type from [IP: '$ip'; CN(AN): $alt_names; Service: '$service'; Type: '$type']",
"Unknown event type: '$type'");
Tomáš Plesník
committed
} elsif (!(exists $WardenCommon::VALID_STRINGS{'source_type'} && grep $source_type eq $_, @{$WardenCommon::VALID_STRINGS{'source_type'}})) {
sendMsg("err",
"Unknown source type from [IP '$ip'; CN(AN): $alt_names; Service: '$service'; Source_type: '$source_type']",
"Unknown source type: '$source_type'");
}
}
Tomáš Plesník
committed
# http://my.safaribooksonline.com/book/programming/regular-expressions/9780596802837/4dot-validation-and-formatting/id2983571
if ($detected !~ /^((?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[0-1]|0[1-9]|[1-2][0-9])T(2[0-3]|[0-1][0-9]):([0-5][0-9]):([0-5][0-9])(\.[0-9]+)?(Z|[+-](?:2[0-3]|[0-1][0-9]):[0-5][0-9])?/) {
Tomáš Plesník
committed
sendMsg("err",
"Unknown detected time format from [IP: '$ip'; CN(AN): $alt_names; Service: '$service'; Detected: '$detected']",
"Unknown detected time format: '$detected'");
}
Tomáš Plesník
committed
my @change_list;
if (defined $target_port && $target_port !~ /^\d+\z/) {
push(@change_list, "target_port: '$target_port'");
$target_port = undef;
}
Tomáš Plesník
committed
if (defined $attack_scale && $attack_scale !~ /^\d+\z/) {
push(@change_list, "attack_scale: '$attack_scale'");
$attack_scale = undef;
}
Tomáš Plesník
committed
if (defined $priority && $priority !~ /^\d+\z/) {
push(@change_list, "priority: '$priority'");
$priority = undef;
}
Tomáš Plesník
committed
if (defined $timeout && $timeout !~ /^\d+\z/) {
push(@change_list, "timeout: '$timeout'");
$timeout = undef;
}
Tomáš Plesník
committed
my $change_string = join(", ", @change_list);
if ($change_string ne "") {
sendMsg("info",
"Unknown event items detected {originaly - $change_string} received in $received from [IP '$ip'; CN(AN): $alt_names; Service: '$service'; Type: '$type'; Detected: $detected]",
undef);
}
Tomáš Plesník
committed
$sth=$DBH->prepare("INSERT INTO events VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?);");
if (!defined $sth) {
Tomáš Plesník
committed
sendMsg("err",
"Cannot prepare statement in function '$function_name': $DBH->errstr",
"Internal 'prepare' server error");
}
Tomáš Plesník
committed
$sth->execute(undef, $detected, $received, $type, $source_type, $source, $target_proto, $target_port, $attack_scale, $note, $priority, $timeout, $valid, $client{'client_id'});
return 1;
Tomáš Plesník
committed
}
} # END of saveNewEvent
#-----------------------------------------------------------------------------
# getNewEvents - get new events from the DB greater than received ID
#-----------------------------------------------------------------------------
sub getNewEvents
{
my ($class, $data) = @_;
Tomáš Plesník
committed
my ($sth, @events, $event, @ids);
my ($id, $hostname, $service, $detected, $type, $source_type, $source, $target_proto, $target_port, $attack_scale, $note, $priority, $timeout, $client_id);
Tomáš Plesník
committed
# client network information
Tomáš Plesník
committed
my $cn = $ENV{'SSL_CLIENT_S_DN_CN'};
my $alt_names = getAltNames(undef);
my $ip = $ENV{'REMOTE_ADDR'};
my $client_type = 'r'; # incoming client MUST be sender
my $function_name = 'getNewEvents';
my $requested_type = $data->{'REQUESTED_TYPE'} || '_any_';
my $last_id = $data->{'LAST_ID'};
Tomáš Plesník
committed
my $max_rcv_events_limit = $data->{'MAX_RCV_EVENTS_LIMIT'}; # client events limit
# comparison of client and server limit - which can be used
Tomáš Plesník
committed
my $used_limit;
Tomáš Plesník
committed
if (defined $max_rcv_events_limit && $max_rcv_events_limit < $WardenCommon::MAX_EVENTS_LIMIT) {
Tomáš Plesník
committed
$used_limit = $max_rcv_events_limit;
} else {
Tomáš Plesník
committed
$used_limit = $WardenCommon::MAX_EVENTS_LIMIT;
Tomáš Plesník
committed
}
my %client = authorizeClient($alt_names, $ip, $requested_type, $client_type, $function_name);
Tomáš Plesník
committed
if ($client{'receive_own'} eq 't') {
if ($requested_type eq '_any_') {
$sth = $DBH->prepare("SELECT * FROM events WHERE type != 'test' AND id > ? AND valid = 't' ORDER BY id ASC LIMIT ?;");
Tomáš Plesník
committed
if (!defined $sth) {
sendMsg("err",
"Cannot prepare ROE-ANY statement in function '$function_name': $DBH->errstr",
"Internal 'prepare' server error");
}
Tomáš Plesník
committed
$sth->execute($last_id, $used_limit);
Tomáš Plesník
committed
} else {
$sth = $DBH->prepare("SELECT * FROM events WHERE type != 'test' AND id > ? AND type = ? AND valid = 't' ORDER BY id ASC LIMIT ?;");
Tomáš Plesník
committed
if (!defined $sth) {
sendMsg("err",
"Cannot prepare ROE statement in function '$function_name': $DBH->errstr",
"Internal 'prepare' server error");
}
Tomáš Plesník
committed
$sth->execute($last_id, $requested_type, $used_limit);
Tomáš Plesník
committed
}
Tomáš Plesník
committed
if ($requested_type eq '_any_') {
$sth = $DBH->prepare("SELECT * FROM events WHERE type != 'test' AND id > ? AND valid = 't' AND hostname NOT LIKE ? ORDER BY id ASC LIMIT ?;");
Tomáš Plesník
committed
if (!defined $sth) {
sendMsg("err",
"Cannot prepare ANY statement in function '$function_name': $DBH->errstr",
"Internal 'prepare' server error");
}
Tomáš Plesník
committed
my ($domain) = $cn =~ /([^\.]+\.[^\.]+)$/;
$domain = '\%' . $domain;
Tomáš Plesník
committed
$sth->execute($last_id, $domain, $used_limit);
Tomáš Plesník
committed
} else {
$sth = $DBH->prepare("SELECT * FROM events WHERE type != 'test' AND id > ? AND type = ? AND valid = 't' AND hostname NOT LIKE ? ORDER BY id ASC LIMIT ?;");
Tomáš Plesník
committed
if (!defined $sth) {
sendMsg("err",
"Cannot prepare statement in function '$function_name': $DBH->errstr\n",
"Internal 'prepare' server error");
}
Tomáš Plesník
committed
my ($domain) = $cn =~ /([^\.]+\.[^\.]+)$/;
$domain = '\%' . $domain;
Tomáš Plesník
committed
$sth->execute($last_id, $requested_type, $domain, $used_limit);
Tomáš Plesník
committed
}
Tomáš Plesník
committed
# obtain items of events stored in events table
while (my @result = $sth->fetchrow()) {
$id = $result[0];
Tomáš Plesník
committed
$detected = $result[1];
$type = $result[3];
$source_type = $result[4];
$source = $result[5];
$target_proto = $result[6];
$target_port = $result[7];
$attack_scale = $result[8];
$note = $result[9];
$priority = $result[10];
$timeout = $result[11];
$client_id = $result[13];
# obtain hostname and service of events based on client_id from clients table
$sth = $DBH->prepare("SELECT hostname, service FROM clients WHERE client_id = ?;");
$sth->execute($client_id);
($hostname, $service) = $sth->fetchrow();
# create SOAP data object
$event = SOAP::Data->name(event => \SOAP::Data->value(
Tomáš Plesník
committed
SOAP::Data->name(ID => $id),
SOAP::Data->name(HOSTNAME => $hostname),
SOAP::Data->name(SERVICE => $service),
SOAP::Data->name(DETECTED => $detected),
SOAP::Data->name(TYPE => $type),
Tomáš Plesník
committed
SOAP::Data->name(SOURCE_TYPE => $source_type),
SOAP::Data->name(SOURCE => $source),
SOAP::Data->name(TARGET_PROTO => $target_proto),
SOAP::Data->name(TARGET_PORT => $target_port),
SOAP::Data->name(ATTACK_SCALE => $attack_scale),
SOAP::Data->name(NOTE => $note),
SOAP::Data->name(PRIORITY => $priority),
SOAP::Data->name(TIMEOUT => $timeout)
));
push(@events, $event);
push(@ids, $id);
}
# log sent ID of events
Tomáš Plesník
committed
if (scalar @events != 0) {
if (scalar @ids == 1) {
Tomáš Plesník
committed
sendMsg("info",
"Sent 1 event [#$ids[0]] to [IP: '$ip'; CN(AN): $alt_names; Client_limit: '$max_rcv_events_limit', Requested_type: '$requested_type']",
Tomáš Plesník
committed
undef);
Tomáš Plesník
committed
} else {
Tomáš Plesník
committed
sendMsg("info",
"Sent " . scalar @ids . " events [#$ids[0] - #$ids[-1]] to [IP: '$ip'; CN(AN): $alt_names, Client_limit: '$max_rcv_events_limit', Requested_type: '$requested_type']",
Tomáš Plesník
committed
undef);
Tomáš Plesník
committed
}
}
return @events;
}
} # END of getNewEvents
Tomáš Plesník
committed
#-----------------------------------------------------------------------------
# getLastId - get lastest saved event ID
#-----------------------------------------------------------------------------
sub getLastId
{
my ($class, $arg) = @_;
Tomáš Plesník
committed
# client network information
my $cn = $ENV{'SSL_CLIENT_S_DN_CN'};
my $alt_names = getAltNames(undef);
my $ip = $ENV{'REMOTE_ADDR'};
my $service = undef;
my $client_type = undef;
my $function_name = 'getLastId';
Tomáš Plesník
committed
my %client = authorizeClient($alt_names, $ip, $service, $client_type, $function_name);
if (defined %client) {
my $sth = $DBH->prepare("SELECT max(id) FROM events;");
if (!defined $sth) {
sendMsg("err",
"Cannot prepare statement in function '$function_name': $DBH->errstr",
"Internal 'prepare' server error");
}
$sth->execute;
my $result = $sth->fetchrow();
return $result;
Tomáš Plesník
committed
}
Tomáš Plesník
committed
#-------------------------------------------------------------------------------
# getClientInfo - get list of registered clients on Warden server
# by Warden client
Tomáš Plesník
committed
#-------------------------------------------------------------------------------
sub getClientInfo
{
my ($class, $data) = @_;
my (@clients, $client);
my ($client_id, $hostname, $registered, $requestor, $service, $client_type, $type, $receive_own_events, $description_tags, $ip_net_client);
# client network information
my $cn = $ENV{'SSL_CLIENT_S_DN_CN'};
my $alt_names = getAltNames(undef);
my $ip = $ENV{'REMOTE_ADDR'};
my $service = undef;
my $client_type = undef;
my $function_name = 'getClientInfo';
my %client = authorizeClient($alt_names, $ip, $service, $client_type, $function_name);
if (defined %client) {
Tomáš Plesník
committed
my $sth = $DBH->prepare("SELECT * FROM clients WHERE valid = 't' ORDER BY client_id ASC;");
Tomáš Plesník
committed
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
if (!defined $sth) {
sendMsg("err",
"Cannot prepare statement in function '$function_name': $DBH->errstr",
"Internal 'prepare' server error");
}
$sth->execute;
while ( my @result = $sth->fetchrow() ) {
$client_id = $result[0];
$hostname = $result[1];
$registered = $result[2];
$requestor = $result[3];
$service = $result[4];
$client_type = $result[5];
$type = $result[6];
$receive_own_events = $result[7];
$description_tags = $result[8];
$ip_net_client = $result[9];
$client = SOAP::Data->name(client => \SOAP::Data->value(
SOAP::Data->name(CLIENT_ID => $client_id),
SOAP::Data->name(HOSTNAME => $hostname),
SOAP::Data->name(REGISTERED => $registered),
SOAP::Data->name(REQUESTOR => $requestor),
SOAP::Data->name(SERVICE => $service),
SOAP::Data->name(CLIENT_TYPE => $client_type),
SOAP::Data->name(TYPE => $type),
SOAP::Data->name(RECEIVE_OWN_EVENTS => $receive_own_events),
SOAP::Data->name(DESCRIPTION_TAGS => $description_tags),
SOAP::Data->name(IP_NET_CLIENT => $ip_net_client),
));
push(@clients, $client);
}
my $sum = scalar @clients;
sendMsg("info",
"Sent information about $sum registered clients from Warden server '$ENV{'SERVER_NAME'}'",
Tomáš Plesník
committed
undef);
return @clients;
}
} # END of getClientInfo