diff --git a/docs/resources/google_logging_project_sinks.md b/docs/resources/google_logging_project_sinks.md new file mode 100644 index 000000000..865a9163d --- /dev/null +++ b/docs/resources/google_logging_project_sinks.md @@ -0,0 +1,77 @@ +--- +title: About the google_logging_project_sinks Resource +platform: gcp +--- + +# google\_logging\_project\_sinks + +Use the `google_logging_project_sinks` InSpec audit resource to test properties of all, or a filtered group of, GCP compute project logging sinks for a project. + +
+ +## Syntax + +A `google_logging_project_sinks` resource block collects GCP project logging sinks by project then tests that group. + + describe google_logging_project_sinks(project: 'chef-inspec-gcp') do + it { should exist } + end + +Use this InSpec resource to enumerate IDs then test in-depth using `google_logging_project_sink`. + + google_logging_project_sinks(project: 'chef-inspec-gcp').sink_names.each do |sink_name| + describe google_logging_project_sink(project: 'chef-inspec-gcp', sink: sink_name) do + it { should exist } + end + end + +
+ +## Examples + +The following examples show how to use this InSpec audit resource. + +### Test that there are no more than a specified number of sinks available for the project + + describe google_logging_project_sinks(project: 'chef-inspec-gcp') do + its('count') { should be <= 100} + end + +### Test that an expected sink name is available for the project + + describe google_logging_project_sinks(project: 'chef-inspec-gcp') do + its('sink_names') { should include "my-sink" } + end + +### Test that an expected sink destination is available for the project + + describe google_logging_project_sinks(project: 'chef-inspec-gcp') do + its('sink_destinations') { should include "storage.googleapis.com/a-logging-bucket" } + end + +### Test that a subset of all sinks matching "project*" have a particular writer identity + + google_logging_project_sinks(project: 'chef-inspec-gcp').where(sink_name: /project/).sink_names.each do |sink_name| + describe google_logging_project_sink(project: 'chef-inspec-gcp', sink: sink_name) do + its('writer_identity') { should eq "serviceAccount:my-logging-service-account.iam.gserviceaccount.com" } + end + end + +
+ +## Filter Criteria + +This resource supports the following filter criteria: `sink_name`; `sink_filter` and `sink_destination`. Any of these may be used with `where`, as a block or as a method. + +## Properties + +* `sink_names` - an array of google_logging_project_sink name strings +* `sink_destinations`- an array of google_logging_project_sink destinations +* `sink_filters`- an array of google_logging_project_sink filters + +
+ + +## GCP Permissions + +Ensure the [Stackdriver Logging API](https://console.cloud.google.com/apis/api/logging.googleapis.com/) is enabled for the project. \ No newline at end of file diff --git a/docs/resources/google_project_logging_audit_config.md b/docs/resources/google_project_logging_audit_config.md new file mode 100644 index 000000000..8287d7510 --- /dev/null +++ b/docs/resources/google_project_logging_audit_config.md @@ -0,0 +1,51 @@ +--- +title: About the google_project_logging_audit_config Resource +platform: gcp +--- + +# google\_project\_logging\_audit\_config + +Use the `google_compute_zone` InSpec audit resource to test properties of a single GCP compute zone. + +
+ +## Syntax + +A `google_project_logging_audit_config` resource block declares the tests for a single GCP zone by project and name. + + describe google_project_logging_audit_config(project: 'chef-inspec-gcp') do + it { should exist } + end + +
+ +## Examples + +The following examples show how to use this InSpec audit resource. + + +### Test that a GCP project logging audit configuration has a default type defined + + describe google_project_logging_audit_config(project: 'chef-inspec-gcp') do + its('default_types') { should include 'ADMIN_READ' } + end + + +### Test that a GCP project logging audit configuration has default exempted members + + describe google_compute_zone(project: 'chef-inspec-gcp', zone: 'us-east1-b') do + it { should_not have_default_exempted_members } + end + +
+ +## Properties + +* `default_types`, `default_exempted_members` + +
+ + +## GCP Permissions + +Ensure the [Cloud Resource Manager API](https://console.cloud.google.com/apis/library/cloudresourcemanager.googleapis.com/) is enabled for the project. \ No newline at end of file diff --git a/docs/resources/google_project_metric.md b/docs/resources/google_project_metric.md new file mode 100644 index 000000000..bc070c96a --- /dev/null +++ b/docs/resources/google_project_metric.md @@ -0,0 +1,49 @@ +--- +title: About the google_project_metric Resource +platform: gcp +--- + +# google\_project\_metric + +Use the `google_project_metric` InSpec audit resource to test properties of a single GCP project metric. + +
+ +## Syntax + +A `google_project_metric` resource block declares the tests for a single GCP zone by project and name. + + describe google_project_metric(project: 'chef-inspec-gcp', metric: 'metric_name') do + it { should exist } + end + +
+ +## Examples + +The following examples show how to use this InSpec audit resource. + +### Test that a GCP project metric exists + + describe google_project_metric(project: 'chef-inspec-gcp', metric: 'metric_name') do + it { should exist } + end + +### Test that a GCP compute zone has an expected CPU platform + + describe google_project_metric(project: 'chef-inspec-gcp', metric: 'metric_name') do + its('filter') { should eq "(protoPayload.serviceName=\"cloudresourcemanager.googleapis.com\")" } + end + +
+ +## Properties + +* `filter`, `name`, `metric_descriptor` + +
+ + +## GCP Permissions + +Ensure the [Stackdriver Logging API](https://console.cloud.google.com/apis/api/logging.googleapis.com/) is enabled for the project. \ No newline at end of file diff --git a/docs/resources/google_project_metrics.md b/docs/resources/google_project_metrics.md new file mode 100644 index 000000000..b948f8ea1 --- /dev/null +++ b/docs/resources/google_project_metrics.md @@ -0,0 +1,70 @@ +--- +title: About the google_project_metrics Resource +platform: gcp +--- + +# google\_project\_metrics + +Use the `google_project_metrics` InSpec audit resource to test properties of all, or a filtered group of, GCP project metrics. + +
+ +## Syntax + +A `google_project_metrics` resource block collects GCP project logging sinks by project then tests that group. + + describe google_project_metrics(project: 'chef-inspec-gcp') do + it { should exist } + end + +Use this InSpec resource to enumerate IDs then test in-depth using `google_project_metric`. + + google_project_metrics(project: 'chef-inspec-gcp').sink_names.each do |metric_name| + describe google_project_metric(project: 'chef-inspec-gcp', metric: metric_name) do + it { should exist } + end + end + +
+ +## Examples + +The following examples show how to use this InSpec audit resource. + +### Test that there are no more than a specified number of metrics available for the project + + describe google_project_metrics(project: 'chef-inspec-gcp') do + its('count') { should be <= 100} + end + +### Test that an expected metric name is available for the project + + describe google_project_metrics(project: 'chef-inspec-gcp') do + its('metric_names') { should include "metric-name" } + end + +### Test that a subset of all metrics with name matching "*project*" have a particular writer identity + + google_project_metrics(project: 'chef-inspec-gcp').where(metric_name: /project/).metric_names.each do |metric_name| + describe google_project_metric(project: 'chef-inspec-gcp', metric: metric_name) do + its('filter') { should eq "(protoPayload.serviceName=\"cloudresourcemanager.googleapis.com\")" } + end + end + +
+ +## Filter Criteria + +This resource supports the following filter criteria: `metric_name` and `metric_filter`. Either of these may be used with `where`, as a block or as a method. + +## Properties + +* `metric_names` - an array of google_project_metric name strings +* `metric_filters`- an array of google_project_metric filters + +
+ + +## GCP Permissions + +Ensure the [Stackdriver Logging API](https://console.cloud.google.com/apis/api/logging.googleapis.com/) is enabled for the project. \ No newline at end of file diff --git a/libraries/google_logging_project_sinks.rb b/libraries/google_logging_project_sinks.rb new file mode 100644 index 000000000..6be98aa60 --- /dev/null +++ b/libraries/google_logging_project_sinks.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'gcp_backend' + +module Inspec::Resources + class GoogleLoggingProjectSinks < GcpResourceBase + name 'google_logging_project_sinks' + desc 'Verifies settings for GCP project logging sinks in bulk' + + example " + describe google_logging_project_sinks(project: 'chef-inspec-gcp') do + it { should exist } + end + " + + def initialize(opts = {}) + # Call the parent class constructor + super(opts) + @project = opts[:project] + end + + # FilterTable setup + filter_table_config = FilterTable.create + filter_table_config.add(:sink_names, field: :sink_name) + filter_table_config.add(:sink_destinations, field: :sink_destination) + filter_table_config.connect(self, :fetch_data) + + def fetch_data + sink_rows = [] + next_page = nil + loop do + catch_gcp_errors do + @sinks = @gcp.gcp_client(Google::Apis::LoggingV2::LoggingService).list_project_sinks("projects/#{@project}", page_token: next_page) + end + return [] if !@sinks || !@sinks.sinks + @sinks.sinks.map do |sink| + logging_sink = @gcp.gcp_client(Google::Apis::LoggingV2::LoggingService).get_project_sink("projects/#{@project}/sinks/#{sink.name}") + sink_rows+=[{ sink_name: sink.name, + sink_destination: sink.destination, + sink_filter: logging_sink.filter }] + end + next_page = @sinks.next_page_token + break unless next_page + end + @table = sink_rows + end + end +end diff --git a/libraries/google_project_logging_audit_config.rb b/libraries/google_project_logging_audit_config.rb new file mode 100644 index 000000000..224fbc9e1 --- /dev/null +++ b/libraries/google_project_logging_audit_config.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require 'gcp_backend' + +module Inspec::Resources + class GoogleProjectLoggingAuditConfig < GcpResourceBase + name 'google_project_logging_audit_config' + desc 'Verifies settings for a GCP project logging audit configuration' + + example " + describe google_project_logging_audit_config(project: 'chef-inspec-gcp') do + it { should exist } + end + " + + def initialize(opts = {}) + # Call the parent class constructor + super(opts) + @project = opts[:project] + catch_gcp_errors do + @audit_logging_configs = @gcp.gcp_project_client.get_project_iam_policy(@project) + @default_types = [] + @default_exempted_members = {} + if defined?(@audit_logging_configs.audit_configs) + @audit_logging_configs.audit_configs.each do |service_config| + next if service_config.service != 'allServices' + service_config.audit_log_configs.each do |config| + @default_types+=[config.log_type] + @default_exempted_members[config.log_type]=config.exempted_members if defined?(config.exempted_members) + end + end + end + end + end + + def exists? + return false if !defined? @audit_logging_configs.audit_configs + !@audit_logging_configs.audit_configs.nil? + end + + attr_reader :default_types + + attr_reader :default_exempted_members + + def has_default_exempted_members? + @default_exempted_members.values.any? + end + + def to_s + "Logging Audit Config For #{@project}" + end + end +end diff --git a/libraries/google_project_metric.rb b/libraries/google_project_metric.rb new file mode 100644 index 000000000..22d70a197 --- /dev/null +++ b/libraries/google_project_metric.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'gcp_backend' + +module Inspec::Resources + class GoogleProjectMetric < GcpResourceBase + name 'google_project_metric' + desc 'Verifies settings for a project metric' + + example " + describe google_project_metric(project: 'chef-inspec-gcp', metric: 'metric_name') do + it { should exist } + end + " + + def initialize(opts = {}) + # Call the parent class constructor + super(opts) + @display_name = opts[:metric] + catch_gcp_errors do + @metric = @gcp.gcp_client(Google::Apis::LoggingV2::LoggingService).get_project_metric("projects/#{opts[:project]}/metrics/#{opts[:metric]}") + create_resource_methods(@metric) + end + end + + def exists? + !@metric.nil? + end + + def to_s + "Project Metric #{@display_name}" + end + end +end diff --git a/libraries/google_project_metrics.rb b/libraries/google_project_metrics.rb new file mode 100644 index 000000000..0ce2c5220 --- /dev/null +++ b/libraries/google_project_metrics.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'gcp_backend' + +module Inspec::Resources + class GoogleProjectMetrics < GcpResourceBase + name 'google_project_metrics' + desc 'Verifies settings for GCP project metrics in bulk' + + example " + describe google_project_metrics(project: 'chef-inspec-gcp') do + it { should exist } + end + " + + def initialize(opts = {}) + # Call the parent class constructor + super(opts) + @project = opts[:project] + end + + # FilterTable setup + filter_table_config = FilterTable.create + filter_table_config.add(:metric_names, field: :metric_name) + filter_table_config.add(:metric_destinations, field: :metric_destination) + filter_table_config.connect(self, :fetch_data) + + def fetch_data + metric_rows = [] + next_page = nil + loop do + catch_gcp_errors do + @metrics = @gcp.gcp_client(Google::Apis::LoggingV2::LoggingService).list_project_metrics("projects/#{@project}", page_token: next_page) + end + return [] if !@metrics || !@metrics.metrics + @metrics.metrics.map do |metric| + metric_rows+=[{ metric_name: metric.name, + metric_filter: metric.filter }] + end + next_page = @metrics.next_page_token + break unless next_page + end + @table = metric_rows + end + end +end diff --git a/libraries/google_storage_bucket.rb b/libraries/google_storage_bucket.rb index df4007632..36caec04e 100644 --- a/libraries/google_storage_bucket.rb +++ b/libraries/google_storage_bucket.rb @@ -29,6 +29,11 @@ def exists? !@bucket.nil? end + def has_versioning_enabled? + return false if !defined?(@bucket.versioning) + @bucket.versioning.enabled + end + def to_s "Bucket #{@display_name}" end diff --git a/test/integration/verify/controls/google_logging_project_sinks.rb b/test/integration/verify/controls/google_logging_project_sinks.rb new file mode 100644 index 000000000..6ad81f403 --- /dev/null +++ b/test/integration/verify/controls/google_logging_project_sinks.rb @@ -0,0 +1,19 @@ +title 'Test GCP project logging sinks' + +gcp_project_id = attribute(:gcp_project_id, default: '', description: 'The GCP project identifier.') +gcp_logging_project_sink_name = attribute(:gcp_logging_project_sink_name, default: '', description: 'The GCP project logging sink name.') +gcp_logging_bucket_name = attribute(:gcp_logging_bucket_name, default: '', description: 'The GCP project logging bucket name.') +gcp_enable_privileged_resources = attribute(:gcp_enable_privileged_resources,default:0,description:'Flag to enable privileged resources requiring elevated privileges in GCP.') + +control 'gcp-project-logging-sinks-1.0' do + + only_if { gcp_enable_privileged_resources.to_i == 1 } + impact 1.0 + title 'Ensure GCP project logging sinks have the correct properties in bulk.' + + describe google_logging_project_sinks(project: gcp_project_id) do + it { should exist } + its('sink_names') { should include gcp_logging_project_sink_name } + its('sink_destinations') { should include "storage.googleapis.com/#{gcp_logging_bucket_name}" } + end +end \ No newline at end of file diff --git a/test/integration/verify/controls/google_project_logging_audit_config.rb b/test/integration/verify/controls/google_project_logging_audit_config.rb new file mode 100644 index 000000000..c31d18423 --- /dev/null +++ b/test/integration/verify/controls/google_project_logging_audit_config.rb @@ -0,0 +1,14 @@ +title 'Test GCP project logging audit configuration' + +gcp_project_id = attribute(:gcp_project_id, default: '', description: 'The GCP project identifier.') + +control 'gcp-project-audit-logging-config-1.0' do + + impact 1.0 + title 'Ensure GCP project logging audit configuration has the correct properties.' + + describe google_project_logging_audit_config(project: gcp_project_id) do + its('default_types') { should_not match /notthere/ } + its('default_exempted_members') { should_not match /notthere/ } + end +end \ No newline at end of file