* Renamed the 'ScanCore' executable to just 'scancore', moved it into the standard 'tools' directory and changed the agents directory to '/usr/sbin/scancore-agents'.

* Got scancore scanning the agents directory, and properly holding on startup until at least one database is available (instead of exiting), and holding on startup until the local system is configured.
* Created the skeleton of the first scan agent; scan-network.
* Fixed a bug in Storage->check_md5sums() where dynamically loaded modules, loaded after the initial md5sum calcs, would cause the calling daemon to exit (possibly on every invocation).
* Created the scancore.README that will eventually be the main scan agent guide / API document.

Signed-off-by: Digimer <digimer@alteeve.ca>
main
Digimer 6 years ago
parent 1c70fceb70
commit 946fce018a
  1. 5
      Anvil/Tools.pm
  2. 35
      Anvil/Tools/Storage.pm
  3. 120
      ScanCore/ScanCore
  4. 22
      notes
  5. 38
      scancore-agents/scan-network/scan-network
  6. 0
      scancore-agents/scan-network/scan-network.sql
  7. 20
      scancore-agents/scan-network/scan-network.xml
  8. 14
      share/words.xml
  9. 2
      tools/anvil-daemon
  10. 5
      tools/anvil-scan-network
  11. 257
      tools/scancore
  12. 67
      tools/scancore.README

