diff --git a/README.md b/README.md index 55179fbe6..a8e60dfdc 100644 --- a/README.md +++ b/README.md @@ -40,11 +40,20 @@ Since this is an InSpec resource pack, it only defines InSpec resources. It incl ```bash $ inspec init profile my-profile +Create new profile at /Users/skpaterson/my-profile + * Create directory libraries + * Create file README.md + * Create directory controls + * Create file controls/example.rb + * Create file inspec.yml + * Create file libraries/.gitkeep ``` +Now update the default `inspec.yml` file to point to the InSpec GCP resource pack: + ```yaml name: my-profile -title: My own Oneview profile +title: My GCP InSpec Profile version: 0.1.0 inspec_version: '>= 2.2.10' depends: @@ -87,7 +96,14 @@ The following resources are available in the InSpec GCP Profile - [google_projects](docs/resources/google_projects.md) - [google_service_account](docs/resources/google_service_account.md) - [google_storage_bucket](docs/resources/google_storage_bucket.md) +- [google_storage_bucket_acl](docs/resources/google_storage_bucket_acl.md) +- [google_storage_bucket_iam_binding](docs/resources/google_storage_bucket_iam_binding.md) +- [google_storage_bucket_iam_bindings](docs/resources/google_storage_bucket_iam_bindings.md) +- [google_storage_bucket_object](docs/resources/google_storage_bucket_object.md) - [google_storage_buckets](docs/resources/google_storage_buckets.md) +- [google_storage_default_object_acl](docs/resources/google_storage_default_object_acl.md) +- [google_storage_object_acl](docs/resources/google_storage_object_acl.md) + ## Examples diff --git a/docs/resources/google_storage_bucket_acl.md b/docs/resources/google_storage_bucket_acl.md new file mode 100644 index 000000000..36845f5bc --- /dev/null +++ b/docs/resources/google_storage_bucket_acl.md @@ -0,0 +1,49 @@ +--- +title: About the google_storage_bucket_acl Resource +platform: gcp +--- + +# google\_storage\_bucket\_acl + +Use the `google_storage_bucket_acl` InSpec audit resource to test properties of a single GCP storage bucket ACL. The 'entity' property below is as described in the [Google documentation here](https://cloud.google.com/storage/docs/json_api/v1/bucketAccessControls). + +
+ +## Syntax + +A `google_storage_bucket_acl` resource block declares the tests for a single GCP storage bucket ACL by bucket name and entity. + + describe google_storage_bucket_acl(bucket: 'bucket-buvsjjcndqz', entity: 'user-object-viewer@spaterson-project.iam.gserviceaccount.com') do + it { should exist } + end + +
+ +## Examples + +The following examples show how to use this InSpec audit resource. + +### Test that a GCP storage bucket ACL exists + + describe google_storage_bucket_acl(bucket: 'bucket-buvsjjcndqz', entity: 'user-object-viewer@spaterson-project.iam.gserviceaccount.com') do + it { should exist } + end + +### Test that a GCP storage bucket ACL has the expected role (READER, WRITER or OWNER) + + describe google_storage_bucket_acl(bucket: 'bucket-buvsjjcndqz', entity: 'user-object-viewer@spaterson-project.iam.gserviceaccount.com') do + its('role') { should eq 'OWNER' } + end + +
+ +## Properties + +* `bucket`, `email`, `entity`, `etag`, `id`, `kind`, `role` + +
+ + +## GCP Permissions + +Ensure the [Google Cloud Storage API](https://console.cloud.google.com/apis/api/storage-component.googleapis.com/) is enabled. \ No newline at end of file diff --git a/docs/resources/google_storage_bucket_iam_binding.md b/docs/resources/google_storage_bucket_iam_binding.md new file mode 100644 index 000000000..40364b4d6 --- /dev/null +++ b/docs/resources/google_storage_bucket_iam_binding.md @@ -0,0 +1,50 @@ +--- +title: About the google_storage_bucket_iam_binding Resource +platform: gcp +--- + +# google\_storage\_bucket\_iam\_binding + +Use the `google_storage_bucket_iam_binding` InSpec audit resource to test properties of a single GCP storage bucket IAM binding. + +
+ +## Syntax + +A `google_storage_bucket_iam_binding` resource block declares the tests for a single GCP storage bucket IAM binding by bucket name and role. + + describe google_storage_bucket_iam_binding(bucket: 'bucket-buvsjjcndqz', role: 'roles/storage.objectViewer') do + it { should exist } + end + +
+ +## Examples + +The following examples show how to use this InSpec audit resource. + +### Test that a GCP storage bucket IAM binding exists + + describe google_storage_bucket_iam_binding(bucket: 'bucket-buvsjjcndqz', role: 'roles/storage.admin') do + it { should exist } + end + +### Test that a GCP storage bucket IAM binding role has the desired user or service account included + + describe google_storage_bucket_iam_binding(bucket: 'bucket-buvsjjcndqz', role: 'roles/storage.admin') do + its('members') {should include 'user:someuser@domain.com' } + its('members') {should include 'serviceAccount:someserviceaccount@domain.com' } + end + +
+ +## Properties + +* `members` + +
+ + +## GCP Permissions + +Ensure the [Google Cloud Storage API](https://console.cloud.google.com/apis/api/storage-component.googleapis.com/) is enabled. \ No newline at end of file diff --git a/docs/resources/google_storage_bucket_iam_bindings.md b/docs/resources/google_storage_bucket_iam_bindings.md new file mode 100644 index 000000000..49c1c2850 --- /dev/null +++ b/docs/resources/google_storage_bucket_iam_bindings.md @@ -0,0 +1,68 @@ +--- +title: About the google_storage_bucket_iam_bindings Resource +platform: gcp +--- + +# google\_storage\_bucket\_iam\_bindings + +Use the `google_storage_bucket_iam_bindings` InSpec audit resource to test properties of all, or a filtered group of, GCP storage bucket IAM bindings. + +
+ +## Syntax + +A `google_storage_bucket_iam_bindings` resource block collects GCP storage bucket IAM bindings then tests that group. + + describe google_storage_bucket_iam_bindings(bucket: 'bucket-buvsjjcndqz') do + it { should exist } + end + +Use this InSpec resource to enumerate roles then test in-depth using `google_project_iam_binding`. + + google_storage_bucket_iam_bindings(bucket: 'bucket-buvsjjcndqz').iam_binding_roles.each do |iam_binding_role| + describe google_storage_bucket_iam_binding(bucket: 'bucket-buvsjjcndqz', 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 bucket + + describe google_storage_bucket_iam_bindings(bucket: 'bucket-buvsjjcndqz') do + its('count') { should be <= 100} + end + +### Test that an expected role is available for the bucket + + describe google_storage_bucket_iam_bindings(bucket: 'bucket-buvsjjcndqz') 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_storage_bucket_iam_bindings(bucket: 'bucket-buvsjjcndqz').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_storage_bucket_iam_binding role strings e.g. `["roles/storage.admin", "roles/owner"]` + +
+ + +## GCP Permissions + +Ensure the [Google Cloud Storage API](https://console.cloud.google.com/apis/api/storage-component.googleapis.com/) is enabled. \ No newline at end of file diff --git a/docs/resources/google_storage_bucket_object.md b/docs/resources/google_storage_bucket_object.md new file mode 100644 index 000000000..999eb68d8 --- /dev/null +++ b/docs/resources/google_storage_bucket_object.md @@ -0,0 +1,70 @@ +--- +title: About the google_storage_bucket_object Resource +platform: gcp +--- + +# google\_storage\_bucket\_object + +Use the `google_storage_bucket_object` InSpec audit resource to test properties of a single GCP storage bucket object. + +
+ +## Syntax + +A `google_storage_bucket_object` resource block declares the tests for a single GCP storage bucket object by bucket name and object name: + + describe google_storage_bucket_object(bucket: 'bucket-buvsjjcndqz', object: 'bucket-object-pmxbiikq') do + it { should exist } + end + +
+ +## Examples + +The following examples show how to use this InSpec audit resource. + +### Test that a GCP compute zone exists + + describe google_storage_bucket_object(bucket: 'bucket-buvsjjcndqz', object: 'bucket-object-pmxbiikq') do + it { should exist } + end + +### Test that a GCP storage bucket object has non-zero size + + describe google_storage_bucket_object(bucket: 'bucket-buvsjjcndqz', object: 'bucket-object-pmxbiikq') do + its('size') { should be > 0 } + end + +### Test that a GCP storage bucket object has the expected content type + + describe google_storage_bucket_object(bucket: 'bucket-buvsjjcndqz', object: 'bucket-object-pmxbiikq') do + its('content_type') { should eq "text/plain; charset=utf-8" } + end + + +### Test that a GCP storage bucket object was created within a certain time period + + describe google_storage_bucket_object(bucket: 'bucket-buvsjjcndqz', object: 'bucket-object-pmxbiikq') do + its('time_created_date') { should be > Time.now - 365*60*60*24*10 } + end + + +### Test that a GCP storage bucket object was last updated within a certain time period + + describe google_storage_bucket_object(bucket: 'bucket-buvsjjcndqz', object: 'bucket-object-pmxbiikq') do + its('updated_date') { should be > Time.now - 365*60*60*24*10 } + end + + +
+ +## Properties + +* `bucket`, `content_type`, `crc32c`, `etag`, `generation`, `id`, `kind`, `md5_hash`, `media_link`, `metageneration`, `name`, `size`, `storage_class`, `time_created_date`, `time_storage_class_updated_date`, `updated_date` + +
+ + +## GCP Permissions + +Ensure the [Google Cloud Storage API](https://console.cloud.google.com/apis/api/storage-component.googleapis.com/) is enabled. \ No newline at end of file diff --git a/docs/resources/google_storage_default_object_acl.md b/docs/resources/google_storage_default_object_acl.md new file mode 100644 index 000000000..f90dbdb08 --- /dev/null +++ b/docs/resources/google_storage_default_object_acl.md @@ -0,0 +1,49 @@ +--- +title: About the google_storage_default_object_acl Resource +platform: gcp +--- + +# google\_storage\_default\_object\_acl + +Use the `google_storage_default_object_acl` InSpec audit resource to test properties of a single GCP storage default object ACL. See the [Google documentation for this here](https://cloud.google.com/storage/docs/access-control/lists) covering the possible values for 'entity' argument below. + +
+ +## Syntax + +A `google_storage_default_object_acl` resource block declares the tests for a single GCP storage default object ACL by bucket name and entity. + + describe google_storage_default_object_acl(bucket: 'bucket-buvsjjcndqz', entity: 'user-object-viewer@spaterson-project.iam.gserviceaccount.com') do + it { should exist } + end + +
+ +## Examples + +The following examples show how to use this InSpec audit resource. + +### Test that a GCP storage bucket ACL exists + + describe google_storage_default_object_acl(bucket: 'bucket-buvsjjcndqz', entity: 'user-object-viewer@spaterson-project.iam.gserviceaccount.com') do + it { should exist } + end + +### Test that a GCP storage default object ACL has the expected role (READER, WRITER or OWNER) + + describe google_storage_default_object_acl(bucket: 'bucket-buvsjjcndqz', entity: 'user-object-viewer@spaterson-project.iam.gserviceaccount.com') do + its('role') { should eq 'OWNER' } + end + +
+ +## Properties + +* `email`, `entity`, `etag`, `kind`, `role` + +
+ + +## GCP Permissions + +Ensure the [Google Cloud Storage API](https://console.cloud.google.com/apis/api/storage-component.googleapis.com/) is enabled. \ No newline at end of file diff --git a/docs/resources/google_storage_object_acl.md b/docs/resources/google_storage_object_acl.md new file mode 100644 index 000000000..1d9c5486f --- /dev/null +++ b/docs/resources/google_storage_object_acl.md @@ -0,0 +1,49 @@ +--- +title: About the google_storage_object_acl Resource +platform: gcp +--- + +# google\_storage\_object\_acl + +Use the `google_storage_object_acl` InSpec audit resource to test properties of a single GCP storage object ACL. See the [Google documentation for this here](https://cloud.google.com/storage/docs/access-control/lists) covering the possible values for 'entity' argument below. + +
+ +## Syntax + +A `google_storage_object_acl` resource block declares the tests for a single GCP storage object ACL by bucket name, object name and entity. + + describe google_storage_object_acl(bucket: 'bucket-buvsjjcndqz', object: 'bucket-object-pmxbiikq', entity: 'user-object-viewer@spaterson-project.iam.gserviceaccount.com') do + it { should exist } + end + +
+ +## Examples + +The following examples show how to use this InSpec audit resource. + +### Test that a GCP storage bucket ACL exists + + describe google_storage_object_acl(bucket: 'bucket-buvsjjcndqz', object: 'bucket-object-pmxbiikq', entity: 'user-object-viewer@spaterson-project.iam.gserviceaccount.com') do + it { should exist } + end + +### Test that a GCP storage object ACL has the expected role (READER, WRITER or OWNER) + + describe google_storage_object_acl(bucket: 'bucket-buvsjjcndqz', object: 'bucket-object-pmxbiikq', entity: 'user-object-viewer@spaterson-project.iam.gserviceaccount.com') do + its('role') { should eq 'OWNER' } + end + +
+ +## Properties + +* `bucket`, `email`, `entity`, `etag`, `generation`, `id`, `kind`, `object`, `role` + +
+ + +## GCP Permissions + +Ensure the [Google Cloud Storage API](https://console.cloud.google.com/apis/api/storage-component.googleapis.com/) is enabled. \ No newline at end of file diff --git a/libraries/google_storage_bucket_acl.rb b/libraries/google_storage_bucket_acl.rb new file mode 100644 index 000000000..6c285f74e --- /dev/null +++ b/libraries/google_storage_bucket_acl.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'gcp_backend' + +module Inspec::Resources + class GoogleStorageBucketAcl < GcpResourceBase + name 'google_storage_bucket_acl' + desc 'Verifies settings for a storage bucket ACL' + + example " + describe google_storage_bucket_acl(bucket: 'bucket-buvsjjcndqz', entity: 'user-object-viewer@spaterson-project.iam.gserviceaccount.com') do + it { should exist } + end + " + + def initialize(opts = {}) + # Call the parent class constructor + super(opts) + @bucket = opts[:bucket] + @entity = opts[:entity] + catch_gcp_errors do + @acl = @gcp.gcp_storage_client.get_bucket_access_control(@bucket, @entity) + create_resource_methods(@acl) + end + end + + def exists? + !@acl.nil? + end + + def to_s + "Storage Bucket ACL #{@bucket}" + end + end +end diff --git a/libraries/google_storage_bucket_iam_binding.rb b/libraries/google_storage_bucket_iam_binding.rb new file mode 100644 index 000000000..49de4b189 --- /dev/null +++ b/libraries/google_storage_bucket_iam_binding.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'gcp_backend' + +module Inspec::Resources + class GoogleStorageBucketIamBinding < GcpResourceBase + name 'google_storage_bucket_iam_binding' + desc 'Verifies settings for a storage bucket IAM binding' + + example " + describe google_storage_bucket_iam_binding(bucket: 'bucket-buvsjjcndqz', role: 'roles/storage.objectViewer') do + it { should exist } + end + " + + def initialize(opts = {}) + # Call the parent class constructor + super(opts) + @bucket = opts[:bucket] + @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_storage_client.get_bucket_iam_policy(@bucket) + raise Inspec::Exceptions::ResourceFailed, "google_storage_bucket_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 + "Storage Bucket IAM Binding #{@role}" + end + end +end diff --git a/libraries/google_storage_bucket_iam_bindings.rb b/libraries/google_storage_bucket_iam_bindings.rb new file mode 100644 index 000000000..6ecb0f341 --- /dev/null +++ b/libraries/google_storage_bucket_iam_bindings.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'gcp_backend' + +module Inspec::Resources + class GoogleStorageBucketIamBindings < GcpResourceBase + name 'google_storage_bucket_iam_bindings' + desc 'Verifies settings for GCP storage bucket IAM bindings in bulk' + + example " + describe google_storage_bucket_iam_bindings(bucket: 'bucket-buvsjjcndqz') do + it { should exist } + ... + end + " + + def initialize(opts = {}) + # Call the parent class constructor + super(opts) + @bucket = opts[:bucket] + 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_storage_client.get_bucket_iam_policy(@bucket) + 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/libraries/google_storage_bucket_object.rb b/libraries/google_storage_bucket_object.rb new file mode 100644 index 000000000..7766ca2c4 --- /dev/null +++ b/libraries/google_storage_bucket_object.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require 'gcp_backend' +require 'time' + +module Inspec::Resources + class GoogleStorageBucketObject < GcpResourceBase + name 'google_storage_bucket_object' + desc 'Verifies settings for a storage bucket object' + + example " + describe google_storage_bucket_object(bucket: 'bucket-buvsjjcndqz', object: 'bucket-object-pmxbiikq') do + it { should exist } + end + " + def initialize(opts = {}) + # Call the parent class constructor + super(opts) + @bucket = opts[:bucket] + @object = opts[:object] + catch_gcp_errors do + @bucket_object = @gcp.gcp_storage_client.get_object(@bucket, @object) + @time_created = @bucket_object.time_created + @time_updated = @bucket_object.updated + @time_class_updated = @bucket_object.time_storage_class_updated + create_resource_methods(@bucket_object) + end + end + + def updated_date + return false if !defined?(@time_updated) + Time.parse(@time_updated.to_s) + end + + def time_storage_class_updated_date + return false if !defined?(@time_class_updated) + Time.parse(@time_class_updated.to_s) + end + + def time_created_date + return false if !defined?(@time_created) + Time.parse(@time_created.to_s) + end + + def exists? + !@bucket_object.nil? + end + + def to_s + "Bucket object #{@bucket}/#{@object}" + end + end +end diff --git a/libraries/google_storage_default_object_acl.rb b/libraries/google_storage_default_object_acl.rb new file mode 100644 index 000000000..7bb327d14 --- /dev/null +++ b/libraries/google_storage_default_object_acl.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'gcp_backend' + +module Inspec::Resources + class GoogleStorageDefaultObjectAcl < GcpResourceBase + name 'google_storage_default_object_acl' + desc 'Verifies settings for a storage default object ACL' + + example " + describe google_storage_default_object_acl(bucket: 'bucket-buvsjjcndqz', entity: 'user-object-viewer@spaterson-project.iam.gserviceaccount.com') do + it { should exist } + end + " + + def initialize(opts = {}) + # Call the parent class constructor + super(opts) + @bucket = opts[:bucket] + @entity = opts[:entity] + catch_gcp_errors do + @acl = @gcp.gcp_storage_client.get_default_object_access_control(@bucket, @entity) + create_resource_methods(@acl) + end + end + + def exists? + !@acl.nil? + end + + def to_s + "Storage Default Object ACL #{@bucket}" + end + end +end diff --git a/libraries/google_storage_object_acl.rb b/libraries/google_storage_object_acl.rb new file mode 100644 index 000000000..7e35c9b3e --- /dev/null +++ b/libraries/google_storage_object_acl.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'gcp_backend' + +module Inspec::Resources + class GoogleStorageObjectAcl < GcpResourceBase + name 'google_storage_object_acl' + desc 'Verifies settings for a storage object ACL' + + example " + describe google_storage_object_acl(bucket: 'bucket-buvsjjcndqz', object: 'bucket-object-pmxbiikq', entity: 'user-object-viewer@spaterson-project.iam.gserviceaccount.com') do + it { should exist } + end + " + + def initialize(opts = {}) + # Call the parent class constructor + super(opts) + @bucket = opts[:bucket] + @object = opts[:object] + @entity = opts[:entity] + catch_gcp_errors do + @acl = @gcp.gcp_storage_client.get_object_access_control(@bucket, @object, @entity) + create_resource_methods(@acl) + end + end + + def exists? + !@acl.nil? + end + + def to_s + "Storage Object ACL #{@object}" + end + end +end diff --git a/test/integration/build/gcp.tf b/test/integration/build/gcp.tf index 6b037f20e..ba79771eb 100644 --- a/test/integration/build/gcp.tf +++ b/test/integration/build/gcp.tf @@ -14,6 +14,8 @@ terraform { variable "gcp_project_name" {} variable "gcp_project_id" {} +variable "gcp_project_number" {} + variable "gcp_location" { default = "europe-west2" } @@ -63,23 +65,29 @@ variable "gcp_kms_key_ring_binding_member_name" {} variable "gcp_kms_crypto_key_name_policy" {} variable "gcp_kms_crypto_key_name_binding" {} -variable "gcp_storage_bucket_name" { - default ="gcp-inspec" -} +variable "gcp_storage_bucket_name" {} +variable "gcp_storage_bucket_acl" {} +variable "gcp_storage_bucket_binding" {} +variable "gcp_storage_bucket_member" {} +variable "gcp_storage_bucket_policy" {} +variable "gcp_storage_bucket_object" {} +variable "gcp_storage_bucket_object_name" {} #variable "gcp_inspec_user_email" {} variable "gcp_enable_privileged_resources" {} provider "google" { - region = "${var.gcp_location}" + region = "${var.gcp_location}", + version = "~> 1.16" } -# TBD: initial GCP account can't create these easily, make as part of the account paving? -#resource "google_project" "Chef_Inspec_GCP" { -# name = "Inspec GCP ${var.project_name}" -# project_id = "${var.project_id}" -#} +resource "google_service_account" "generic_service_account_object_viewer" { + count = "${var.gcp_enable_privileged_resources}" + project = "${var.gcp_project_id}" + account_id = "object-viewer" + display_name = "${var.gcp_service_account_display_name}" +} resource "google_compute_instance" "generic_internal_vm_instance" { project = "${var.gcp_project_id}" @@ -98,20 +106,12 @@ resource "google_compute_instance" "generic_internal_vm_instance" { } } -resource "google_storage_bucket" "generic-storage-bucket" { - project = "${var.gcp_project_id}" - name = "${var.gcp_storage_bucket_name}" - location = "${var.gcp_location}" -} - - resource "google_compute_address" "generic_external_vm_address" { project = "${var.gcp_project_id}" name = "${var.gcp_ext_compute_address_name}" region = "${var.gcp_location}" } - resource "google_compute_instance" "generic_external_vm_instance" { project = "${var.gcp_project_id}" name = "${var.gcp_ext_vm_name}" @@ -150,13 +150,6 @@ resource "google_compute_instance" "generic_windows_internal_vm_instance" { } } -resource "google_service_account" "generic_service_account_object_viewer" { - count = "${var.gcp_enable_privileged_resources}" - project = "${var.gcp_project_id}" - account_id = "object-viewer" - display_name = "${var.gcp_service_account_display_name}" -} - resource "google_project_iam_custom_role" "generic_project_iam_custom_role" { count = "${var.gcp_enable_privileged_resources}" project = "${var.gcp_project_id}" @@ -511,4 +504,163 @@ resource "google_kms_crypto_key_iam_binding" "crypto_key_iam_binding" { } -# End GCP KMS resources \ No newline at end of file +# End GCP KMS resources + +# Start storage bucket resources + +resource "google_storage_bucket" "generic-storage-bucket" { + project = "${var.gcp_project_id}" + name = "${var.gcp_storage_bucket_name}" + location = "${var.gcp_location}" +} + +# let's add a default ACL on the previous bucket +resource "google_storage_default_object_acl" "bucket-default-acl" { + count = "${var.gcp_enable_privileged_resources}" + bucket = "${google_storage_bucket.generic-storage-bucket.name}" + role_entity = [ + "OWNER:user-${google_service_account.generic_service_account_object_viewer.email}", + "OWNER:project-owners-${var.gcp_project_number}", + ] +} + +# now test adding an ACL to a bucket + +resource "google_storage_bucket" "bucket-with-acl" { + count = "${var.gcp_enable_privileged_resources}" + project = "${var.gcp_project_id}" + name = "${var.gcp_storage_bucket_acl}" + location = "${var.gcp_location}" +} + +# make use of project convenience values as described here - https://cloud.google.com/storage/docs/access-control/lists +resource "google_storage_bucket_acl" "bucket-acl" { + count = "${var.gcp_enable_privileged_resources}" + bucket = "${google_storage_bucket.bucket-with-acl.name}" + + role_entity = [ + "OWNER:user-${google_service_account.generic_service_account_object_viewer.email}", + "OWNER:project-owners-${var.gcp_project_number}", + ] +} + +# Note: google_storage_bucket_iam_binding resources can be used in conjunction with google_storage_bucket_iam_member resources only if they do not grant privilege to the same role. +# for simplicity here, create a bucket for iam binding and member cases + +resource "google_storage_bucket" "bucket-with-iam-binding" { + count = "${var.gcp_enable_privileged_resources}" + project = "${var.gcp_project_id}" + name = "${var.gcp_storage_bucket_binding}" + location = "${var.gcp_location}" +} + +resource "google_storage_bucket_iam_binding" "bucket-iam-binding" { + count = "${var.gcp_enable_privileged_resources}" + bucket = "${google_storage_bucket.bucket-with-iam-binding.name}" + role = "roles/storage.objectViewer" + + members = [ + "serviceAccount:${google_service_account.generic_service_account_object_viewer.email}", + ] +} + +resource "google_storage_bucket" "bucket-with-iam-member" { + count = "${var.gcp_enable_privileged_resources}" + project = "${var.gcp_project_id}" + name = "${var.gcp_storage_bucket_member}" + location = "${var.gcp_location}" +} + +resource "google_storage_bucket_iam_member" "bucket-iam-member" { + count = "${var.gcp_enable_privileged_resources}" + bucket = "${google_storage_bucket.bucket-with-iam-member.name}" + role = "roles/storage.objectViewer" + member = "serviceAccount:${google_service_account.generic_service_account_object_viewer.email}" +} + +# now for the IAM policy case + +resource "google_storage_bucket" "bucket-with-iam-policy" { + count = "${var.gcp_enable_privileged_resources}" + project = "${var.gcp_project_id}" + name = "${var.gcp_storage_bucket_policy}" + location = "${var.gcp_location}" +} + +data "google_iam_policy" "bucket-iam-policy" { + count = "${var.gcp_enable_privileged_resources}" + binding { + role = "roles/storage.admin" + + members = [ "serviceAccount:${google_service_account.generic_service_account_object_viewer.email}" ] + } +} + +resource "google_storage_bucket_iam_policy" "bucket-iam-policy-add" { + count = "${var.gcp_enable_privileged_resources}" + bucket = "${google_storage_bucket.bucket-with-iam-policy.name}" + policy_data = "${data.google_iam_policy.bucket-iam-policy.policy_data}" +} + +# finally let's create a bucket with object plus an object ACL + +resource "google_storage_bucket" "bucket-with-object" { + count = "${var.gcp_enable_privileged_resources}" + project = "${var.gcp_project_id}" + name = "${var.gcp_storage_bucket_object}" + location = "${var.gcp_location}" +} + +resource "google_storage_bucket_object" "bucket-object" { + count = "${var.gcp_enable_privileged_resources}" + name = "${var.gcp_storage_bucket_object_name}" + bucket = "${google_storage_bucket.bucket-with-object.name}" + content = "Bucket Object ${var.gcp_storage_bucket_object_name} for bucket ${var.gcp_storage_bucket_object} in ${var.gcp_project_id} with ACL." +} + +#finally, add object ACL + +resource "google_storage_object_acl" "bucket-object-acl" { + count = "${var.gcp_enable_privileged_resources}" + bucket = "${google_storage_bucket.bucket-with-object.name}" + object = "${google_storage_bucket_object.bucket-object.name}" + + role_entity = [ + "OWNER:project-owners-${var.gcp_project_number}", + "OWNER:user-${google_service_account.generic_service_account_object_viewer.email}", + ] +} + +# try the last scenario of adding an IAM policy to an object + +# note at the time of writing, terraform isn't supporting the IAM policy applied to storage object case +# https://www.terraform.io/docs/providers/google/r/storage_bucket_object.html + +# will revisit based on outcome of https://github.com/terraform-providers/terraform-provider-google/issues/1871 + +//resource "google_storage_bucket_object" "bucket-object-attach-policy" { +// count = "${var.gcp_enable_privileged_resources}" +// name = "${var.gcp_storage_bucket_object_name}-iam" +// bucket = "${google_storage_bucket.bucket-with-object.name}" +// content = "Bucket Object ${var.gcp_storage_bucket_object_name} for bucket ${var.gcp_storage_bucket_object} in ${var.gcp_project_id} with IAM policy." +//} +// +//data "google_iam_policy" "object-iam-policy" { +// count = "${var.gcp_enable_privileged_resources}" +// binding { +// role = "roles/storage.admin" +// +// members = [ "serviceAccount:${google_service_account.generic_service_account_object_viewer.email}" ] +// } +//} +// +//# would expect this to be something like below: +//resource "google_storage_object_iam_policy" "object-iam-policy-add" { +// count = "${var.gcp_enable_privileged_resources}" +// bucket = "${google_storage_bucket.bucket-with-object.name}" +// object = "${google_storage_bucket_object.bucket-object-attach-policy.name}" +// policy_data = "${data.google_iam_policy.object-iam-policy.policy_data}" +//} + + +# END storage bucket resources \ No newline at end of file diff --git a/test/integration/configuration/gcp_inspec_config.rb b/test/integration/configuration/gcp_inspec_config.rb index bb584c5ce..298355873 100644 --- a/test/integration/configuration/gcp_inspec_config.rb +++ b/test/integration/configuration/gcp_inspec_config.rb @@ -41,7 +41,13 @@ module GCPInspecConfig :gcp_ext_vm_data_disk_name => "gcp-inspec-generic-ext-linux-vm-data-disk", :gcp_ext_vm_data_disk_size => "f1-micro", :gcp_ext_vm_data_disk_image => "ubuntu-os-cloud/ubuntu-1604-lts", - :gcp_storage_bucket_name => "gcp-inspec-storage-bucket-#{(0...15).map { (65 + rand(26)).chr }.join.downcase}", + :gcp_storage_bucket_name => "gcp-inspec-storage-bucket-#{(0...25).map { (65 + rand(26)).chr }.join.downcase}", + :gcp_storage_bucket_acl => "gcp-inspec-storage-bucket-acl-#{(0...25).map { (65 + rand(26)).chr }.join.downcase}", + :gcp_storage_bucket_binding => "gcp-inspec-storage-bucket-iam-binding-#{(0...25).map { (65 + rand(26)).chr }.join.downcase}", + :gcp_storage_bucket_member => "gcp-inspec-storage-bucket-iam-member-#{(0...25).map { (65 + rand(26)).chr }.join.downcase}", + :gcp_storage_bucket_policy => "gcp-inspec-storage-bucket-iam-policy-#{(0...25).map { (65 + rand(26)).chr }.join.downcase}", + :gcp_storage_bucket_object => "gcp-inspec-storage-bucket-object-#{(0...25).map { (65 + rand(26)).chr }.join.downcase}", + :gcp_storage_bucket_object_name => "bucket-object-#{(0...15).map { (65 + rand(26)).chr }.join.downcase}", # Google Load Balanced App example parameters :gcp_lb_region => "europe-west2", :gcp_lb_zone => "europe-west2-a", diff --git a/test/integration/verify/controls/generic_storage_bucket.rb b/test/integration/verify/controls/google_storage_bucket.rb similarity index 90% rename from test/integration/verify/controls/generic_storage_bucket.rb rename to test/integration/verify/controls/google_storage_bucket.rb index 3781d40bb..6df5d4934 100644 --- a/test/integration/verify/controls/generic_storage_bucket.rb +++ b/test/integration/verify/controls/google_storage_bucket.rb @@ -4,7 +4,7 @@ gcp_location = attribute(:gcp_location, default: '', description: 'The GCP region being used.') gcp_storage_bucket_name = attribute(:gcp_storage_bucket_name, default:'', description: 'The Storage Bucket name.') -control 'gcp-generic-storage-bucket-1.0' do +control 'gcp-storage-bucket-1.0' do impact 1.0 title 'Ensure that the Storage Bucket has been created correctly' @@ -17,6 +17,5 @@ its('kind') { should eq "storage#bucket" } its('project_number') {should eq gcp_project_number.to_i } its('storage_class') { should eq 'STANDARD' } - # revisit acl / owner / retention policy etc. end end \ No newline at end of file diff --git a/test/integration/verify/controls/google_storage_bucket_acl.rb b/test/integration/verify/controls/google_storage_bucket_acl.rb new file mode 100644 index 000000000..1a27c74a4 --- /dev/null +++ b/test/integration/verify/controls/google_storage_bucket_acl.rb @@ -0,0 +1,20 @@ +title 'Test single GCP storage bucket ACL' + +gcp_project_id = attribute(:gcp_project_id, default: '', description: 'The GCP project identifier.') +gcp_storage_bucket_acl = attribute(:gcp_storage_bucket_acl, default: '', description: 'The GCP bucket with ACL set.') +gcp_enable_privileged_resources = attribute(:gcp_enable_privileged_resources,default:0,description:'Flag to enable privileged resources requiring elevated privileges in GCP.') + +control 'gcp-storage-bucket-acl-1.0' do + + only_if { gcp_enable_privileged_resources.to_i == 1 } + impact 1.0 + title 'Ensure storage bucket ACL has the correct properties.' + + describe google_storage_bucket_acl(bucket: gcp_storage_bucket_acl, entity: "user-object-viewer@#{gcp_project_id}.iam.gserviceaccount.com") do + it { should exist } + its('email') { should include "object-viewer@#{gcp_project_id}.iam.gserviceaccount.com" } + its('role') { should eq "OWNER" } + its('bucket') { should eq gcp_storage_bucket_acl } + end + +end \ No newline at end of file diff --git a/test/integration/verify/controls/google_storage_bucket_iam_binding.rb b/test/integration/verify/controls/google_storage_bucket_iam_binding.rb new file mode 100644 index 000000000..aba633784 --- /dev/null +++ b/test/integration/verify/controls/google_storage_bucket_iam_binding.rb @@ -0,0 +1,35 @@ +title 'Test single GCP storage bucket IAM binding' + +gcp_project_id = attribute(:gcp_project_id, default: '', description: 'The GCP project identifier.') +gcp_storage_bucket_binding = attribute(:gcp_storage_bucket_binding, default: '', description: 'The GCP bucket with IAM binding.') +gcp_storage_bucket_member = attribute(:gcp_storage_bucket_member, default: '', description: 'The GCP bucket with IAM member.') +gcp_storage_bucket_policy = attribute(:gcp_storage_bucket_policy, default: '', description: 'The GCP bucket with IAM policy.') +gcp_enable_privileged_resources = attribute(:gcp_enable_privileged_resources,default:0,description:'Flag to enable privileged resources requiring elevated privileges in GCP.') + +control 'gcp-storage-bucket-iam-binding-1.0' do + + only_if { gcp_enable_privileged_resources.to_i == 1 } + impact 1.0 + title 'Ensure storage bucket IAM binding has the correct properties.' + + describe google_storage_bucket_iam_binding(bucket: gcp_storage_bucket_binding, role: 'roles/storage.objectViewer') do + it { should exist } + its ('members.count'){ should eq 1 } # i.e. our service account + # below is brittle, could extract service account email from tf in the future... + its('members') {should include "serviceAccount:object-viewer@#{gcp_project_id}.iam.gserviceaccount.com" } + end + + describe google_storage_bucket_iam_binding(bucket: gcp_storage_bucket_member, role: 'roles/storage.objectViewer') do + it { should exist } + its ('members.count'){ should eq 1 } # i.e. our service account + # below is brittle, could extract service account email from tf in the future... + its('members') {should include "serviceAccount:object-viewer@#{gcp_project_id}.iam.gserviceaccount.com" } + end + + describe google_storage_bucket_iam_binding(bucket: gcp_storage_bucket_policy, role: 'roles/storage.admin') do + it { should exist } + its ('members.count'){ should eq 1 } # i.e. our service account + # below is brittle, could extract service account email from tf in the future... + its('members') {should include "serviceAccount:object-viewer@#{gcp_project_id}.iam.gserviceaccount.com" } + end +end \ No newline at end of file diff --git a/test/integration/verify/controls/google_storage_bucket_iam_bindings.rb b/test/integration/verify/controls/google_storage_bucket_iam_bindings.rb new file mode 100644 index 000000000..ea8a4c280 --- /dev/null +++ b/test/integration/verify/controls/google_storage_bucket_iam_bindings.rb @@ -0,0 +1,29 @@ +title 'GCP Storage Bucket IAM Bindings Properties' + +gcp_storage_bucket_binding = attribute(:gcp_storage_bucket_binding, default: '', description: 'The GCP bucket with IAM binding.') +gcp_storage_bucket_member = attribute(:gcp_storage_bucket_member, default: '', description: 'The GCP bucket with IAM member.') +gcp_storage_bucket_policy = attribute(:gcp_storage_bucket_policy, default: '', description: 'The GCP bucket with IAM policy.') +gcp_enable_privileged_resources = attribute(:gcp_enable_privileged_resources,default:0,description:'Flag to enable privileged resources requiring elevated privileges in GCP.') + +control 'gcp-storage-bucket-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_storage_bucket_iam_bindings(bucket: gcp_storage_bucket_binding) do + it { should exist } + its('count') { should be <= 100} + its('iam_binding_roles') { should include "roles/storage.objectViewer" } + end + + describe google_storage_bucket_iam_bindings(bucket: gcp_storage_bucket_member) do + it { should exist } + its('iam_binding_roles') { should include "roles/storage.objectViewer" } + end + + describe google_storage_bucket_iam_bindings(bucket: gcp_storage_bucket_policy) do + it { should exist } + its('iam_binding_roles') { should include "roles/storage.admin" } + end +end \ No newline at end of file diff --git a/test/integration/verify/controls/google_storage_bucket_object.rb b/test/integration/verify/controls/google_storage_bucket_object.rb new file mode 100644 index 000000000..8d447d6a7 --- /dev/null +++ b/test/integration/verify/controls/google_storage_bucket_object.rb @@ -0,0 +1,21 @@ +title 'Storage Bucket Object Properties' + +gcp_storage_bucket_object = attribute(:gcp_storage_bucket_object, default: '', description: 'The GCP bucket with objects.') +gcp_storage_bucket_object_name = attribute(:gcp_storage_bucket_object_name, default: '', description: 'The GCP bucket object 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-storage-bucket-object-1.0' do + + only_if { gcp_enable_privileged_resources.to_i == 1 } + impact 1.0 + title 'Ensure that the Storage Bucket Object has been created correctly' + + describe google_storage_bucket_object(bucket: gcp_storage_bucket_object, object: gcp_storage_bucket_object_name) do + it { should exist } + its('name') { should eq gcp_storage_bucket_object_name } + its('size') { should be > 0 } + its('content_type') { should eq "text/plain; charset=utf-8" } + its('time_created_date') { should be > Time.now - 365*60*60*24*10 } + its('updated_date') { should be > Time.now - 365*60*60*24*10 } + end +end \ No newline at end of file diff --git a/test/integration/verify/controls/google_storage_buckets.rb b/test/integration/verify/controls/google_storage_buckets.rb index f61902d8c..e5226e67b 100644 --- a/test/integration/verify/controls/google_storage_buckets.rb +++ b/test/integration/verify/controls/google_storage_buckets.rb @@ -12,7 +12,7 @@ describe google_storage_buckets(project: gcp_project_id) do it { should exist } - its('count') { should be <= 10} + its('count') { should be <= 100} its('bucket_names') { should include gcp_storage_bucket_name } its('bucket_ids') { should include gcp_storage_bucket_name } its('bucket_locations') { should include gcp_location.upcase } diff --git a/test/integration/verify/controls/google_storage_default_object_acl.rb b/test/integration/verify/controls/google_storage_default_object_acl.rb new file mode 100644 index 000000000..aff0ef739 --- /dev/null +++ b/test/integration/verify/controls/google_storage_default_object_acl.rb @@ -0,0 +1,19 @@ +title 'Test single GCP storage default object ACL' + +gcp_project_id = attribute(:gcp_project_id, default: '', description: 'The GCP project identifier.') +gcp_storage_object_default_acl = attribute(:gcp_storage_bucket_name, default: '', description: 'The GCP bucket with default ACL set.') +gcp_enable_privileged_resources = attribute(:gcp_enable_privileged_resources,default:0,description:'Flag to enable privileged resources requiring elevated privileges in GCP.') + +control 'gcp-storage-default-object-acl-1.0' do + + only_if { gcp_enable_privileged_resources.to_i == 1 } + impact 1.0 + title 'Ensure storage default object ACL has the correct properties.' + + describe google_storage_default_object_acl(bucket: gcp_storage_object_default_acl, entity: "user-object-viewer@#{gcp_project_id}.iam.gserviceaccount.com") do + it { should exist } + its('email') { should include "object-viewer@#{gcp_project_id}.iam.gserviceaccount.com" } + its('role') { should eq "OWNER" } + end + +end \ No newline at end of file diff --git a/test/integration/verify/controls/google_storage_object_acl.rb b/test/integration/verify/controls/google_storage_object_acl.rb new file mode 100644 index 000000000..40de6ae84 --- /dev/null +++ b/test/integration/verify/controls/google_storage_object_acl.rb @@ -0,0 +1,19 @@ +title 'Test single GCP storage object ACL' + +gcp_project_id = attribute(:gcp_project_id, default: '', description: 'The GCP project identifier.') +gcp_storage_bucket_object = attribute(:gcp_storage_bucket_object, default: '', description: 'The GCP bucket with objects.') +gcp_storage_bucket_object_name = attribute(:gcp_storage_bucket_object_name, default: '', description: 'The GCP bucket object 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-storage-object-acl-1.0' do + + only_if { gcp_enable_privileged_resources.to_i == 1 } + impact 1.0 + title 'Ensure storage default object ACL has the correct properties.' + + describe google_storage_object_acl(bucket: gcp_storage_bucket_object, object: gcp_storage_bucket_object_name, entity: "user-object-viewer@#{gcp_project_id}.iam.gserviceaccount.com") do + it { should exist } + its('email') { should include "object-viewer@#{gcp_project_id}.iam.gserviceaccount.com" } + its('role') { should eq "OWNER" } + end +end \ No newline at end of file