mirror of
https://github.com/ovh/the-bastion.git
synced 2024-09-20 15:05:58 +08:00
feat: add NRPE probes
This commit is contained in:
parent
e71aa7b975
commit
bbdf5a36b8
|
@ -53,6 +53,8 @@ chmod 0755 "$basedir"/docker/entrypoint.sh \
|
|||
"$basedir"/tests/functional/proxy/remote-daemon \
|
||||
"$basedir"/tests/functional/fake_ttyrec.sh
|
||||
|
||||
find "$basedir"/contrib/nrpe/probes -type f -print0 | xargs -r0 chmod 0755
|
||||
|
||||
find "$basedir"/tests/unit -type f -name "*.pl" -print0 | xargs -r0 chmod 0755
|
||||
|
||||
while IFS= read -r -d '' plugin
|
||||
|
|
15
contrib/nrpe/README.md
Normal file
15
contrib/nrpe/README.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
NRPE Probes
|
||||
===========
|
||||
|
||||
A few NRPE probes are available in the ``probes/`` subdirectory.
|
||||
|
||||
Some of these probes might need to have elevated rights, an example of sudoers file is included.
|
||||
|
||||
You might want to also use the nice ``check_logfiles`` probe, courtesy of
|
||||
Consol Labs (https://labs.consol.de/nagios/check_logfiles/index.html), to ensure
|
||||
that the cron scripts behave correctly and that no error is happening during the backup process,
|
||||
the encrypt & rsync process, the HA synchronization daemon, etc.
|
||||
|
||||
The configuration of the ``check_logfiles`` probe can be found in ``etc/nagios/plugins.d``.
|
||||
|
||||
The bastion-side NRPE daemon configuration for these probes can be found in the ``etc/nagios/nrpe.d``.
|
|
@ -0,0 +1 @@
|
|||
command[check_bastion_healthcheck]=/usr/bin/sudo -u healthcheck /opt/bastion/contrib/nrpe/probes/bastion-healthcheck --host 127.0.0.1 --port 22 --account healthcheck --keyfile /home/healthcheck/.ssh/id_healthcheck
|
|
@ -0,0 +1 @@
|
|||
command[check_bastion_http_proxy]=/opt/bastion/contrib/nrpe/probes/bastion-http-proxy --host 127.0.0.1 --port 8443 --disabled-ok
|
1
contrib/nrpe/etc/nagios/nrpe.d/check_bastion_locked.cfg
Normal file
1
contrib/nrpe/etc/nagios/nrpe.d/check_bastion_locked.cfg
Normal file
|
@ -0,0 +1 @@
|
|||
command[check_bastion_locked]=/opt/bastion/contrib/nrpe/probes/bastion-locked
|
|
@ -0,0 +1 @@
|
|||
command[check_bastion_root_connected_too_long]=/opt/bastion/contrib/nrpe/probes/bastion-root-connected-too-long --warn-after-minutes 60 --crit-after-minutes 180
|
|
@ -0,0 +1 @@
|
|||
command[check_bastion_sync_daemon]=/usr/lib/nagios/plugins/check_dummy 0 'osh-sync-watcher is not intended to run on slave bastions'
|
1
contrib/nrpe/etc/nagios/nrpe.d/check_bastion_version.cfg
Normal file
1
contrib/nrpe/etc/nagios/nrpe.d/check_bastion_version.cfg
Normal file
|
@ -0,0 +1 @@
|
|||
command[check_bastion_version]=/opt/bastion/contrib/nrpe/probes/bastion-version
|
|
@ -0,0 +1 @@
|
|||
command[check_log_bastion_backup]=/usr/bin/sudo -u root /usr/lib/nagios/check_logfiles -f /etc/nagios/plugins.d/ovh/check_logfiles.cfg --searches=bastion_backup
|
|
@ -0,0 +1 @@
|
|||
command[check_log_bastion_encrypt_rsync]=/usr/bin/sudo -u root /usr/lib/nagios/check_logfiles -f /etc/nagios/plugins.d/ovh/check_logfiles.cfg --searches=bastion_encrypt_rsync
|
|
@ -0,0 +1 @@
|
|||
command[check_log_bastion_guest_key_cleanup]=/usr/bin/sudo -u root /usr/lib/nagios/check_logfiles -f /etc/nagios/plugins.d/ovh/check_logfiles.cfg --searches=bastion_guest_key_cleanup
|
|
@ -0,0 +1 @@
|
|||
command[check_log_bastion_misc]=/usr/bin/sudo -u root /usr/lib/nagios/check_logfiles -f /etc/nagios/plugins.d/ovh/check_logfiles.cfg --searches=bastion_misc
|
|
@ -0,0 +1 @@
|
|||
command[check_log_bastion_orphaned_homedir]=/usr/bin/sudo -u root /usr/lib/nagios/check_logfiles -f /etc/nagios/plugins.d/ovh/check_logfiles.cfg --searches=bastion_orphaned_homedir
|
|
@ -0,0 +1 @@
|
|||
command[check_log_bastion_piv_grace]=/usr/bin/sudo -u root /usr/lib/nagios/check_logfiles -f /etc/nagios/plugins.d/ovh/check_logfiles.cfg --searches=bastion_piv_grace
|
|
@ -0,0 +1 @@
|
|||
command[check_log_bastion_secondaries_sync]=/usr/bin/sudo -u root /usr/lib/nagios/check_logfiles -f /etc/nagios/plugins.d/ovh/check_logfiles.cfg --searches=bastion_secondaries_sync
|
56
contrib/nrpe/etc/nagios/plugins.d/check_logfiles.cfg
Normal file
56
contrib/nrpe/etc/nagios/plugins.d/check_logfiles.cfg
Normal file
|
@ -0,0 +1,56 @@
|
|||
# where the state information will be saved.
|
||||
$seekfilesdir = '/var/cache/nagios';
|
||||
|
||||
# where protocols with found patterns will be stored.
|
||||
$protocolsdir = $seekfilesdir;
|
||||
|
||||
@searches = (
|
||||
{
|
||||
tag => 'bastion_backup',
|
||||
logfile => '/var/log/bastion/bastion-scripts.log',
|
||||
criticalpatterns => ["will not be encrypted", "ERROR:"],
|
||||
okpatterns => ["Done, got 0 error"],
|
||||
options => 'allyoucaneat, sticky=86400, syslogclient=osh-backup-acl-keys.sh',
|
||||
},
|
||||
{
|
||||
tag => 'bastion_encrypt_rsync',
|
||||
logfile => '/var/log/bastion/bastion-scripts.log',
|
||||
criticalpatterns => ["ERROR:"],
|
||||
okpatterns => ["Done, got 0 error"],
|
||||
options => 'allyoucaneat, sticky=86400, syslogclient=osh-encrypt-rsync.pl',
|
||||
},
|
||||
{
|
||||
tag => 'bastion_orphaned_homedir',
|
||||
logfile => '/var/log/bastion/bastion-scripts.log',
|
||||
criticalpatterns => ["ERROR:"],
|
||||
okpatterns => ["Done, got 0 error"],
|
||||
options => 'allyoucaneat, sticky=900, syslogclient=osh-orphaned-homedir.sh',
|
||||
},
|
||||
{
|
||||
tag => 'bastion_piv_grace',
|
||||
logfile => '/var/log/bastion/bastion-scripts.log',
|
||||
criticalpatterns => ["ERROR:"],
|
||||
okpatterns => ["Done, got 0 error"],
|
||||
options => 'allyoucaneat, sticky=900, syslogclient=osh-piv-grace-reaper.pl',
|
||||
},
|
||||
{
|
||||
tag => 'bastion_guest_key_cleanup',
|
||||
logfile => '/var/log/bastion/bastion-scripts.log',
|
||||
criticalpatterns => ["ERROR:"],
|
||||
okpatterns => ["Done, got 0 error"],
|
||||
options => 'allyoucaneat, sticky=900, syslogclient=osh-cleanup-guest-key-access.pl',
|
||||
},
|
||||
{
|
||||
tag => 'bastion_misc',
|
||||
logfile => '/var/log/bastion/bastion-scripts.log',
|
||||
criticalpatterns => ["osh-lingering-sessions-reaper.sh.*ERROR:", "osh-rotate-ttyrec.sh.*ERROR:"],
|
||||
options => 'allyoucaneat, sticky=900',
|
||||
},
|
||||
{
|
||||
tag => 'bastion_secondaries_sync',
|
||||
logfile => '/var/log/bastion/bastion-scripts.log',
|
||||
criticalpatterns => ["ERROR:"],
|
||||
okpatterns => ["All secondaries have been synchronized successfully"],
|
||||
options => 'allyoucaneat, sticky=900, syslogclient=osh-sync-watcher.sh, criticalthreshold=6',
|
||||
},
|
||||
);
|
107
contrib/nrpe/probes/bastion-healthcheck
Executable file
107
contrib/nrpe/probes/bastion-healthcheck
Executable file
|
@ -0,0 +1,107 @@
|
|||
#! /usr/bin/env perl
|
||||
# vim: set filetype=perl ts=4 sw=4 sts=4 et:
|
||||
#
|
||||
# DESC: Check that the bastion code works (healtheck)
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use File::Basename;
|
||||
use Getopt::Long;
|
||||
|
||||
my $PROBE_NAME = basename($0);
|
||||
my $debug;
|
||||
|
||||
## no critic (Subroutines::RequireArgUnpacking)
|
||||
## no critic (Subroutines::RequireFinalReturn)
|
||||
|
||||
sub _out {
|
||||
my ($criticity, $msg) = @_;
|
||||
printf "%s %4s - %s\n", $PROBE_NAME, $criticity, $msg;
|
||||
}
|
||||
|
||||
sub _dbg { _out('dbg', $_[0]) if $debug; }
|
||||
sub _info { _out('info', $_[0]); }
|
||||
sub _warn { _out('WARN', $_[0]); }
|
||||
sub _err { _out('ERR!', $_[0]); }
|
||||
|
||||
sub success { my $msg = shift; _info($msg) if $msg; _info("status=OK"); exit(0); }
|
||||
sub warning { my $msg = shift; _warn($msg) if $msg; _info("status=WARN"); exit(1); }
|
||||
sub failure { my $msg = shift; _err($msg) if $msg; _info("status=FAILURE"); exit(2); }
|
||||
sub unknown { my $msg = shift; _err($msg) if $msg; _info("status=UNKNOWN"); exit(3); }
|
||||
|
||||
# OPTIONS
|
||||
|
||||
my $host = "127.0.0.1";
|
||||
my $port = 22;
|
||||
my $account = 'healthcheck';
|
||||
my $keyfile = '/home/healthcheck/.ssh/id_healthcheck';
|
||||
my $kbdinteractive = 0;
|
||||
|
||||
GetOptions(
|
||||
"help" => \my $help,
|
||||
"debug!" => \$debug,
|
||||
"host=s" => \$host,
|
||||
"port=i" => \$port,
|
||||
"account=s" => \$account,
|
||||
"keyfile=s" => \$keyfile,
|
||||
"kbd-interactive" => \$kbdinteractive,
|
||||
) or unknown("Failed parsing command-line");
|
||||
|
||||
# HELP
|
||||
|
||||
if ($help) {
|
||||
print <<"EOF";
|
||||
|
||||
$PROBE_NAME [options]
|
||||
|
||||
--help This help message
|
||||
--debug Increase verbosity of logs
|
||||
--host HOST Host to connect to. Default: $host
|
||||
--port PORT Port to connect to. Default: $port
|
||||
--account ACCOUNT Account name to use to authenticate. Default: $account
|
||||
--keyfile PATH Path to the private SSH key file to authenticate. Defaut: $keyfile
|
||||
--kbd-interactive Allow keyboard-interactive authentication. Default: $kbdinteractive
|
||||
|
||||
Note: don't specify an other option than --help to get the proper default values.
|
||||
|
||||
EOF
|
||||
unknown();
|
||||
}
|
||||
|
||||
# CODE
|
||||
|
||||
if ($account !~ /^[a-zA-Z0-9._-]+$/) {
|
||||
unknown("Specified account is invalid ($account)");
|
||||
}
|
||||
|
||||
if ($host !~ /^[a-zA-Z0-9._-]+$/) {
|
||||
unknown("Specified host is invalid ($host)");
|
||||
}
|
||||
|
||||
if ($port <= 0 || $port > 65535) {
|
||||
unknown("Specified port is invalid ($port)");
|
||||
}
|
||||
|
||||
if (!-f -r $keyfile) {
|
||||
unknown("Specified keyfile '$keyfile' is not readable or not a file");
|
||||
}
|
||||
|
||||
# first; check that sudo is healthy
|
||||
_dbg("Checking sudo viability...");
|
||||
my $sysret = system(qw{ sudo -n -v });
|
||||
if ($sysret != 0) {
|
||||
critical("sudo is broken!");
|
||||
}
|
||||
|
||||
my @cmd = ('ssh', '-l', $account, '-i', $keyfile, '-p', $port);
|
||||
if ($kbdinteractive) {
|
||||
push @cmd, ('-o', 'KbdInteractiveAuthentication=yes', '-o', 'PreferredAuthentications=publickey,keyboard-interactive');
|
||||
}
|
||||
push @cmd, ($host, '--', '-q', '--osh', 'info');
|
||||
_dbg("Executing: " . join(" ", @cmd));
|
||||
|
||||
$sysret = system(@cmd);
|
||||
if ($sysret == 0) {
|
||||
success("Connection worked and ended successfully");
|
||||
}
|
||||
failure("Connection failed (SSH return code = " . ($sysret >> 8) . ")");
|
133
contrib/nrpe/probes/bastion-http-proxy
Executable file
133
contrib/nrpe/probes/bastion-http-proxy
Executable file
|
@ -0,0 +1,133 @@
|
|||
#! /usr/bin/env perl
|
||||
# vim: set filetype=perl ts=4 sw=4 sts=4 et:
|
||||
#
|
||||
# DESC: Warn if the bastion HTTPS proxy is down
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use File::Basename;
|
||||
use Getopt::Long;
|
||||
use LWP::UserAgent;
|
||||
use IO::Socket::SSL;
|
||||
use JSON;
|
||||
|
||||
my $PROBE_NAME = basename($0);
|
||||
my $debug;
|
||||
|
||||
## no critic (Subroutines::RequireArgUnpacking)
|
||||
## no critic (Subroutines::RequireFinalReturn)
|
||||
|
||||
sub _out {
|
||||
my ($criticity, $msg) = @_;
|
||||
printf "%s %4s - %s\n", $PROBE_NAME, $criticity, $msg;
|
||||
}
|
||||
|
||||
sub _dbg { _out('dbg', $_[0]) if $debug; }
|
||||
sub _info { _out('info', $_[0]); }
|
||||
sub _warn { _out('WARN', $_[0]); }
|
||||
sub _err { _out('ERR!', $_[0]); }
|
||||
|
||||
sub success { my $msg = shift; _info($msg) if $msg; _info("status=OK"); exit(0); }
|
||||
sub warning { my $msg = shift; _warn($msg) if $msg; _info("status=WARN"); exit(1); }
|
||||
sub failure { my $msg = shift; _err($msg) if $msg; _info("status=FAILURE"); exit(2); }
|
||||
sub unknown { my $msg = shift; _err($msg) if $msg; _info("status=UNKNOWN"); exit(3); }
|
||||
|
||||
# OPTIONS
|
||||
|
||||
my $host = "127.0.0.1";
|
||||
my $DEFAULT_PORT = 8443;
|
||||
my $disabledOk = 0; # don't warn if proxy is disabled
|
||||
my $port;
|
||||
|
||||
GetOptions(
|
||||
"help" => \my $help,
|
||||
"debug!" => \$debug,
|
||||
"host=s" => \$host,
|
||||
"port=i" => \$port,
|
||||
"disabled-ok" => \$disabledOk,
|
||||
) or unknown("Failed parsing command-line");
|
||||
|
||||
# attempt to get a better shot at the default port
|
||||
my $json_data;
|
||||
if (open(my $conf, "<", "/etc/bastion/osh-http-proxy.conf")) {
|
||||
_dbg("opened https bastion config");
|
||||
local $/ = undef;
|
||||
$json_data = <$conf>;
|
||||
close($conf);
|
||||
|
||||
$json_data =~ s/#.*//g;
|
||||
my $json;
|
||||
eval { $json = decode_json($json_data); };
|
||||
if ($@) {
|
||||
_dbg("error decoding json ($@), keeping default port to $DEFAULT_PORT, and assuming proxy is enabled");
|
||||
$json->{'enabled'} = 1;
|
||||
}
|
||||
|
||||
# if config has a port and no port is specified on cmdline
|
||||
if ($json->{'port'} && !$port) {
|
||||
$port = $json->{'port'};
|
||||
_dbg("will use port $port as default, from config");
|
||||
}
|
||||
|
||||
# proxy is disabled by config
|
||||
if (!$json->{'enabled'}) {
|
||||
if ($disabledOk) {
|
||||
success("Proxy is disabled, and got --disabled-ok");
|
||||
}
|
||||
else {
|
||||
_warn("Proxy is disabled, but didn't get --disabled-ok, attempting to test nevertheless");
|
||||
}
|
||||
}
|
||||
|
||||
close($conf);
|
||||
}
|
||||
else {
|
||||
if ($disabledOk) {
|
||||
success("Specified --disabled-ok but couldn't find config file, assuming it's not installed");
|
||||
}
|
||||
_dbg("Couldn't open https bastion config, keeping default port to $DEFAULT_PORT");
|
||||
}
|
||||
|
||||
$port = $DEFAULT_PORT if not defined $port;
|
||||
|
||||
# HELP
|
||||
|
||||
if ($help) {
|
||||
print <<"EOF";
|
||||
|
||||
$PROBE_NAME [options]
|
||||
|
||||
--help This help message
|
||||
--debug Increase verbosity of logs
|
||||
--host HOST Host to connect to. Default: $host
|
||||
--port PORT Port to connect to. Default: $port (tentatively
|
||||
autodected from the HTTPS Bastion proxy configuration)
|
||||
--disabled-ok Return success even if Proxy is disabled (from config)
|
||||
|
||||
EOF
|
||||
unknown();
|
||||
}
|
||||
|
||||
# CODE
|
||||
|
||||
# verify_hostname == 0 is ok because that's not what we're verifying here
|
||||
my $ua = LWP::UserAgent->new(
|
||||
agent => 'NRPE',
|
||||
ssl_opts => {
|
||||
verify_hostname => 0,
|
||||
SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE
|
||||
}
|
||||
);
|
||||
my $result = $ua->get("https://$host:$port/bastion-health-check");
|
||||
|
||||
_info("Got HTTP result code " . $result->code);
|
||||
|
||||
if ($result->code == 200) {
|
||||
success("> $_") for split /\n/, $result->decoded_content;
|
||||
}
|
||||
elsif ($result->code == 202) {
|
||||
warning("> $_") for split /\n/, $result->decoded_content; # daemon should be reloaded
|
||||
}
|
||||
else {
|
||||
failure("> $_") for split /\n/, $result->decoded_content;
|
||||
}
|
60
contrib/nrpe/probes/bastion-locked
Executable file
60
contrib/nrpe/probes/bastion-locked
Executable file
|
@ -0,0 +1,60 @@
|
|||
#! /usr/bin/env perl
|
||||
# vim: set filetype=perl ts=4 sw=4 sts=4 et:
|
||||
#
|
||||
# DESC: Warn if bastion is locked (/home encrypted)
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use File::Basename;
|
||||
use Getopt::Long;
|
||||
|
||||
my $PROBE_NAME = basename($0);
|
||||
my $debug;
|
||||
|
||||
## no critic (Subroutines::RequireArgUnpacking)
|
||||
## no critic (Subroutines::RequireFinalReturn)
|
||||
|
||||
sub _out {
|
||||
my ($criticity, $msg) = @_;
|
||||
printf "%s %4s - %s\n", $PROBE_NAME, $criticity, $msg;
|
||||
}
|
||||
|
||||
sub _dbg { _out('dbg', $_[0]) if $debug; }
|
||||
sub _info { _out('info', $_[0]); }
|
||||
sub _warn { _out('WARN', $_[0]); }
|
||||
sub _err { _out('ERR!', $_[0]); }
|
||||
|
||||
sub success { my $msg = shift; _info($msg) if $msg; _info("status=OK"); exit(0); }
|
||||
sub warning { my $msg = shift; _warn($msg) if $msg; _info("status=WARN"); exit(1); }
|
||||
sub failure { my $msg = shift; _err($msg) if $msg; _info("status=FAILURE"); exit(2); }
|
||||
sub unknown { my $msg = shift; _err($msg) if $msg; _info("status=UNKNOWN"); exit(3); }
|
||||
|
||||
# OPTIONS
|
||||
|
||||
GetOptions(
|
||||
"help" => \my $help,
|
||||
"debug!" => \$debug,
|
||||
) or unknown("Failed parsing command-line");
|
||||
|
||||
# HELP
|
||||
|
||||
if ($help) {
|
||||
print <<"EOF";
|
||||
|
||||
$PROBE_NAME [options]
|
||||
|
||||
--help This help message
|
||||
--debug Increase verbosity of logs
|
||||
|
||||
EOF
|
||||
unknown();
|
||||
}
|
||||
|
||||
# CODE
|
||||
|
||||
if (-d "/home/allowkeeper") {
|
||||
_dbg("/home/allowkeeper exists and is a directory");
|
||||
success("bastion /home is unlocked");
|
||||
}
|
||||
_dbg("/home/allowkeeper doesn't exists or is not a directory");
|
||||
failure("bastion /home is locked!");
|
192
contrib/nrpe/probes/bastion-root-connected-too-long
Executable file
192
contrib/nrpe/probes/bastion-root-connected-too-long
Executable file
|
@ -0,0 +1,192 @@
|
|||
#! /usr/bin/env perl
|
||||
# vim: set filetype=perl ts=4 sw=4 sts=4 et:
|
||||
#
|
||||
# DESC: Check that a process with attached tty running as root
|
||||
# is not there since more than X hours
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use File::Basename;
|
||||
use List::Util qw/first/;
|
||||
use IPC::Open3 'open3';
|
||||
use Getopt::Long;
|
||||
$SIG{'CHLD'} = 'IGNORE'; # don't bother using waitpid on this short-lived probe
|
||||
|
||||
my $PROBE_NAME = basename($0);
|
||||
my $debug;
|
||||
|
||||
## no critic (Subroutines::RequireArgUnpacking)
|
||||
## no critic (Subroutines::RequireFinalReturn)
|
||||
|
||||
sub _out {
|
||||
my ($criticity, $msg) = @_;
|
||||
printf "%s %4s - %s\n", $PROBE_NAME, $criticity, $msg;
|
||||
}
|
||||
|
||||
sub _dbg { _out('dbg', $_[0]) if $debug; }
|
||||
sub _info { _out('info', $_[0]); }
|
||||
sub _warn { _out('WARN', $_[0]); }
|
||||
sub _err { _out('ERR!', $_[0]); }
|
||||
|
||||
sub success { my $msg = shift; _info($msg) if $msg; _info("status=OK"); exit(0); }
|
||||
sub warning { my $msg = shift; _warn($msg) if $msg; _info("status=WARN"); exit(1); }
|
||||
sub failure { my $msg = shift; _err($msg) if $msg; _info("status=FAILURE"); exit(2); }
|
||||
sub unknown { my $msg = shift; _err($msg) if $msg; _info("status=UNKNOWN"); exit(3); }
|
||||
|
||||
# OPTIONS
|
||||
|
||||
my $warnAfterMinutes = 30;
|
||||
my $critAfterMinutes = 120;
|
||||
|
||||
GetOptions(
|
||||
"help" => \my $help,
|
||||
"debug!" => \$debug,
|
||||
"warn-after-minutes=i" => \$warnAfterMinutes,
|
||||
"crit-after-minutes=i" => \$critAfterMinutes,
|
||||
) or unknown("Failed parsing command-line");
|
||||
|
||||
# HELP
|
||||
|
||||
if ($help) {
|
||||
print <<"EOF";
|
||||
|
||||
$PROBE_NAME [options]
|
||||
|
||||
--help This help message
|
||||
--debug Increase verbosity of logs
|
||||
--warn-after-minutes NB Exit with a WARN exit code after a root process has been logged in for more than
|
||||
this amount of minutes. Use 0 to never WARN. Default: $warnAfterMinutes
|
||||
--crit-after-minutes NB Exit with a CRIT exit code after a root process has been logged in for more than
|
||||
this amount of minutes. Use 0 to never CRIT. Default: $critAfterMinutes
|
||||
|
||||
Note: don't specify an other option than --help to get the proper default values.
|
||||
|
||||
EOF
|
||||
unknown();
|
||||
}
|
||||
|
||||
# CODE
|
||||
|
||||
_dbg("Getting system clock tick");
|
||||
my ($stdin, $stdout);
|
||||
eval { open3($stdin, $stdout, '>&STDERR', qw{ getconf CLK_TCK }); };
|
||||
if ($@) {
|
||||
unknown("Couldn't start 'getconf' process");
|
||||
}
|
||||
close($stdin);
|
||||
|
||||
my $clockTick = <$stdout>;
|
||||
close($stdout);
|
||||
chomp($clockTick);
|
||||
_dbg("clocktick is $clockTick");
|
||||
|
||||
_dbg("Getting uptime");
|
||||
open(my $fh, '<', "/proc/uptime") or unknown("Cannot open /proc/uptime: $!");
|
||||
my $uptimeData = <$fh>;
|
||||
close $fh;
|
||||
my $uptime;
|
||||
if ($uptimeData =~ /^(\d+)/) {
|
||||
$uptime = $1;
|
||||
}
|
||||
else {
|
||||
unknown("Cannot parse uptime! '$uptimeData'");
|
||||
}
|
||||
|
||||
_dbg("Uptime is $uptime seconds");
|
||||
|
||||
_dbg('Getting the list of processes that have a tty');
|
||||
$stdin = $stdout = undef;
|
||||
eval { open3($stdin, $stdout, '>&STDERR', qw{ ps aho pid }); };
|
||||
if ($@) {
|
||||
unknown("Couldn't start 'ps' process");
|
||||
}
|
||||
close($stdin);
|
||||
my @pidlist = <$stdout>;
|
||||
close($stdout);
|
||||
s/^\s+|\s+$//g for @pidlist;
|
||||
_dbg('Found ' . (scalar @pidlist) . ' PIDs having a tty');
|
||||
|
||||
my $criticalCount = 0;
|
||||
my $warningCount = 0;
|
||||
|
||||
PID: foreach my $pid (@pidlist) {
|
||||
next if $pid !~ /^\d+$/;
|
||||
my $fh;
|
||||
if (not open($fh, '<', "/proc/$pid/status")) {
|
||||
_dbg("Couldn't open /proc/$pid/status ($!), probably a disappeared process (race condition)");
|
||||
next;
|
||||
}
|
||||
while (<$fh>) {
|
||||
next if (not /^[UG]id:/); # parse Uid / Gid numbers
|
||||
my ($id1, $id2, undef, $id4) = /(\d+)/g;
|
||||
next PID if (not grep { $_ == 0 } ($id1, $id2, $id4)); # Root detected
|
||||
_dbg("process $pid running as root, analyzing tty");
|
||||
|
||||
# Checking if exe is agetty, as it triggers the probe but is NOT a security issue
|
||||
my $binary = readlink("/proc/$pid/exe");
|
||||
chomp($binary);
|
||||
_dbg("Binary is $binary");
|
||||
|
||||
# The regex with 'deleted' handles upgrade of binaries, which are tagged deleted in proc/exe
|
||||
next PID if ($binary =~ m{^(/usr)?/s?bin/agetty( \(deleted\))?$});
|
||||
next PID if ($binary =~ m{^/usr/bin/sudo( \(deleted\))?$});
|
||||
next PID if ($binary =~ m{^(/usr)?/bin/minijail0( \(deleted\))?$});
|
||||
|
||||
_dbg("check age of $pid");
|
||||
my $stat;
|
||||
if (not open($stat, '<', "/proc/$pid/stat")) {
|
||||
_dbg("couldn't open /proc/$pid/stat ($!), probably a disappeared process (race condition), getting to the next one");
|
||||
next PID;
|
||||
}
|
||||
my @stats = split(/\s+/, <$stat>);
|
||||
close $stat;
|
||||
|
||||
my $startTime = $stats[21]; # in %llu, number of clock ticks, see man 5 proc
|
||||
my $processUptime = $uptime - ($startTime / $clockTick);
|
||||
$processUptime = int($processUptime / 60); # minutes conversion
|
||||
|
||||
_dbg("$pid Up since $processUptime minutes");
|
||||
|
||||
# Get guilty Admin
|
||||
my $guilty = '??UNKNOWN??';
|
||||
if (open(my $envfh, "<", "/proc/$pid/environ")) {
|
||||
my $guiltyEnv = first { /LC_BASTION=(\w+)/ } <$envfh>;
|
||||
if (defined $guiltyEnv and $guiltyEnv =~ /LC_BASTION=(\w+)/) {
|
||||
$guilty = $1;
|
||||
}
|
||||
close $envfh;
|
||||
}
|
||||
|
||||
# Get cmdline
|
||||
my $cmdline = '??UNKNOWN??';
|
||||
if (open(my $envfh, "<", "/proc/$pid/cmdline")) {
|
||||
$cmdline = <$envfh>;
|
||||
chomp $cmdline;
|
||||
close $envfh;
|
||||
|
||||
# Just in case the cmdline contains sensitive info, we just keep the first word
|
||||
$cmdline =~ s/ .+//;
|
||||
}
|
||||
|
||||
if ($critAfterMinutes > 0 && $processUptime > $critAfterMinutes) {
|
||||
_info "Root process $pid ($cmdline) by $guilty, up for $processUptime minutes (> than $critAfterMinutes min)";
|
||||
$criticalCount += 1;
|
||||
}
|
||||
elsif ($warnAfterMinutes > 0 && $processUptime > $warnAfterMinutes) {
|
||||
_info "Root process $pid ($cmdline) by $guilty, up for $processUptime minutes (> than $warnAfterMinutes min)";
|
||||
$warningCount += 1;
|
||||
}
|
||||
}
|
||||
close $fh;
|
||||
}
|
||||
|
||||
# check Results
|
||||
if ($criticalCount) {
|
||||
failure("$criticalCount critical cases found");
|
||||
}
|
||||
elsif ($warningCount) {
|
||||
warning("$warningCount warning cases found");
|
||||
}
|
||||
|
||||
# is ok
|
||||
success("No long-lived root process found");
|
131
contrib/nrpe/probes/bastion-version
Executable file
131
contrib/nrpe/probes/bastion-version
Executable file
|
@ -0,0 +1,131 @@
|
|||
#! /usr/bin/env perl
|
||||
# vim: set filetype=perl ts=4 sw=4 sts=4 et:
|
||||
#
|
||||
# DESC: Warn if a more recent bastion version is available
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use File::Basename;
|
||||
use Getopt::Long;
|
||||
|
||||
my $PROBE_NAME = basename($0);
|
||||
my $debug;
|
||||
|
||||
## no critic (Subroutines::RequireArgUnpacking)
|
||||
## no critic (Subroutines::RequireFinalReturn)
|
||||
## no critic (InputOutput::ProhibitBacktickOperators)
|
||||
|
||||
sub _out {
|
||||
my ($criticity, $msg) = @_;
|
||||
printf "%s %4s - %s\n", $PROBE_NAME, $criticity, $msg;
|
||||
}
|
||||
|
||||
sub _dbg { _out('dbg', $_[0]) if $debug; }
|
||||
sub _info { _out('info', $_[0]); }
|
||||
sub _warn { _out('WARN', $_[0]); }
|
||||
sub _err { _out('ERR!', $_[0]); }
|
||||
|
||||
sub success { my $msg = shift; _info($msg) if $msg; _info("status=OK"); exit(0); }
|
||||
sub warning { my $msg = shift; _warn($msg) if $msg; _info("status=WARN"); exit(1); }
|
||||
sub failure { my $msg = shift; _err($msg) if $msg; _info("status=FAILURE"); exit(2); }
|
||||
sub unknown { my $msg = shift; _err($msg) if $msg; _info("status=UNKNOWN"); exit(3); }
|
||||
|
||||
# OPTIONS
|
||||
|
||||
GetOptions(
|
||||
"help" => \my $help,
|
||||
"debug!" => \$debug,
|
||||
"basedir=s" => \my $basedir,
|
||||
"no-warn-on-diff" => \my $noWarnOnDiff,
|
||||
) or unknown("Failed parsing command-line");
|
||||
|
||||
# HELP
|
||||
|
||||
if ($help) {
|
||||
print <<"EOF";
|
||||
|
||||
$PROBE_NAME [options]
|
||||
|
||||
--help This help message
|
||||
--debug Increase verbosity of logs
|
||||
--basedir DIR Specify the base directory of The Bastion (default: /opt/bastion)
|
||||
--no-warn-on-diff Never return a WARN code even if we find a git diff
|
||||
|
||||
EOF
|
||||
unknown();
|
||||
}
|
||||
|
||||
$basedir ||= "/opt/bastion";
|
||||
|
||||
# CODE
|
||||
|
||||
# get current version
|
||||
my $current_version;
|
||||
if (open(my $fh, '<', "$basedir/lib/perl/OVH/Bastion.pm")) {
|
||||
while (<$fh>) {
|
||||
if (m{^\s*our\s+\$VERSION\s*=\s*.([0-9a-zA-Z.-]+)}) {
|
||||
$current_version = $1;
|
||||
}
|
||||
}
|
||||
close($fh);
|
||||
if ($current_version) {
|
||||
_info("Bastion version $current_version found");
|
||||
}
|
||||
else {
|
||||
unknown("Couldn't find version in Bastion.pm file!");
|
||||
}
|
||||
}
|
||||
else {
|
||||
unknown("Couldn't find current bastion version ($!)");
|
||||
}
|
||||
|
||||
my @out;
|
||||
my $ret;
|
||||
|
||||
if (!chdir("$basedir")) {
|
||||
unknown("Couldn't chdir to $basedir!");
|
||||
}
|
||||
|
||||
@out = qx{git rev-parse --abbrev-ref HEAD};
|
||||
$ret = $?;
|
||||
if ($ret != 0) {
|
||||
_info("Bastion main path is not a git repo, or failed to rev-parse");
|
||||
}
|
||||
else {
|
||||
_dbg("output: $_") for @out;
|
||||
my $branch = $out[0];
|
||||
chomp $branch;
|
||||
_info("Bastion is on branch $branch");
|
||||
}
|
||||
|
||||
@out = qx{git rev-parse HEAD};
|
||||
$ret = $?;
|
||||
if ($ret != 0) {
|
||||
_info("Bastion main path is not a git repo, or failed to rev-parse");
|
||||
}
|
||||
else {
|
||||
_dbg("output: $_") for @out;
|
||||
my $commit = $out[0];
|
||||
chomp $commit;
|
||||
_info("Bastion is on commit $commit");
|
||||
}
|
||||
|
||||
@out = qx{git diff};
|
||||
if ($ret != 0) {
|
||||
_info("Bastion main path is not a git repo, or failed to diff");
|
||||
}
|
||||
else {
|
||||
_dbg("output: $_") for @out;
|
||||
my $difflines = @out;
|
||||
if ($difflines > 0) {
|
||||
if ($noWarnOnDiff) {
|
||||
success("Found $difflines lines of diff");
|
||||
}
|
||||
else {
|
||||
warning("Found $difflines lines of diff");
|
||||
}
|
||||
}
|
||||
else {
|
||||
success("Found no git diff");
|
||||
}
|
||||
}
|
5
contrib/nrpe/sudoers.example
Normal file
5
contrib/nrpe/sudoers.example
Normal file
|
@ -0,0 +1,5 @@
|
|||
# This is a sudoers example file for The Bastion contrib NRPE probes. Adjust to your system.
|
||||
|
||||
nagios ALL=(healthcheck) NOPASSWD: /opt/bastion/contrib/nrpe/probes/bastion-healthcheck
|
||||
nagios ALL=(root) NOPASSWD: /opt/bastion/contrib/nrpe/probes/bastion-version
|
||||
|
Loading…
Reference in a new issue