@ -732,9 +732,10 @@ sub _set_defaults
{ {
my ($anvil) = shift; my ($anvil) = shift;
$anvil->data->{ScanCore} = { $anvil->data->{scancore} = {
timing => { timing => {
# Delay between DB connection attempts when no databases are available? # Delay between DB connection attempts when no databases are available?
agent_runtime => 30,
db_retry_interval => 2, db_retry_interval => 2,
# Delay between scans? # Delay between scans?
run_interval => 30, run_interval => 30,
@ -936,7 +937,7 @@ sub _set_paths
firewalld_zones => "/etc/firewalld/zones", firewalld_zones => "/etc/firewalld/zones",
html => "/var/www/html", html => "/var/www/html",
ifcfg => "/etc/sysconfig/network-scripts", ifcfg => "/etc/sysconfig/network-scripts",
scan_agents => "/usr/sbin/ScanCore/agents", scan_agents => "/usr/sbin/scancore-agents",
skins => "/var/www/html/skins", skins => "/var/www/html/skins",
syslinux => "/usr/share/syslinux", syslinux => "/usr/share/syslinux",
tftpboot => "/var/lib/tftpboot", tftpboot => "/var/lib/tftpboot",

@ -497,13 +497,18 @@ sub check_md5sums
"md5sum::${caller}::now" => $anvil->data->{md5sum}{$caller}{now}, "md5sum::${caller}::now" => $anvil->data->{md5sum}{$caller}{now},
}}); }});
if ($anvil->data->{md5sum}{$caller}{now} ne $anvil->data->{md5sum}{$caller}{start_time}) if ($anvil->data->{md5sum}{$caller}{start_time} ne $anvil->data->{md5sum}{$caller}{now})
{ {
# Exit. # Exit.
$exit = 1; $exit = 1;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "warn", key => "message_0013", variables => { file => $0 }}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "warn", key => "log_0250", variables => {
file => $0,
old_sum => $anvil->data->{md5sum}{$caller}{start_time},
new_sum => $anvil->data->{md5sum}{$caller}{now},
}});
} }
### NOTE: Some modules are loaded dynamically, so if there is no old hash, we'll record it now.
# What about our modules? # What about our modules?
foreach my $module (sort {$a cmp $b} keys %INC) foreach my $module (sort {$a cmp $b} keys %INC)
{ {
@ -515,6 +520,15 @@ sub check_md5sums
module_sum => $module_sum, module_sum => $module_sum,
}}); }});
# Is this the first time I've seen this module?
if (not defined $anvil->data->{md5sum}{$module_file}{start_time})
{
# New one!
$anvil->data->{md5sum}{$module_file}{start_time} = $module_sum;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"md5sum::${module_file}::start_time" => $anvil->data->{md5sum}{$module_file}{start_time},
}});
}
$anvil->data->{md5sum}{$module_file}{now} = $module_sum; $anvil->data->{md5sum}{$module_file}{now} = $module_sum;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"md5sum::${module_file}::start_time" => $anvil->data->{md5sum}{$module_file}{start_time}, "md5sum::${module_file}::start_time" => $anvil->data->{md5sum}{$module_file}{start_time},
@ -524,7 +538,11 @@ sub check_md5sums
{ {
# Changed. # Changed.
$exit = 1; $exit = 1;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "warn", key => "message_0013", variables => { file => $module_file }}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "warn", key => "log_0250", variables => {
file => $module_file,
old_sum => $anvil->data->{md5sum}{$module_file}{start_time},
new_sum => $anvil->data->{md5sum}{$module_file}{now},
}});
} }
} }
@ -545,7 +563,11 @@ sub check_md5sums
if ($anvil->data->{md5sum}{$file}{start_time} ne $anvil->data->{md5sum}{$file}{now}) if ($anvil->data->{md5sum}{$file}{start_time} ne $anvil->data->{md5sum}{$file}{now})
{ {
$exit = 1; $exit = 1;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "warn", key => "message_0013", variables => { file => $file }}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "warn", key => "log_0250", variables => {
file => $file,
old_sum => $anvil->data->{md5sum}{$file}{start_time},
new_sum => $anvil->data->{md5sum}{$file}{now},
}});
} }
} }
@ -1454,9 +1476,12 @@ sub record_md5sums
my $anvil = $self->parent; my $anvil = $self->parent;
my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3;
# Record the caller's MD5 sum
my $caller = $0; my $caller = $0;
$anvil->data->{md5sum}{$caller}{start_time} = $anvil->Get->md5sum({file => $0}); $anvil->data->{md5sum}{$caller}{start_time} = $anvil->Get->md5sum({file => $0});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "md5sum::${caller}::start_time" => $anvil->data->{md5sum}{$caller}{start_time} }}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "md5sum::${caller}::start_time" => $anvil->data->{md5sum}{$caller}{start_time} }});
# Record the sums of our perl modules.
foreach my $module (sort {$a cmp $b} keys %INC) foreach my $module (sort {$a cmp $b} keys %INC)
{ {
my $module_file = $INC{$module}; my $module_file = $INC{$module};
@ -1471,7 +1496,7 @@ sub record_md5sums
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "md5sum::${module_file}::start_time" => $anvil->data->{md5sum}{$module_file}{start_time} }}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "md5sum::${module_file}::start_time" => $anvil->data->{md5sum}{$module_file}{start_time} }});
} }
# Record sums for word files. # Record sum(s) for the words file(s).
foreach my $file (sort {$a cmp $b} keys %{$anvil->data->{words}}) foreach my $file (sort {$a cmp $b} keys %{$anvil->data->{words}})
{ {
my $words_sum = $anvil->Get->md5sum({file => $file}); my $words_sum = $anvil->Get->md5sum({file => $file});

@ -1,120 +0,0 @@
#!/usr/bin/perl
#
# This is the main ScanCore program. It is started/killed/recovered by anvil-daemon.
#
# Examples;
#
# Exit codes;
# 0 = Normal exit.
# 1 = No database connections available.
#
# TODO:
#
use strict;
use warnings;
use Anvil::Tools;
use Data::Dumper;
# Disable buffering
$| = 1;
my $THIS_FILE = ($0 =~ /^.*\/(.*)$/)[0];
my $running_directory = ($0 =~ /^(.*?)\/$THIS_FILE$/)[0];
if (($running_directory =~ /^\./) && ($ENV{PWD}))
{
$running_directory =~ s/^\./$ENV{PWD}/;
}
my $anvil = Anvil::Tools->new({log_level => 2, log_secure => 1});
$anvil->Storage->read_config({file => $anvil->data->{path}{configs}{'anvil.conf'}});
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0115", variables => { program => $THIS_FILE }});
# Read switches
$anvil->data->{switches}{'run-once'} = "";
$anvil->Get->switches;
# Connect to DBs.
$anvil->Database->connect;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "log_0132"});
if (not $anvil->data->{sys}{database}{connections})
{
# No databases, exit.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, key => "error_0003"});
# We sleep before exiting so that we don't get into a high-speed loop of systemd re-invoking us.
sleep 2;
$anvil->nice_exit({exit_code => 1});
}
# Calculate my sum so that we can exit if it changes later.
$anvil->Storage->record_md5sums;
# Disconnect. We'll reconnect inside the loop
$anvil->Database->disconnect();
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 3, key => "log_0203"});
# The main loop
while(1)
{
# Reload defaults, re-read the config and then connect to the database(s)
$anvil->_set_paths();
$anvil->_set_defaults();
$anvil->Storage->read_config({force_read => 1, file => $anvil->data->{path}{configs}{'anvil.conf'}});
$anvil->Database->connect({check_if_configured => $check_if_database_is_configured});
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0132"});
# Mark that we don't want to check the database now.
$check_if_database_is_configured = 0;
if ($anvil->data->{sys}{database}{connections})
{
# Run the normal tasks
call_agents($anvil);
}
else
{
# No databases available, we can't do anything this run. Sleep for a couple of seconds and
# then try again.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, key => "log_0202"});
my $db_retry_interval = 2;
if ((exists $anvil->data->{ScanCore}{timing}{db_retry_interval}) && ($anvil->data->{ScanCore}{timing}{db_retry_interval} =~ /^\d+$/))
{
$db_retry_interval = $anvil->data->{ScanCore}{timing}{db_retry_interval};
}
sleep($db_retry_interval);
next;
}
# Exit if 'run-once' selected.
if ($anvil->data->{switches}{'run-once'})
{
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, key => "message_0055"});
$anvil->nice_exit({code => 0});
}
# Disconnect from the database(s) and sleep now.
$anvil->Database->disconnect();
my $run_interval = 30;
if ((exists $anvil->data->{ScanCore}{timing}{run_interval}) && ($anvil->data->{ScanCore}{timing}{run_interval} =~ /^\d+$/))
{
$run_interval = $anvil->data->{ScanCore}{timing}{run_interval};
}
sleep($run_interval);
}
$anvil->nice_exit({code => 0});
#############################################################################################################
# Functions #
#############################################################################################################
# This invokes all scan agents found in 'path::directories::scan_agents'
sub call_agents
{
}

