From 644c7df26a250eaef5a2a2d10f4967e067937b55 Mon Sep 17 00:00:00 2001 From: itoussies <65223458+itoussies@users.noreply.github.com> Date: Fri, 30 Oct 2020 14:44:03 +0100 Subject: [PATCH] new(plugin)apps-graylog-restapi (#2243) * add plugin graylog restapi --- apps/graylog/restapi/custom/api.pm | 329 +++++++++++++++++++++ apps/graylog/restapi/mode/notifications.pm | 141 +++++++++ apps/graylog/restapi/mode/query.pm | 113 +++++++ apps/graylog/restapi/plugin.pm | 50 ++++ 4 files changed, 633 insertions(+) create mode 100644 apps/graylog/restapi/custom/api.pm create mode 100644 apps/graylog/restapi/mode/notifications.pm create mode 100644 apps/graylog/restapi/mode/query.pm create mode 100644 apps/graylog/restapi/plugin.pm diff --git a/apps/graylog/restapi/custom/api.pm b/apps/graylog/restapi/custom/api.pm new file mode 100644 index 000000000..6dc190abf --- /dev/null +++ b/apps/graylog/restapi/custom/api.pm @@ -0,0 +1,329 @@ +# +# Copyright 2020 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 apps::graylog::restapi::custom::api; + +use strict; +use warnings; +use centreon::plugins::http; +use centreon::plugins::statefile; +use JSON::XS; +use Digest::MD5 qw(md5_hex); + +sub new { + my ($class, %options) = @_; + my $self = {}; + bless $self, $class; + + if (!defined($options{output})) { + print "Class Custom: Need to specify 'output' argument.\n"; + exit 3; + } + if (!defined($options{options})) { + $options{output}->add_option_msg(short_msg => "Class Custom: Need to specify 'options' argument."); + $options{output}->option_exit(); + } + + if (!defined($options{noptions})) { + $options{options}->add_options(arguments => { + 'hostname:s' => { name => 'hostname' }, + 'url-path:s' => { name => 'url_path' }, + 'port:s' => { name => 'port' }, + 'proto:s' => { name => 'proto' }, + 'api-username:s' => { name => 'api_username' }, + 'api-password:s' => { name => 'api_password' }, + 'timeout:s' => { name => 'timeout' }, + 'requested-by:s' => { name => 'requested_by' }, + 'unknown-http-status:s' => { name => 'unknown_http_status' }, + 'warning-http-status:s' => { name => 'warning_http_status' }, + 'critical-http-status:s' => { name => 'critical_http_status' } + }); + } + $options{options}->add_help(package => __PACKAGE__, sections => 'REST API OPTIONS', once => 1); + + $self->{output} = $options{output}; + $self->{http} = centreon::plugins::http->new(%options); + $self->{cache} = centreon::plugins::statefile->new(%options); + + return $self; +} + +sub set_options { + my ($self, %options) = @_; + + $self->{option_results} = $options{option_results}; +} + +sub set_defaults {} + +sub check_options { + my ($self, %options) = @_; + + $self->{hostname} = (defined($self->{option_results}->{hostname})) ? $self->{option_results}->{hostname} : ''; + $self->{port} = (defined($self->{option_results}->{port})) ? $self->{option_results}->{port} : 9000; + $self->{proto} = (defined($self->{option_results}->{proto})) ? $self->{option_results}->{proto} : 'http'; + $self->{url_path} = (defined($self->{option_results}->{url_path})) ? $self->{option_results}->{url_path} : '/api/'; + $self->{timeout} = (defined($self->{option_results}->{timeout})) ? $self->{option_results}->{timeout} : 10; + $self->{api_username} = (defined($self->{option_results}->{api_username})) ? $self->{option_results}->{api_username} : ''; + $self->{api_password} = (defined($self->{option_results}->{api_password})) ? $self->{option_results}->{api_password} : ''; + $self->{requested_by} = (defined($self->{option_results}->{requested_by})) ? $self->{option_results}->{requested_by} : 'cli'; + $self->{unknown_http_status} = (defined($self->{option_results}->{unknown_http_status})) ? $self->{option_results}->{unknown_http_status} : '%{http_code} < 200 or %{http_code} >= 300'; + $self->{warning_http_status} = (defined($self->{option_results}->{warning_http_status})) ? $self->{option_results}->{warning_http_status} : ''; + $self->{critical_http_status} = (defined($self->{option_results}->{critical_http_status})) ? $self->{option_results}->{critical_http_status} : ''; + + if ($self->{hostname} eq '') { + $self->{output}->add_option_msg(short_msg => 'Need to specify hostname option.'); + $self->{output}->option_exit(); + } + if ($self->{api_username} eq '') { + $self->{output}->add_option_msg(short_msg => 'Need to specify --api-username option.'); + $self->{output}->option_exit(); + } + if ($self->{api_password} eq '') { + $self->{output}->add_option_msg(short_msg => 'Need to specify --api-password option.'); + $self->{output}->option_exit(); + } + + $self->{cache}->check_options(option_results => $self->{option_results}); + return 0; +} + +sub build_options_for_httplib { + my ($self, %options) = @_; + + $self->{option_results}->{hostname} = $self->{hostname}; + $self->{option_results}->{timeout} = $self->{timeout}; + $self->{option_results}->{port} = $self->{port}; + $self->{option_results}->{proto} = $self->{proto}; +} + +sub settings { + my ($self, %options) = @_; + + $self->build_options_for_httplib(); + $self->{http}->add_header(key => 'Content-Type', value => 'application/json'); + $self->{http}->add_header(key => 'Accept', value => 'application/json'); + $self->{http}->add_header(key => 'X-Requested-By', value => $self->{requested_by}); + $self->{http}->set_options(%{$self->{option_results}}); +} + +sub get_connection_info { + my ($self, %options) = @_; + + return $self->{hostname} . ":" . $self->{port}; +} + +sub get_hostname { + my ($self, %options) = @_; + + return $self->{hostname}; +} + +sub get_port { + my ($self, %options) = @_; + + return $self->{port}; +} + +sub json_decode { + my ($self, %options) = @_; + + my $decoded; + eval { + $decoded = JSON::XS->new->utf8->decode($options{content}); + }; + + if ($@) { + $self->{output}->add_option_msg(short_msg => "Cannot decode json response: $@"); + $self->{output}->option_exit(); + } + + return $decoded; +} + +sub clean_session_token { + my ($self, %options) = @_; + + my $datas = {}; + $options{statefile}->write(data => $datas); + $self->{session_token} = undef; +} + +sub authenticate { + my ($self, %options) = @_; + + my $has_cache_file = $options{statefile}->read(statefile => 'graylog_api_' . md5_hex($self->{option_results}->{hostname}) . '_' . md5_hex($self->{option_results}->{api_username})); + my $session_token = $options{statefile}->get(name => 'session_token'); + + if ($has_cache_file == 0 || !defined($session_token)) { + my $json_request = { + username => $self->{api_username}, + password => $self->{api_password}, + host => $self->{hostname} + }; + + my $encoded; + eval { + $encoded = encode_json($json_request); + }; + if ($@) { + $self->{output}->add_option_msg(short_msg => 'cannot encode json request'); + $self->{output}->option_exit(); + } + + my ($content) = $self->{http}->request( + method => 'POST', + url_path => $self->{url_path} . 'system/sessions/', + query_form_post => $encoded, + warning_status => '', + unknown_status => '', + critical_status => '' + ); + + if ($self->{http}->get_code() != 200) { + $self->{output}->add_option_msg(short_msg => "login error [code: '" . $self->{http}->get_code() . "'] [message: '" . $self->{http}->get_message() . "']"); + $self->{output}->option_exit(); + } + + my $decoded = $self->json_decode(content => $content); + + if (!defined($decoded) || !defined($decoded->{session_id})) { + $self->{output}->add_option_msg(short_msg => 'error retrieving session_token'); + $self->{output}->option_exit(); + } + + $session_token = $decoded->{session_id}; + + my $datas = { session_token => $session_token }; + $options{statefile}->write(data => $datas); + } + + $self->{session_token} = $session_token; +} + +sub query_relative { + my ($self, %options) = @_; + + my $content = $self->request_api( + endpoint => 'search/universal/relative', + get_param => ['query=' . $options{query}, 'range=' . $options{timeframe}] + ); + + return $content; +} + +sub request_api { + my ($self, %options) = @_; + + $self->settings(); + if (!defined($self->{session_token})) { + $self->authenticate(statefile => $self->{cache}); + } + + my $content = $self->{http}->request( + url_path => $self->{url_path} . $options{endpoint}, + get_param => $options{get_param}, + username => $self->{session_token}, + password => 'session', + credentials => 1, + basic => 1, + warning_status => '', + unknown_status => '', + critical_status => '' + ); + + if ($self->{http}->get_code() < 200 || $self->{http}->get_code() >= 300) { + $self->clean_session_token(statefile => $self->{cache}); + $self->authenticate(statefile => $self->{cache}); + $content = $self->{http}->request( + url_path => $self->{url_path} . $options{endpoint}, + get_param => $options{get_param}, + username => $self->{session_token}, + password => 'session', + credentials => 1, + basic => 1, + unknown_status => $self->{unknown_http_status}, + warning_status => $self->{warning_http_status}, + critical_status => $self->{critical_http_status} + ); + } + + my $decoded = $self->json_decode(content => $content); + if (!defined($decoded)) { + $self->{output}->add_option_msg(short_msg => 'error while retrieving data (add --debug option for detailed message)'); + $self->{output}->option_exit(); + } + + return $decoded; +} + +1; + +__END__ + +=head1 NAME + +Graylog Rest API + +=head1 REST API OPTIONS + +Graylog Rest API + +=over 8 + +=item B<--hostname> + +Graylog hostname. + +=item B<--url-path> + +API url path (Default: '/api/') + +=item B<--port> + +API port (Default: 9000) + +=item B<--proto> + +Specify https if needed (Default: 'http') + +=item B<--username> + +Specify username for authentication (Mandatory if --credentials is specified) + +=item B<--password> + +Specify password for authentication (Mandatory if --credentials is specified) + +=item B<--timeout> + +Set HTTP timeout + +=item B<--requested-by> + +Set request HTTP header (Default: 'cli') + +=back + +=head1 DESCRIPTION + +B. + +=cut diff --git a/apps/graylog/restapi/mode/notifications.pm b/apps/graylog/restapi/mode/notifications.pm new file mode 100644 index 000000000..43939c091 --- /dev/null +++ b/apps/graylog/restapi/mode/notifications.pm @@ -0,0 +1,141 @@ +# +## Copyright 2020 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 apps::graylog::restapi::mode::notifications; + +use base qw(centreon::plugins::templates::counter); + +use strict; +use warnings; + +sub prefix_global_output { + my ($self, %options) = @_; + + return 'System notifications '; +} + +sub set_counters { + my ($self, %options) = @_; + + $self->{maps_counters_type} = [ + { name => 'global', type => 0, cb_prefix_output => 'prefix_global_output', skipped_code => { -10 => 1 } } + ]; + + $self->{maps_counters}->{global} = [ + { label => 'notifications-total', nlabel => 'graylog.system.notifications.total.count', set => { + key_values => [ { name => 'total' } ], + output_template => 'total: %s', + perfdatas => [ + { template => '%d', min => 0 } + ] + } + }, + { label => 'notifications-normal', nlabel => 'graylog.system.notifications.normal.count', set => { + key_values => [ { name => 'normal' } ], + output_template => 'normal: %s', + perfdatas => [ + { template => '%d', min => 0 } + ] + } + }, + { label => 'notifications-urgent', nlabel => 'graylog.system.notifications.urgent.count', set => { + key_values => [ { name => 'urgent' } ], + output_template => 'urgent: %s', + perfdatas => [ + { template => '%d', min => 0 }, + ] + } + } + ]; +} + +sub new { + my ($class, %options) = @_; + my $self = $class->SUPER::new(package => __PACKAGE__, %options, force_new_perfdata => 1); + bless $self, $class; + + $options{options}->add_options(arguments => { + 'filter-severity:s' => { name => 'filter_severity' }, + 'filter-node:s' => { name => 'filter_node' } + }); + + return $self; +} + +sub manage_selection { + my ($self, %options) = @_; + + my $result = $options{custom}->request_api(endpoint => 'system/notifications'); + + $self->{global} = { normal => 0, urgent => 0 }; + foreach my $notification (@{$result->{notifications}}) { + next if (defined($self->{option_results}->{filter_severity}) + && $self->{option_results}->{filter_severity} ne '' + && $notification->{severity} !~ /$self->{option_results}->{filter_severity}/); + + next if (defined($self->{option_results}->{filter_node}) + && $self->{option_results}->{filter_node} ne '' + && $notification->{node_id} !~ /$self->{option_results}->{filter_node}/); + + $self->{global}->{normal}++ if ($notification->{severity} =~ /normal/); + $self->{global}->{urgent}++ if ($notification->{severity} =~ /urgent/); + } + + $self->{global}->{total} = $self->{global}->{normal} + $self->{global}->{urgent}; +} + +1; + +__END__ + +=head1 MODE + +Check Graylog system notifications using Graylog API + +Example: +perl centreon_plugins.pl --plugin=apps::graylog::restapi::plugin +--mode=notifications --hostname=10.0.0.1 --api-username='username' --api-password='password' + +More information on https://docs.graylog.org/en//pages/configuration/rest_api.html + +=over 8 + +=item B<--filter-severity> + +Filter on specific notification severity. +Can be 'normal' or 'urgent'. +(Default: both severities shown). + +=item B<--filter-node> + +Filter notifications by node ID. +(Default: all notifications shown). + +=item B<--warning-notifications-*> + +Set warning threshold for notifications count (Default: '') where '*' can be 'total', 'normal' or 'urgent'. + +=item B<--critical-notifications-*> + +Set critical threshold for notifications count (Default: '') where '*' can be 'total', 'normal' or 'urgent'. + +=back + +=cut diff --git a/apps/graylog/restapi/mode/query.pm b/apps/graylog/restapi/mode/query.pm new file mode 100644 index 000000000..a96b046ab --- /dev/null +++ b/apps/graylog/restapi/mode/query.pm @@ -0,0 +1,113 @@ +# +# Copyright 2020 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 apps::graylog::restapi::mode::query; + +use base qw(centreon::plugins::templates::counter); + +use strict; +use warnings; + +sub set_counters { + my ($self, %options) = @_; + + $self->{maps_counters_type} = [ + { name => 'global', type => 0, skipped_code => { -10 => 1 } } + ]; + + $self->{maps_counters}->{global} = [ + { label => 'query-matches', nlabel => 'graylog.query.match.count', set => { + key_values => [ { name => 'query_matches' } ], + output_template => 'current queue messages: %s', + perfdatas => [ + { template => '%d', min => 0 } + ] + } + } + ]; +} + +sub new { + my ($class, %options) = @_; + my $self = $class->SUPER::new(package => __PACKAGE__, %options, force_new_perfdata => 1); + bless $self, $class; + + $options{options}->add_options(arguments => { + 'query:s' => { name => 'query' }, + 'timeframe:s' => { name => 'timeframe', default => 300 } + }); + + return $self; +} + +sub check_options { + my ($self, %options) = @_; + $self->SUPER::check_options(%options); + + if (!defined($self->{option_results}->{query}) || $self->{option_results}->{query} eq '') { + $self->{output}->add_option_msg(short_msg => 'Please set --query option.'); + $self->{output}->option_exit(); + } +} + +sub manage_selection { + my ($self, %options) = @_; + + my $result = $options{custom}->query_relative( + query => $self->{option_results}->{query}, + timeframe => $self->{option_results}->{timeframe} + ); + + $self->{global} = { + query_matches => $result->{total_results} + }; +} + +1; + +__END__ + +=head1 MODE + +Perform Lucene queries against Graylog API + +Example: +perl centreon_plugins.pl --plugin=apps::graylog::restapi::plugin +--mode=query --hostname=10.0.0.1 --api-username='username' --api-password='password' --query='my query' + +More information on https://docs.graylog.org/en//pages/configuration/rest_api.html + +=over 8 + +=item B<--query> + +Set a Lucene query. + +=item B<--timeframe> + +Set timeframe in seconds (E.g '300' to check last 5 minutes). + +=item B<--warning-query-matches> B<--critical-query-matches> + +Threshold on the number of results. + +=back + +=cut diff --git a/apps/graylog/restapi/plugin.pm b/apps/graylog/restapi/plugin.pm new file mode 100644 index 000000000..552a5106c --- /dev/null +++ b/apps/graylog/restapi/plugin.pm @@ -0,0 +1,50 @@ +# +# Copyright 2020 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 apps::graylog::restapi::plugin; + +use strict; +use warnings; +use base qw(centreon::plugins::script_custom); + +sub new { + my ($class, %options) = @_; + my $self = $class->SUPER::new(package => __PACKAGE__, %options); + bless $self, $class; + + $self->{version} = '0.1'; + $self->{modes} = { + query => 'apps::graylog::restapi::mode::query', + notifications => 'apps::graylog::restapi::mode::notifications' + }; + + $self->{custom_modes}->{api} = 'apps::graylog::restapi::custom::api'; + return $self; +} + +1; + +__END__ + +=head1 PLUGIN DESCRIPTION + +Check Graylogs through HTTP/REST API. + +=cut