diff --git a/.github/actions/package/action.yml b/.github/actions/package/action.yml index 5110f9e93cc..8315141fc63 100644 --- a/.github/actions/package/action.yml +++ b/.github/actions/package/action.yml @@ -116,6 +116,6 @@ runs: name: Upload package artifacts uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: - name: ${{ inputs.arch != '' && format('packages-{0}-{1}', inputs.distrib, inputs.arch) || format('packages-{0}', inputs.distrib) }} + name: ${{ inputs.arch != '' && format('packages-{0}-{1}', inputs.distrib, inputs.arch) || format('packages-{0}', inputs.distrib) }}-${{ inputs.stability }} path: ./*.${{ inputs.package_extension}} retention-days: 1 diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 205cf67ae4a..c47c68e63c5 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -94,13 +94,13 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Run unit tests - run: yath -L test ./perl-libs/lib/ + run: yath -L test ./perl-libs/lib/ ./gorgone/tests/unit/ - name: Upload logs as artifacts if tests failed if: failure() uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: - name: plugin-installation-${{ matrix.distrib }} + name: centreon-collect-perl-unit-tests-${{ matrix.distrib }} path: ./lastlog.jsonl retention-days: 1 @@ -348,7 +348,7 @@ jobs: deliver-deb: runs-on: [self-hosted, common] - needs: [get-environment, package] + needs: [get-environment, package, robot-test-gorgone, unit-test-perl] if: | needs.get-environment.outputs.skip_workflow == 'false' && contains(fromJson('["unstable", "testing"]'), needs.get-environment.outputs.stability) && diff --git a/gorgone/docs/configuration.md b/gorgone/docs/configuration.md index 0789429e9b7..b634673c72d 100644 --- a/gorgone/docs/configuration.md +++ b/gorgone/docs/configuration.md @@ -100,6 +100,22 @@ configuration: proxy_name: proxy ``` +## *centreon vault* + +Centreon Vault is a tool that secures the passwords present in the Centreon configuration.\ + +It stores passwords in a vault and retrieves them when needed by each component.\ + +Gorgone allows you to use a vault to store any string in the configuration. It cannot store an array or a hash. + + +To use Vault, read the official documentation to set up Vault and the configuration file in `/var/lib/centreon/vault/vault.json`\ + +Then replace any password present in the Gorgone configuration with a Vault string. See the official format here : + + +https://github.com/centreon/centreon-collect/blob/develop/perl-libs/lib/centreon/common/centreonvault.pm#L391 + ## *modules* See the *configuration* titles of the modules documentations listed [here](../docs/modules.md). diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index b432de30721..0e4b0e909e9 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -35,7 +35,7 @@ use gorgone::class::listener; use gorgone::class::frame; use Time::HiRes; use Try::Tiny; - +use centreon::common::centreonvault; my ($gorgone); use base qw(gorgone::class::script); @@ -163,10 +163,17 @@ sub init { $self->{logger}->writeLogError("[core] can't find config file '$self->{config_file}'"); exit(1); } + # before loading the config, we need to load initialize vault. + # Gorgone don't know how to reload for now, but once it will be done, we will need to retry the vault connexion if it failed when starting, and read again the configuration + $self->{vault_file} = defined($self->{vault_file}) ? $self->{vault_file} : '/var/lib/centreon/vault/vault.json'; + $self->{vault} = centreon::common::centreonvault->new(logger => $self->{logger}, 'config_file' => $self->{vault_file}); + $self->{config} = $self->yaml_load_config( - file => $self->{config_file}, + file => $self->{config_file}, + # the filter is used to remove anything from the configuration not related to gorgone or centreon filter => '!($ariane eq "configuration##" || $ariane =~ /^configuration##(?:gorgone|centreon)##/)' ); + $self->init_server_keys(); $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_zmq_tcp_keepalive} = diff --git a/gorgone/gorgone/class/logger.pm b/gorgone/gorgone/class/logger.pm deleted file mode 100644 index 90b13859819..00000000000 --- a/gorgone/gorgone/class/logger.pm +++ /dev/null @@ -1,256 +0,0 @@ -# -# Copyright 2019 Centreon (http://www.centreon.com/) -# -# Centreon is a full-fledged industry-strength solution that meets -# the needs in IT infrastructure and application monitoring for -# service performance. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -package gorgone::class::logger; - -=head1 NOM - -gorgone::class::logger - Simple logging module - -=head1 SYNOPSIS - - #!/usr/bin/perl -w - - use strict; - use warnings; - - use centreon::polling; - - my $logger = new gorgone::class::logger(); - - $logger->writeLogInfo("information"); - -=head1 DESCRIPTION - -This module offers a simple interface to write log messages to various output: - -* standard output -* file -* syslog - -=cut - -use strict; -use warnings; -use Sys::Syslog qw(:standard :macros); -use IO::Handle; -use Encode; - -my %severities = ( - 1 => LOG_INFO, - 2 => LOG_ERR, - 4 => LOG_DEBUG -); - -sub new { - my $class = shift; - - my $self = bless - { - file => 0, - filehandler => undef, - # 0 = nothing, 1 = critical, 3 = info, 7 = debug - severity => 3, - old_severity => 3, - # 0 = stdout, 1 = file, 2 = syslog - log_mode => 0, - # Output pid of current process - withpid => 0, - # syslog - log_facility => undef, - log_option => LOG_PID, - }, $class; - return $self; -} - -sub file_mode($$) { - my ($self, $file) = @_; - - if (defined($self->{filehandler})) { - $self->{filehandler}->close(); - } - if (open($self->{filehandler}, ">>", $file)){ - $self->{log_mode} = 1; - $self->{filehandler}->autoflush(1); - $self->{file_name} = $file; - return 1; - } - $self->{filehandler} = undef; - print STDERR "Cannot open file $file: $!\n"; - return 0; -} - -sub is_file_mode { - my $self = shift; - - if ($self->{log_mode} == 1) { - return 1; - } - return 0; -} - -sub is_debug { - my $self = shift; - - if (($self->{severity} & 4) == 0) { - return 0; - } - return 1; -} - -sub syslog_mode($$$) { - my ($self, $logopt, $facility) = @_; - - $self->{log_mode} = 2; - openlog($0, $logopt, $facility); - return 1; -} - -# For daemons -sub redirect_output { - my $self = shift; - - if ($self->is_file_mode()) { - open my $lfh, '>>', $self->{file_name}; - open STDOUT, '>&', $lfh; - open STDERR, '>&', $lfh; - } -} - -sub flush_output { - my ($self, %options) = @_; - - $| = 1 if (defined($options{enabled})); -} - -sub force_default_severity { - my ($self, %options) = @_; - - $self->{old_severity} = defined($options{severity}) ? $options{severity} : $self->{severity}; -} - -sub set_default_severity { - my $self = shift; - - $self->{severity} = $self->{old_severity}; -} - -# Getter/Setter Log severity -sub severity { - my $self = shift; - if (@_) { - my $save_severity = $self->{severity}; - if ($_[0] =~ /^[012347]$/) { - $self->{severity} = $_[0]; - } elsif ($_[0] eq 'none') { - $self->{severity} = 0; - } elsif ($_[0] eq 'error') { - $self->{severity} = 1; - } elsif ($_[0] eq 'info') { - $self->{severity} = 3; - } elsif ($_[0] eq 'debug') { - $self->{severity} = 7; - } else { - $self->writeLogError('Wrong severity value set.'); - return -1; - } - $self->{old_severity} = $save_severity; - } - return $self->{severity}; -} - -sub withpid { - my $self = shift; - if (@_) { - $self->{withpid} = $_[0]; - } - return $self->{withpid}; -} - -sub get_date { - my $self = shift; - my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time()); - return sprintf( - '%04d-%02d-%02d %02d:%02d:%02d', - $year+1900, $mon+1, $mday, $hour, $min, $sec - ); -} - -sub writeLog { - my ($self) = shift; - - my $withdate = (defined $_[0]->{withdate}) ? $_[0]->{withdate} : 1; - my $withseverity = (defined $_[0]->{withseverity}) ? $_[0]->{withseverity} : 1; - - if (($self->{severity} & $_[0]->{severity}) == 0) { - return; - } - - if (length($_[0]->{message}) > 20000) { - $_[0]->{message} = substr($_[0]->{message}, 0, 20000) . '...'; - } - if ($self->{log_mode} == 2) { - syslog($severities{$_[0]->{severity}}, $_[0]->{message}); - return; - } - - $_[0]->{message} = (($self->{withpid} == 1) ? "$$ - $_[0]->{message} " : $_[0]->{message}); - $_[0]->{message} = ($withseverity) - ? $_[0]->{severity_str} . " - $_[0]->{message}" : $_[0]->{message}; - $_[0]->{message} = ($withdate) - ? $self->get_date . " - $_[0]->{message}" : $_[0]->{message}; - - chomp($_[0]->{message}); - if ($self->{log_mode} == 0) { - print "$_[0]->{message}\n"; - } elsif ($self->{log_mode} == 1) { - if (defined $self->{filehandler}) { - print { $self->{filehandler} } "$_[0]->{message}\n"; - } - } -} - -sub writeLogDebug { - my ($self) = shift; - - $self->writeLog({ severity => 4, severity_str => 'DEBUG', message => $_[0] }); -} - -sub writeLogInfo { - my ($self) = shift; - - $self->writeLog({ severity => 2, severity_str => 'INFO', message => $_[0] }); -} - -sub writeLogError { - my ($self) = shift; - - $self->writeLog({ severity => 1, severity_str => 'ERROR', message => $_[0] }); -} - -sub DESTROY { - my $self = shift; - - if (defined $self->{filehandler}) { - $self->{filehandler}->close(); - } -} - -1; diff --git a/gorgone/gorgone/class/script.pm b/gorgone/gorgone/class/script.pm index a5891101799..63318549a7c 100644 --- a/gorgone/gorgone/class/script.pm +++ b/gorgone/gorgone/class/script.pm @@ -25,7 +25,7 @@ use warnings; use FindBin; use Getopt::Long; use Pod::Usage; -use gorgone::class::logger; +use centreon::common::logger; use gorgone::class::db; use gorgone::class::lock; use YAML::XS; @@ -53,9 +53,10 @@ sub new { bless $self, $class; $self->{name} = $name; - $self->{logger} = gorgone::class::logger->new(); + $self->{logger} = centreon::common::logger->new(); $self->{options} = { 'config=s' => \$self->{config_file}, + 'vault=s' => \$self->{vault_config_file}, 'logfile=s' => \$self->{log_file}, 'severity=s' => \$self->{severity}, 'flushoutput' => \$self->{flushoutput}, @@ -141,6 +142,11 @@ sub run { $self->init(); } +# yaml_get_include: return a flat array of files defined by an !include directive. +# it will resolve the wildcard and return a sorted list of files. +# include: string with the directive. It can be a comma separated list, each element can contain '*' at the start of the string to specify 0 or more character (any character). +# current_dir: current directory to resolve relative path of !include directive. +# if the path is not absolute, it will be prefixed by the binary current path, so the first top level include should be an absolute path. sub yaml_get_include { my ($self, %options) = @_; @@ -151,16 +157,19 @@ sub yaml_get_include { my $dirname = File::Basename::dirname($dir); $dirname = $options{current_dir} . '/' . $dirname if ($dirname !~ /^\//); my $match_files = File::Basename::basename($dir); + # \Q\E is used to escape every special characters in the regex. + # we replace * by .* to match any character and disable \Q\E locally. + # so the extension will correctly match the file. $match_files =~ s/\*/\\E.*\\Q/g; $match_files = '\Q' . $match_files . '\E'; - my @sorted_files = (); my $DIR; + if (!opendir($DIR, $dirname)) { $self->{logger}->writeLogError("config - cannot opendir '$dirname' error: $!"); return (); } - + # opened the directory for the tested file, we will now test every file in the directory to see if they match the pattern. while (readdir($DIR)) { if (-f "$dirname/$_" && eval "/^$match_files\$/") { push @sorted_files, "$dirname/$_"; @@ -170,13 +179,17 @@ sub yaml_get_include { @sorted_files = sort { $a cmp $b } @sorted_files; push @all_files, @sorted_files; } - + # the list can be empty, for exemple if the client disable all the cron or whitelist of gorgone there should not be any error. return @all_files; } - +# yaml_parse_config: recursive function to parse yaml content and honor the inclusion of other files and vault password decryption. +# depending on the type of the yaml object, it will call itself recursively. +# config: yaml object as perl reference (hash, array, scalar, hash of hash...). $YAML::XS::LoadBlessed should be set to 1 to transform !include in blessed reference. +# current_dir: current directory to resolve relative path of !include directive. +# filter: a string to eval to filter the yaml content. you can for exemple return only children of a node. +# ariane: Ariadne's thread to know where we are in the yaml content. It is used by the filter. example : 'configuration##gorgone##gorgonecore##' sub yaml_parse_config { my ($self, %options) = @_; - if (ref(${$options{config}}) eq 'HASH') { foreach (keys %{${$options{config}}}) { my $ariane = $options{ariane} . $_ . '##'; @@ -206,6 +219,7 @@ sub yaml_parse_config { ariane => $ariane ); } + # $YAML::XS::LoadBlessed must be set, when YAML::XS will load a property with !include, it will be a blessed reference instead of a scalar. } elsif (ref(${$options{config}}) eq 'include') { my @files = $self->yaml_get_include( include => ${${$options{config}}}, @@ -236,9 +250,23 @@ sub yaml_parse_config { } else { ${$options{config}} = 'false'; } + + } elsif (ref(${$options{config}}) eq '') { + # this is a scalar value, we check if this is a vault path to replace it. + if ($self->{vault} and $self->{vault}->can('get_secret')) { + ${$options{config}} = $self->{vault}->get_secret( ${$options{config}}); + } + } else { + $self->{logger}->writeLogError("config - unknown type of data: " . ref(${$options{config}})); } } +# yaml_load_config: entry point for yaml parsing. +# can be called by yaml_parse_config if there is !include in the yaml, and will call yaml_parse_config to parse the content of the file. +# file: filename to parse. The file can contain !include directive to include other files. +# filter: is a string to eval to filter the yaml content. you can for exemple return only children of a node named configuration with this filter : +# '$ariane eq "configuration##"' +# arianne: Ariadne's thread to know where we are in the yaml content. It is used by the filter. example : 'configuration##gorgone##gorgonecore##' sub yaml_load_config { my ($self, %options) = @_; diff --git a/gorgone/gorgoned b/gorgone/gorgoned index fdb423af470..8006a14463b 100644 --- a/gorgone/gorgoned +++ b/gorgone/gorgoned @@ -46,6 +46,10 @@ gorgoned [options] Specify the path to the yaml configuration file (default: ''). +=item B<--vault> + +Specify the path to the vault json configuration file (default: '/var/lib/centreon/vault/vault.json'). + =item B<--help> Print a brief help message and exits. diff --git a/gorgone/packaging/centreon-gorgone.yaml b/gorgone/packaging/centreon-gorgone.yaml index 16e86d297b1..a94092299df 100644 --- a/gorgone/packaging/centreon-gorgone.yaml +++ b/gorgone/packaging/centreon-gorgone.yaml @@ -157,6 +157,7 @@ overrides: rpm: depends: - centreon-common + - centreon-perl-libs-common - bzip2 - perl-Libssh-Session >= 0.8 - perl-CryptX @@ -196,6 +197,7 @@ overrides: deb: depends: # those dependencies are taken from centreon-gorgone/packaging/debian/control - centreon-common + - centreon-perl-libs-common - libdatetime-perl - libtime-parsedate-perl - libtry-tiny-perl diff --git a/gorgone/tests/robot/resources/LogResearch.py b/gorgone/tests/robot/resources/LogResearch.py index 0fe4afbd9d4..fe272a870c0 100644 --- a/gorgone/tests/robot/resources/LogResearch.py +++ b/gorgone/tests/robot/resources/LogResearch.py @@ -129,7 +129,7 @@ def ctn_find_in_log(log: str, date, content, regex=False): def ctn_extract_date_from_log(line: str): - p = re.compile(r"(^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})") + p = re.compile(r"^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})") m = p.match(line) if m is None: return None diff --git a/gorgone/tests/unit/class/config_examples/centreon-gorgone/config.d/30-centreon.yaml b/gorgone/tests/unit/class/config_examples/centreon-gorgone/config.d/30-centreon.yaml new file mode 100644 index 00000000000..35c0259e1b6 --- /dev/null +++ b/gorgone/tests/unit/class/config_examples/centreon-gorgone/config.d/30-centreon.yaml @@ -0,0 +1,12 @@ +name: centreon.yaml +description: Configure Centreon Gorgone to work with Centreon Web. +centreon: + database: + db_configuration: + dsn: "mysql:host=localhost:port=3306;dbname=centreon" + username: "centreon" + password: "secret::hashicorp_vault::SecretPathArg::secretNameFromApiResponse" + db_realtime: + dsn: "mysql:host=localhost:port=3306;dbname=centreon_storage" + username: "centreon" + password: "password" diff --git a/gorgone/tests/unit/class/config_examples/centreon-gorgone/config.d/31-centreon-api.yaml b/gorgone/tests/unit/class/config_examples/centreon-gorgone/config.d/31-centreon-api.yaml new file mode 100644 index 00000000000..2929e7829de --- /dev/null +++ b/gorgone/tests/unit/class/config_examples/centreon-gorgone/config.d/31-centreon-api.yaml @@ -0,0 +1,9 @@ +gorgone: + tpapi: + - name: centreonv2 + base_url: "http://127.0.0.1/centreon/api/latest/" + username: "centreon-gorgone" + password: "secret::hashicorp_vault::SecretPathArg::secretNameFromApiResponse" + - name: clapi + username: "centreon-gorgone" + password: "webapiPassword!" diff --git a/gorgone/tests/unit/class/config_examples/centreon-gorgone/config.d/39-action.yaml b/gorgone/tests/unit/class/config_examples/centreon-gorgone/config.d/39-action.yaml new file mode 100644 index 00000000000..91cef328e94 --- /dev/null +++ b/gorgone/tests/unit/class/config_examples/centreon-gorgone/config.d/39-action.yaml @@ -0,0 +1,8 @@ +gorgone: + modules: + - name: action + package: "gorgone::modules::core::action::hooks" + enable: true + command_timeout: 30 + whitelist_cmds: secret::hashicorp_vault::SecretPathArg::secretNameFromApiResponse + allowed_cmds: !include whitelist.conf.d/*.yaml diff --git a/gorgone/tests/unit/class/config_examples/centreon-gorgone/config.d/40-gorgoned.yaml b/gorgone/tests/unit/class/config_examples/centreon-gorgone/config.d/40-gorgoned.yaml new file mode 100644 index 00000000000..bcb66cbe241 --- /dev/null +++ b/gorgone/tests/unit/class/config_examples/centreon-gorgone/config.d/40-gorgoned.yaml @@ -0,0 +1,97 @@ +gorgone: + gorgonecore: + privkey: "/var/lib/centreon-gorgone/.keys/rsakey.priv.pem" + pubkey: "/var/lib/centreon-gorgone/.keys/rsakey.pub.pem" + id: 1 + + modules: + - name: httpserver + package: "gorgone::modules::core::httpserver::hooks" + enable: true + address: "0.0.0.0" + port: "8085" + ssl: true + ssl_cert_file: /var/lib/centreon-gorgone/.keys/server_api_cert.pem + ssl_key_file: /var/lib/centreon-gorgone/.keys/server_api_key.pem + auth: + enabled: false + user: web-user-gorgone-api + password: password + allowed_hosts: + enabled: true + subnets: + - 127.0.0.1/32 + + - name: action + package: "gorgone::modules::core::action::hooks" + enable: true + command_timeout: 30 + whitelist_cmds: true + allowed_cmds: + - ^sudo\s+(/bin/)?systemctl\s+(reload|restart)\s+(centengine|centreontrapd|cbd)\s*$ + - ^(sudo\s+)?(/usr/bin/)?service\s+(centengine|centreontrapd|cbd|cbd-sql)\s+(reload|restart)\s*$ + - ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/centengine\.cfg\s*$ + - ^cat\s+/var/lib/centreon-engine/[a-zA-Z0-9\-]+-stats\.json\s*$ + - ^/usr/lib/centreon/plugins/.*$ + - ^/bin/perl /usr/share/centreon/bin/anomaly_detection --seasonality >> /var/log/centreon/anomaly_detection\.log 2>&1\s*$ + - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ + - ^centreon + - ^mkdir + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host + - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ + + - name: cron + package: "gorgone::modules::core::cron::hooks" + enable: true + cron: !include cron.d/*.yaml + + - name: register + package: "gorgone::modules::core::register::hooks" + enable: true + + - name: nodes + package: "gorgone::modules::centreon::nodes::hooks" + enable: true + + - name: proxy + package: "gorgone::modules::core::proxy::hooks" + enable: true + buffer_size: 10 + pool: 1 + httpserver: + enable: true + token: "^$*ù^é&àérç(é/*-+$$z@ze%r¨£µ~zz" + address: "0.0.0.0" + port: 8099 + + + - name: legacycmd + package: "gorgone::modules::centreon::legacycmd::hooks" + enable: true + buffer_size: 100 + cmd_dir: "/var/lib/centreon/centcore/" + cmd_file: "/var/lib/centreon/centcore.cmd" + cache_dir: "/var/cache/centreon/" + cache_dir_trap: "/etc/snmp/centreon_traps" + remote_dir: "/var/cache/centreon//config/remote-data/" + + - name: engine + package: "gorgone::modules::centreon::engine::hooks" + enable: true + command_file: "/var/lib/centreon-engine/rw/centengine.cmd" + + - name: statistics + package: "gorgone::modules::centreon::statistics::hooks" + enable: true + broker_cache_dir: "/var/cache/centreon//broker-stats/" + cron: + - id: broker_stats + timespec: "*/5 * * * *" + action: BROKERSTATS + parameters: + timeout: 10 + - id: engine_stats + timespec: "*/5 * * * *" + action: ENGINESTATS + parameters: + timeout: 10 diff --git a/gorgone/tests/unit/class/config_examples/centreon-gorgone/config.d/41-autodiscovery.yaml b/gorgone/tests/unit/class/config_examples/centreon-gorgone/config.d/41-autodiscovery.yaml new file mode 100644 index 00000000000..56bae5eb0fb --- /dev/null +++ b/gorgone/tests/unit/class/config_examples/centreon-gorgone/config.d/41-autodiscovery.yaml @@ -0,0 +1,5 @@ +gorgone: + modules: + - name: autodiscovery + package: "gorgone::modules::centreon::autodiscovery::hooks" + enable: true diff --git a/gorgone/tests/unit/class/config_examples/centreon-gorgone/config.d/50-centreon-audit.yaml b/gorgone/tests/unit/class/config_examples/centreon-gorgone/config.d/50-centreon-audit.yaml new file mode 100644 index 00000000000..ae0f8c96c62 --- /dev/null +++ b/gorgone/tests/unit/class/config_examples/centreon-gorgone/config.d/50-centreon-audit.yaml @@ -0,0 +1,5 @@ +gorgone: + modules: + - name: audit + package: "gorgone::modules::centreon::audit::hooks" + enable: true diff --git a/gorgone/tests/unit/class/config_examples/centreon-gorgone/config.d/cron.d/41-service-discovery.yaml b/gorgone/tests/unit/class/config_examples/centreon-gorgone/config.d/cron.d/41-service-discovery.yaml new file mode 100644 index 00000000000..b2796c7d284 --- /dev/null +++ b/gorgone/tests/unit/class/config_examples/centreon-gorgone/config.d/cron.d/41-service-discovery.yaml @@ -0,0 +1,3 @@ +- id: service_discovery + timespec: "30 22 * * *" + action: LAUNCHSERVICEDISCOVERY diff --git a/gorgone/tests/unit/class/config_examples/centreon-gorgone/config.d/whitelist.conf.d/centreon.yaml b/gorgone/tests/unit/class/config_examples/centreon-gorgone/config.d/whitelist.conf.d/centreon.yaml new file mode 100644 index 00000000000..e4d0ce5e784 --- /dev/null +++ b/gorgone/tests/unit/class/config_examples/centreon-gorgone/config.d/whitelist.conf.d/centreon.yaml @@ -0,0 +1,21 @@ +# Configuration brought by Centreon Gorgone package. +# SHOULD NOT BE EDITED! CREATE YOUR OWN FILE IN WHITELIST.CONF.D DIRECTORY! +- ^sudo\s+(/bin/|/usr/bin/)?systemctl\s+(reload|restart)\s+(centengine|centreontrapd|cbd)\s*$ +- ^(sudo\s+)?(/usr/bin/)?service\s+(centengine|centreontrapd|cbd|cbd-sql)\s+(reload|restart)\s*$ +- ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/+centengine\.cfg\s*$ +- ^cat\s+/var/lib/centreon-engine/+[a-zA-Z0-9\-]+-stats\.json\s*$ +- ^/usr/lib/centreon/plugins/.*$ +- ^/bin/perl /usr/share/centreon/bin/anomaly_detection --seasonality >> /var/log/centreon/anomaly_detection\.log 2>&1\s*$ +- ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ +- ^centreon +- ^mkdir +- ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host +- ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ +- ^/usr/bin/php (-q )?/usr/share/centreon/cron/[\w,\s.-]+ >> /var/log/centreon-gorgone/[\w,\s.-]+\s+2>&1$ +- ^/usr/bin/php -q /usr/share/centreon/www/modules/centreon-bi-server/tools/purgeArchivesFiles\.php >> /var/log/centreon-gorgone/centreon-bi-archive-retention\.log 2>&1$ +- ^/usr/share/centreon/cron/eventReportBuilder --config=/etc/centreon/conf\.pm >> /var/log/centreon-gorgone/eventReportBuilder\.log 2>&1$ +- ^/usr/share/centreon/cron/dashboardBuilder --config=/etc/centreon/conf\.pm >> /var/log/centreon-gorgone/dashboardBuilder\.log 2>&1$ +- ^/usr/share/centreon/www/modules/centreon-dsm/+cron/centreon_dsm_purge\.pl --config=\"/etc/centreon/conf.pm\" --severity=\S+ >> /var/log/centreon-gorgone/centreon_dsm_purge\.log 2>&1\s*$ +- ^/usr/share/centreon-bi-backup/centreon-bi-backup-web\.sh >> /var/log/centreon-gorgone/centreon-bi-backup-web\.log 2>&1$ +- ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/+cron/centreon_autodisco.pl --config='/etc/centreon/conf.pm' --config-extra='/etc/centreon/centreon_autodisco.pm' --severity=\S+ >> /var/log/centreon-gorgone/centreon_service_discovery.log 2>&1$ +- secret::hashicorp_vault::SecretPathArg::secretNameFromApiResponse \ No newline at end of file diff --git a/gorgone/tests/unit/class/config_examples/centreon-gorgone/config.yaml b/gorgone/tests/unit/class/config_examples/centreon-gorgone/config.yaml new file mode 100644 index 00000000000..d843c9b7d46 --- /dev/null +++ b/gorgone/tests/unit/class/config_examples/centreon-gorgone/config.yaml @@ -0,0 +1,3 @@ +name: config.yaml +description: Configuration brought by Centreon Gorgone package. SHOULD NOT BE EDITED! USE CONFIG.D DIRECTORY! +configuration: !include config.d/*.yaml \ No newline at end of file diff --git a/gorgone/tests/unit/class/config_examples/centreon-gorgone/expectedConfiguration.pl b/gorgone/tests/unit/class/config_examples/centreon-gorgone/expectedConfiguration.pl new file mode 100644 index 00000000000..819c3556a1e --- /dev/null +++ b/gorgone/tests/unit/class/config_examples/centreon-gorgone/expectedConfiguration.pl @@ -0,0 +1,195 @@ +return { + 'configuration' => { + 'centreon' => { + 'database' => { + 'db_configuration' => { + 'dsn' => 'mysql:host=localhost:port=3306;dbname=centreon', + 'password' => 'VaultSentASecret', + 'username' => 'centreon' + }, + 'db_realtime' => { + 'dsn' => 'mysql:host=localhost:port=3306;dbname=centreon_storage', + 'password' => 'password', + 'username' => 'centreon' + } + } + }, + 'gorgone' => { + 'tpapi' => [ + { + 'password' => 'VaultSentASecret', + 'base_url' => 'http://127.0.0.1/centreon/api/latest/', + 'name' => 'centreonv2', + 'username' => 'centreon-gorgone' + }, + { + 'username' => 'centreon-gorgone', + 'name' => 'clapi', + 'password' => 'webapiPassword!' + } + ], + 'gorgonecore' => { + 'id' => 1, + 'privkey' => '/var/lib/centreon-gorgone/.keys/rsakey.priv.pem', + 'pubkey' => '/var/lib/centreon-gorgone/.keys/rsakey.pub.pem' + }, + 'modules' => [ + { + 'package' => 'gorgone::modules::core::action::hooks', + 'whitelist_cmds' => 'VaultSentASecret', + 'command_timeout' => 30, + 'allowed_cmds' => [ + '^sudo\\s+(/bin/|/usr/bin/)?systemctl\\s+(reload|restart)\\s+(centengine|centreontrapd|cbd)\\s*$', + '^(sudo\\s+)?(/usr/bin/)?service\\s+(centengine|centreontrapd|cbd|cbd-sql)\\s+(reload|restart)\\s*$', + '^/usr/sbin/centenginestats\\s+-c\\s+/etc/centreon-engine/+centengine\\.cfg\\s*$', + '^cat\\s+/var/lib/centreon-engine/+[a-zA-Z0-9\\-]+-stats\\.json\\s*$', + '^/usr/lib/centreon/plugins/.*$', + '^/bin/perl /usr/share/centreon/bin/anomaly_detection --seasonality >> /var/log/centreon/anomaly_detection\\.log 2>&1\\s*$', + '^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\\.php >> /var/log/centreon-helios\\.log 2>&1\\s*$', + '^centreon', + '^mkdir', + '^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host', + '^/usr/share/centreon/bin/centreon -u \\"centreon-gorgone\\" -p \\S+ -w -o CentreonWorker -a processQueue$', + '^/usr/bin/php (-q )?/usr/share/centreon/cron/[\\w,\\s.-]+ >> /var/log/centreon-gorgone/[\\w,\\s.-]+\\s+2>&1$', + '^/usr/bin/php -q /usr/share/centreon/www/modules/centreon-bi-server/tools/purgeArchivesFiles\\.php >> /var/log/centreon-gorgone/centreon-bi-archive-retention\\.log 2>&1$', + '^/usr/share/centreon/cron/eventReportBuilder --config=/etc/centreon/conf\\.pm >> /var/log/centreon-gorgone/eventReportBuilder\\.log 2>&1$', + '^/usr/share/centreon/cron/dashboardBuilder --config=/etc/centreon/conf\\.pm >> /var/log/centreon-gorgone/dashboardBuilder\\.log 2>&1$', + '^/usr/share/centreon/www/modules/centreon-dsm/+cron/centreon_dsm_purge\\.pl --config=\\"/etc/centreon/conf.pm\\" --severity=\\S+ >> /var/log/centreon-gorgone/centreon_dsm_purge\\.log 2>&1\\s*$', + '^/usr/share/centreon-bi-backup/centreon-bi-backup-web\\.sh >> /var/log/centreon-gorgone/centreon-bi-backup-web\\.log 2>&1$', + '^/usr/share/centreon/www/modules/centreon-autodiscovery-server/+cron/centreon_autodisco.pl --config=\'/etc/centreon/conf.pm\' --config-extra=\'/etc/centreon/centreon_autodisco.pm\' --severity=\\S+ >> /var/log/centreon-gorgone/centreon_service_discovery.log 2>&1$', + 'VaultSentASecret' + ], + 'name' => 'action', + 'enable' => 'true' + }, + { + 'enable' => 'true', + 'ssl_cert_file' => '/var/lib/centreon-gorgone/.keys/server_api_cert.pem', + 'ssl' => 'true', + 'auth' => { + 'user' => 'web-user-gorgone-api', + 'enabled' => 'false', + 'password' => 'password' + }, + 'name' => 'httpserver', + 'address' => '0.0.0.0', + 'allowed_hosts' => { + 'enabled' => 'true', + 'subnets' => [ + '127.0.0.1/32' + ] + }, + 'port' => '8085', + 'package' => 'gorgone::modules::core::httpserver::hooks', + 'ssl_key_file' => '/var/lib/centreon-gorgone/.keys/server_api_key.pem' + }, + { + 'whitelist_cmds' => 'true', + 'enable' => 'true', + 'package' => 'gorgone::modules::core::action::hooks', + 'allowed_cmds' => [ + '^sudo\\s+(/bin/)?systemctl\\s+(reload|restart)\\s+(centengine|centreontrapd|cbd)\\s*$', + '^(sudo\\s+)?(/usr/bin/)?service\\s+(centengine|centreontrapd|cbd|cbd-sql)\\s+(reload|restart)\\s*$', + '^/usr/sbin/centenginestats\\s+-c\\s+/etc/centreon-engine/centengine\\.cfg\\s*$', + '^cat\\s+/var/lib/centreon-engine/[a-zA-Z0-9\\-]+-stats\\.json\\s*$', + '^/usr/lib/centreon/plugins/.*$', + '^/bin/perl /usr/share/centreon/bin/anomaly_detection --seasonality >> /var/log/centreon/anomaly_detection\\.log 2>&1\\s*$', + '^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\\.php >> /var/log/centreon-helios\\.log 2>&1\\s*$', + '^centreon', + '^mkdir', + '^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host', + '^/usr/share/centreon/bin/centreon -u \\"centreon-gorgone\\" -p \\S+ -w -o CentreonWorker -a processQueue$' + ], + 'name' => 'action', + 'command_timeout' => 30 + }, + { + 'enable' => 'true', + 'cron' => [ + { + 'action' => 'LAUNCHSERVICEDISCOVERY', + 'timespec' => '30 22 * * *', + 'id' => 'service_discovery' + } + ], + 'name' => 'cron', + 'package' => 'gorgone::modules::core::cron::hooks' + }, + { + 'package' => 'gorgone::modules::core::register::hooks', + 'name' => 'register', + 'enable' => 'true' + }, + { + 'enable' => 'true', + 'package' => 'gorgone::modules::centreon::nodes::hooks', + 'name' => 'nodes' + }, + { + 'enable' => 'true', + 'httpserver' => { + 'enable' => 'true', + 'port' => 8099, + 'token' => "^\$*\x{f9}^\x{e9}&\x{e0}\x{e9}r\x{e7}(\x{e9}/*-+\$\$z\@ze%r\x{a8}\x{a3}\x{b5}~zz", + 'address' => '0.0.0.0' + }, + 'package' => 'gorgone::modules::core::proxy::hooks', + 'name' => 'proxy', + 'pool' => 1, + 'buffer_size' => 10 + }, + { + 'cmd_dir' => '/var/lib/centreon/centcore/', + 'buffer_size' => 100, + 'cache_dir' => '/var/cache/centreon/', + 'enable' => 'true', + 'name' => 'legacycmd', + 'remote_dir' => '/var/cache/centreon//config/remote-data/', + 'cmd_file' => '/var/lib/centreon/centcore.cmd', + 'cache_dir_trap' => '/etc/snmp/centreon_traps', + 'package' => 'gorgone::modules::centreon::legacycmd::hooks' + }, + { + 'enable' => 'true', + 'command_file' => '/var/lib/centreon-engine/rw/centengine.cmd', + 'name' => 'engine', + 'package' => 'gorgone::modules::centreon::engine::hooks' + }, + { + 'name' => 'statistics', + 'package' => 'gorgone::modules::centreon::statistics::hooks', + 'enable' => 'true', + 'cron' => [ + { + 'action' => 'BROKERSTATS', + 'timespec' => '*/5 * * * *', + 'id' => 'broker_stats', + 'parameters' => { + 'timeout' => 10 + } + }, + { + 'action' => 'ENGINESTATS', + 'id' => 'engine_stats', + 'timespec' => '*/5 * * * *', + 'parameters' => { + 'timeout' => 10 + } + } + ], + 'broker_cache_dir' => '/var/cache/centreon//broker-stats/' + }, + { + 'enable' => 'true', + 'package' => 'gorgone::modules::centreon::autodiscovery::hooks', + 'name' => 'autodiscovery' + }, + { + 'package' => 'gorgone::modules::centreon::audit::hooks', + 'name' => 'audit', + 'enable' => 'true' + } + ] + } + } +}; diff --git a/gorgone/tests/unit/class/config_examples/include_other_files/first_module.yaml b/gorgone/tests/unit/class/config_examples/include_other_files/first_module.yaml new file mode 100644 index 00000000000..767a5f36e7d --- /dev/null +++ b/gorgone/tests/unit/class/config_examples/include_other_files/first_module.yaml @@ -0,0 +1,3 @@ +gorgone: + gorgonecore: + global_variable: "value" \ No newline at end of file diff --git a/gorgone/tests/unit/class/config_examples/include_other_files/main.yaml b/gorgone/tests/unit/class/config_examples/include_other_files/main.yaml new file mode 100644 index 00000000000..fc5b54b1b49 --- /dev/null +++ b/gorgone/tests/unit/class/config_examples/include_other_files/main.yaml @@ -0,0 +1,4 @@ +--- +name: config.yaml +description: simple configuration exemple without other file include. +configuration: !include ./first_module.yaml diff --git a/gorgone/tests/unit/class/config_examples/simple_no_recursion/norecursion.yaml b/gorgone/tests/unit/class/config_examples/simple_no_recursion/norecursion.yaml new file mode 100644 index 00000000000..c903827c7b1 --- /dev/null +++ b/gorgone/tests/unit/class/config_examples/simple_no_recursion/norecursion.yaml @@ -0,0 +1,15 @@ +--- +name: config.yaml +description: simple configuration exemple without other file include. +configuration: + gorgone: + key1: a string with all char &é"'(-è_çà)=!:;,*$^ù%µ£¨/.\e?/§ + key2: + - array1 + - array2 + - array3 + TrueVal: true + FalseVal: false + vault: + badFormat: "secret::hashicorp::thereIsOnlyOneColon" + correctFormat: "secret::hashicorp_vault::SecretPathArg::secretNameFromApiResponse" diff --git a/gorgone/tests/unit/class/core.t b/gorgone/tests/unit/class/core.t new file mode 100644 index 00000000000..3fef51c4de7 --- /dev/null +++ b/gorgone/tests/unit/class/core.t @@ -0,0 +1,124 @@ +#!/usr/bin/perl + +# we can't use mock() on a non loaded package, so we need to create the class we want to mock first. +# We could have set centreon-common as a dependancy for the test, but it's not that package we are testing right now, so let mock it. +BEGIN { + package centreon::common::centreonvault; + sub get_secret {}; + sub new {}; + $INC{ (__PACKAGE__ =~ s{::}{/}rg) . ".pm" } = 1; +} + +# same here, gorgone use a logger, but we don't want to test it right now, so we mock it. +BEGIN { + package centreon::common::logger; + sub severity {}; + sub new {}; + $INC{ (__PACKAGE__ =~ s{::}{/}rg) . ".pm" } = 1; # this allow the module to be available for other modules anywhere in the code. +} + +package main; + +use strict; +use warnings; +use Test2::V0; +use Test2::Plugin::NoWarnings echo => 1; +use Test2::Tools::Compare qw{is like match}; +use Data::Dumper; +use FindBin; +use lib "$FindBin::Bin/../../../"; +use gorgone::class::script; +use gorgone::class::core; + +sub create_data_set { + my $set = {}; + # as we are in unit test, we can't be sure of our current path, but the tests require that we start from the same directory than the script. + chdir($FindBin::Bin); + $set->{logger} = mock 'centreon::common::logger'; # this is from Test2::Tools::Mock, included by Test2::V0 + $set->{vault} = mock 'centreon::common::centreonvault'; + + $set->{vault}->override('get_secret' => sub { + if ($_[1] eq 'secret::hashicorp_vault::SecretPathArg::secretNameFromApiResponse') { + return 'VaultSentASecret'; + } + return $_[1]; + }, 'new' => sub { + return bless({}, 'centreon::common::centreonvault'); + }); + + return $set; +} + +sub test_configuration_read { + my $set = shift; + # let's make a simple object and try to industryalize the yaml read configuration. + my $gorgone = gorgone::class::core->new(); + $gorgone->{logger} = $set->{logger}; + $gorgone->{vault} = centreon::common::centreonvault->new(); + + my $tests_cases = [ + { + file => './config_examples/simple_no_recursion/norecursion.yaml', + expected => { configuration => { gorgone => { + key1 => 'a string with all char &é"\'(-è_çà)=!:;,*$^ù%µ£¨/.\e?/§', + key2 => ["array1", "array2", "array3"], + TrueVal => 'true', + FalseVal => 'false', + vault => { + badFormat => 'secret::hashicorp::thereIsOnlyOneColon', + correctFormat => 'VaultSentASecret'}, + + } } }, + msg => 'simple configuration without recursion' + }, + { + file => './config_examples/include_other_files/main.yaml', + expected => { configuration => { gorgone => { + gorgonecore => { global_variable => "value" } + } } }, + msg => 'simple configuration with !include.' + }, + { # this is a real world exemple with all parameter I could think of. The default configuration don't have all of them. + # this is more an integration test than a unit test, but allow to test the whole configuration. + file => './config_examples/centreon-gorgone/config.yaml', + expected => require("./config_examples/centreon-gorgone/expectedConfiguration.pl"), + msg => 'complete configuration with multiples include and many files.' + } + ]; + + for my $test (@$tests_cases) { + my $config = $gorgone->yaml_load_config( + file => $test->{file}, + filter => '!($ariane eq "configuration##" || $ariane =~ /^configuration##(?:gorgone|centreon)##/)' + ); + is($config, $test->{expected}, $test->{msg}); + } + +} + +sub test_yaml_get_include { + my $set = shift; + my $gorgone = gorgone::class::core->new(); + $gorgone->{logger} = $set->{logger}; + #$gorgone->{vault} = centreon::common::centreonvault->new(); + my @result = $gorgone->yaml_get_include('include' => '*.yaml', + 'current_dir' => './config_examples/include_other_files', + 'filter' => '!($ariane eq "configuration##" || $ariane =~ /^configuration##(?:gorgone|centreon)##/)'); + my @expected = ("./config_examples/include_other_files/./first_module.yaml", "./config_examples/include_other_files/./main.yaml"); + is(\@result, \@expected, 'found both files of the directory'); + + my @emptyResult = $gorgone->yaml_get_include('include' => '/notAFile.yaml', + 'current_dir' => './config_examples/include_other_files', + 'filter' => '!($ariane eq "configuration##" || $ariane =~ /^configuration##(?:gorgone|centreon)##/)'); + is(scalar(@emptyResult), 0, 'no file found return empty'); +} +sub main { + my $set = create_data_set(); + test_yaml_get_include($set); + test_configuration_read($set); + + print "\n"; + done_testing; +} +&main; + diff --git a/perl-libs/lib/centreon/common/logger.pm b/perl-libs/lib/centreon/common/logger.pm index f3faaaca1d9..db5441e006a 100644 --- a/perl-libs/lib/centreon/common/logger.pm +++ b/perl-libs/lib/centreon/common/logger.pm @@ -234,12 +234,14 @@ sub writeLog($$$%) { # do nothing if the configured severity does not imply logging this message return if ($self->{severity} < $severity); + if (length($msg) > 20000) { + $msg = substr($msg, 0, 20000) . '...'; + } + $msg = ($self->withpid()) ? "$$ - $msg" : $msg; - $msg = ($self->withpid()) ? "[$$] $msg" : $msg; - - my $datedmsg = "[" . $human_severities{$severity} . "] " . $msg . "\n"; + my $datedmsg = $human_severities{$severity} . " - " . $msg . "\n"; if ($self->withdate()) { - $datedmsg = "[" . $self->get_date . "] " . $datedmsg; + $datedmsg = $self->get_date . " - " . $datedmsg; } if ($self->{log_mode} == 1 and defined($self->{filehandler})) { print {$self->{filehandler}} $datedmsg; diff --git a/perl-libs/lib/t/common/logger.t b/perl-libs/lib/t/common/logger.t index 7af11ff59ba..3632876aad7 100644 --- a/perl-libs/lib/t/common/logger.t +++ b/perl-libs/lib/t/common/logger.t @@ -87,9 +87,9 @@ sub check_log_format { # as it is a builtin function, it is possible but hard to setup and can have various hard to debug side effect. my %options = @_; - my $regex = '^\[\d{4}\-\d{2}\-\d{2} \d{2}:\d{2}:\d{2}\] \[' . $options{severity} . '\] '; + my $regex = '^\d{4}\-\d{2}\-\d{2} \d{2}:\d{2}:\d{2} - ' . $options{severity} . ' - '; # if there is pid we ignore it in the regex - $options{pid} == 1 and $regex .= '\[\d+\] '; + $options{pid} == 1 and $regex .= '\d+ - '; $regex .= '(.*)$'; like($options{log}, qr/$regex/, "log format is respected."); $options{log} =~ /$regex/; # like() don't seem to return the matched string