22
notes

@ -795,3 +795,25 @@ CREATE TRIGGER trigger_alerts
FOR EACH ROW EXECUTE PROCEDURE history_alerts(); FOR EACH ROW EXECUTE PROCEDURE history_alerts();
COMMIT; COMMIT;
====
3rd party stuff;
dnf install autoconf automake bzip2-devel corosynclib-devel gnutls-devel help2man libqb-devel libtool \
libtool-ltdl-devel libuuid-devel libxml2-devel libxslt-devel pam-devel "pkgconfig(dbus-1)" \
"pkgconfig(glib-2.0)" python3-devel asciidoc inkscape publican booth-site diffstat \
fence-agents-apc fence-agents-ipmilan fence-agents-scsi fence-virt python3-lxml ruby-devel \
rubygem-backports rubygem-ethon rubygem-ffi rubygem-multi_json rubygem-open4 rubygem-rack \
rubygem-rack-protection rubygem-rack-test rubygem-sinatra rubygem-test-unit flex perl-generators
----
pacemaker;
git tag
git checkout <2.x>
make srpm
mv /home/digimer/anvil/builds/pacemaker/pacemaker-2.0.0-0.1.rc2.fedora.src.rpm ~/rpmbuild/SRPMS/
----
pcs;
- Requires pacemaker 2.x install
git tag
git checkout <0.10.x>

