Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • 713/warden/warden
1 result
Show changes
Showing
with 1841 additions and 1792 deletions
#
# warden-apache.conf - configuration file for the Apache server
#
SSLEngine on
SSLVerifyDepth 3
SSLVerifyClient require
SSLOptions +StdEnvVars +ExportCertData
SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL
SSLCertificateFile /etc/ssl/certs/warden-dev.cesnet.cz.pem
SSLCertificateKeyFile /opt/warden-client/etc/warden-dev.cesnet.cz.key
SSLCACertificateFile /etc/ssl/certs/tcs-ca-bundle.pem
PerlOptions +Parent
PerlSwitches -I/opt/warden-server/lib
<Location /Warden>
SetHandler perl-script
PerlHandler Warden::ApacheDispatch
SSLOptions +StdEnvVars
</Location>
#
# warden-client.conf - configuration file for registration and status clients
#
#-------------------------------------------------------------------------------
# URI - URI address of Warden server
#-------------------------------------------------------------------------------
$URI = "https://warden-dev.cesnet.cz:443/Warden";
#-------------------------------------------------------------------------------
# SSL_KEY_FILE - path to server SSL certificate key file
#-------------------------------------------------------------------------------
$SSL_KEY_FILE = "/etc/ssl/private/warden-dev.cesnet.cz.key";
#-------------------------------------------------------------------------------
# SSL_CERT_FILE - path toserver SSL certificate file
#-------------------------------------------------------------------------------
$SSL_CERT_FILE = "/etc/ssl/certs/warden-dev.cesnet.cz.pem";
#-------------------------------------------------------------------------------
# SSL_CA_FILE - path to CA certificate file
#-------------------------------------------------------------------------------
$SSL_CA_FILE = "/etc/ssl/certs/tcs-ca-bundle.pem";
#
# warden-server.conf - configuration file for Warden server
#
#-------------------------------------------------------------------------------
# BASEDIR - base directory of Warden server
#-------------------------------------------------------------------------------
$BASEDIR = "/opt/warden-server";
#-------------------------------------------------------------------------------
# FACILITY - syslog facility
#-------------------------------------------------------------------------------
$FACILITY = "local7";
#-------------------------------------------------------------------------------
# DB_NAME - database name of Warden server
#-------------------------------------------------------------------------------
$DB_NAME = "warden";
#-------------------------------------------------------------------------------
# DB_USER - user of Warden server database
#-------------------------------------------------------------------------------
$DB_USER = "root";
#-------------------------------------------------------------------------------
# DB_PASS - password of Warden server database
#-------------------------------------------------------------------------------
$DB_PASS = "";
#-------------------------------------------------------------------------------
# DB_HOST - what IP address to listen on of Warden server
#-------------------------------------------------------------------------------
$DB_HOST = "localhost";
#-------------------------------------------------------------------------------
# MAX_EVENTS_LIMIT - maximum number of events that can be downloaded from
# Warden server in a single getNewEvents client function call
#-------------------------------------------------------------------------------
$MAX_EVENTS_LIMIT = "1000000";
#-------------------------------------------------------------------------------
# VALID_STRINGS - validation hash containing allowed event attributes
#-------------------------------------------------------------------------------
%VALID_STRINGS = (
"type" => ["portscan", "bruteforce", "probe", "spam", "phishing", "botnet_c_c", "dos", "malware", "copyright", "webattack", "test", "other"],
);
#!/usr/bin/perl -w
#
# Warden.pm
#
# Copyright (C) 2011-2012 Cesnet z.s.p.o
#
# Use of this source is governed by a BSD-style license, see LICENSE file.
package Warden;
use strict;
use DBI;
use DBD::mysql;
use Format::Human::Bytes;
use Sys::Syslog qw(:DEFAULT setlogsock);
Sys::Syslog::setlogsock('unix');
use File::Basename;
use Net::CIDR::Lite;
use DateTime;
use MIME::Base64;
use Crypt::X509;
our $VERSION = "2.1";
################################################################################
# READING OF CONFIGURATION VARIABLES
################################################################################
my $conf_file = "/opt/warden-server/etc/warden-server.conf"; # path is updated by install.sh
our $FACILITY = undef;
our $DB_NAME = undef;
our $DB_USER = undef;
our $DB_PASS = undef;
our $DB_HOST = undef;
our $MAX_EVENTS_LIMIT = undef;
our %VALID_STRINGS = undef;
# load set variables by user
unless (do $conf_file) {
errMsg("Errors in config file '$conf_file': $@") if $@;
errMsg("Can't read config file '$conf_file': $!") unless defined $_;
# if $_ defined, it's retvalue of last statement of conf, for which we don't care
}
################################################################################
# VARIABLES
################################################################################
our $DBH = DBI->connect("DBI:mysql:database=$DB_NAME;host=$DB_HOST", $DB_USER, $DB_PASS, {RaiseError => 1, mysql_auto_reconnect => 1}) || die "Could not connect to database: $DBI::errstr";
################################################################################
# LOCAL FUNCTIONS
################################################################################
#-------------------------------------------------------------------------------
# errMsg - print error message and die
#-------------------------------------------------------------------------------
sub errMsg
{
my $msg = shift;
$msg = trim($msg);
print $msg . "\n";
exit 1;
} # End of errMsg
#-------------------------------------------------------------------------------
# trim - remove whitespace from the start and end of the string
#-------------------------------------------------------------------------------
sub trim
{
my $string = shift;
$string =~ s/^\s+//;
$string =~ s/\s+$//;
return $string;
} # End of trim
#-------------------------------------------------------------------------------
# write2log - writing message to syslog
#-------------------------------------------------------------------------------
sub write2log
{
my $priority = shift;
my $msg = shift;
my $filename = File::Basename::basename($0);
Sys::Syslog::openlog($filename, "cons,pid", $FACILITY);
Sys::Syslog::syslog("$priority", "$msg");
Sys::Syslog::closelog();
} # End of write2log
#-------------------------------------------------------------------------------
# getAltNames - parse Alternate names from SSL certifiate
#-------------------------------------------------------------------------------
sub getAltNames
{
my @an_array;
my $cn = $ENV{'SSL_CLIENT_S_DN_CN'};
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}) {
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
{
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') {
$sth = $DBH->prepare("SELECT hostname, 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;");
} elsif($function_name eq 'getNewEvents') {
$sth = $DBH->prepare("SELECT hostname, ip_net_client, receive_own_events
FROM clients WHERE hostname IN ($alt_names) AND type = ? AND client_type = ?
ORDER BY SUBSTRING_INDEX(ip_net_client,'/', -1) DESC;");
}
if (!defined $sth) { die("Cannot prepare authorization statement in $function_name: $DBI::errstr\n")}
$sth->execute($service_type, $client_type);
my ($an, $cidr, $receive_own, $cidr_list);
my $correct_ip_source = 0;
my %ret;
while(($an, $cidr, $receive_own) = $sth->fetchrow()) {
my $cidr_list = Net::CIDR::Lite-> new -> add($cidr);
$ret{'dns'} = $an;
$ret{'cidr'} = $cidr;
$ret{'receive_own'} = $receive_own;
if ($cidr_list->bin_find($ip)) {
$correct_ip_source = 1;
last;
}
}
# check if client is registered
if ($sth->rows == 0) {
write2log ("err", "Unauthorized access to '$function_name' from: '$ip'; CN(AN): $alt_names; used service: '$service_type' - client is not registered");
die("Access denied - client is not registered at warden server!");
return undef;
}
# check if client has IP from registered CIDR
if (!$correct_ip_source) {
write2log ("err", "Unauthorized access to '$function_name' from: '$ip'; CN(AN): $alt_names; used service: '$service_type' - access from bad subnet: Registered subnet '" . $ret{'cidr'} . "'");
die("Access denied - access from unauthorized subnet!");
return undef;
}
return %ret;
} # END of authorizeClient
################################################################################
# SOAP Functions
################################################################################
#-----------------------------------------------------------------------------
# saveNewEvent - save new received event into database
#-----------------------------------------------------------------------------
sub saveNewEvent
{
my ($class, $data) = @_;
my ($sth, $cidr_list);
# client network information
my $cn = $ENV{'SSL_CLIENT_S_DN_CN'};
my $alt_names = getAltNames(undef);
my $ip = $ENV{'REMOTE_ADDR'};
# variables defined by server
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)
# parse object (event) parameters
my $service = $data->{'SERVICE'};
my $detected = $data->{'DETECTED'};
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'};
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) {
# if validator is configured, check validity of event attributes - TYPE
if (!exists $VALID_STRINGS{'type'} or grep $type eq $_, @{$VALID_STRINGS{'type'}}) {
$sth=$DBH->prepare("INSERT INTO events VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);");
if (!defined $sth) { die("Cannot do insert statement in $function_name: $DBI::errstr\n") }
$sth->execute(undef, $client{'dns'}, $service, $detected, $received, $type, $source_type, $source, $target_proto, $target_port, $attack_scale, $note, $priority, $timeout, $valid);
return 1;
} else {
write2log ("err", "Unknown event type - client from: '$ip'; CN(AN): $alt_names; used type: '$type'");
die("Unknown event type - client sent event with unknown type '$type'!");
}
}
} # END of saveNewEvent
#-----------------------------------------------------------------------------
# getNewEvents - get new events from the DB greater than received ID
#-----------------------------------------------------------------------------
sub getNewEvents
{
my ($class, $data) = @_;
my ($sth, @events, $event, @ids, $cidr_list);
my ($id, $hostname, $service, $detected, $type, $source_type, $source, $target_proto, $target_port, $attack_scale, $note, $priority, $timeout);
# client network information
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';
# parse SOAP data object
my $requested_type = $data->{'REQUESTED_TYPE'} || '_any_';
my $last_id = $data->{'LAST_ID'};
my $max_rcv_events_limit = $data->{'MAX_RCV_EVENTS_LIMIT'};
my %client = authorizeClient($alt_names, $ip, $requested_type, $client_type, $function_name);
if(defined %client) {
if ($client{'receive_own'} eq 't') { # check if client want your own events or not
if ($requested_type eq '_any_') { # check if client want each or only one type of messages
$sth = $DBH->prepare("SELECT * FROM events WHERE type != 'test' AND id > ? AND valid = 't' ORDER BY id ASC LIMIT ?;");
if (!defined $sth) {die("Cannot prepare ROE-ANY statement in $function_name: $DBI::errstr\n")}
(defined $max_rcv_events_limit && $max_rcv_events_limit < $MAX_EVENTS_LIMIT) ? $sth->execute($last_id, $max_rcv_events_limit) : $sth->execute($last_id, $MAX_EVENTS_LIMIT);
} else {
$sth = $DBH->prepare("SELECT * FROM events WHERE type != 'test' AND id > ? AND type = ? AND valid = 't' ORDER BY id ASC LIMIT ?;");
if (!defined $sth) {die("Cannot prepare ROE statement in $function_name: $DBI::errstr\n")}
(defined $max_rcv_events_limit && $max_rcv_events_limit < $MAX_EVENTS_LIMIT) ? $sth->execute($last_id, $requested_type, $max_rcv_events_limit) : $sth->execute($last_id, $requested_type, $MAX_EVENTS_LIMIT);
}
} else {
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 ?;");
if (!defined $sth) {die("Cannot prepare ANY statement in $function_name: $DBI::errstr\n")}
my ($domain) = $cn =~ /([^\.]+\.[^\.]+)$/;
$domain = '\%' . $domain;
(defined $max_rcv_events_limit && $max_rcv_events_limit < $MAX_EVENTS_LIMIT) ? $sth->execute($last_id, $domain, $max_rcv_events_limit) : $sth->execute($last_id, $domain, $MAX_EVENTS_LIMIT);
} 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 ?;");
if (!defined $sth) {die("Cannot prepare statement in $function_name: $DBI::errstr\n")}
my ($domain) = $cn =~ /([^\.]+\.[^\.]+)$/;
$domain = '\%' . $domain;
(defined $max_rcv_events_limit && $max_rcv_events_limit < $MAX_EVENTS_LIMIT) ? $sth->execute($last_id, $requested_type, $domain, $max_rcv_events_limit) : $sth->execute($last_id, $requested_type, $domain, $MAX_EVENTS_LIMIT);
}
}
# parse items of events stored in DB
while (my @result = $sth->fetchrow()) {
$id = $result[0];
$hostname = $result[1];
$service = $result[2];
$detected = $result[3];
$type = $result[5];
$source_type = $result[6];
$source = $result[7];
$target_proto = $result[8];
$target_port = $result[9];
$attack_scale = $result[10];
$note = $result[11];
$priority = $result[12];
$timeout = $result[13];
# create SOAP data object
$event = SOAP::Data->name(event => \SOAP::Data->value(
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),
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
if (scalar @events != 0) {
if (scalar @ids == 1) {
write2log("info", "Sent 1 event [#$ids[0]] to '$ip' (CN(AN): $alt_names) with client limit '$max_rcv_events_limit' events");
} else {
write2log("info", "Sent " . scalar @ids . " events [#$ids[0] - #$ids[-1]] to '$ip' (CN(AN): $alt_names) with client limit '$max_rcv_events_limit' events");
}
}
return @events;
}
} # END of getNewEvents
#-----------------------------------------------------------------------------
# getLastId - get lastest saved event ID
#-----------------------------------------------------------------------------
sub getLastId
{
my ($class, $arg) = @_;
my $function_name = 'getLastId';
my $sth = $DBH->prepare("SELECT max(id) FROM events;");
if ( !defined $sth ) { die("Cannot prepare statement in '$function_name': $DBI::errstr\n") }
$sth->execute;
my $result = $sth->fetchrow();
return $result;
} # END of getLastID
#-----------------------------------------------------------------------------
# registerSender - register new sender
#-----------------------------------------------------------------------------
sub registerSender
{
my ($class, $data) = @_;
my $sth;
# client network information
my $cn = $ENV{'SSL_CLIENT_S_DN_CN'};
my $ip = $ENV{'REMOTE_ADDR'};
my $local_ip = $ENV{'SERVER_ADDR'};
my $function_name = 'registerSender';
if ($local_ip ne $ip) {
write2log ("err", "Unauthorized access to '$function_name' from: '$ip' ('$cn') - access allowed only from localhost");
die("Access denied - access allowed only from localhost!");
} else {
# defined variables by method
my $client_type = "s";
my $registered = DateTime->now;
my $type = undef;
my $receive_own_events = undef;
# parse SOAP data oject
my $hostname = $data->{'HOSTNAME'};
my $requestor = $data->{'REQUESTOR'};
my $service = $data->{'SERVICE'};
my $description_tags = $data->{'DESCRIPTION_TAGS'};
my $ip_net_client = $data->{'IP_NET_CLIENT'};
# check if sender has been already registered
$sth = $DBH->prepare("SELECT registered FROM clients WHERE hostname = ? AND requestor = ? AND service = ? AND client_type = ? AND type = ? AND receive_own_events = ? AND description_tags = ? AND ip_net_client = ? LIMIT 1;");
if (!defined $sth) {die("Cannot prepare check statement in '$function_name': $DBI::errstr\n")}
$sth->execute($hostname, $requestor, $service, $client_type, $type, $receive_own_events, $description_tags, $ip_net_client);
my $result = $sth->fetchrow();
# register new sender
if (defined $result) {
write2log ("err", "Attempt to re-register the sender");
die("Error - sender has already been registered at '$result'");
} else {
$sth = $DBH->prepare("INSERT INTO clients VALUES (?,?,?,?,?,?,?,?,?,?);");
if (!defined $sth) {die("Cannot do statement in '$function_name': $DBI::errstr\n")}
$sth->execute(undef, $hostname, $registered, $requestor, $service, $client_type, $type, $receive_own_events, $description_tags, $ip_net_client);
write2log("info", "New sender '$hostname' (service: '$service', cidr: '$ip_net_client') was registered");
return 1;
}
}
} # END of registerSender
##-----------------------------------------------------------------------------
## registerReceiver - register new receiver
##-----------------------------------------------------------------------------
sub registerReceiver
{
my ($class, $data) = @_;
my $sth;
# client network information
my $cn = $ENV{'SSL_CLIENT_S_DN_CN'};
my $ip = $ENV{'REMOTE_ADDR'};
my $local_ip = $ENV{'SERVER_ADDR'};
my $function_name = 'registerReceiver';
if ($local_ip ne $ip) {
write2log ("err", "Unauthorized access to '$function_name' from: '$ip' ('$cn') - access allowed only from localhost");
die("Access denied - access allowed only from localhost!");
} else {
# variables defined by method
my $client_type = "r";
my $registered = DateTime->now;
my $service = undef;
my $description_tags = undef;
# parse SOAP data oject
my $hostname = $data->{'HOSTNAME'};
my $requestor = $data->{'REQUESTOR'};
my $type = $data->{'TYPE'};
my $receive_own_events = $data->{'RECEIVE_OWN_EVENTS'};
my $ip_net_client = $data->{'IP_NET_CLIENT'};
# check if receiver has been already registered
$sth = $DBH->prepare("SELECT registered FROM clients WHERE hostname = ? AND requestor = ? AND service = ? AND client_type = ? AND type = ? AND receive_own_events = ? AND description_tags = ? AND ip_net_client = ? LIMIT 1;");
if (!defined $sth) {die("Cannot prepare check statement in '$function_name': $DBI::errstr\n")}
$sth->execute($hostname, $requestor, $service, $client_type, $type, $receive_own_events, $description_tags, $ip_net_client);
my $result = $sth->fetchrow();
# register new receiver
if (defined $result) {
write2log ("err", "Attempt to re-register the receiver");
die("Error - receiver has already been registered at '$result'");
} else {
$sth = $DBH->prepare("INSERT INTO clients VALUES (?,?,?,?,?,?,?,?,?,?);");
if (!defined($sth)) {die("Cannot do statement in '$function_name': $DBI::errstr\n")}
$sth->execute(undef, $hostname, $registered, $requestor, $service, $client_type, $type, $receive_own_events, $description_tags, $ip_net_client);
write2log("info", "New receiver '$hostname' (type: '$type', cidr: '$ip_net_client', receive_own_events: '$receive_own_events') was registered");
return 1;
}
}
} # END of registerReceiver
#-----------------------------------------------------------------------------
# unregisterClient - unregister client
#-----------------------------------------------------------------------------
sub unregisterClient
{
my ($class, $data) = @_;
my $sth;
# client network information
my $cn = $ENV{'SSL_CLIENT_S_DN_CN'};
my $ip = $ENV{'REMOTE_ADDR'};
my $local_ip = $ENV{'SERVER_ADDR'};
my $function_name = 'unregisterClient';
if ($local_ip ne $ip) {
write2log ("err", "Unauthorized access to '$function_name' from: '$ip' ('$cn') - access allowed only from localhost");
die("Access denied - access allowed only from localhost!");
} else {
# parse SOAP data oject
my $client_id = $data->{'CLIENT_ID'};
# check if receiver has been already registered
$sth = $DBH->prepare("SELECT client_id, hostname, service, client_type FROM clients WHERE client_id = ? LIMIT 1;");
if (!defined $sth) {die("Cannot prepare check statement in '$function_name': $DBI::errstr\n")}
$sth->execute($client_id);
my ($id, $hostname, $service, $client_type) = $sth->fetchrow();
# delete registered client
if (!defined $id) {
write2log ("err", "Attempt to delete unregister client");
die("Error - client (#$client_id) is not registered");
} else {
if ($client_type eq 's') {
$sth = $DBH->prepare("DELETE FROM clients WHERE client_id = ?;");
if (!defined $sth) {die("Cannot do delete statement of sender in '$function_name': $DBI::errstr\n")}
$sth->execute($client_id);
$sth = $DBH->prepare("UPDATE events SET valid = 'f' where hostname = ? AND service = ?;");
if (!defined $sth) {die("Cannot do unvalidation statement in '$function_name': $DBI::errstr\n")}
$sth->execute($hostname, $service);
write2log("info", "Sender '$hostname' (client_id: '$client_id', service: '$service') was deleted and its data were invalidated" );
return 1;
} else {
$sth = $DBH->prepare("DELETE FROM clients WHERE client_id = ?;");
if (!defined $sth) {die("Cannot do delete statement of receiver in '$function_name': $DBI::errstr\n")}
$sth->execute($client_id);
write2log("info", "Receiver '$hostname' (client_id: '$client_id') was deleted" );
return 1;
}
}
}
} # END of unregisterClient
#-----------------------------------------------------------------------------
# getClients - get list of clients which were registered at warden server
#-----------------------------------------------------------------------------
sub getClients
{
my ($class, $arg) = @_;
# client network information
my $cn = $ENV{'SSL_CLIENT_S_DN_CN'};
my $ip = $ENV{'REMOTE_ADDR'};
my $local_ip = $ENV{'SERVER_ADDR'};
my $function_name = 'getClients';
if ($local_ip ne $ip) {
write2log ("err", "Unauthorized access to '$function_name' from: '$ip' ('$cn') - access allowed only from localhost");
die("Access denied - access allowed only from localhost!");
} else {
my (@clients, $client);
my ($client_id, $hostname, $registered, $requestor, $service, $client_type, $type, $receive_own_events, $description_tags, $ip_net_client);
my $sth = $DBH->prepare("SELECT * FROM clients ORDER BY client_id ASC;");
if (!defined $sth) { die("Cannot prepare statement in '$function_name': $DBI::errstr\n") }
$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;
write2log("info", "Sending information about '$sum' registered clients");
return @clients;
}
} # END of getClients
#-----------------------------------------------------------------------------
# getStatus - get list of status items of warden server
#-----------------------------------------------------------------------------
sub getStatus
{
my ($class, $arg) = @_;
# client network information
my $cn = $ENV{'SSL_CLIENT_S_DN_CN'};
my $ip = $ENV{'REMOTE_ADDR'};
my $local_ip = $ENV{'SERVER_ADDR'};
my $function_name = 'getStatus';
if ($local_ip ne $ip) {
write2log ("err", "Unauthorized access to '$function_name' from: '$ip' ('$cn') - access allowed only from localhost");
die("Access denied - access allowed only from localhost!");
} else {
my ($sth, @status);
# Warden server hostname
my $hostname = $ENV{'SERVER_NAME'};
# IP address of Warden server
my $ip_address = $ENV{'REMOTE_ADDR'};
# used port
my $port = $ENV{'SERVER_PORT'};
# size of database events
$sth = $DBH->prepare("SELECT data_length + index_length FROM information_schema.TABLES WHERE table_schema = ? AND TABLE_NAME = ?");
$sth->execute('warden', 'events');
my $size = $sth->fetchrow();
my $db_size = (defined $size ? Format::Human::Bytes::base10($size) : "none");
# sum of records in table events
$sth = $DBH->prepare("SELECT count(*) FROM events WHERE valid = 't';");
if (!defined $sth) { die("Cannot prepare statement in '$function_name': $DBI::errstr\n") }
$sth->execute;
my $events_sum = $sth->fetchrow();
if (!defined $events_sum) { $events_sum = "none" }
# id of last record in table events
$sth = $DBH->prepare("SELECT max(id) FROM events;");
if (!defined $sth) { die("Cannot prepare statement in '$function_name': $DBI::errstr\n") }
$sth->execute;
my $events_last_id = $sth->fetchrow();
if (!defined $events_last_id) { $events_last_id = "none" }
# timestamp of first record in table events
$sth = $DBH->prepare("SELECT received FROM events WHERE id = (SELECT min(id) FROM events);");
if (!defined $sth) { die("Cannot prepare statement in '$function_name': $DBI::errstr\n") }
$sth->execute;
my $events_first_timestamp = $sth->fetchrow();
if (!defined $events_first_timestamp) { $events_first_timestamp = "none" }
# timestamp of last record in table events
$sth = $DBH->prepare("SELECT received FROM events WHERE id = (SELECT max(id) FROM events);");
if (!defined $sth) { die("Cannot prepare statement in '$function_name': $DBI::errstr\n") }
$sth->execute;
my $events_last_timestamp = $sth->fetchrow();
if (!defined $events_last_timestamp) { $events_last_timestamp = "none" }
# sum of records in table clients
$sth = $DBH->prepare("SELECT count(*) FROM clients;");
if (!defined $sth) { die("Cannot prepare statement in '$function_name': $DBI::errstr\n") }
$sth->execute;
my $clients_sum = $sth->fetchrow();
if (!defined $clients_sum) { $clients_sum = "none" }
my $server_status = SOAP::Data->name(server_status => \SOAP::Data->value(
SOAP::Data->name(VERSION => $VERSION),
SOAP::Data->name(HOSTNAME => $hostname),
SOAP::Data->name(IP_ADDRESS => $ip_address),
SOAP::Data->name(PORT => $port),
SOAP::Data->name(FACILITY => $FACILITY),
SOAP::Data->name(DB_NAME => $DB_NAME),
SOAP::Data->name(DB_USER => $DB_USER),
SOAP::Data->name(DB_HOST => $DB_HOST),
SOAP::Data->name(DB_SIZE => $db_size),
SOAP::Data->name(EVENTS_SUM => $events_sum),
SOAP::Data->name(EVENTS_LAST_ID => $events_last_id),
SOAP::Data->name(EVENTS_FIRST_TIMESTAMP => $events_first_timestamp),
SOAP::Data->name(EVENTS_LAST_TIMESTAMP => $events_last_timestamp),
SOAP::Data->name(CLIENTS_SUM => $clients_sum)
));
push(@status, $server_status);
# statistics of senders
if ($clients_sum != 0) {
$sth = $DBH->prepare("SELECT client_id, hostname, service FROM clients WHERE client_type = 's' ORDER BY client_id ASC;");
if (!defined $sth) {die("Cannot prepare statement in '$function_name': $DBI::errstr\n")}
$sth->execute;
my ($client_id, $hostname, $service);
my $client_status;
while(($client_id, $hostname, $service) = $sth->fetchrow()) {
my $sth2;
# sum of stored events
$sth2 = $DBH->prepare("SELECT count(*) FROM events WHERE hostname = ? AND service = ?;");
if (!defined $sth2) {die("Cannot prepare statement in '$function_name': $DBI::errstr\n")}
$sth2->execute($hostname, $service);
my $count = $sth2->fetchrow();
if (!defined $count) {$count = "none"}
# timestamp of last stored event
$sth2 = $DBH->prepare("SELECT max(received) FROM events WHERE hostname = ? AND service = ?;");
if (!defined $sth2) {die("Cannot prepare statement in '$function_name': $DBI::errstr\n")}
$sth2->execute($hostname, $service);
my $timestamp = $sth2->fetchrow();
if (!defined $timestamp) {$timestamp = "none"}
# create SOAP data object
$client_status = SOAP::Data->name(client_status => \SOAP::Data->value(
SOAP::Data->name(CLIENT_ID => $client_id),
SOAP::Data->name(HOSTNAME => $hostname),
SOAP::Data->name(SERVICE => $service),
SOAP::Data->name(COUNT => $count),
SOAP::Data->name(TIMESTAMP => $timestamp),
));
push(@status, $client_status);
}
}
write2log("info", "Sent of warden server status info");
return @status;
}
} # END of getStatus
1;
#!/usr/bin/perl -w
#
# ApacheDispatch.pm
#
# Copyright (C) 2011-2012 Cesnet z.s.p.o
#
# Use of this source is governed by a BSD-style license, see LICENSE file.
package Warden::ApacheDispatch;
use strict;
use SOAP::Transport::HTTP;
our $VERSION = "2.0";
# set server dispatch_to
my $server = SOAP::Transport::HTTP::Apache->dispatch_to('.','Warden');
#-------------------------------------------------------------------------------
# handler - call handler for Warden server
#-------------------------------------------------------------------------------
sub handler {
$server->handler(@_)
}
1;
#!/usr/bin/perl -w
#
# WardenConf.pm
#
# Copyright (C) 2011-2012 Cesnet z.s.p.o
#
# Use of this source is governed by a BSD-style license, see LICENSE file.
package WardenConf;
use strict;
our $VERSION = "2.0";
#-------------------------------------------------------------------------------
# loadConf - load variables from configuration file
#-------------------------------------------------------------------------------
sub loadConf
{
my $conf_file = shift;
# preset of default variables
our $URI = undef;
our $SSL_KEY_FILE = undef;
our $SSL_CERT_FILE = undef;
our $SSL_CA_FILE = undef;
# load set variables by user
unless (do $conf_file) {
die("Errors in config file '$conf_file': $@") if $@;
die("Can't read config file '$conf_file': $!") unless defined $_;
# if $_ defined, it's retvalue of last statement of conf, for which we don't care
}
return ($URI, $SSL_KEY_FILE, $SSL_CERT_FILE, $SSL_CA_FILE);
} # End of loadConf
1;
#!/usr/bin/perl -w
#
# WardenReg.pm
#
# Copyright (C) 2011-2012 Cesnet z.s.p.o
#
# Use of this source is governed by a BSD-style license, see LICENSE file.
package WardenReg;
use strict;
use SOAP::Lite;
use IO::Socket::SSL qw(debug1);
use SOAP::Transport::HTTP;
our $VERSION = "2.0";
#-------------------------------------------------------------------------------
# errMsg - print error message and die
#-------------------------------------------------------------------------------
sub errMsg
{
my $msg = shift;
die($msg . "\n");
} # End of errMsg
#-------------------------------------------------------------------------------
# c2s - connect to server, send request and receive response
#-------------------------------------------------------------------------------
sub c2s
{
my $uri = shift;
my $ssl_key_file = shift;
my $ssl_cert_file = shift;
my $ssl_ca_file = shift;
my $method = shift;
my $data = shift;
my $client;
my ($server, $port, $service) = $uri =~ /https:\/\/(.+)\:(\d+)\/(.+)/;
if (!($client = SOAP::Transport::HTTP::Client->new())) {
errMsg("Sorry, unable to create socket: " . &SOAP::Transport::HTTP::Client::errstr)
}
$client->timeout(10);
$client->ssl_opts( verify_hostname => 1,
SSL_use_cert => 1,
SSL_verify_mode => 0x02,
SSL_key_file => $ssl_key_file,
SSL_cert_file => $ssl_cert_file,
SSL_ca_file => $ssl_ca_file);
# set URI and serialize SOAP envelope and data object
my $soap = SOAP::Lite->uri($service)->proxy($uri);
my $envelope = $soap->serializer->envelope(method => $method, $data);
# set URI and send serialized SOAP envelope and data
my $server_uri = "https://$server:$port/$service";
my $result = $client->send_receive(envelope => $envelope, endpoint => $server_uri);
# check server response
if (!defined $result) {
errMsg("Error: server returned empty response." . "\n" . "Problem with used SSL ceritificates or Warden server at $server:$port is down.");
} else {
# deserialized response from server -> create SOAP envelope and data object
my $response = $soap->deserializer->deserialize($result);
# check SOAP fault status
$response->fault ? errMsg("Server sent error message:: " . $response->faultstring) : return 1;
}
}
#-------------------------------------------------------------------------------
# registerSender - register new warden sender
#-------------------------------------------------------------------------------
sub registerSender
{
my $warden_path = shift;
my $hostname = shift;
my $requestor = shift;
my $service = shift;
my $description_tags = shift;
my $ip_net_client = shift;
my $etcdir = $warden_path . "/etc/";
my $libdir = $warden_path . "/lib/";
# read the config file
require $libdir . "WardenConf.pm";
my $conf_file = $etcdir . "warden-client.conf";
my ($uri, $ssl_key_file, $ssl_cert_file, $ssl_ca_file) = WardenConf::loadConf($conf_file);
# create SOAP data obejct
my $request_data = SOAP::Data->name(client => \SOAP::Data->value(
SOAP::Data->name(HOSTNAME => $hostname),
SOAP::Data->name(REQUESTOR => $requestor),
SOAP::Data->name(SERVICE => $service),
SOAP::Data->name(DESCRIPTION_TAGS => $description_tags),
SOAP::Data->name(IP_NET_CLIENT => $ip_net_client)
));
my $result = c2s($uri, $ssl_key_file, $ssl_cert_file, $ssl_ca_file, "registerSender", $request_data);
$result ? return 1 : return 0;
} # End of registerSender
#-------------------------------------------------------------------------------
# registerReceiver - register new warden receiver
#-------------------------------------------------------------------------------
sub registerReceiver
{
my $warden_path = shift;
my $hostname = shift;
my $requestor = shift;
my $type = shift;
my $receive_own_events = shift;
my $ip_net_client = shift;
my $etcdir = $warden_path . "/etc/";
my $libdir = $warden_path . "/lib/";
# read the config file
require $libdir . "WardenConf.pm";
my $conf_file = $etcdir . "warden-client.conf";
my ($uri, $ssl_key_file, $ssl_cert_file, $ssl_ca_file) = WardenConf::loadConf($conf_file);
# create SOAP data obejct
my $request_data = SOAP::Data->name(client => \SOAP::Data->value(
SOAP::Data->name(HOSTNAME => $hostname),
SOAP::Data->name(REQUESTOR => $requestor),
SOAP::Data->name(TYPE => $type),
SOAP::Data->name(RECEIVE_OWN_EVENTS => $receive_own_events),
SOAP::Data->name(IP_NET_CLIENT => $ip_net_client)
));
my $result = c2s($uri, $ssl_key_file, $ssl_cert_file, $ssl_ca_file, "registerReceiver", $request_data);
$result ? return 1 : return 0;
} # End of registerReceiver
#-------------------------------------------------------------------------------
# unregisterClient - unregister client from warden server
#-------------------------------------------------------------------------------
sub unregisterClient
{
my $warden_path = shift;
my $client_id = shift;
my $etcdir = $warden_path . "/etc/";
my $libdir = $warden_path . "/lib/";
# read the config file
require $libdir . "WardenConf.pm";
my $conf_file = $etcdir . "warden-client.conf";
my ($uri, $ssl_key_file, $ssl_cert_file, $ssl_ca_file) = WardenConf::loadConf($conf_file);
# create SOAP data obejct
my $request_data = SOAP::Data->name(client => \SOAP::Data->value(
SOAP::Data->name(CLIENT_ID => $client_id)
));
my $result = c2s($uri, $ssl_key_file, $ssl_cert_file, $ssl_ca_file, "unregisterClient", $request_data);
$result ? return 1 : return 0;
} # End of unregisterClient
1;
#!/usr/bin/perl -w
#
# WardenStatus.pm
#
# Copyright (C) 2011-2012 Cesnet z.s.p.o
#
# Use of this source is governed by a BSD-style license, see LICENSE file.
package WardenStatus;
use strict;
use SOAP::Lite;
use IO::Socket::SSL qw(debug1);
use SOAP::Transport::HTTP;
our $VERSION = "2.0";
#-------------------------------------------------------------------------------
# errMsg - print error message and die
#-------------------------------------------------------------------------------
sub errMsg
{
my $msg = shift;
die($msg . "\n");
} # End of errMsg
#-------------------------------------------------------------------------------
# c2s - connect to server, send request and receive response
#-------------------------------------------------------------------------------
sub c2s
{
my $uri = shift;
my $ssl_key_file = shift;
my $ssl_cert_file = shift;
my $ssl_ca_file = shift;
my $method = shift;
my $data = shift;
my $client;
my ($server, $port, $service) = $uri =~ /https:\/\/(.+)\:(\d+)\/(.+)/;
if (!($client = SOAP::Transport::HTTP::Client->new())) {
errMsg("Sorry, unable to create socket: " . &SOAP::Transport::HTTP::Client::errstr)
}
$client->timeout(10);
$client->ssl_opts(verify_hostname => 1,
SSL_use_cert => 1,
SSL_verify_mode => 0x02,
SSL_key_file => $ssl_key_file,
SSL_cert_file => $ssl_cert_file,
SSL_ca_file => $ssl_ca_file);
# set URI and serialize SOAP envelope and data object
my $soap = SOAP::Lite->uri($service)->proxy($uri);
my $envelope = $soap->serializer->envelope(method => $method);
# setting of TCP URI and send serialized SOAP envelope and data
my $server_uri = "https://$server:$port/$service";
my $result = $client->send_receive(envelope => $envelope, endpoint => $server_uri);
# check server response
if (!defined $result) {
errMsg("Error: server returned empty response." . "\n" . "Problem with used SSL ceritificates or Warden server at $server:$port is down.");
} else {
# deserialized response from server -> create SOAP envelope and data object
my $response = $soap->deserializer->deserialize($result);
# check SOAP fault status
$response->fault ? errMsg("Server sent error message:: " . $response->faultstring) : return $response;
}
}
#-------------------------------------------------------------------------------
# getClients - get list of registered clients
#-------------------------------------------------------------------------------
sub getClients
{
my $warden_path = shift;
my $etcdir = $warden_path . "/etc/";
my $libdir = $warden_path . "/lib/";
# read the config file
require $libdir . "WardenConf.pm";
my $conf_file = $etcdir . "warden-client.conf";
my ($uri, $ssl_key_file, $ssl_cert_file, $ssl_ca_file) = WardenConf::loadConf($conf_file);
# call method getClients on the Warden server
my $response = c2s($uri, $ssl_key_file, $ssl_cert_file, $ssl_ca_file, "getClients");
# parse returned SOAP data object with clients
my @clients;
my ($client_id, $hostname, $registered, $requestor, $service, $client_type, $type, $receive_own_events, $description_tags, $ip_net_client);
my @response_list = $response->valueof('/Envelope/Body/getClientsResponse/client/');
while (scalar @response_list) {
my $response_data = shift(@response_list);
my @client;
$client_id = $response_data->{'CLIENT_ID'} ;
$hostname = $response_data->{'HOSTNAME'};
$registered = $response_data->{'REGISTERED'};
$requestor = $response_data->{'REQUESTOR'};
$service = defined $response_data->{'SERVICE'} ? $response_data->{'SERVICE'} : "-";
$client_type = $response_data->{'CLIENT_TYPE'};
$type = defined $response_data->{'TYPE'} ? $response_data->{'TYPE'} : "-";
$receive_own_events = defined $response_data->{'RECEIVE_OWN_EVENTS'} ? $response_data->{'RECEIVE_OWN_EVENTS'} : "-";
$description_tags = defined $response_data->{'DESCRIPTION_TAGS'} ? $response_data->{'DESCRIPTION_TAGS'} : "-";
$ip_net_client = $response_data->{'IP_NET_CLIENT'};
# push received clients from warden server into @clients which is returned
@client = ($client_id, $hostname, $registered, $requestor, $service, $client_type, $type, $receive_own_events, $description_tags, $ip_net_client);
push (@clients,\@client);
}
return @clients;
} # End of getClients
#-------------------------------------------------------------------------------
# getStatus - get warden server status
#-------------------------------------------------------------------------------
sub getStatus
{
my $warden_path = shift;
my $etcdir = $warden_path . "/etc/";
my $libdir = $warden_path . "/lib/";
# read the config file
require $libdir . "WardenConf.pm";
my $conf_file = $etcdir . "warden-client.conf";
my ($uri, $ssl_key_file, $ssl_cert_file, $ssl_ca_file) = WardenConf::loadConf($conf_file);
# call method getStatus on Warden server
my $response = c2s($uri, $ssl_key_file, $ssl_cert_file, $ssl_ca_file, "getStatus");
#-----------------------------------------------------------------------------
# parse returned SOAP object with server status
my @response_list = $response->valueof('/Envelope/Body/getStatusResponse/server_status/');
my $response_data = shift(@response_list);
my $version = $response_data->{'VERSION'};
my $server_hostname = $response_data->{'HOSTNAME'};
my $ip_address = $response_data->{'IP_ADDRESS'};
my $port = $response_data->{'PORT'};
my $db_name = $response_data->{'DB_NAME'};
my $db_user = $response_data->{'DB_USER'};
my $db_host = $response_data->{'DB_HOST'};
my $facility = $response_data->{'FACILITY'};
my $db_size = $response_data->{'DB_SIZE'};
my $events_sum = $response_data->{'EVENTS_SUM'};
my $events_last_id = $response_data->{'EVENTS_LAST_ID'};
my $events_first_timestamp = $response_data->{'EVENTS_FIRST_TIMESTAMP'};
my $events_last_timestamp = $response_data->{'EVENTS_LAST_TIMESTAMP'};
my $clients_sum = $response_data->{'CLIENTS_SUM'};
my @server_status = ($version, $server_hostname, $ip_address, $port, $db_name, $db_user, $db_host, $facility, $db_size, $events_sum, $events_last_id, $events_first_timestamp, $events_last_timestamp, $clients_sum);
my @status;
push(@status, \@server_status);
#-----------------------------------------------------------------------------
# parse returned SOAP object with client status
@response_list = $response->valueof('/Envelope/Body/getStatusResponse/client_status/');
my ($client_id, $hostname, $service, $count, $timestamp);
while (scalar @response_list) {
my $response_data = shift(@response_list);
my @client_status;
my $client_id = $response_data->{'CLIENT_ID'};
my $hostname = $response_data->{'HOSTNAME'};
my $service = $response_data->{'SERVICE'};
my $count = $response_data->{'COUNT'};
my $timestamp = $response_data->{'TIMESTAMP'};
@client_status = ($client_id, $hostname, $service, $count, $timestamp);
push(@status, \@client_status);
}
return @status;
} # End of getStatus
1;
#!/bin/bash
#
# install.sh
#
# Copyright (C) 2011-2012 Cesnet z.s.p.o
#
# Use of this source is governed by a BSD-style license, see LICENSE file.
VERSION="2.0"
#-------------------------------------------------------------------------------
# FUNCTIONS
#-------------------------------------------------------------------------------
usage()
{
echo "Usage: `basename $0` [-d <directory>] [-k <ssl_key_file>] [-c <ssl_cert_file>] [-a <ssl_ca_file>] [-hV]"
echo "-d <directory> installation directory (default: /opt)"
echo "-k <ssl_key_file> path to SSL certificate key file"
echo "-c <ssl_cert_file> path to SSL certificate file"
echo "-a <ssl_ca_file> path to CA certificate file"
echo "-h print this help"
echo "-V print script version number and exit"
echo
echo "Example: # ./`basename $0` -d /opt -k /etc/ssl/private/server.key -c /etc/ssl/certs/server.pem -a /etc/ssl/certs/tcs-ca-bundle.pem"
echo
echo "Note: You must be root for running this script."
echo " For more information about installation process, see README file (section Installation)."
echo
exit 0
}
version()
{
echo "`basename ${0}` - current version is $VERSION"
exit 0
}
err()
{
echo "FAILED!"
cat $err
rm -rf $err
echo
echo "Installation of $package_version package FAILED!!!"
exit 1
}
err_clean()
{
echo "FAILED!"
echo " -> Uninstalling server package ... OK"
rm -rf $server_path > /dev/null 2>&1
cat $err
rm -rf $err
echo
echo "Installation of $package_version package FAILED!!!"
exit 1
}
root_chck()
{
if [ $UID -ne 0 ]; then
echo "You must be root for running this script!"
exit 1
fi
}
params_chck()
{
if [ -z $prefix ]; then
prefix=/opt
echo "Warning: parameter -d <directory> is not set - default installation directory is ${prefix}!"
fi
if [ -z $key ]; then
echo "Parameter -k <ssl_key_file> is not set!"
exit 1
fi
if [ -z $cert ]; then
echo "Parameter -c <ssl_cert_file> is not set!"
exit 1
fi
if [ -z $ca_file ]; then
echo "Parameter -a <ssl_ca_file> is not set!"
exit 1
fi
}
old_package_chck()
{
old_package_version_file="${etc}/package_version"
if [ -f $old_package_version_file ]; then
old_package_version=`cat $old_package_version_file`
echo "Sorry, but $old_package_version package is installed!"
echo "For update of warden server package please use update.sh"
exit 1
fi
}
perl_chck()
{
echo -n "Checking Perl interpreter ... "
if which perl 1> /dev/null; then
echo "OK"
else
echo "FAILED!"
echo "Error: Perl interpreter is not installed!"
exit 1
fi
}
modules_chck()
{
for module in ${modules[@]};
do
echo -n "Checking $module module ... "
if perl -e "use $module" 2> $err; then
echo "OK"
else
err
fi
done
}
make_warden_dir()
{
echo -n "Creating warden server directory ... "
test -d $prefix || mkdir -p $prefix
if cp -R ${dirname}/warden-server $prefix 2> $err; then
echo "OK"
else
err_clean
fi
files=(CHANGELOG INSTALL LICENSE README)
for file in ${files[@]};
do
cp ${dirname}/$file ${server_path}/doc
done
cp ${dirname}/uninstall.sh $server_path
}
make_client_conf()
{
echo -n "Creating client configuration file ... "
echo "#
# warden-client.conf - configuration file for the warden sender/receiver client
#
#-------------------------------------------------------------------------------
# URI - URI address of Warden server
#-------------------------------------------------------------------------------
\$URI = \"https://${hostname}:443/Warden\";
#-------------------------------------------------------------------------------
# SSL_KEY_FILE - path to client SSL certificate key file
#-------------------------------------------------------------------------------
\$SSL_KEY_FILE = \"${key}\";
#-------------------------------------------------------------------------------
# SSL_CERT_FILE - path to client SSL certificate file
#-------------------------------------------------------------------------------
\$SSL_CERT_FILE = \"${cert}\";
#-------------------------------------------------------------------------------
# SSL_CA_FILE - path to CA certificate file
#-------------------------------------------------------------------------------
\$SSL_CA_FILE = \"${ca_file}\";
" > $client_conf 2> $err; ret_val=`echo $?`
if [ $ret_val -eq 0 ]; then
echo "OK"
else
err_clean
fi
}
make_server_conf()
{
echo -n "Creating server configuration file ... "
echo "#
# warden-server.conf - configuration file for Warden server
#
#-------------------------------------------------------------------------------
# BASEDIR - base directory of Warden server
#-------------------------------------------------------------------------------
\$BASEDIR = \"${server_path}\";
#-------------------------------------------------------------------------------
# FACILITY - syslog facility
#-------------------------------------------------------------------------------
\$FACILITY = \"local7\";
#-------------------------------------------------------------------------------
# DB_NAME - database name of Warden server
#-------------------------------------------------------------------------------
\$DB_NAME = \"warden\";
#-------------------------------------------------------------------------------
# DB_USER - user of Warden server database
#-------------------------------------------------------------------------------
\$DB_USER = \"username\";
#-------------------------------------------------------------------------------
# DB_PASS - password of Warden server database
#-------------------------------------------------------------------------------
\$DB_PASS = \"\";
#-------------------------------------------------------------------------------
# DB_HOST - what IP address to listen on of Warden server
#-------------------------------------------------------------------------------
\$DB_HOST = \"localhost\";
#-------------------------------------------------------------------------------
# MAX_EVENTS_LIMIT - maximum number of events that can be downloaded from
# Warden server in a single getNewEvents client function call
#-------------------------------------------------------------------------------
\$MAX_EVENTS_LIMIT = \"1000000\";
#-------------------------------------------------------------------------------
# VALID_STRINGS - validation hash containing allowed event attributes
#-------------------------------------------------------------------------------
%VALID_STRINGS = (
\"type\" => [\"portscan\", \"bruteforce\", \"probe\", \"spam\", \"phishing\", \"botnet_c_c\", \"dos\", \"malware\", \"copyright\", \"webattack\", \"test\", \"other\", \"_any_\"],
);
" > $server_conf 2> $err; ret_val=`echo $?`
if [ $ret_val -eq 0 ]; then
echo "OK"
else
err_clean
fi
}
changeServerPath()
{
echo "Update server path ...";
for file in `ls -1 $bin`
do
echo "- update server path: ${bin}/$file"
perl -pi -e "s#/opt#${prefix}#" ${bin}/$file
done
echo "- update server path: $apache_conf"
perl -pi -e "s#/opt#${prefix}#" $apache_conf
echo "- update server path: ${lib}/Warden.pm"
perl -pi -e "s#/opt#${prefix}#" ${lib}/Warden.pm
}
updateCertsPath()
{
echo "- update certs path: $apache_conf"
perl -pi -e "s#server-cert.pem#${cert}#" $apache_conf
perl -pi -e "s#server-key.pem#${key}#" $apache_conf
perl -pi -e "s#ca-cert.pem#${ca_file}#" $apache_conf
}
create_symlinks()
{
echo "Creating symbolic links ..."
for file in `ls -1 $bin`
do
echo "- making symlink: ${local_bin}/$file -> ${bin}/$file"
ln -s ${bin}/$file ${local_bin}/$file
done
}
#-------------------------------------------------------------------------------
# MAIN
#-------------------------------------------------------------------------------
# list of used Perl modules
modules=(SOAP::Lite SOAP::Transport::HTTP DBI DBD::mysql Format::Human::Bytes Sys::Syslog File::Basename Net::CIDR::Lite DateTime Getopt::Std Switch IO::Socket::SSL MIME::Base64 Crypt::X509)
# read input
while getopts "d:k:c:a:Vh" options; do
case $options in
d ) prefix=$OPTARG;;
k ) key=$OPTARG;;
c ) cert=$OPTARG;;
a ) ca_file=$OPTARG;;
h ) usage;;
V ) version;;
* ) usage;;
esac
done
# root test
root_chck
# params test
params_chck
# create variables
dirname=`dirname $0`
hostname=`hostname`
key_file=`basename $key`
cert_file=`basename $cert`
package_version=`cat ${dirname}/warden-server/etc/package_version`
[[ $prefix == */ ]] && prefix="${prefix%?}" # remove last char (slash) from prefix
server_path="${prefix}/warden-server"
bin="${server_path}/bin"
local_bin="/usr/local/bin"
etc="${server_path}/etc"
client_conf="${etc}/warden-client.conf"
server_conf="${etc}/warden-server.conf"
apache_conf="${etc}/warden-apache.conf"
var="${server_path}/var"
lib="${server_path}/lib"
err="/tmp/warden-err"
# check if warden-server is installed
old_package_chck
echo
echo "------------------------- Dependencies check-in -------------------------"
# Perl interpreter test
perl_chck
# Perl modules test
modules_chck
echo
echo "------------------------- Installation process --------------------------"
# make warden client directory
make_warden_dir
# create client configuration file
make_client_conf
# create server configuration file
make_server_conf
#update paths in utilities
changeServerPath
#update paths in apachefile
updateCertsPath
# crate symlinks from warden server bin directory to /usr/local/bin
create_symlinks
echo
echo "Please check client configuration file in ${client_conf}!"
echo "Please check server configuration file in ${server_conf}!"
echo "Please check Apache configuration file in ${apache_conf}!"
echo
echo "Warden server directory: $server_path"
echo
echo "Installation of $package_version package was SUCCESSFUL!!!"
# cleanup section
rm -rf $err
exit 0
#!/bin/bash
#
# uninstall.sh
#
# Copyright (C) 2011-2012 Cesnet z.s.p.o
#
VERSION="2.0"
#-------------------------------------------------------------------------------
# FUNCTIONS
#-------------------------------------------------------------------------------
usage()
{
echo "Usage: `basename $0` [-d <directory>] [-hV]"
echo "-d <directory> uninstallation directory (default: /opt)"
echo "-h print this help"
echo "-V print script version number and exit"
echo
echo "Example: # ./`basename $0` -d /opt"
echo
echo "Note: You must be root for running this script."
echo " For more information about uninstallation process, see README file (section Uninstallation)."
echo
exit 0
}
version()
{
echo "`basename ${0}` - current version is $VERSION"
exit 0
}
err()
{
echo "FAILED!"
cat $err
rm -rf $err $backup_dir
echo
echo "Uninstallation of $package_version package FAILED!!!"
exit 1
}
err_clean()
{
echo "FAILED!"
echo " -> Reverting changes of warden server package ... OK"
rm -rf ${server_path}/* > /dev/null 2>&1 # delete new version
cp -R ${backup_dir}/* $server_path # copy old backuped server
for file in `ls -1 $bin`
do
ln -s ${bin}/$file ${local_bin}/$file # create symlinks to /usr/local/bin
done
$init start # start server
cat $err
rm -rf $err $backup_dir
echo
echo "Uninstallation of $package_version package FAILED!!!"
exit 1
}
root_chck()
{
if [ $UID -ne 0 ]; then
echo "You must be root for running this script!"
exit 1
fi
}
params_chck()
{
if [ -z $prefix ]; then
prefix=/opt
echo "Warning: parameter -d <directory> is not set - default uninstallation directory is ${prefix}!"
fi
}
obtain_package_version()
{
if [ -f $old_package_version_file ]; then
package_version=`cat $old_package_version_file`
else
package_version="unknown"
fi
}
warden_dir_chck()
{
echo -n "Checking warden server directory ... "
if [ ! -d $server_path ]; then
echo "FAILED!"
ls $server_path
exit 1
else
echo "OK"
fi
}
stop_warden_server()
{
echo "Stopping warden server ... "
${init} stop 1>/dev/null 2>&1
}
backup()
{
echo -n "Backing-up warden server directory ... "
mkdir $backup_dir
if cp -R ${server_path}/* $backup_dir 2> $err; then
echo "OK"
else
err
fi
}
delete_symlinks()
{
echo -n "Deleting symlinks from /usr/local/bin ..."
for file in `ls -1 $bin`
do
rm -rf ${local_bin}/$file 2> /dev/null
done
echo "OK"
}
uninstall_warden_server()
{
echo -n "Uninstalling $package_version package ... "
if rm -rf $server_path 2> $err; then
echo "OK"
else
err_clean
fi
}
#-------------------------------------------------------------------------------
# MAIN
#-------------------------------------------------------------------------------
# OS test
os_chck
# Shell test
shell_chck
# read input
while getopts "d:Vh" options; do
case $options in
d ) prefix=$OPTARG;;
h ) usage;;
V ) version;;
* ) usage;;
esac
done
# root test
root_chck
# params test
params_chck
# create variables
[[ $prefix == */ ]] && prefix="${prefix%?}" # remove last char (slash) from prefix
server_path="${prefix}/warden-server"
bin="${server_path}/bin"
local_bin="/usr/local/bin"
etc="${server_path}/etc"
var="${server_path}/etc"
old_package_version_file="${etc}/package_version"
err="/tmp/warden-err"
backup_dir="/tmp/warden-backup"
init="/etc/init.d/apache2"
# obtain version of installed warden-server package
obtain_package_version
echo
echo "------------------------- Uninstallation process --------------------------------"
# check if $prefix/warden-server directory exist
warden_dir_chck
# stop running warden server
stop_warden_server
# make backup of currently installed warden-server package
backup
# delete symbolic links
delete_symlinks
# do uninstallation
uninstall_warden_server
echo
echo "Uninstallation of $package_version package was SUCCESSFUL!!!"
# cleanup section
rm -rf $err $backup_dir
exit 0
BSD License
Copyright © 2011-2015 Cesnet z.s.p.o
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the Cesnet z.s.p.o nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE Cesnet z.s.p.o BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------+
| Warden Client Library 3.0-beta3 |
+---------------------------------+
Content
A. Introduction
B. Quick start (TL;DR)
C. Concepts
D. HTTP/JSON API
E. Python API
------------------------------------------------------------------------------
A. Introduction
The main goal of Warden 3 is to address the shortcomings, which emerged
during several years of Warden 2.X operation. Warden 3 uses flexible and
descriptive event format, based on JSON. Warden 3 protocol is based on plain
HTTPS queries with help of JSON (Warden 2 SOAP is heavyweight, outdated and
draws in many dependencies). Clients can be multilanguage, unlike SOAP/HTTPS,
plain HTTPS and JSON is mature in many mainstream programming languages.
Server is written in Python - mature language with consistent and coherent
libraries and many skilled developers.
------------------------------------------------------------------------------
B. Quick start (TL;DR)
* Obtain X509 key/cert, corresponding with DNS name of you machine.
* Obtain X509 CA chain for server validation.
* Choose client name ("reverse DNS", like org.example.warden.client, but
it does not necessary need to correspond to your machine's DNS name).
* Ask Warden server admins for registration. They will want to know at least
client name and dns name, and short description of the (planned) client
and its purpose. Work with them. They may request some changes or
clarifications, offer you useful guidelines, provide you with alternative
sandbox URL, etc.
If succesful, you will receive authentication secret.
* Use warden_curl_test.sh to check you are able to talk to server.
* See warden_client_examples.py on how to integrate sending/receiving
into your Python application.
* Alternatively, check 'contrib' directory in Warden GIT for various
ready to use tools or recipes. You may find senders for various
honeypots, or warden_filer may get handy if you do not want to delve
into Python at all.
* Welcome! Thanks for participating in in the data exchange to improve
network security awareness.
------------------------------------------------------------------------------
C. Concepts
C.1. Event description format
IDEA - Intrusion Detection Extensible Alert, flexible extensible format
for security events, see:
https://idea.cesnet.cz/
C.2. Event serial ID
Each received event gets assigned integer serial number. These numbers are
sequential, so each recipient can keep track of the last event "id" it
received and next time ask only for following events.
C.3. Authentication
In Warden 2, clients get authenticated by server certificate, however
server certificate is usually same for the whole machine, so individual
clients are differentiated only by telling their own name. However, client name
is widely known, so this allows for client impersonation within one machine.
Warden 3 slightly improves this schema by replacing client name in
authentication phase by "secret", random string, shared among particular
client and main server, which makes it harder to forge client identity (be it
by mistake or intentional).
However, best solution for these cases is of course specific certificate
for each particular client (which is also fully supported).
Client also has to have server CA certificate (or chain) at its disposal
to be able to verify server authenticity.
C.4. Client name
Unlike Warden 2, client names in Warden 3 have hierarchy. Modelled after
Java class names, client name is dot separated list of labels, with
significance from left to right – leftmost denoting largest containing realm,
rightmost denoting single entity.
Country.organisation.suborganizations.machine.local realm scheme akin to
"org.example.csirt.northwest.honeypot.jabberwock" is strongly recommended.
Label case is significant, label can contain only letters, numbers or
underscore and must not start with number.
The reason is the possibility to filter incoming events based not only on
particular client, or (for some recipients flawed) notion of "own" messages,
but based on wider units.
------------------------------------------------------------------------------
D. HTTP/JSON API
Client must know the base URL of the Warden server. Warden 3 accepts
queries on paths under base URL (which correspond to called method), with
usual query string variable=data pairs separated by ampersand as arguments.
Multivalues are specified by repeating same variable with each value several
times.
https://warden.example.org/warden3/getEvents?secret=PwD&cat=Abusive.Spam&cat=Fraud.Phishing
\________________ _______________/ \___ ___/ \____ ___/ \______ _______/ \________ _______/
V V V V V
Base URL --' | | | |
Called method ------------------------' | | |
Key/value pair -----------------------------------' | |
Multivalue ------------------------------------------------'------------------'
Method may expect bulk data (events to save, for example) - query then
must be POST, with POST JSON data, formed appropriately as documented in
particular method.
If HTTPS call succeeds (200 OK), method returns JSON object containing
requested data.
D.1. Error handling
Should the call fail, server returns HTTP status code, together with JSON
object, describing the errors (there may be multiple ones, especially when
sending events). The keys of the object, which may be available, are:
* method - name of the method called
* req_id - unique identifier or the request (for troubleshooting, Warden
administrator can uniquely identify related log lines)
* errors - always present list of JSON objects, which contain:
* error - HTTP status code
* message - human readable error description
* Other context dependent fields may appear, see particular method
description.
Client errors (4xx) are considered permanent - client must not try to send
same event again as it will always get rejected - client administrator
will need to inspect logs and rectify the cause.
Server errors (5xx) may be considered by client as temporary and client is
advised to try again after reasonable recess.
D.2. Common arguments
* secret - shared secret, assigned to client during registration
* client - client name, optional, can be used to mimic Warden 2
authentication behavior if explicitly allowed for this client by server
administrator
= getEvents =
Fetches events from server.
Arguments:
* count - number of requested events
* id - starting serial number requested, id of all received events will
be greater
* cat, nocat - selects only events with categories, which are/are not
present in the event Category field (mutually exclusive)
* group, nogroup - selects only events originated/not originated from
this realms and/or client names, as denoted in the event Node.Name field
(mutually exclusive)
* tag, notag - selects only events with/without this client description
tags, as denoted in the event Node.Type field (mutually exclusive)
Returns:
* lastid - serial number of the last received event
* events - array of Idea events
Example:
$ curl \
--key key.pem \
--cert cert.pem \
--cacert ca.pem \
--connect-timeout 3 \
--request POST \
\
"https://warden.example.org/getEvents?\
secret=SeCrEt\
&count=1\
&nogroup=org.example\
&cat=Abusive.Spam\
&cat=Fraud.Phishing"
{"lastid": 581,
"events": [{
"Format": "IDEA0",
"DetectTime": "2015-02-03T09:55:21.563638Z",
"Target": [{"URL": ["http://example.com/kocHq"]}],
"Category": ["Fraud.Phishing"],
"Note": "Example event"}]}
= sendEvents =
Uploads events to server.
Arguments:
* POST data - JSON array of Idea events
Returns:
Returns object with number of saved messages in "saved" attribute.
In case of error, multiple errors may be returned in "errors" list (see
[[Warden3#Error-handling|Error handling]] section). Each of the error objects
may contain "events" key with list of indexes of events affected by this
particular error. If there is error object without "events" key, caller
must consider all events affected.
Should the call fail because of errors in just couple of events, error
message will contain JSON object in "detail.errors" section. The keys of the
object are indexes into POST data array, values are error messages for each
particular failed Idea event.
Example:
$ eventid=$RANDOM$RANDOM$RANDOM$RANDOM$RANDOM
$ detecttime=$(date --rfc-3339=seconds|tr " " "T")
$ client="cz.example.warden.test"
$ printf '
[
{
"Format": "IDEA0",
"ID": "%s",
"DetectTime": "%s",
"Category": ["Test"],
"Node": [{"Name": "%s"}]
}
]' $eventid $detecttime $client |\
curl \
--key $keyfile \
--cert $certfile \
--cacert $cafile \
--connect-timeout 3 \
--request POST \
--data-binary "@-" \
"https://warden.example.org/sendEvents?client=$client&secret=SeCrEt"
{"saved":1}
(However note that this is not the best way to generate Idea messages. :) )
Example with error:
$ curl \
--key $keyfile \
--cert $certfile \
--cacert $cafile \
--connect-timeout 3 \
--request POST \
--data-binary '[{"Format": "IDEA0","ID":"ASDF","Category":[],"DetectTime":"asdf"}]' \
"https://warden.example.org/sendEvents?client=cz.example.warden.test&secret=SeCrEt"
{"errors":
[
{"message": "Validation error: key \"DetectTime\", value \"asdf\", expected - RFC3339 timestamp.",
"events": [0],
"error": 460
}
],
"method": "sendEvents",
"req_id": 3726454025
}
= getInfo =
Returns basic server information.
Returns:
* version - Warden server version string
* description - server greeting
* send_events_limit - sendEvents will be rejected if client sends more
events in one call
* get_events_limit - getEvents will return at most that much events
Example:
$ curl \
--key key.pem \
--cert cert.pem \
--cacert ca.pem \
--connect-timeout 3 \
--request POST \
"https://warden.example.org/getInfo?secret=SeCrEt"
{"version": "3.0",
"send_events_limit": 500,
"get_events_limit": 1000,
"description": "Warden 3 server"}
E. Python API
Python API tries to abstract from raw HTTPS/URL/JSON details. User
instantiates Client class with necessary settings (certificates, secret,
client name, logging, limits, ...) and then uses its method to access server.
= Client constructor =
wclient = warden.Client(
url,
certfile=None,
keyfile=None,
cafile=None,
timeout=60,
retry=3,
pause=5,
get_events_limit=6000,
send_events_limit=500,
errlog={},
syslog=None,
filelog=None,
idstore=None,
name="org.example.warden_client",
secret=None)
* url - Warden server base URL
* certfile, keyfile, cafile - paths to X509 material
* timeout - network timeout value in seconds
* retry - number retries on transitional errors during sending events
* pause - wait time in seconds between transitional error retries
* get_events_limit - maximum number of events to receive (note that server
may have its own notion)
* send_events_limit - when sending, event lists will be split and sent by
chunks of at most this size (note that used value will get adjusted according
to the limit reported by server)
* errlog - stderr logging configuration dict
* level - most verbose loglevel to log
* syslog - syslog logging configuration dict
* level - most verbose loglevel to log
* socket - syslog socket path (defaults to "/dev/log")
* facility - syslog facility (defaults to "local7")
* filelog - file logging configuration dict
* level - most verbose loglevel to log
* file - path to log file
* idstore - path to simple text file, in which last received event ID gets
stored. If None, server notion is used
* name - client name
* secret - authentication secret
= Configuration file helper =
warden.read_cfg(cfgfile)
Warden object can get initialized from JSON like configuration file. It's
essentially JSON, but full line comments, starting with "#" or "//", are
allowed. read_cfg reads the configuration file and returns dict suitable
for passing as Client constructor arguments.
Usage example:
wclient = warden.Client(**warden.read_cfg("warden_client.cfg"))
= warden.Client.getEvents =
wclient.getEvents(
id=None,
idstore=None,
count=1,
cat=None, nocat=None,
tag=None, notag=None,
group=None, nogroup=None)
* id - can be used to explicitly override value from idstore file
* idstore - can be used to explicitly override idstore for this request
* count - number of requested events
* cat, nocat - selects only events with categories, which are/are not
present in the event Category field (mutually exclusive)
* group, nogroup - selects only events originated/not originated from
this realms and/or client names, as denoted in the event Node.Name field
(mutually exclusive)
* tag, notag - selects only events with/without this client description
tags, as denoted in the event Node.Type field (mutually exclusive)
Returns:
* list of Idea events
= warden.Client.sendEvents =
wclient.sendEvents(self, events=[], retry=None, pause=None):
* events - list of Idea events to be sent to server
* retry - use this retry value just for this call instead from value from
constructor
* pause - use this pause value just for this call instead from value from
constructor
Returns:
* dict with number of sent events under "saved" key
Note:
events list length is limited only by available resources, sendEvents
will split it and send separately in at most send_events_limit long chunks
(however note that sendEvents will also need additional memory for its
internal data structures).
Server errors (5xx) are considered transitional and sendEvents will do
retry number of attempts to deliver corresponding events, delayed by
pause seconds.
Should the call fail because of errors, particular errors may contain
"events" list. Values of the list are then indexes into POST data array. If
no "events" list is present, all events attempted to send must be
considered as failed (with this particular error). See also
[[Warden3#Error-handling|Error handling]] section.
Errors may also contain event IDs from Idea messages in "events_id" list.
This is primarily for logging - client administrator may identify offending
messages by stable identifiers.
= warden.Client.getInfo =
wclient.getInfo()
Returns dictionary of information from getInfo Warden call.
= Error class =
Error(
message,
logger=None,
error=None,
prio="error",
method=None,
req_id=None,
detail=None,
exc=None)
Class, which gets returned in case of client or server error. Caller can
test whether it received data or error by checking:
isinstance(res, Error).
However if he does not want to deal with errors altogether, this error
object also returns False value if used in Bool context and acts as an
empty iterator - in following examples do_stuff() is not evaluated:
if res:
do_stuff(res)
for e in res:
do_stuff(e)
str(Error_Instance) outputs formatted error, info_str() and
debug_str() output increasingly more detailed info.
------------------------------------------------------------------------------
Copyright (C) 2011-2022 Cesnet z.s.p.o
{
"url": "https://warden-hub.example.org/warden3",
"certfile": "cert.pem",
"keyfile": "key.pem",
"filelog": {"level": "debug"},
"name": "org.example.warden_client",
"secret": "ToP_SeCrEt"
}
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2011-2015 Cesnet z.s.p.o
# Use of this source is governed by a 3-clause BSD-style license, see LICENSE file.
import hashlib # Some Python/ssl versions incorrectly initialize hashes, this helps
from sys import stderr, exc_info, version_info
import json, ssl, socket, logging, logging.handlers, time
from traceback import format_tb
from os import path
from operator import itemgetter
fix_logging_filename = str if version_info<(2, 7) else lambda x: x
if version_info[0] >= 3:
import http.client as httplib
from urllib.parse import urlparse
from urllib.parse import urlencode
basestring = str
else:
import httplib
from urlparse import urlparse
from urllib import urlencode
VERSION = "3.0-beta3"
DEFAULT_CA_STORES = [
"/etc/ssl/certs/ca-certificates.crt", # Deb
"/etc/pki/tls/certs/ca-bundle.crt", # RH
"/var/lib/ca-certificates/ca-bundle.pem" # SuSE
]
class HTTPSConnection(httplib.HTTPSConnection):
'''
Overridden to allow peer certificate validation, configuration
of SSL/ TLS version and cipher selection. See:
http://hg.python.org/cpython/file/c1c45755397b/Lib/httplib.py#l1144
and `ssl.wrap_socket()`
Used only if ssl.SSLContext is not available (Python version < 2.7.9)
'''
def __init__(self, host, **kwargs):
self.ciphers = kwargs.pop('ciphers', None)
self.ca_certs = kwargs.pop('ca_certs', None)
self.ssl_version = kwargs.pop('ssl_version', getattr(ssl, "PROTOCOL_TLS", ssl.PROTOCOL_SSLv23))
httplib.HTTPSConnection.__init__(self,host,**kwargs)
def connect(self):
sock = socket.create_connection( (self.host, self.port), self.timeout)
if self._tunnel_host:
self.sock = sock
self._tunnel()
self.sock = ssl.wrap_socket(
sock,
keyfile = self.key_file,
certfile = self.cert_file,
ca_certs = self.ca_certs,
cert_reqs = ssl.CERT_REQUIRED if self.ca_certs else ssl.CERT_NONE,
ssl_version = self.ssl_version)
class Error(Exception):
""" Object for returning error messages to calling application.
Caller can test whether it received data or error by checking
isinstance(res, Error).
However if he does not want to deal with errors altogether,
this error object also returns False value if used in Bool
context (e.g. in "if res: print res" print is not evaluated),
and also acts as empty iterator (e.g. in "for e in res: print e"
print is also not evaluated).
Also, it can be raised as an exception.
"""
def __init__(self, method=None, req_id=None, errors=None, **kwargs):
self.errors = []
if errors:
self.extend(method, req_id, errors)
if kwargs:
self.append(method, req_id, **kwargs)
def append(self, method=None, req_id=None, **kwargs):
# We shift method and req_id into each and every error, because
# we want to be able to simply merge more Error arrays (for
# returning errors from more Warden calls at once
if method and not "method" in kwargs:
kwargs["method"] = method
if req_id and not "req_id" in kwargs:
kwargs["req_id"] = req_id
# Ugly, but be paranoid, don't rely on server reply to be well formed
try:
kwargs["error"] = int(kwargs["error"])
except Exception:
kwargs["error"] = 0
if "events" in kwargs:
evlist = kwargs["events"]
try:
evlist_new = []
for ev in evlist:
try:
evlist_new.append(int(ev))
except Exception:
pass
kwargs["events"] = evlist_new
except Exception:
kwargs["events"] = []
if "events_id" in kwargs:
try:
dummy = iter(kwargs["events_id"])
except TypeError:
kwargs["events_id"] = [None]*len(kwargs["events"])
if "send_events_limit" in kwargs:
try:
kwargs["send_events_limit"] = int(kwargs["send_events_limit"])
except Exception:
del kwargs["send_events_limit"]
if "exc" in kwargs:
# Traceback objects cause reference loops, so memory may be not
# correctly free'd. We only need traceback to log it in str_debug(),
# so let's get the string representation now and forget the
# traceback object, thus preventing the loop.
exctype, excvalue, tb = kwargs["exc"]
tb = format_tb(tb)
kwargs["exc"] = exctype, excvalue, tb
self.errors.append(kwargs)
def extend(self, method=None, req_id=None, iterable=[]):
try:
dummy = iter(iterable)
except TypeError:
iterable = [] # Bad joke from server
for e in iterable:
try:
args = dict(e)
except TypeError:
args = {} # Not funny!
self.append(method, req_id, **args)
def __len__ (self):
""" In list or iterable context we're empty """
return 0
def __iter__(self):
""" We are the iterator """
return self
def next(self):
""" In list or iterable context we're empty """
raise StopIteration
__next__ = next
def __bool__(self):
""" In boolean context we're never True """
return False
def __str__(self):
out = []
for e in self.errors:
out.append(self.str_err(e))
out.append(self.str_info(e))
return "\n".join(out)
def log(self, logger=None, prio=logging.ERROR):
if not logger:
logger = logging.getLogger()
for e in self.errors:
logger.log(prio, self.str_err(e))
info = self.str_info(e)
if info:
logger.info(info)
debug = self.str_debug(e)
if debug:
logger.debug(debug)
def str_preamble(self, e):
return "%08x/%s" % (e.get("req_id", 0), e.get("method", "?"))
def str_err(self, e):
out = []
out.append(self.str_preamble(e))
out.append(" Error(%s) %s " % (e.get("error", 0), e.get("message", "Unknown error")))
if "exc" in e and e["exc"]:
out.append("(cause was %s: %s)" % (e["exc"][0].__name__, str(e["exc"][1])))
return "".join(out)
def str_info(self, e):
ecopy = dict(e) # shallow copy
ecopy.pop("req_id", None)
ecopy.pop("method", None)
ecopy.pop("error", None)
ecopy.pop("message", None)
ecopy.pop("exc", None)
if ecopy:
out = "%s Detail: %s" % (self.str_preamble(e), json.dumps(ecopy, default=lambda v: str(v)))
else:
out = ""
return out
def str_debug(self, e):
out = []
out.append(self.str_preamble(e))
if not "exc" in e or not e["exc"]:
return ""
exc_tb = e["exc"][2] # exc_tb is string repr. of traceback object
if exc_tb:
out.append("Traceback:\n")
out.extend(exc_tb)
return "".join(out)
class Client(object):
def __init__(self,
url,
certfile=None,
keyfile=None,
cafile=None,
timeout=60,
retry=20,
pause=10,
get_events_limit=6000,
send_events_limit=500,
errlog={},
syslog=None,
filelog=None,
idstore=None,
name="org.example.warden.test",
secret=None):
self.name = name
self.secret = secret
# Init logging as soon as possible and make sure we don't
# spit out exceptions but just log or return Error objects
self.init_log(errlog, syslog, filelog)
self.url = urlparse(url, allow_fragments=False)
self.conn = None
base = path.join(path.dirname(__file__))
self.certfile = self.get_readable_file(certfile or "cert.pem", base)
self.keyfile = self.get_readable_file(keyfile or "key.pem", base)
self.cafile = self.get_readable_file(cafile if cafile is not None else DEFAULT_CA_STORES, base)
self.timeout = int(timeout)
self.get_events_limit = int(get_events_limit)
self.idstore = path.join(base, idstore) if idstore is not None else None
self.send_events_limit = int(send_events_limit)
self.retry = int(retry)
self.pause = int(pause)
self.ciphers = None
self.sslversion = getattr(ssl, "PROTOCOL_TLS", ssl.PROTOCOL_SSLv23)
# If Python is new enough to have SSLContext, use it for SSL settings,
# otherwise our own class derived from httplib.HTTPSConnection is used
# later in connect().
if hasattr(ssl, 'SSLContext'):
self.sslcontext = ssl.SSLContext(self.sslversion)
self.sslcontext.load_cert_chain(self.certfile, self.keyfile)
if self.cafile:
self.sslcontext.load_verify_locations(self.cafile)
self.sslcontext.verify_mode = ssl.CERT_REQUIRED
else:
self.sslcontext.verify_mode = ssl.CERT_NONE
else:
self.sslcontext = None
self.getInfo() # Call to align limits with server opinion
def get_readable_file(self, name, base):
names = [name] if isinstance(name, basestring) else name
names = [path.join(base, n) for n in names]
errors = []
for n in names:
try:
open(n, "r").close()
self.logger.debug("Using %s" % n)
return n
except IOError as e:
errors.append(e)
for e in errors:
self.logger.error(str(e))
return names[0] if names else None
def init_log(self, errlog, syslog, filelog):
def loglevel(lev):
try:
return int(getattr(logging, lev.upper()))
except (AttributeError, ValueError):
self.logger.warning("Unknown loglevel \"%s\", using \"debug\"" % lev)
return logging.DEBUG
def facility(fac):
try:
return int(getattr(logging.handlers.SysLogHandler, "LOG_" + fac.upper()))
except (AttributeError, ValueError):
self.logger.warning("Unknown syslog facility \"%s\", using \"local7\"" % fac)
return logging.handlers.SysLogHandler.LOG_LOCAL7
form = "%(filename)s[%(process)d]: %(name)s (%(levelname)s) %(message)s"
format_notime = logging.Formatter(form)
format_time = logging.Formatter('%(asctime)s ' + form)
self.logger = logging.getLogger(self.name)
self.logger.propagate = False # Don't bubble up to root logger
self.logger.setLevel(logging.DEBUG)
if errlog is not None:
el = logging.StreamHandler(stderr)
el.setFormatter(format_time)
el.setLevel(loglevel(errlog.get("level", "info")))
self.logger.addHandler(el)
if filelog is not None:
try:
fl = logging.FileHandler(
filename=path.join(
path.dirname(__file__),
filelog.get("file", "%s.log" % self.name)),
encoding="utf-8")
fl.setLevel(loglevel(filelog.get("level", "debug")))
fl.setFormatter(format_time)
self.logger.addHandler(fl)
except Exception as e:
Error(message="Unable to setup file logging", exc=exc_info()).log(self.logger)
if syslog is not None:
try:
sl = logging.handlers.SysLogHandler(
address=fix_logging_filename(syslog.get("socket", "/dev/log")),
facility=facility(syslog.get("facility", "local7")))
sl.setLevel(loglevel(syslog.get("level", "debug")))
sl.setFormatter(format_notime)
self.logger.addHandler(sl)
except Exception as e:
Error(message="Unable to setup syslog logging", exc=exc_info()).log(self.logger)
if not (errlog or filelog or syslog):
# User wants explicitly no logging, so let him shoot his socks off.
# This silences complaining of logging module about no suitable
# handler.
self.logger.addHandler(logging.NullHandler())
def log_err(self, err, prio=logging.ERROR):
if isinstance(err, Error):
err.log(self.logger, prio)
return err
def connect(self):
try:
if self.url.scheme=="https":
if self.sslcontext:
conn = httplib.HTTPSConnection(
self.url.netloc,
timeout = self.timeout,
context = self.sslcontext)
else:
conn = HTTPSConnection(
self.url.netloc,
key_file = self.keyfile,
cert_file = self.certfile,
timeout = self.timeout,
ciphers = self.ciphers,
ca_certs = self.cafile,
ssl_version = self.sslversion)
elif self.url.scheme=="http":
conn = httplib.HTTPConnection(
self.url.netloc,
timeout = self.timeout)
else:
return Error(message="Don't know how to connect to \"%s\"" % self.url.scheme,
url=self.url.geturl())
except Exception:
return Error(message="HTTP(S) connection failed", exc=exc_info(),
url=self.url.geturl(),
timeout=self.timeout,
key_file=self.keyfile,
cert_file=self.certfile,
cafile=self.cafile,
ciphers=self.ciphers,
ssl_version=self.sslversion)
return conn
def sendRequest(self, func="", payload=None, **kwargs):
if self.secret is None:
kwargs["client"] = self.name
else:
kwargs["secret"] = self.secret
if kwargs:
for k in list(kwargs.keys()):
if kwargs[k] is None:
del kwargs[k]
argurl = "?" + urlencode(kwargs, doseq=True)
else:
argurl = ""
self.headers = {"Accept": "application/json"}
data = None
if payload is None:
method = "GET"
else:
method = "POST"
try:
data = json.dumps(payload)
except:
return Error(message="Serialization to JSON failed",
exc=exc_info(), method=func, payload=payload)
self.headers.update({
"Content-Type": "application/json",
"Content-Length": str(len(data))
})
# HTTP(S)Connection is oneshot object (and we don't speak "pipelining")
conn = self.connect()
if not conn:
return conn # either False of Error instance
loc = '%s/%s%s' % (self.url.path, func, argurl)
try:
conn.request(method, loc, data, self.headers)
except:
conn.close()
return Error(message="Sending of request to server failed",
exc=exc_info(), method=func, log=loc, headers=self.headers, data=data)
try:
res = conn.getresponse()
except:
conn.close()
return Error(method=func, message="HTTP reply failed",
exc=exc_info(), loc=loc, headers=self.headers, data=data)
try:
response_data = res.read()
except:
conn.close()
return Error(method=func, message="Fetching HTTP data from server failed",
exc=exc_info(), loc=loc, headers=self.headers, data=data)
conn.close()
if res.status==httplib.OK:
try:
data = json.loads(response_data.decode("utf-8"))
except:
data = Error(method=func, message="JSON message parsing failed",
exc=exc_info(), response=response_data)
else:
try:
data = json.loads(response_data.decode("utf-8"))
data["errors"] # trigger exception if not dict or no error key
except:
data = Error(method=func, message="Generic server HTTP error",
error=res.status, exc=exc_info(), response=response_data)
else:
data = Error(
method=data.get("method", None),
req_id=data.get("req_id", None),
errors=data.get("errors", []))
return data
def _saveID(self, id, idstore=None):
idf = idstore or self.idstore
if not idf:
return False
try:
with open(idf, "w+") as f:
f.write(str(id))
except (ValueError, IOError) as e:
# Use Error instance just for proper logging
Error(message="Writing id file \"%s\" failed" % idf, exc=exc_info(),
idstore=idf).log(self.logger, logging.INFO)
return id
def _loadID(self, idstore=None):
idf = idstore or self.idstore
if not idf:
return None
try:
with open(idf, "r") as f:
id = int(f.read())
except (ValueError, IOError) as e:
Error(message="Reading id file \"%s\" failed, relying on server" % idf,
exc=exc_info(), idstore=idf).log(self.logger, logging.INFO)
id = None
return id
def getDebug(self):
return self.log_err(self.sendRequest("getDebug"))
def getInfo(self):
res = self.sendRequest("getInfo")
if isinstance(res, Error):
res.log(self.logger)
else:
try:
self.send_events_limit = min(res["send_events_limit"], self.send_events_limit)
self.get_events_limit = min(res["get_events_limit"], self.get_events_limit)
except (AttributeError, TypeError, KeyError):
pass
return res
def send_events_raw(self, events=[]):
return self.sendRequest("sendEvents", payload=events)
def send_events_chunked(self, events=[]):
""" Split potentially long "events" list to send_events_limit
long chunks to avoid slap from server.
"""
count = len(events)
err = Error()
send_events_limit = self.send_events_limit # object stored value can change during sending
for offset in range(0, count, send_events_limit):
res = self.send_events_raw(events[offset:min(offset+send_events_limit, count)])
if isinstance(res, Error):
# Shift all error indices by offset to correspond with 'events' list
for e in res.errors:
evlist = e.get("events", [])
# Update sending limit advice, if present in error
srv_limit = e.get("send_events_limit")
if srv_limit:
self.send_events_limit = min(self.send_events_limit, srv_limit)
for i in range(len(evlist)):
evlist[i] += offset
err.errors.extend(res.errors)
return err if err.errors else {}
def sendEvents(self, events=[], retry=None, pause=None):
""" Send out "events" list to server, retrying on server errors.
"""
ev = events
idx_xlat = list(range(len(ev)))
err = Error()
retry = retry or self.retry
attempt = retry
while ev and attempt:
if attempt<retry:
self.logger.info("%d transient errors, retrying (%d to go)" % (len(ev), attempt))
time.sleep(pause or self.pause)
res = self.send_events_chunked(ev)
attempt -= 1
next_ev = []
next_idx_xlat = []
if isinstance(res, Error):
# Sort to process fatal errors first
res.errors.sort(key=itemgetter("error"))
for e in res.errors:
errno = e["error"]
evlist = e.get("events", list(range(len(ev)))) # none means all
if errno < 500 or not attempt:
# Fatal error or last try, translate indices
# to original and prepare for returning to caller
for i in range(len(evlist)):
evlist[i] = idx_xlat[evlist[i]]
err.errors.append(e)
else:
# Maybe transient error, prepare to try again
for evlist_i in evlist:
next_ev.append(ev[evlist_i])
next_idx_xlat.append(idx_xlat[evlist_i])
ev = next_ev
idx_xlat = next_idx_xlat
return self.log_err(err) if err.errors else {"saved": len(events)}
def getEvents(self, id=None, idstore=None, count=None,
cat=None, nocat=None,
tag=None, notag=None,
group=None, nogroup=None):
if id is None:
id = self._loadID(idstore)
res = self.sendRequest(
"getEvents", id=id, count=self.get_events_limit if count is None else count, cat=cat,
nocat=nocat, tag=tag, notag=notag, group=group, nogroup=nogroup)
if res:
try:
events = res["events"]
newid = res["lastid"]
except KeyError:
events = Error(method="getEvents", message="Server returned bogus reply",
exc=exc_info(), response=res)
self._saveID(newid)
else:
events = res
return self.log_err(events)
def close(self):
if hasattr(self, "conn") and hasattr(self.conn, "close"):
self.conn.close()
__del__ = close
def format_timestamp(epoch=None, utc=True, utcoffset=None):
if utcoffset is None:
utcoffset = -(time.altzone if time.daylight else time.timezone)
if epoch is None:
epoch = time.time()
if utc:
epoch += utcoffset
us = int(epoch % 1 * 1000000 + 0.5)
return format_time(*time.gmtime(epoch)[:6], microsec=us, utcoffset=utcoffset)
def format_time(year, month, day, hour, minute, second, microsec=0, utcoffset=None):
if utcoffset is None:
utcoffset = -(time.altzone if time.daylight else time.timezone)
tstr = "%04d-%02d-%02dT%02d:%02d:%02d" % (year, month, day, hour, minute, second)
usstr = "." + str(microsec).rstrip("0") if microsec else ""
offsstr = ("%+03d:%02d" % divmod((utcoffset+30)//60, 60)) if utcoffset else "Z"
return tstr + usstr + offsstr
def read_cfg(cfgfile):
with open(cfgfile, "r") as f:
stripcomments = "\n".join((l for l in f if not l.lstrip().startswith(("#", "//"))))
return json.loads(stripcomments)
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2011-2015 Cesnet z.s.p.o
# Use of this source is governed by a 3-clause BSD-style license, see LICENSE file.
import string
from time import time
from uuid import uuid4
from pprint import pprint
from random import randint, randrange, choice, random
from base64 import b64encode
from warden_client import Client, Error, read_cfg, format_timestamp
def gen_min_idea():
return {
"Format": "IDEA0",
"ID": str(uuid4()),
"DetectTime": format_timestamp(),
"Category": ["Test"],
}
def gen_random_idea(client_name="cz.example.warden.test"):
def geniprange(gen):
def iprange():
u = v = 0
while u==v:
u, v = gen(), gen()
u, v = min(u, v), max(u, v)
return "%s-%s" % (u, v)
return iprange
def rand4ip():
return "%s%d" % ('192.0.2.', randint(1, 254))
def rand4cidr():
return "%s%d/%d" % ('192.0.2.', randint(1, 254), randint(24, 31))
def randip4():
return [rand4ip, geniprange(rand4ip), rand4cidr][randint(0, 2)]()
def rand6ip():
return "2001:DB8:%s" % ":".join("%x" % randint(0, 65535) for i in range(6))
def rand6cidr():
m = randint(0, 5)
return "2001:DB8%s%s::/%d" % (":" if m else "", ":".join("%x" % randint(0, 65535) for i in range(m)), (m+2)*16)
def randip6():
return [rand6ip, geniprange(rand6ip), rand6cidr][randint(0, 2)]()
def randstr(charlist=string.ascii_letters, maxlen=32, minlen=1):
return ''.join(choice(charlist) for i in range(randint(minlen, maxlen)))
event = {
"Format": "IDEA0",
"ID": str(uuid4()),
"CreateTime": format_timestamp(),
"DetectTime": format_timestamp(),
"WinStartTime": format_timestamp(),
"WinEndTime": format_timestamp(),
"EventTime": format_timestamp(),
"CeaseTime": format_timestamp(),
#"Category": ["Abusive.Spam","Abusive.Harassment","Malware","Fraud.Copyright","Test","Fraud.Phishing","Fraud.Scam"],
# "Category": ["Abusive.Spam","Fraud.Copyright"],
"Category": [choice(["Abusive.Spam","Abusive.Harassment","Malware","Fraud.Copyright","Test","Fraud.Phishing","Fraud.Scam"]) for dummy in range(randint(1, 3))],
"Ref": ["cve:CVE-%s-%s" % (randstr(string.digits, 4), randstr()), "http://www.example.com/%s" % randstr()],
"Confidence": random(),
"Note": "Random event",
"ConnCount": randint(0, 65535),
# "ConnCount": choice([randint(0, 65535), "asdf"]), # Send wrong event sometimes
"Source": [
{
"Type": ["Phishing"],
"IP4": [randip4() for i in range(randrange(1, 5))],
"IP6": [randip6() for i in range(randrange(1, 5))],
"Hostname": ["example.com"],
"Port": [randint(1, 65535) for i in range(randrange(1, 3))],
"AttachHand": ["att1"],
"Netname": ["arin:TEST-NET-1"]
}
],
"Target": [
{
"IP4": [randip4() for i in range(randrange(1, 5))],
"IP6": [randip6() for i in range(randrange(1, 5))],
"URL": ["http://example.com/%s" % randstr()],
"Proto": ["tcp", "http"],
"Netname": ["arin:TEST-NET-1"]
}
],
"Attach": [
{
"Handle": "att1",
"FileName": [randstr()],
"Type": ["Malware"],
"ContentType": "application/octet-stream",
"Hash": ["sha1:%s" % randstr(string.hexdigits, 24)],
"Size": 46,
"Ref": ["cve:CVE-%s-%s" % (randstr(string.digits, 4), randstr())],
"ContentEncoding": "base64",
"Content": b64encode(randstr().encode('ascii')).decode("ascii")
}
],
"Node": [
{
"Name": client_name,
"Type": [choice(["Data", "Protocol", "Honeypot", "Heuristic", "Log"]) for dummy in range(randint(1, 3))],
"SW": ["Kippo"],
"AggrWin": "00:05:00"
},
{
"Name": "org.example.warden.client",
"Type": [choice(["Connection", "Datagram"]) for dummy in range(randint(1, 2))],
}
]
}
return event
def main():
wclient = Client(**read_cfg("warden_client.cfg"))
# Also inline arguments are possible:
# wclient = Client(
# url = 'https://warden.example.com/warden3',
# keyfile = '/opt/warden3/etc/key.pem',
# certfile = '/opt/warden3/etc/cert.pem',
# cafile = '/opt/warden3/etc/tcs-ca-bundle.pem',
# timeout=10,
# errlog={"level": "debug"},
# filelog={"level": "debug"},
# idstore="MyClient.id",
# name="cz.example.warden.test")
print("=== Debug ===")
info = wclient.getDebug()
pprint(info)
# All methods return something.
# If you want to catch possible errors (for example implement some
# form of persistent retry, or save failed events for later, you may
# check for Error instance and act based on contained info.
# If you want just to be informed, this is not necessary, just
# configure logging correctly and check logs.
if isinstance(info, Error):
print(info)
print("=== Server info ===")
info = wclient.getInfo()
print("=== Sending 10 event(s) ===")
start = time()
ret = wclient.sendEvents([gen_random_idea(client_name=wclient.name) for i in range(10)])
print(ret)
print("Time: %f" % (time()-start))
print("=== Getting 10 events ===")
start = time()
# cat = ['Availability', 'Abusive.Spam','Attempt.Login']
# cat = ['Attempt', 'Information','Fraud.Scam','Malware.Virus']
# cat = ['Fraud', 'Abusive.Spam']
# nocat = ['Availability', 'Information', 'Fraud.Scam']
cat = []
nocat = []
#tag = ['Log', 'Data']
#notag = ['Flow', 'Datagram']
tag = []
notag = []
#group = ['cz.tul.ward.kippo','cz.vsb.buldog.kippo']
#nogroup = ['cz.zcu.civ.afrodita','cz.vutbr.net.bee.hpscan']
group = []
nogroup = []
ret = wclient.getEvents(count=10, cat=cat, nocat=nocat, tag=tag, notag=notag, group=group, nogroup=nogroup)
print("Time: %f" % (time()-start))
print("Got %i events" % len(ret))
for e in ret:
print(e.get("Category"), e.get("Node")[0].get("Type"), e.get("Node")[0].get("Name"))
if __name__ == "__main__":
main()
#!/bin/sh
#
# Copyright (C) 2011-2015 Cesnet z.s.p.o
# Use of this source is governed by a 3-clause BSD-style license, see LICENSE file.
if [ "$#" -ne 6 ]; then
echo "Run me like:"
echo "${0##*/} 'https://warden-hub.example.org/warden3' org.example.warden.client 'ToPsEcReT' key.pem cert.pem tcs-ca-bundle.pem"
exit 1
fi
url="$1"
client="$2"
secret="$3"
keyfile="$4"
certfile="$5"
cafile="$6"
echo "Test 404"
curl \
--key $keyfile \
--cert $certfile \
--cacert $cafile \
--connect-timeout 3 \
"$url/blefub?client=$client&secret=$secret"
echo
echo "Test 404"
curl \
--key $keyfile \
--cert $certfile \
--cacert $cafile \
--connect-timeout 3 \
"$url/?client=$client&secret=$secret"
echo
echo "Test 403 - no secret"
curl \
--key $keyfile \
--cert $certfile \
--cacert $cafile \
--connect-timeout 3 \
"$url/getEvents?client=$client"
echo
echo "Test 403 - no client, no secret"
curl \
--key $keyfile \
--cert $certfile \
--cacert $cafile \
--connect-timeout 3 \
"$url/getEvents"
echo
echo "Test 403 - wrong client"
curl \
--key $keyfile \
--cert $certfile \
--cacert $cafile \
--connect-timeout 3 \
"$url/getEvents?client=asdf.blefub"
echo
echo "Test 403 - wrong client, right secret"
curl \
--key $keyfile \
--cert $certfile \
--cacert $cafile \
--connect-timeout 3 \
"$url/getEvents?client=asdf.blefub&secret=$secret"
echo
echo "Test 403 - right client, wrong secret"
curl \
--key $keyfile \
--cert $certfile \
--cacert $cafile \
--connect-timeout 3 \
"$url/getEvents?client=$client&secret=ASDFblefub"
echo
echo "Test - no client, but secret, should be ok"
curl \
--key $keyfile \
--cert $certfile \
--cacert $cafile \
--connect-timeout 3 \
"$url/getEvents?secret=$secret"
echo
echo "Test Deserialization"
curl \
--key $keyfile \
--cert $certfile \
--cacert $cafile \
--connect-timeout 3 \
--request POST \
--data '{#$%^' \
"$url/getEvents?client=$client&secret=$secret"
echo
echo "Test Called with unknown category"
curl \
--key $keyfile \
--cert $certfile \
--cacert $cafile \
--connect-timeout 3 \
"$url/getEvents?client=$client&secret=$secret&cat=bflm"
echo
echo "Test Called with both cat and nocat"
curl \
--key $keyfile \
--cert $certfile \
--cacert $cafile \
--connect-timeout 3 \
"$url/getEvents?client=$client&secret=$secret&cat=Other&nocat=Test"
echo
echo "Test Invalid data for getEvents - silently discarded"
curl \
--key $keyfile \
--cert $certfile \
--cacert $cafile \
--connect-timeout 3 \
--request POST \
--data '[1]' \
"$url/getEvents?client=$client&secret=$secret"
echo
echo "Test Called with internal args - just in log"
curl \
--key $keyfile \
--cert $certfile \
--cacert $cafile \
--connect-timeout 3 \
"$url/getEvents?client=$client&secret=$secret&self=test"
echo
echo "Test Called with superfluous args - just in log"
curl \
--key $keyfile \
--cert $certfile \
--cacert $cafile \
--connect-timeout 3 \
"$url/getEvents?client=$client&secret=$secret&bad=guy"
echo
echo "Test getEvents with no args - should be OK"
curl \
--key $keyfile \
--cert $certfile \
--cacert $cafile \
--connect-timeout 3 \
"$url/getEvents?client=$client&secret=$secret"
echo
echo "Test getEvents - should be OK"
curl \
--key $keyfile \
--cert $certfile \
--cacert $cafile \
--connect-timeout 3 \
"$url/getEvents?client=$client&secret=$secret&count=3&id=10"
echo
echo "Test getDebug"
curl \
--key $keyfile \
--cert $certfile \
--cacert $cafile \
--connect-timeout 3 \
"$url/getDebug?client=$client&secret=$secret"
echo
echo "Test getInfo"
curl \
--key $keyfile \
--cert $certfile \
--cacert $cafile \
--connect-timeout 3 \
"$url/getInfo?client=$client&secret=$secret"
echo
BSD License
Copyright © 2011-2015 Cesnet z.s.p.o
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the Cesnet z.s.p.o nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE Cesnet z.s.p.o BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------+
| Warden Filer 3.0-beta3 for Warden 3.X |
+---------------------------------------+
Content
A. Introduction
B. Dependencies
C. Usage
D. Configuration
E. Directories and locking issues
------------------------------------------------------------------------------
A. Introduction
Warden Filer (executable warden_filer.py) is daemon for easy handling of
Idea events transfer between plain local files and Warden server. The tool can
be instructed to run as one of two daemons - reader and sender.
In reader mode, Filer polls Warden server and saves incoming events as
plain files in directory.
In writer mode, Filer polls directory and sends out all new files out to
Warden server.
------------------------------------------------------------------------------
B. Dependencies
1. Platform
Python 2.7+
2. Python packages
python-daemon 1.5+, warden_client 3.0+
------------------------------------------------------------------------------
C. Usage
warden_filer.py [-h] [-c CONFIG] [--oneshot] {sender,receiver}
Save Warden events as files or send files to Warden
positional arguments:
{sender,receiver} choose direction: sender picks up files and submits
them to Warden, receiver pulls events from Warden
and saves them as files
optional arguments:
-h, --help show this help message and exit
-c CONFIG, --config CONFIG
configuration file path
--oneshot don't daemonise, run just once
-d, --daemon daemonize
-p PID_FILE, --pid_file PID_FILE
create PID file with this name
CONFIG denotes path to configuration file, default is warden_filer.cfg in
current directory.
--oneshot instructs Filer to just do its work once (fetch available events
or send event files present in directory), but obeys all other applicable
options from configuration file (concerning logging, filtering, directories,
etc.)
--daemon instructs Filer to go to full unix daemon mode. Without it,
Filer just stays on foreground.
--pid_file makes Filer to create the usual PID file. Without it, no PID
file gets created.
------------------------------------------------------------------------------
D. Configuration
Configuration is JSON object in file - however, lines starting with "#"
or "//" are allowed and will be ignored as comments. File must contain valid
JSON object, containing configuration. See also warden_filer.cfg as example.
warden - can contain Warden 3 configuration (see Warden doc), or path
to Warden configuration file
sender - configuration section for sender mode
dir - directory, whose "incoming" subdir will be checked for Idea
events to send out
done_dir - directory, into which the messages will be moved after
successful sending. If not set, processed messages will get
deleted, which is default, and usually what you want. Note that
this is just regular directory, no special locking precautions
and no subdirectories are done here, however if "done_dir" is on
the same filesystem as "dir"
filter - filter fields (same as in Warden query, see Warden and Idea
doc, possible keys: cat, nocat, group, nogroup, tag, notag),
unmatched events get discarded and deleted
node - o information about detector to be prepended into event Node
array (see Idea doc). Note that Warden server may require it to
correspond with client registration
poll_time - how often to check incoming directory (in seconds, defaults
to 5)
owait_timeout - how long to opportunistically wait for possible new
incoming files when number of files to process is less than
send_events_limit (in seconds, defaults to poll_time)
owait_poll_time - how often to check incoming directory during
opportunistic timeout (in seconds, defaults to 1)
receiver - configuration section for receiver mode
dir - directory, whose "incoming" subdir will serve as target for events
filter - filter fields for Warden query (see Warden and Idea doc,
possible keys: cat, nocat, group, nogroup, tag, notag)
node - o information about detector to be prepended into event Node
array (see Idea doc). Be careful here, you may ruin Idea
messages by wrongly formatted data and they are not checked
here in any way
poll_time - how often to check Warden server for new events (in seconds,
defaults to 5)
file_limit - limit number of files in "incoming" directory. When the limit
is reached, polling is paused for "limit_wait_time" seconds
limit_wait_time - wait this number of seconds if limit on number of files
is reached (defaults to 5)
Both the "sender" and "reciever" sections can also bear daemon
configuration.
work_dir - where should daemon chdir
chroot_dir - confine daemon into chroot directory
umask - explicitly set umask for created files
uid, gid - uid/gid, under which daemon will run
------------------------------------------------------------------------------
E. Directories and locking issues
Working directories are not just simple paths, but contain structure,
loosely mimicked from Maildir with slightly changed names to avoid first look
confusion. Simple path suffers locking issue: when one process saves file
there, another process has no way to know whether file is already complete
or not, and starting to read prematurely can lead to corrupted data read.
Also, two concurrent processes may decide to work on one file, stomping on
others legs.
So, your scripts and tools inserting data or taking data from working
directories must obey simple protocols, which use atomic "rename" to avoid
locking issues.
Also, your directory (and its structure) _must_ reside on the same
filesystem to keep "rename" atomic. _Never_ try to mount some of the
subdirectories ("tmp", "incoming", "errors") from other filesystem.
1. Inserting file
* The file you want to create _must_ be created in the "tmp" subdirectory
first, _not_ "incoming". Filename is arbitrary, but must be unique among
all subdirectories.
* When done writing, rename the file into "incoming" subdir. Rename is
atomic operation, so for readers, file will appear either nonexistent
or complete.
For simple usage (bash scripts, etc.), just creating sufficiently random
filename in "tmp" and then moving into "incoming" may be enough.
Concatenating $RANDOM couple of times will do. :)
For advanced or potentially concurrent usage inserting enough of unique
information into name is recommended - Filer itself uses hostname, pid,
unixtime, milliseconds, device number and file inode number to avoid
locking issues both on local and network based filesystems and to be
prepared for high traffic.
2. Picking up file
* Rename the file to work with into "tmp" directory.
* Do whatever you want with contents, and when finished, rename file back
into "incoming", or remove, or move somewhere else, or move into "errors"
directory - what suits your needs, after all, it's your file.
Note that in concurrent environment file can disappear between directory
enumeration and attempt to rename - then just pick another one (and
possibly repeat), someone was swifter.
------------------------------------------------------------------------------
Copyright (C) 2011-2015 Cesnet z.s.p.o
#!/bin/bash
script=${0##*/}
warn=0
crit=65536
read -rd '' helps <<EOF
$script: Icinga plugin to check too high number of files in directory.
Usage: $script -d dir [-w num] [-c num] [-h ]
-d dir directory to watch
-w num warning if number of files exceeds this value (default $warn)
-c num critical if number of files exceeds this value (default $crit)
EOF
function bailout {
echo -n "$script" | tr '[:lower:]' '[:upper:]'
echo " $2 $3"
exit "$1"
}
while getopts hvVd:w:c: opt; do
case "$opt" in
h) bailout 3 "UNKNOWN" "$helps";;
d) dir="$OPTARG";;
w) warn="$OPTARG";;
c) crit="$OPTARG";;
"?") bailout 3 "UNKNOWN" "Unknown option, use -h for help";;
esac
done
[ -z "$dir" ] && bailout 3 "UNKNOWN" "-d not specified"
count=$(find "$dir" -mindepth 1 -maxdepth 1 | wc -l)
[ "$count" -gt "$crit" ] && bailout 2 "CRIT" "$count"
[ "$count" -gt "$warn" ] && bailout 1 "WARN" "$count"
bailout 0 "OK" "$count"
// For all options see documentation
{
// Warden config can be also referenced as:
// "warden": "/path/to/warden_client.cfg"
"warden": {
"url": "https://example.com/warden3",
"cafile": "tcs-ca-bundle.pem",
"keyfile": "my.key.pem",
"certfile": "my.cert.pem",
"timeout": 60,
"retry": 20,
"pause": 5,
"filelog": {"level": "debug"},
"name": "com.example.warden.test",
"secret": "SeCrEt"
},
"sender": {
// Maildir like directory, whose "incoming" subdir will be checked
// for Idea events to send out
"dir": "warden_sender",
// Optional filter fields, unmatched events are discarded (and removed)
//"filter": {
// "cat": ["Test", "Recon.Scanning"],
// "nocat": null,
// "group": ["cz.example"],
// "nogroup": null,
// "tag": null,
// "notag": ["Honeypot"]
//},
// Optional information about detector to be prepended into Idea Node array
//"node": {
// "Name": "cz.example.warden.test_sender",
// "Type": ["Relay"]
//}
},
"receiver": {
// Maildir like directory, whose "incoming" will serve as target for events
"dir": "warden_receiver",
// Optional filter fields for Warden query
//"filter": {
// "cat": ["Test", "Recon.Scanning"],
// "nocat": null,
// "group": ["cz.cesnet"],
// "nogroup": null,
// "tag": null,
// "notag": ["Honeypot"]
//},
// Optional information about detector to be prepended into Idea Node array
//"node": {
// "Name": "cz.example.warden.test_receiver",
// "Type": ["Relay"]
//},
// Optional limit on number of files in "incoming" directory
//"file_limit": 10000
}
}