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