@ -0,0 +1,38 @@
#!/usr/bin/perl
#
# This scans the local network (interfaces, bonds and bridges).
#
# Examples;
#
# Exit codes;
# 0 = Normal exit.
# 1 = No database connections available.
#
# TODO:
# - Decide if it's worth having a separate ScanCore.log file or just feed into anvil.log.
use strict;
use warnings;
use Anvil::Tools;
use Data::Dumper;
# Disable buffering
$| = 1;
my $THIS_FILE = ($0 =~ /^.*\/(.*)$/)[0];
my $running_directory = ($0 =~ /^(.*?)\/$THIS_FILE$/)[0];
if (($running_directory =~ /^\./) && ($ENV{PWD}))
{
$running_directory =~ s/^\./$ENV{PWD}/;
}
my $anvil = Anvil::Tools->new({log_level => 2, log_secure => 1});
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0115", variables => { program => $THIS_FILE }});
$anvil->nice_exit({exit_code => 0});
#############################################################################################################
# Functions #
#############################################################################################################

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Company: Alteeve's Niche, Inc.
License: GPL v2+
Author: Madison Kelly <mkelly@alteeve.ca>
NOTE: All string keys MUST be prefixed with the agent name! ie: 'scan_network_log_0001'.
-->
<words>
<meta version="3.0.0" languages="en_CA,jp"/>
<!-- Canadian English -->
<language name="en_CA" long_name="Canadian English" description="ScanCore scan agent that monitors local network interfaces, bonds and bridges.">
<!-- Log entries -->
<key name="scan_network_log_0001">Starting: [#!variable!program!#].</key>
</language>
</words>

@ -34,8 +34,8 @@ Author: Madison Kelly <mkelly@alteeve.ca>
<key name="message_0010">The SSH session to: [#!variable!target!#] was closed because 'no_cache' was set and there was an open SSH connection.</key> <key name="message_0010">The SSH session to: [#!variable!target!#] was closed because 'no_cache' was set and there was an open SSH connection.</key>
<key name="message_0011">Wrote the system UUID to the file: [#!variable!file!#] to enable the web based tools to read this system's UUID.</key> <key name="message_0011">Wrote the system UUID to the file: [#!variable!file!#] to enable the web based tools to read this system's UUID.</key>
<key name="message_0012">Wrote the journald config file: [#!variable!file!#] to disable rate limiting to ensure high log levels are not lost.</key> <key name="message_0012">Wrote the journald config file: [#!variable!file!#] to disable rate limiting to ensure high log levels are not lost.</key>
<key name="message_0013">The md5sum of: [#!variable!file!#] has changed since the daemon started.</key> <key name="message_0013">#!free!#</key>
<key name="message_0014">Exiting to reload.</key> <key name="message_0014">One or more files on disk have changed. Exiting to reload.</key>
<key name="message_0015">The reconfigure of the network has begun.</key> <key name="message_0015">The reconfigure of the network has begun.</key>
<key name="message_0016">The hostname: [#!variable!hostname!#] has been set.</key> <key name="message_0016">The hostname: [#!variable!hostname!#] has been set.</key>
<key name="message_0017">Failed to set the hostname: [#!variable!hostname!#]! The hostname is currently [#!variable!bad_hostname!#]. This is probably a program error.</key> <key name="message_0017">Failed to set the hostname: [#!variable!hostname!#]! The hostname is currently [#!variable!bad_hostname!#]. This is probably a program error.</key>
@ -526,6 +526,16 @@ The body of the file: [#!variable!file!#] does not match the new body. The file
<key name="log_0241">Package list loaded.</key> <key name="log_0241">Package list loaded.</key>
<key name="log_0242">The firewall zone: [#!variable!zone!#] is not needed. The zone file: [#!variable!file!#] has been backed up to: [#!variable!backup!#] and will now be removed.</key> <key name="log_0242">The firewall zone: [#!variable!zone!#] is not needed. The zone file: [#!variable!file!#] has been backed up to: [#!variable!backup!#] and will now be removed.</key>
<key name="log_0243">[ Error ] - Failed to delete the file: [#!variable!file!#].</key> <key name="log_0243">[ Error ] - Failed to delete the file: [#!variable!file!#].</key>
<key name="log_0244">[ Warning ] - None of the databases are accessible. ScanCore will try to connect once a minute until a database is accessible.</key>
<key name="log_0245">[ Cleared ] - We now have databases accessible, proceeding.</key>
<key name="log_0246">[ Warning ] - The local system is not yet configured. Scancore will check once a minute and start running once configured.</key>
<key name="log_0247">[ Cleared ] - The local system is now configured, proceeding.</key>
<key name="log_0248">ScanCore has entered the main loop.</key>
<key name="log_0249">----=] ScanCore loop finished. Sleeping for: [#!variable!run_interval!#] seconds. ]=--------------------------------------</key> <!-- This is meant to be easily seen in the logs, hence the dashes. -->
<key name="log_0250">
The md5sum of: [#!variable!file!#] has changed since the daemon started.
* [#!variable!old_sum!#] -> [#!variable!new_sum!#]
</key>
<!-- Test words. Do NOT change unless you update 't/Words.t' or tests will needlessly fail. --> <!-- Test words. Do NOT change unless you update 't/Words.t' or tests will needlessly fail. -->
<key name="t_0000">Test</key> <key name="t_0000">Test</key>

@ -121,7 +121,7 @@ while(1)
# Reload defaults, re-read the config and then connect to the database(s) # Reload defaults, re-read the config and then connect to the database(s)
$anvil->_set_paths(); $anvil->_set_paths();
$anvil->_set_defaults(); $anvil->_set_defaults();
$anvil->Storage->read_config({force_read => 1, file => $anvil->data->{path}{configs}{'anvil.conf'}}); $anvil->Storage->read_config();
$anvil->Database->connect({check_if_configured => $check_if_database_is_configured}); $anvil->Database->connect({check_if_configured => $check_if_database_is_configured});
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 3, secure => 0, key => "log_0132"}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 3, secure => 0, key => "log_0132"});

@ -1,8 +1,8 @@
#!/usr/bin/perl #!/usr/bin/perl
# #
# This daemon watches for network changes and updates a cache file when changes are found. # This tool watches for network changes and updates a cache file when changes are found.
# #
# NOTE: This daemon does NOT connect to the databases. This is meant to be as quick as possible and to use as # NOTE: This tool does NOT connect to the databases. This is meant to be as quick as possible and to use as
# few resources as possible. It will exit with 'ok' or 'change' depending on if something in the # few resources as possible. It will exit with 'ok' or 'change' depending on if something in the
# network changed. # network changed.
# #
@ -10,6 +10,7 @@
# 0 = Normal exit # 0 = Normal exit
# #
# TODO: # TODO:
# -
# #
use strict; use strict;

@ -0,0 +1,257 @@
#!/usr/bin/perl
#
# This is the main ScanCore program. It is started/killed/recovered by anvil-daemon.
#
# Examples;
#
# Exit codes;
# 0 = Normal exit.
# 1 = No database connections available.
#
# TODO:
# - Decide if it's worth having a separate ScanCore.log file or just feed into anvil.log.
#
use strict;
use warnings;
use Anvil::Tools;
use Data::Dumper;
# Disable buffering
$| = 1;
my $THIS_FILE = ($0 =~ /^.*\/(.*)$/)[0];
my $running_directory = ($0 =~ /^(.*?)\/$THIS_FILE$/)[0];
if (($running_directory =~ /^\./) && ($ENV{PWD}))
{
$running_directory =~ s/^\./$ENV{PWD}/;
}
my $anvil = Anvil::Tools->new({log_level => 2, log_secure => 1});
$anvil->Storage->read_config();
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0115", variables => { program => $THIS_FILE }});
# Read switches
$anvil->data->{switches}{'run-once'} = "";
$anvil->Get->switches;
# Calculate my sum so that we can exit if it changes later.
$anvil->Storage->record_md5sums;
# Connect to DBs.
wait_for_database($anvil);
# If we're not configured, sleep.
wait_until_configured($anvil);
# Disconnect. We'll reconnect inside the loop
$anvil->Database->disconnect();
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "log_0203"});
# The main loop
while(1)
{
# Reload defaults, re-read the config and then connect to the database(s)
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "log_0248"});
$anvil->_set_paths();
$anvil->_set_defaults();
$anvil->Storage->read_config();
$anvil->Database->connect();
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0132"});
if ($anvil->data->{sys}{database}{connections})
{
# Run the normal tasks
call_agents($anvil);
}
else
{
# No databases available, we can't do anything this run. Sleep for a couple of seconds and
# then try again.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, key => "log_0202"});
my $db_retry_interval = 2;
if ((exists $anvil->data->{scancore}{timing}{db_retry_interval}) && ($anvil->data->{scancore}{timing}{db_retry_interval} =~ /^\d+$/))
{
$db_retry_interval = $anvil->data->{scancore}{timing}{db_retry_interval};
}
sleep($db_retry_interval);
next;
}
# Exit if 'run-once' selected.
if ($anvil->data->{switches}{'run-once'})
{
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, key => "message_0055"});
$anvil->nice_exit({code => 0});
}
# Disconnect from the database(s) and sleep now.
$anvil->Database->disconnect();
my $run_interval = 30;
if ((exists $anvil->data->{scancore}{timing}{run_interval}) && ($anvil->data->{scancore}{timing}{run_interval} =~ /^\d+$/))
{
$run_interval = $anvil->data->{scancore}{timing}{run_interval};
}
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "log_0249", variables => { run_interval => $run_interval }});
sleep($run_interval);
# In case something has changed, exit.
exit_if_sums_changed($anvil);
}
$anvil->nice_exit({code => 0});
#############################################################################################################
# Functions #
#############################################################################################################
# This invokes all scan agents found in 'path::directories::scan_agents'
sub call_agents
{
my ($anvil) = @_;
# Get the list of scan agents on this system.
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
"path::directories::scan_agents" => $anvil->data->{path}{directories}{scan_agents},
}});
scan_directory($anvil, $anvil->data->{path}{directories}{scan_agents});
return(0);
}
# This checks to see if any files on disk have changed and, if so, exits.
sub exit_if_sums_changed
{
my ($anvil) = @_;
if ($anvil->Storage->check_md5sums)
{
# NOTE: We exit with '0' to prevent systemctl from showing a scary red message.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, priority => "warn", key => "message_0014"});
$anvil->nice_exit({code => 0});
}
return(0);
}
# This looks in the passed-in directory for scan agents or sub-directories (which will in turn be scanned).
sub scan_directory
{
my ($anvil, $directory) = @_;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { directory => $directory }});
local(*DIRECTORY);
opendir(DIRECTORY, $directory);
while(my $file = readdir(DIRECTORY))
{
next if $file eq ".";
next if $file eq "..";
my $full_path = $directory."/".$file;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
file => $file,
full_path => $full_path,
}});
# If we're looking at a directory, scan it. Otherwise, see if it's an executable and that it
# starts with 'scan-*'.
if (-d $full_path)
{
# This is a directory, dive into it.
scan_directory($anvil, $full_path);
}
elsif (-x $full_path)
{
# Now I only want to know if the file starts with 'scan-'
next if $file !~ /^scan-/;
# If I am still alive, I am looking at a scan agent!
$anvil->data->{scancore}{agent}{$file} = $full_path;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
"scancore::agent::${file}" => $anvil->data->{scancore}{agent}{$file},
}});
}
}
closedir(DIRECTORY);
return(0);
}
# This loops until it can connect to at least one database.
sub wait_for_database
{
my ($anvil) = @_;
$anvil->Database->connect;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0132"});
if (not $anvil->data->{sys}{database}{connections})
{
# No databases, sleep until one comes online.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0244"});
until($anvil->data->{sys}{database}{connections})
{
# Disconnect, Sleep, then check again.
$anvil->Database->disconnect();
sleep 60;
$anvil->_set_paths();
$anvil->_set_defaults();
$anvil->Storage->read_config();
$anvil->Database->connect;
if ($anvil->data->{sys}{database}{connections})
{
# We're good
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0245"});
}
else
{
# Not yet...
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "log_0244"});
}
# In case something has changed, exit.
exit_if_sums_changed($anvil);
}
}
return(0);
}
# Wait until the local system has been configured.
sub wait_until_configured
{
my ($anvil) = @_;
my $configured = $anvil->System->check_if_configured;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { configured => $configured }});
if (not $configured)
{
# Sleep for a minute and check again.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0246"});
until ($configured)
{
# Sleep, then check.
sleep 60;
$configured = $anvil->System->check_if_configured;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { configured => $configured }});
if ($configured)
{
# We're good
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0247"});
}
else
{
# Not yet...
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "log_0246"});
}
# In case something has changed, exit.
exit_if_sums_changed($anvil);
}
}
return(0);
}

