From abff755972fe3ffb417e76ad5ddb47c146ae3046 Mon Sep 17 00:00:00 2001 From: qgarnier Date: Wed, 10 Nov 2021 16:46:02 +0100 Subject: [PATCH] enh(rubrik): add token authentication (#3239) --- .../apps/backup/rubrik/restapi/custom/api.pm | 123 ++++++++++++++++-- 1 file changed, 113 insertions(+), 10 deletions(-) diff --git a/centreon-plugins/apps/backup/rubrik/restapi/custom/api.pm b/centreon-plugins/apps/backup/rubrik/restapi/custom/api.pm index 5430ac39c..c3f8deb5f 100644 --- a/centreon-plugins/apps/backup/rubrik/restapi/custom/api.pm +++ b/centreon-plugins/apps/backup/rubrik/restapi/custom/api.pm @@ -23,7 +23,9 @@ package apps::backup::rubrik::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) = @_; @@ -49,13 +51,15 @@ sub new { 'timeout:s' => { name => 'timeout' }, 'unknown-http-status:s' => { name => 'unknown_http_status' }, 'warning-http-status:s' => { name => 'warning_http_status' }, - 'critical-http-status:s' => { name => 'critical_http_status' } + 'critical-http-status:s' => { name => 'critical_http_status' }, + 'token:s' => { name => 'token' } }); } $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; } @@ -79,11 +83,17 @@ sub check_options { $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} : ''; + $self->{token} = $self->{option_results}->{token}; if (!defined($self->{option_results}->{hostname}) || $self->{option_results}->{hostname} eq '') { $self->{output}->add_option_msg(short_msg => 'Need to specify --hostname option.'); $self->{output}->option_exit(); } + if (defined($self->{token})) { + $self->{cache}->check_options(option_results => $self->{option_results}); + return 0 if ($self->{token} ne ''); + } + if ($self->{api_username} eq '') { $self->{output}->add_option_msg(short_msg => 'Need to specify --api-username option.'); $self->{output}->option_exit(); @@ -102,10 +112,6 @@ sub settings { return if (defined($self->{settings_done})); $self->{http}->add_header(key => 'Accept', value => 'application/json'); $self->{http}->add_header(key => 'Content-Type', value => 'application/json'); - $self->{option_results}->{credentials} = 1; - $self->{option_results}->{basic} = 1; - $self->{option_results}->{username} = $self->{api_username}; - $self->{option_results}->{password} = $self->{api_password}; $self->{http}->set_options(%{$self->{option_results}}); $self->{settings_done} = 1; } @@ -113,21 +119,114 @@ sub settings { sub get_connection_info { my ($self, %options) = @_; - return $self->{option_results}->{hostname} . ":" . $self->{option_results}->{port}; + return $self->{option_results}->{hostname} . ':' . $self->{option_results}->{port}; +} + +sub get_token { + my ($self, %options) = @_; + + my $has_cache_file = $self->{cache}->read(statefile => 'rubrik_api_' . md5_hex($self->{option_results}->{hostname} . '_' . $self->{api_username})); + my $token = $self->{cache}->get(name => 'token'); + my $md5_secret_cache = $self->{cache}->get(name => 'md5_secret'); + my $md5_secret = md5_hex($self->{api_username} . $self->{api_password}); + + if ($has_cache_file == 0 || + !defined($token) || + (defined($md5_secret_cache) && $md5_secret_cache ne $md5_secret) + ) { + $self->settings(); + my $content = $self->{http}->request( + url_path => '/api/v1/cluster/me', + credentials => 1, + basic => 1, + username => $self->{api_username}, + password => $self->{api_password} + ); + + my $decoded; + eval { + $decoded = JSON::XS->new->utf8->decode($content); + }; + if ($@) { + $self->{output}->add_option_msg(short_msg => "Cannot decode json response"); + $self->{output}->option_exit(); + } + + $token = $decoded->{token}; + my $datas = { + updated => time(), + token => $decoded->{token}, + md5_secret => $md5_secret + }; + $self->{cache}->write(data => $datas); + } + + return $token; +} + +sub clean_token { + my ($self, %options) = @_; + + my $datas = { updated => time() }; + $self->{cache}->write(data => $datas); +} + +sub credentials { + my ($self, %options) = @_; + + my $token = $self->{token}; + if (defined($self->{token}) && $self->{token} eq '') { + $token = $self->get_token(); + } + + my $creds = {}; + if (defined($self->{token})) { + $creds = { + header => ['Authorization: Bearer ' . $token], + unknown_status => '', + warning_status => '', + critical_status => '' + }; + } else { + $creds = { + credentials => 1, + basic => 1, + username => $self->{api_username}, + password => $self->{api_password}, + unknown_status => $self->{unknown_http_status}, + warning_status => $self->{warning_http_status}, + critical_status => $self->{critical_http_status} + }; + } + + return $creds; } sub request_api { my ($self, %options) = @_; $self->settings(); + my $creds = $self->credentials(); my ($content) = $self->{http}->request( url_path => '/api/internal' . $options{endpoint}, - unknown_status => $self->{unknown_http_status}, - warning_status => $self->{warning_http_status}, - critical_status => $self->{critical_http_status}, - get_param => $options{get_param} + get_param => $options{get_param}, + %$creds ); + # Maybe token is invalid. so we retry + if (defined($self->{token}) && $self->{http}->get_code() < 200 || $self->{http}->get_code() >= 300) { + $self->clean_token(); + $creds = $self->credentials(); + $content = $self->{http}->request( + url_path => '/api/internal' . $options{endpoint}, + get_param => $options{get_param}, + %$creds, + unknown_status => $self->{unknown_http_status}, + warning_status => $self->{warning_http_status}, + critical_status => $self->{critical_http_status} + ); + } + if (!defined($content) || $content eq '') { $self->{output}->add_option_msg(short_msg => "API returns empty content [code: '" . $self->{http}->get_code() . "'] [message: '" . $self->{http}->get_message() . "']"); $self->{output}->option_exit(); @@ -179,6 +278,10 @@ API username. API password. +=item B<--token> + +Use token authentication. If option is empty, token is created. + =item B<--timeout> Set timeout in seconds (Default: 30).