diff --git a/README.md b/README.md index f94bfd213..5a0295bb1 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,8 @@ The following resources are available in the InSpec GCP Profile - [google_container_node_pool](docs/resources/google_container_node_pool.md) - [google_container_node_pools](docs/resources/google_container_node_pools.md) - [google_project](docs/resources/google_project.md) +- [google_project_iam_binding](docs/resources/google_project_iam_binding.md) +- [google_project_iam_bindings](docs/resources/google_project_iam_bindings.md) - [google_project_iam_custom_role](docs/resources/google_project_iam_custom_role.md) - [google_projects](docs/resources/google_projects.md) - [google_service_account](docs/resources/google_service_account.md) diff --git a/docs/resources/google_project_iam_binding.md b/docs/resources/google_project_iam_binding.md new file mode 100644 index 000000000..e53aee2e5 --- /dev/null +++ b/docs/resources/google_project_iam_binding.md @@ -0,0 +1,58 @@ +--- +title: About the google_project_iam_binding Resource +platform: gcp +--- + +# google\_project\_iam\_binding + +Use the `google_project_iam_binding` InSpec audit resource to test properties of a single GCP project IAM binding. + +
+ +## Syntax + +A `google_project_iam_binding` resource block declares the tests for a single GCP project IAM binding by role. + + describe google_project_iam_binding(project: 'chef-inspec-gcp', role: 'roles/compute.admin') do + it { should exist } + its('members') {should include 'user:someuser@domain.com' } + end + +
+ +## Examples + +The following examples show how to use this InSpec audit resource. + +### Test that a GCP project iam_binding exists + + describe google_project_iam_binding(project: 'chef-inspec-gcp', iam_binding: 'us-east1-b') do + it { should exist } + end + +### Test that a GCP project iam_binding is in the expected state + + describe google_project_iam_binding(project: 'chef-inspec-gcp', iam_binding: 'us-east1-b') do + its('status') { should eq 'UP' } + # or equivalently + it { should be_up } + end + +### Test that a GCP project iam_binding has an expected CPU platform + + describe google_project_iam_binding(project: 'chef-inspec-gcp', iam_binding: 'us-east1-b') do + its('available_cpu_platforms') { should include "Intel Skylake" } + end + +
+ +## Properties + +* `available_cpu_platforms`, `creation_timestamp`, `description`, `id`, `kind`, `name`, `region`, `status`, `region_name` + +
+ + +## GCP Permissions + +Ensure the [Compute Engine API](https://console.cloud.google.com/apis/library/project.googleapis.com/) is enabled for the project where the resource is located. \ No newline at end of file diff --git a/docs/resources/google_project_iam_bindings.md b/docs/resources/google_project_iam_bindings.md new file mode 100644 index 000000000..23becbc38 --- /dev/null +++ b/docs/resources/google_project_iam_bindings.md @@ -0,0 +1,68 @@ +--- +title: About the google_project_iam_bindings Resource +platform: gcp +--- + +# google\_project\_iam\_bindings + +Use the `google_project_iam_bindings` InSpec audit resource to test properties of all, or a filtered group of, GCP project IAM bindings. + +
+ +## Syntax + +A `google_project_iam_bindings` resource block collects GCP project IAM bindings then tests that group. + + describe google_project_iam_bindings(project: 'chef-inspec-gcp') do + it { should exist } + end + +Use this InSpec resource to enumerate roles then test in-depth using `google_project_iam_binding`. + + google_project_iam_bindings(project: 'chef-inspec-gcp').iam_binding_roles.each do |iam_binding_role| + describe google_project_iam_binding(project: 'chef-inspec-gcp', role: iam_binding_role) do + it { should exist } + its('members') {should include 'user:someuser@domain.com' } + 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 IAM bindings roles available for the project + + describe google_project_iam_bindings(project: 'chef-inspec-gcp') do + its('count') { should be <= 100} + end + +### Test that an expected iam_binding is available for the project + + describe google_project_iam_bindings(project: 'chef-inspec-gcp') do + its('iam_binding_roles') { should include "roles/storage.admin" } + end + +### Test that a particular role does not exist using filtering of the plural resource + + describe google_project_iam_bindings(project: 'chef-inspec-gcp').where(iam_binding_role: "roles/iam.securityReviewer") do + it { should_not exist } + end + +
+ +## Filter Criteria + +This resource supports the following filter criteria: `iam_binding_role`. This may be used with `where`, as a block or as a method. + +## Properties + +* `iam_binding_roles` - an array of google_project_iam_binding role strings e.g. `["roles/compute.admin", "roles/owner"]` + +
+ + +## 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/libraries/google_project_iam_binding.rb b/libraries/google_project_iam_binding.rb new file mode 100644 index 000000000..49c369dbd --- /dev/null +++ b/libraries/google_project_iam_binding.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'gcp_backend' + +module Inspec::Resources + class GoogleProjectIAMBinding < GcpResourceBase + name 'google_project_iam_binding' + desc 'Verifies settings for a single project IAM binding' + + example " + describe google_project_iam_binding(project: 'chef-inspec-gcp', role: 'roles/compute.admin') do + it { should exist } + its('members') {should include 'user:someuser@domain.com' } + end + " + + def initialize(opts = {}) + # Call the parent class constructor + super(opts) + @display_name = opts[:name] + @project = opts[:project] + @role = opts[:role] + @iam_binding_exists = false + @members_list=[] + catch_gcp_errors do + # note this is the same call as for the plural iam_bindings resource because there isn't an easy way to pull out a singular binding + @iam_bindings = @gcp.gcp_project_client.get_project_iam_policy(@project) + raise Inspec::Exceptions::ResourceFailed, "google_project_iam_binding is missing expected IAM policy 'bindings' property" if !@iam_bindings || !@iam_bindings.bindings + @iam_bindings.bindings.each do |binding| + next if binding.role != @role + @iam_binding_exists=true + @members_list=binding.members + end + end + end + + # return the list of users corresponding to the role + def members + @members_list + end + + def exists? + @iam_binding_exists + end + + def to_s + "Project IAM Binding #{@display_name}" + end + end +end diff --git a/libraries/google_project_iam_bindings.rb b/libraries/google_project_iam_bindings.rb new file mode 100644 index 000000000..8a5f29929 --- /dev/null +++ b/libraries/google_project_iam_bindings.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'gcp_backend' + +module Inspec::Resources + class GoogleProjectIAMBindings < GcpResourceBase + name 'google_project_iam_bindings' + desc 'Verifies settings for GCP project iam_bindings in bulk' + + example " + describe google_project_iam_bindings(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(:iam_binding_roles, field: :iam_binding_role) + filter_table_config.connect(self, :fetch_data) + + def fetch_data + iam_binding_rows = [] + catch_gcp_errors do + @iam_bindings = @gcp.gcp_project_client.get_project_iam_policy(@project) + end + return [] if !@iam_bindings || !@iam_bindings.bindings + @iam_bindings.bindings.map do |iam_binding| + iam_binding_rows+=[{ iam_binding_role: iam_binding.role }] + end + @table = iam_binding_rows + end + end +end diff --git a/test/integration/verify/controls/google_compute_zone.rb b/test/integration/verify/controls/google_compute_zone.rb new file mode 100644 index 000000000..2bf2d4174 --- /dev/null +++ b/test/integration/verify/controls/google_compute_zone.rb @@ -0,0 +1,15 @@ +title 'Test single GCP Zone' + +gcp_project_id = attribute(:gcp_project_id, default: '', description: 'The GCP project identifier.') +gcp_zone = attribute(:gcp_zone, default: '', description: 'The GCP zone being used.') + +control 'gcp-single-zone-1.0' do + + impact 1.0 + title 'Ensure single zone has the correct properties.' + + describe google_compute_zone(project: gcp_project_id, name: gcp_zone) do + it { should exist } + it { should be_up } + end +end \ No newline at end of file diff --git a/test/integration/verify/controls/google_project_iam_binding.rb b/test/integration/verify/controls/google_project_iam_binding.rb new file mode 100644 index 000000000..ca1d982d1 --- /dev/null +++ b/test/integration/verify/controls/google_project_iam_binding.rb @@ -0,0 +1,16 @@ +title 'Test single GCP project IAM Binding' + +gcp_project_id = attribute(:gcp_project_id, default: '', description: 'The GCP project identifier.') +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-iam-binding-1.0' do + + only_if { gcp_enable_privileged_resources.to_i == 1 } + impact 1.0 + title 'Ensure single GCP project IAM binding has the correct properties.' + + describe google_project_iam_binding(project: gcp_project_id, role: "roles/compute.admin") do + it { should exist } + its ('members.count'){ should eq 1 } + end +end \ No newline at end of file diff --git a/test/integration/verify/controls/google_project_iam_bindings.rb b/test/integration/verify/controls/google_project_iam_bindings.rb new file mode 100644 index 000000000..1cb2e4504 --- /dev/null +++ b/test/integration/verify/controls/google_project_iam_bindings.rb @@ -0,0 +1,18 @@ +title 'GCP Project IAM Bindings Properties' + +gcp_project_id = attribute(:gcp_project_id, default: '', description: 'The GCP project identifier.') +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-iam-bindings-1.0' do + + only_if { gcp_enable_privileged_resources.to_i == 1 } + impact 1.0 + title 'Ensure project IAM bindings have the correct properties in bulk' + + describe google_project_iam_bindings(project: gcp_project_id) do + it { should exist } + its('count') { should be <= 100} + its('iam_binding_roles') { should include "roles/owner" } + end + +end \ No newline at end of file