From ba830eba5e10466c8b5f7a94718d4374ff649be4 Mon Sep 17 00:00:00 2001 From: sdepassio <114986849+sdepassio@users.noreply.github.com> Date: Tue, 14 Mar 2023 11:03:48 +0100 Subject: [PATCH] (plugin) cloud::azure::policyinsights::policystates - new (#4224) --- .../deb.json | 4 + .../pkg.json | 10 ++ .../rpm.json | 4 + src/cloud/azure/custom/api.pm | 65 ++++++- .../policystates/mode/compliance.pm | 158 ++++++++++++++++++ .../policyinsights/policystates/plugin.pm | 57 +++++++ 6 files changed, 291 insertions(+), 7 deletions(-) create mode 100644 packaging/centreon-plugin-Cloud-Azure-PolicyInsights-PolicyStates-Api/deb.json create mode 100644 packaging/centreon-plugin-Cloud-Azure-PolicyInsights-PolicyStates-Api/pkg.json create mode 100644 packaging/centreon-plugin-Cloud-Azure-PolicyInsights-PolicyStates-Api/rpm.json create mode 100644 src/cloud/azure/policyinsights/policystates/mode/compliance.pm create mode 100644 src/cloud/azure/policyinsights/policystates/plugin.pm diff --git a/packaging/centreon-plugin-Cloud-Azure-PolicyInsights-PolicyStates-Api/deb.json b/packaging/centreon-plugin-Cloud-Azure-PolicyInsights-PolicyStates-Api/deb.json new file mode 100644 index 000000000..2a6bcc090 --- /dev/null +++ b/packaging/centreon-plugin-Cloud-Azure-PolicyInsights-PolicyStates-Api/deb.json @@ -0,0 +1,4 @@ +{ + "dependencies": [ + ] +} \ No newline at end of file diff --git a/packaging/centreon-plugin-Cloud-Azure-PolicyInsights-PolicyStates-Api/pkg.json b/packaging/centreon-plugin-Cloud-Azure-PolicyInsights-PolicyStates-Api/pkg.json new file mode 100644 index 000000000..bd864f75b --- /dev/null +++ b/packaging/centreon-plugin-Cloud-Azure-PolicyInsights-PolicyStates-Api/pkg.json @@ -0,0 +1,10 @@ +{ + "pkg_name": "centreon-plugin-Cloud-Azure-PolicyInsights-PolicyStates-Api", + "pkg_summary": "Centreon Plugin to monitor Microsoft Azure Policy States using RestAPI", + "plugin_name": "centreon_azure_policyinsights_policystates_api.pl", + "files": [ + "centreon/plugins/script_custom.pm", + "cloud/azure/custom/", + "cloud/azure/policyinsights/policystates/" + ] +} \ No newline at end of file diff --git a/packaging/centreon-plugin-Cloud-Azure-PolicyInsights-PolicyStates-Api/rpm.json b/packaging/centreon-plugin-Cloud-Azure-PolicyInsights-PolicyStates-Api/rpm.json new file mode 100644 index 000000000..2a6bcc090 --- /dev/null +++ b/packaging/centreon-plugin-Cloud-Azure-PolicyInsights-PolicyStates-Api/rpm.json @@ -0,0 +1,4 @@ +{ + "dependencies": [ + ] +} \ No newline at end of file diff --git a/src/cloud/azure/custom/api.pm b/src/cloud/azure/custom/api.pm index 85f5f5137..f4e6a8dfc 100644 --- a/src/cloud/azure/custom/api.pm +++ b/src/cloud/azure/custom/api.pm @@ -150,8 +150,8 @@ sub get_access_token { my $has_cache_file = $options{statefile}->read( statefile => - 'azure_api_' . - md5_hex($self->{tenant}) . '_' . + 'azure_api_' . + md5_hex($self->{tenant}) . '_' . md5_hex($self->{client_id}) . '_' . md5_hex($self->{management_endpoint}) ); @@ -243,7 +243,7 @@ sub convert_duration { my ($self, %options) = @_; my $duration; - + if ($options{time_string} =~ /^P.*S$/) { centreon::plugins::misc::mymodule_load( output => $self->{output}, module => 'DateTime::Format::Duration::ISO8601', @@ -268,7 +268,7 @@ sub convert_duration { sub convert_iso8601_to_epoch { my ($self, %options) = @_; - + if ($options{time_string} =~ /(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}).(\d.*)Z/) { my $dt = DateTime->new( year => $1, @@ -284,7 +284,7 @@ sub convert_iso8601_to_epoch { return $epoch_time; } - + $self->{output}->add_option_msg(short_msg => "Wrong date format: $options{time_string}"); $self->{output}->option_exit(); @@ -310,7 +310,7 @@ sub json_decode { sub azure_get_subscription_cost_management_set_url { my ($self, %options) = @_; - my $url = $self->{management_endpoint} . "/subscriptions/" . $self->{subscription} . "/providers/Microsoft.CostManagement/query?api-version=" . $self->{api_version} ; + my $url = $self->{management_endpoint} . "/subscriptions/" . $self->{subscription} . "/providers/Microsoft.CostManagement/query?api-version=" . $self->{api_version}; return $url; } @@ -321,7 +321,7 @@ sub azure_get_subscription_cost_management { my $results = {}; my $encoded_form_post; - my $full_url = $self->azure_get_subscription_cost_management_set_url(); + my $full_url = $self->azure_get_subscription_cost_management_set_url(); eval { $encoded_form_post = JSON::XS->new->utf8->encode($options{body_post}); @@ -1177,6 +1177,57 @@ sub azure_list_sqlelasticpools { return $full_response; } +sub azure_set_url { + my ($self, %options) = @_; + + my $url = $self->{management_endpoint} . "/subscriptions/" . $self->{subscription}; + $url .= "/resourceGroups/" . $options{resource_group} if length $options{resource_group}; + $url .= "/providers/" . $options{providers} if length $options{providers}; + $url .= "/" . $options{resource} if length $options{resource}; + $url .= "/" . $options{query_name}; +} + +sub azure_list_policystates { + my ($self, %options) = @_; + + my $get_params = [ + 'api-version', $self->{api_version}, + '$select', 'ResourceId, ResourceType, ResourceLocation, ResourceGroup, PolicyDefinitionName, IsCompliant, ComplianceState' + ]; + my $resource_location; + if (length $options{resource_location}) { + $resource_location = ("ResourceLocation eq '" . $options{resource_location} . "'"); + delete $options{$resource_location}; + } + my $resource_type; + if (length $options{resource_type}) { + $resource_type = ("ResourceType eq '" . $options{resource_type} . "'"); + delete $options{$resource_type}; + } + my $filter; + if (length $resource_location && length $resource_type) { + $filter = $resource_location . " and " . $resource_type; + } elsif (length $resource_location) { + $filter = $resource_location + } elsif (length $resource_type) { + $filter = $resource_type + } + if (length($filter)) { + push(@$get_params, '$filter', $filter); + } + + my ($url) = $self->azure_set_url(%options); + my $response = $self->request_api( + method => 'POST', + full_url => $url, + hostname => '', + header => ['Content-Type: application/json'], + get_params => $get_params + ); + + return $response->{value}; +} + 1; __END__ diff --git a/src/cloud/azure/policyinsights/policystates/mode/compliance.pm b/src/cloud/azure/policyinsights/policystates/mode/compliance.pm new file mode 100644 index 000000000..4c2838f44 --- /dev/null +++ b/src/cloud/azure/policyinsights/policystates/mode/compliance.pm @@ -0,0 +1,158 @@ +# +# Copyright 2022 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 cloud::azure::policyinsights::policystates::mode::compliance; + +use base qw(centreon::plugins::templates::counter); + +use strict; +use warnings; +use centreon::plugins::templates::catalog_functions qw(catalog_status_threshold_ng); + +sub custom_compliance_state_output { + my ($self, %options) = @_; + return "Compliance state for policy '$self->{result_values}->{policy_name}' on resource '$self->{result_values}->{resource_name}' is '$self->{result_values}->{compliance_state}'"; +} + +sub set_counters { + my ($self, %options) = @_; + + $self->{maps_counters_type} = [ + { name => 'non_compliant_policies', type => 0 }, + { name => 'compliance_state', type => 1, message_multiple => 'All compliances states are ok' } + ]; + + $self->{maps_counters}->{non_compliant_policies} = [ + { label => 'non-compliant-policies', nlabel => 'policies.non_compliant.count', set => { + key_values => [ { name => 'non_compliant_policies' } ], + output_template => 'Number of non compliant policies: %d', + perfdatas => [ + { label => 'total_non_compliant_policies', template => '%d', min => 0, unit => '' } + ] + } + } + ]; + + $self->{maps_counters}->{compliance_state} = [ + { label => 'compliance-state', + type => 2, + critical_default => '%{compliance_state} eq "NonCompliant"', + set => { + key_values => [ { name => 'compliance_state' }, { name => 'policy_name' }, { name => 'resource_name' }, { name => 'display' } ], + closure_custom_output => $self->can('custom_compliance_state_output'), + closure_custom_threshold_check => \&catalog_status_threshold_ng + } + } + ]; +} + +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 => { + 'api-version:s' => { name => 'api_version', default => '2019-10-01'}, + 'policy-states:s' => { name => 'policy_states', default => 'default' }, + 'resource-group:s' => { name => 'resource_group' }, + 'resource-location:s' => { name => 'resource_location' }, + 'resource-type:s' => { name => 'resource_type' } + }); + return $self; +} + +sub check_options { + my ($self, %options) = @_; + $self->SUPER::check_options(%options); + + $self->{policy_states} = (defined($self->{option_results}->{policy_states}) && $self->{option_results}->{policy_states} ne "") ? $self->{option_results}->{policy_states} : "default"; + $self->{api_version} = (defined($self->{option_results}->{api_version}) && $self->{option_results}->{api_version} ne "") ? $self->{option_results}->{api_version} : "2019-10-01"; +} + +sub manage_selection { + my ($self, %options) = @_; + + my $policy_states = $options{custom}->azure_list_policystates( + providers => 'Microsoft.PolicyInsights/policyStates', + resource => $self->{policy_states}, + resource_group => $self->{option_results}->{resource_group}, + query_name => 'queryResults', + resource_location => $self->{option_results}->{resource_location}, + resource_type => $self->{option_results}->{resource_type} + ); + + my $non_compliant_policies = 0; + $self->{compliance_state} = {}; + foreach my $policy_state (@{ $policy_states }) { + my $resource_name = $policy_state->{resourceId}; + $resource_name =~ /.*\/(\w*)$/; + $resource_name = $1; + my $display = $policy_state->{policyDefinitionName} . "_" . $resource_name; + $self->{compliance_state}->{ $display } = { + display => $display, + compliance_state => $policy_state->{complianceState}, + policy_name => $policy_state->{policyDefinitionName}, + resource_name => $resource_name + }; + $non_compliant_policies = $non_compliant_policies + 1 if $policy_state->{complianceState} eq 'NonCompliant'; + }; + $self->{non_compliant_policies} = { non_compliant_policies => $non_compliant_policies }; +} + +1; + +__END__ + +=head1 MODE + +Check Azure policies compliance. + +Example: + +perl centreon_plugins.pl --plugin=cloud::azure::policyinsights::policystates::plugin --mode=compliance --policy-states=default +[--resource-group='MYRESOURCEGROUP'] --api-version=2019-10-01 + + +=over 8 + +=item B<--policy-states> + +The virtual resource under PolicyStates resource type. In a given time range, 'latest' represents the latest policy state(s), whereas 'default' represents all policy state(s). + +=item B<--resource-group> + +Set resource group (Optional). + +=item B<--resource-location> + +Set resource location (Optional). + +=item B<--resource-type> + +Set resource type (Optional). + +=item B<--warning-*> B<--critical-*> + +Thresholds. +Can be: 'non-compliant-policies' ,'compliance-state'. + +=back + +=cut diff --git a/src/cloud/azure/policyinsights/policystates/plugin.pm b/src/cloud/azure/policyinsights/policystates/plugin.pm new file mode 100644 index 000000000..d8d4be0ff --- /dev/null +++ b/src/cloud/azure/policyinsights/policystates/plugin.pm @@ -0,0 +1,57 @@ +# +# Copyright 2022 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 cloud::azure::policyinsights::policystates::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} } = ( + 'compliance' => 'cloud::azure::policyinsights::policystates::mode::compliance' + ); + + $self->{custom_modes}{api} = 'cloud::azure::custom::api'; + return $self; +} + +sub init { + my ($self, %options) = @_; + + $self->{options}->add_options(arguments => { }); + + $self->SUPER::init(%options); +} + +1; + +__END__ + +=head1 PLUGIN DESCRIPTION + +Check Microsoft Azure data factories. + +=cut