@ -0,0 +1,67 @@
ScanCore notes;
= ScanCore =
ScanCore runs as a daemon, periodically scanning for "Scan Agents" and invoking all agents it finds in (and
under) 'path::directories::scan_agents'. See 'Agents' below for details on writing new agents.
If the local system is not configured, or if none of the databases are available, ScanCore will go into a
loop, sleeping for a period of time and then re-checking to see if the system is not configured or if at
least one database is available. Once read, it will serially execute all scan agents it finds.
Each agent is given a period of time it is allowed to run for before it is terminated. This is controlled by
'scancore::timing::agent_runtime', but can be overridden on a per-agent basis with
'scancore::agent::<agent_name>::agent_runtime'.
NOTE: It is strongly recommended to keep the average runtime of an agent as low as possible!
To prevent putting too high a load on the host system, agents are called sequentially. So an agent that takes
a long time to run will cause all other agents to be delayed, and slow down how often post-scan checks can be
performed.
= Agents =
ScanCore Agents are self-contained executables that can be written in any language the user chooses. A
typical agent contains three files under a dedicated directory, itself under
'path::directories::scan_agents'. For example, the agent 'scan-network';
* /usr/sbin/scancore-agents/scan-network/scan-network - Main program
* /usr/sbin/scancore-agents/scan-network/scan-network.sql - SQL schema
* /usr/sbin/scancore-agents/scan-network/scan-network.xml - XML 'words'
== Permissions ==
Given most agents are interacting with core systems, all agents are called with root priviledges. If your
agent doesn't need priviledged access, it is recommended that you drop to an unpriviledged user.
If you provide your agent via an external RPM (or other mechanism), be sure to properly setup SELinux. It is
enabled and enforcing on production systems!
== Naming ==
All scan agents *must* start with the name 'scan-X'. When ScanCore walks the agents directory, any file that
does not start with this name is ignored.
== Main Program ==
This is the executable invoked by ScanCore. It should do a single scan and then exit. Keeping the total
runtime as short as possible should be a high priority!
== SQL Schema ==
Most agents will want to store data in the ScanCore database (usually a postgres database called 'anvil', see
'database::X' entries in anvil.conf). If your tables are not found in a given database, this schema will be
loaded.
At this time, there are Perl libraries (see 'perldoc Anvil::Tools::Database') that dramatically simplify
connecting to any available databases, handling resync when a given database falls behind, etc. If you plan
to write a scan agent in another language, porting these tools would be very much appreciated. Let us know
and we will be happy to assist however we can.
== XML 'words' ==
Any strings used for logging or sending "alerts" to notification recipients are found in this file. Please
see 'words.xml' for more information on how this file is structured.
NOTE: To avoid namespace collisions, it is STRONGLY recommended that all string keys start with your agent
name! Ie: 'scan_network_X'.
Loading…
Cancel
Save