diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..c8891f2 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,19 @@ +FROM cincproject/auditor:6.8.24 + +RUN apt-get update && \ + apt-get install -y \ + build-essential \ + ruby-dev \ + zlib1g-dev \ + libssl-dev \ + liblzma-dev \ + && rm -rf /var/lib/apt/lists/* + +# Install the bundler gem +RUN gem install bundler --no-document + +# Install the rubocop gem +RUN gem install rubocop --no-document + +# Default command when the container starts (optional, but good for interactive shells) +CMD ["/bin/bash"] \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..93616ff --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,29 @@ +{ + "name": "InSpec Custom Controls Development", + "build": { + "dockerfile": "Dockerfile" + }, + "customizations": { + "vscode": { + "extensions": [ + "burtlo.inspec", + "misogi.ruby-rubocop", + "Shopify.ruby-lsp" + ], + // VS Code settings specific to this dev container + "settings": { + // Use the Ruby LSP as the default formatter + "[ruby]": { + "editor.defaultFormatter": "Shopify.ruby-lsp", + "editor.formatOnSave": true, + "editor.tabSize": 2, + "editor.insertSpaces": true + }, + // Optional: Configure rdbg for debugging if you use it + "ruby_lsp.debug.rdbg.attachUrl": "tcp://localhost:1234", + "ruby_lsp.debug.rdbg.logOutput": true + } + } + }, + "postCreateCommand": "bundle check || bundle install" +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 71ca813..25726fa 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ Gemfile.lock inputs.yml vendor .DS_Store +credentials/* \ No newline at end of file diff --git a/.rubocop.yml b/.rubocop.yml index fd5fbe1..9508aff 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,6 +1,7 @@ --- AllCops: - TargetRubyVersion: 2.6.3p62 + TargetRubyVersion: 3.1.6p260 + NewCops: disable Exclude: - Gemfile diff --git a/README.md b/README.md index cbc018d..ddb5cb7 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,35 @@ -# GCP CIS 1.2.0 Benchmark Inspec Profile +# GCP CIS 4.0.0 Benchmark Inspec Profile -This repository holds the [Google Cloud Platform (GCP)](https://cloud.google.com/) [Center for Internet Security (CIS)](https://www.cisecurity.org) [version 1.2 Benchmark](https://www.cisecurity.org/benchmark/google_cloud_computing_platform/) [Inspec](https://www.inspec.io/) Profile. +This repository holds the [Google Cloud Platform (GCP)](https://cloud.google.com/) [Center for Internet Security (CIS)](https://www.cisecurity.org) [version 4.0 Benchmark](https://www.cisecurity.org/benchmark/google_cloud_computing_platform/) [Inspec](https://www.inspec.io/) Profile. -## Required Disclaimer +## 🚧 Required Disclaimer This is not an officially supported Google product. This code is intended to help users assess their security posture on the Google Cloud against the CIS Benchmark. This code is not certified by CIS. -## Coverage +## ✅ Coverage -The following GCP CIS v1.2.0 Benchmark Controls are not covered: +The following GCP CIS v4.0.0 Benchmark Controls are not covered: -- Identity and Access Management 1.2 - "Ensure that multi-factor authentication is enabled for all non-service accounts" - Identity and Access Management 1.3 - "Ensure that Security Key Enforcement is enabled for all admin accounts" - Identity and Access Management 1.12 - "Ensure API keys are not created for a project" - Identity and Access Management 1.13 - "Ensure API keys are restricted to use by only specified Hosts and Apps" - Identity and Access Management 1.14 - "Ensure API keys are restricted to only APIs that application needs access" - Identity and Access Management 1.15 - "Ensure API keys are rotated every 90 days" -- Cloud SQL Database Services 6.3 - "Ensure that MySql database instance does not allow anyone to connect with administrative privileges" -- Cloud SQL Database Services 6.4 - "Ensure that MySQL Database Instance does not allows root login from any Host" +- Identity and Access Management 1.17 - "Ensure Secrets are Not Stored in Cloud Functions Environment Variables by Using Secret Manager" +- Logging 2.14 - "Ensure 'Access Transparency' is 'Enabled'" +- Networking 3.10 - "Use Identity Aware Proxy (IAP) to Ensure Only Traffic From Google IP Addresses are 'Allowed'" +- VMs 4.10 - " Ensure That App Engine Applications Enforce HTTPS Connections" +- VMs 4.12 - "Ensure the Latest Operating System Updates Are Installed On Your Virtual Machines in All Projects" +- MySQL 6.1 - "Ensure that MySql database instances are secure" +- BigQuery 7.4 - "Ensure that MySQL Database Instance does not allows root login from any Host" -## Usage +## 🚀 Usage ### Profile Inputs (see `inspec.yml` file) This profile uses InSpec Inputs to make the tests more flexible. You are able to provide inputs at runtime either via the `cli` or via `YAML files` to help the profile work best in your deployment. -**pro tip**: Do not change the inputs in the `inspec.yml` file directly, either: +**pro-tip**: Do not change the inputs in the `inspec.yml` file directly, either: - update them via the cli - via the `--input` flag - pass them in via a YAML file as shown in the `Example` - via the `--input-file` flag @@ -49,17 +53,19 @@ Use this Cloud Shell Walkthrough for a hands-on example. ### CLI Example -#### Ruby Gem +#### 💎 Ruby Gem ``` -#install inspec +# install inspec $ gem install inspec-bin -v 4.26.15 --no-document --quiet ``` ``` # make sure you're authenticated to GCP $ gcloud auth list +``` +``` # acquire credentials to use with Application Default Credentials $ gcloud auth application-default login @@ -73,7 +79,7 @@ Profile Summary: 48 successful controls, 5 control failures, 7 controls skipped Test Summary: 166 successful, 7 failures, 7 skipped ``` -#### Docker +#### 🐳 Docker ``` # pull inspec image $ docker pull chef/inspec:4.26.15 @@ -82,7 +88,9 @@ $ docker pull chef/inspec:4.26.15 ``` # make sure you're authenticated to GCP $ gcloud auth list +``` +``` # acquire credentials to use with Application Default Credentials $ gcloud auth application-default login @@ -91,7 +99,9 @@ $ gcloud auth application-default login ``` # create function for convenience $ function inspec-docker { docker run -it -e GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS=true --rm -v ~/.config:/root/.config -v $(pwd):/share chef/inspec:4.26.15 "$@"; } +``` +``` # scan a project with this profile, replace {{project-id}} with your project ID $ inspec-docker exec https://github.com/GoogleCloudPlatform/inspec-gcp-cis-benchmark.git -t gcp:// --input gcp_project_id={{project-id}} --reporter cli json:{{project-id}}_scan.json ...snip... @@ -99,7 +109,7 @@ Profile Summary: 48 successful controls, 5 control failures, 7 controls skipped Test Summary: 166 successful, 7 failures, 7 skipped ``` -### Required APIs +## ⚙️ Required APIs Consider these GCP projects, which may all be the same or different: @@ -119,7 +129,7 @@ The following GCP APIs should be enabled in **all** of these projects: - sqladmin.googleapis.com - storage-api.googleapis.com -### Required Permissions +## 🔑 Required Permissions The following permissions are required to run the CIS benchmark profile: @@ -160,6 +170,7 @@ On project level: - logging.logMetrics.list - logging.sinks.get - logging.sinks.list +- monitoring.alertPolicies.get - monitoring.alertPolicies.list - resourcemanager.projects.get - resourcemanager.projects.getIamPolicy diff --git a/controls/1.01-iam.rb b/controls/1.01-iam.rb index 1e80dc9..616baa0 100644 --- a/controls/1.01-iam.rb +++ b/controls/1.01-iam.rb @@ -20,6 +20,8 @@ control_id = '1.1' control_abbrev = 'iam' +# Initialize the IAMBindingsCache outside the control for efficiency +# This resource likely holds the IAM bindings for the project. iam_bindings_cache = IAMBindingsCache(project: gcp_project_id) control "cis-gcp-#{control_id}-#{control_abbrev}" do @@ -40,29 +42,105 @@ ref 'CIS Benchmark', url: cis_url.to_s ref 'GCP Docs', url: 'https://cloud.google.com/docs/enterprise/best-practices-for-enterprise-organizations#use_corporate_login_credentials' - # determine the organization's email domain - case google_project(project: gcp_project_id).parent.type - when 'organization' - org_domain = google_organization(name: "organizations/#{google_project(project: gcp_project_id).parent.id}").display_name - when 'folder' - parent = 'folder' - folder_id = google_project(project: gcp_project_id).parent.id - while parent == 'folder' - if google_resourcemanager_folder(name: "folders/#{folder_id}").parent.include?('folders') - folder_id = google_resourcemanager_folder(name: "folders/#{folder_id}").parent.sub('folders/', '') + org_domain = nil + project_parent = google_project(project: gcp_project_id).parent + + if project_parent.nil? + # This project is likely a standalone project with no organization parent. + # In this scenario, checking corporate login credentials might not be applicable + # or you might need a different way to define the 'corporate domain'. + # For now, we'll skip if no parent is found. + describe "Project #{gcp_project_id} has no organization or folder parent." do + skip 'Cannot determine corporate domain. Skipping check for corporate login credentials.' + end + else + case project_parent.type + when 'organization' + org_id = project_parent.id + org_resource = google_organization(name: "organizations/#{org_id}") + if org_resource.exists? + org_domain = org_resource.display_name else - parent = 'organization' - org_domain = google_organization(name: google_resourcemanager_folder(name: "folders/#{folder_id}").parent.to_s).display_name + # Fallback if organization resource doesn't exist or is inaccessible + describe "Could not retrieve organization details for ID: #{org_id}." do + skip 'Cannot determine corporate domain. Skipping check for corporate login credentials.' + end + end + when 'folder' + current_folder_id = project_parent.id + found_organization = false + # Loop upwards until an organization is found or we run out of parents + while current_folder_id + folder_resource = google_resourcemanager_folder(name: "folders/#{current_folder_id}") + + if folder_resource.exists? # rubocop:disable Metrics/BlockNesting + parent_name = folder_resource.parent # This will be like "folders/123" or "organizations/456" + + if parent_name.include?('organizations/') + org_id = parent_name.sub('organizations/', '') + org_resource = google_organization(name: "organizations/#{org_id}") + if org_resource.exists? + org_domain = org_resource.display_name + found_organization = true + break # Exit loop once organization is found + else + # Organization parent exists but cannot be retrieved + describe "Could not retrieve organization details for ID: #{org_id} (parent of folder #{current_folder_id})." do + skip 'Cannot determine corporate domain. Skipping check for corporate login credentials.' + end + break # Exit loop as we hit an issue + end + elsif parent_name.include?('folders/') + current_folder_id = parent_name.sub('folders/', '') # Move up to the next folder + else + # Unexpected parent type for a folder + describe "Unexpected parent type '#{parent_name}' for folder #{current_folder_id}." do + skip 'Cannot determine corporate domain. Skipping check for corporate login credentials.' + end + break # Exit loop + end + else + # Folder resource itself doesn't exist or is inaccessible + describe "Folder resource 'folders/#{current_folder_id}' not found or inaccessible." do + skip 'Cannot determine corporate domain. Skipping check for corporate login credentials.' + end + break # Exit loop + end + end + + unless found_organization + describe "Could not find an organization parent for project #{gcp_project_id} through its folder hierarchy." do + skip 'Cannot determine corporate domain. Skipping check for corporate login credentials.' + end + end + else + # Handle other potential parent types (e.g., `None` for standalone projects, or future types) + describe "Project #{gcp_project_id} has an unsupported parent type: #{project_parent.type}." do + skip 'Cannot determine corporate domain. Skipping check for corporate login credentials.' end end end - iam_bindings_cache.iam_binding_roles.each do |role| - iam_bindings_cache.iam_bindings[role].members.each do |member| - next if member.to_s.end_with?('.gserviceaccount.com') - describe "[#{gcp_project_id}] [Role:#{role}] Its member #{member}" do - subject { member.to_s } - it { should match(/@#{org_domain}/) } + # Proceed with IAM checks only if org_domain was successfully determined + if org_domain.nil? + describe 'Corporate domain could not be determined.' do + skip 'Skipping IAM member checks as corporate domain is unknown.' + end + else + iam_bindings_cache.iam_binding_roles.each do |role| + iam_bindings_cache.iam_bindings[role].members.each do |member| + # Skip service accounts + next if member.to_s.end_with?('.gserviceaccount.com') + # Skip allUsers and allAuthenticatedUsers + next if %w[allUsers allAuthenticatedUsers].include?(member.to_s) + + describe "[#{gcp_project_id}] [Role:#{role}] Its member #{member}" do + subject { member.to_s } + # Using `should not match` for personal accounts implies any non-corporate domain + # If the intent is strictly to allow ONLY corporate domains, then `should match` + # but usually the goal is to exclude personal ones. + it { should match(/@#{Regexp.escape(org_domain)}/) } # Use Regexp.escape for safety + end end end end diff --git a/controls/1.02-iam.rb b/controls/1.02-iam.rb index fca69a0..19e7122 100644 --- a/controls/1.02-iam.rb +++ b/controls/1.02-iam.rb @@ -1,3 +1,5 @@ +# controls/1.02-iam.rb (assuming 1.02 is the actual file name) + # Copyright 2019 The inspec-gcp-cis-benchmark Authors # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,18 +19,17 @@ gcp_project_id = input('gcp_project_id') cis_version = input('cis_version') cis_url = input('cis_url') -control_id = '1.2' +control_id = '1.2' # This should match the actual control ID control_abbrev = 'iam' control "cis-gcp-#{control_id}-#{control_abbrev}" do impact 'medium' - title "[#{control_abbrev.upcase}] Ensure that multi-factor authentication is enabled for all non-service accounts" - desc 'Setup multi-factor authentication for Google Cloud Platform accounts.' - desc 'rationale', 'Multi-factor authentication requires more than one mechanism to authenticate a user. This secures your logins from attackers exploiting stolen or weak credentials.' + desc 'rationale', 'Multi-factor authentication requires more than one mechanism to authenticate a user. This secures user logins from attackers exploiting stolen or weak credentials.' + desc 'notes', 'This control generally requires manual verification because InSpec does not directly query user MFA status from Cloud Identity or Google Workspace. Verification steps are provided in the references.' - tag cis_scored: false + tag cis_scored: false # Keep this as false tag cis_level: 1 tag cis_gcp: control_id.to_s tag cis_version: cis_version.to_s @@ -37,6 +38,7 @@ ref 'CIS Benchmark', url: cis_url.to_s ref 'GCP Docs', url: 'https://cloud.google.com/solutions/securing-gcp-account-u2f' + ref 'GCP Docs', url: 'https://support.google.com/accounts/answer/185839' describe 'This control is not scored' do skip 'This control is not scored' diff --git a/controls/1.03-iam.rb b/controls/1.03-iam.rb index de1de0d..da397c4 100644 --- a/controls/1.03-iam.rb +++ b/controls/1.03-iam.rb @@ -22,9 +22,7 @@ control "cis-gcp-#{control_id}-#{control_abbrev}" do impact 'medium' - title "[#{control_abbrev.upcase}] Ensure that Security Key Enforcement is enabled for all admin accounts" - desc 'Setup Security Key Enforcement for Google Cloud Platform admin accounts.' desc 'rationale', 'Google Cloud Platform users with Organization Administrator roles have the highest level of privilege in the organization. These accounts should be protected with the strongest form of two-factor authentication: Security Key Enforcement. Ensure that admins use Security Keys to log in instead of weaker second factors like SMS or one-time passwords (OTP). Security Keys are actual physical keys used to access Google Organization Administrator Accounts. They send an encrypted signature rather than a code, ensuring that logins cannot be phished.' @@ -37,6 +35,7 @@ ref 'CIS Benchmark', url: cis_url.to_s ref 'GCP Docs', url: 'https://cloud.google.com/security-key/' + ref 'GCP Docs', url: 'https://gsuite.google.com/learn-more/key_for_working_smarter_faster_and_more_securely.html' describe 'This control is not scored' do skip 'This control is not scored' diff --git a/controls/1.04-iam.rb b/controls/1.04-iam.rb index 0ade612..45a9a99 100644 --- a/controls/1.04-iam.rb +++ b/controls/1.04-iam.rb @@ -20,25 +20,12 @@ control_id = '1.4' control_abbrev = 'iam' -service_account_cache = ServiceAccountCache(project: gcp_project_id) - control "cis-gcp-#{control_id}-#{control_abbrev}" do impact 'none' title "[#{control_abbrev.upcase}] Ensure that there are only GCP-managed service account keys for each service account" - desc 'User managed service account should not have user managed keys.' - desc 'rationale', 'Anyone who has access to the keys will be able to access resources through the service account. GCP-managed keys are used by Cloud Platform services such as App Engine and Compute Engine. These keys cannot be downloaded. Google will keep the keys and automatically rotate them on an approximately weekly basis. User-managed keys are created, downloadable, and managed by users. They expire 10 years from creation. - -For user-managed keys, user have to take ownership of key management activities which includes: -- Key storage -- Key distribution -- Key revocation -- Key rotation -- Protecting the keys from unauthorized users -- Key recovery - -Even after owners precaution, keys can be easily leaked by common development malpractices like checking keys into the source code or leaving them in Downloads directory, or accidentally leaving them on support blogs/channels. It is recommended to prevent use of User-managed service account keys.' + desc 'rationale', 'Anyone who has access to the keys will be able to access resources through the service account. GCP-managed keys are used by Cloud Platform services such as App Engine and Compute Engine. These keys cannot be downloaded. Google will keep the keys and automatically rotate them on an approximately weekly basis. User-managed keys are created, downloadable, and managed by users. They expire 10 years from creation. For user-managed keys, user have to take ownership of key management activities which includes: - Key storage - Key distribution - Key revocation - Key rotation - Protecting the keys from unauthorized users - Key recovery Even after owners precaution, keys can be easily leaked by common development malpractices like checking keys into the source code or leaving them in Downloads directory, or accidentally leaving them on support blogs/channels. It is recommended to prevent use of User-managed service account keys.' tag cis_scored: true tag cis_level: 1 @@ -49,17 +36,17 @@ ref 'CIS Benchmark', url: cis_url.to_s ref 'GCP Docs', url: 'https://cloud.google.com/iam/docs/understanding-service-accounts#managing_service_account_keys' + ref 'GCP Docs', url: 'https://cloud.google.com/resource-manager/docs/organization-policy/restricting-service-accounts' - service_account_cache.service_account_emails.each do |sa_email| - if service_account_cache.service_account_keys[sa_email].key_names.count > 1 - impact 'medium' - describe "[#{gcp_project_id}] Service Account: #{sa_email}" do - subject { service_account_cache.service_account_keys[sa_email] } - its('key_types') { should_not include 'USER_MANAGED' } - end - else - describe "[#{gcp_project_id}] ServiceAccount [#{sa_email}] does not have user-managed keys. This test is Not Applicable." do - skip "[#{gcp_project_id}] ServiceAccount [#{sa_email}] does not have user-managed keys." + # Use google_service_accounts instead of the custom cache. + google_service_accounts(project: gcp_project_id).service_account_emails.each do |service_account| + # Use google_service_account_keys to get key types directly. + keys = google_service_account_keys(project: gcp_project_id, service_account: service_account) + + describe "[#{gcp_project_id}] Service Account: #{service_account}" do + subject { keys } + it 'should not have user-managed keys' do + expect(keys.key_types).to_not include('USER_MANAGED') end end end diff --git a/controls/1.05-iam.rb b/controls/1.05-iam.rb index 3a23690..a554b96 100644 --- a/controls/1.05-iam.rb +++ b/controls/1.05-iam.rb @@ -27,9 +27,8 @@ title "[#{control_abbrev.upcase}] Ensure that ServiceAccount has no Admin privileges." - desc "A service account is a special Google account that belongs to your application or a VM, instead of to an individual end user. Your application uses the service account to call the Google API of a service, so that the users aren't directly involved. It's recommended not to use admin access for ServiceAccount." - desc 'rationale', "Service accounts represent service-level security of the Resources (application or a VM) which can be determined by the roles assigned to it. Enrolling ServiceAccount with Admin rights gives full access to assigned application or a VM, ServiceAccount Access holder can perform critical actions like delete, update change settings etc. without the intervention of user, so It's recommended not to have Admin rights. -This recommendation is applicable only for User-Managed user created service account (Service account with nomenclature: SERVICE_ACCOUNT_NAME@PROJECT_ID.iam.gserviceaccount.com)." + desc "A service account is a special Google account that belongs to an application or a VM, instead of to an individual end-user. The application uses the service account to call the service's Google API so that users aren't directly involved. It's recommended not to use admin access for ServiceAccount." + desc 'rationale', "Service accounts represent service-level security of the Resources (application or a VM) which can be determined by the roles assigned to it. Enrolling ServiceAccount with Admin rights gives full access to an assigned application or a VM. A ServiceAccount Access holder can perform critical actions like delete, update change settings, etc. without user intervention. For this reason, it's recommended that service accounts not have Admin rights." tag cis_scored: true tag cis_level: 1 diff --git a/controls/1.06-iam.rb b/controls/1.06-iam.rb index 405aa74..c64fba6 100644 --- a/controls/1.06-iam.rb +++ b/controls/1.06-iam.rb @@ -27,15 +27,14 @@ title "[#{control_abbrev.upcase}] Ensure that IAM users are not assigned Service Account User role at project level" - desc "It is recommended to assign Service Account User (iam.serviceAccountUser) role to a -user for a specific service account rather than assigning the role to a user at project level." - desc 'rationale', "A service account is a special Google account that belongs to application or a virtual machine (VM), instead of to an individual end user. Application/VM-Instance uses the service account to call the Google API of a service, so that the users aren't directly involved. In addition to being an identity, a service account is a resource which has IAM policies attached to it. These policies determine who can use the service account. + desc 'It is recommended to assign the Service Account User (iam.serviceAccountUser) and Service Account Token Creator (iam.serviceAccountTokenCreator) roles to a user for a specific service account rather than assigning the role to a user at project level.' + desc 'rationale', "A service account is a special Google account that belongs to an application or a virtual machine (VM), instead of to an individual end-user. Application/VM-Instance uses the service account to call the service's Google API so that users aren't directly involved. In addition to being an identity, a service account is a resource that has IAM policies attached to it. These policies determine who can use the service account. -Users with IAM roles to update the App Engine and Compute Engine instances (such as App Engine Deployer or Compute Instance Admin) can effectively run code as the service accounts used to run these instances, and indirectly gain access to all the resources for which the service accounts has access. Similarly, SSH access to a Compute Engine instance may also provide the ability to execute code as that instance/Service account. + Users with IAM roles to update the App Engine and Compute Engine instances (such as App Engine Deployer or Compute Instance Admin) can effectively run code as the service accounts used to run these instances, and indirectly gain access to all the resources for which the service accounts have access. Similarly, SSH access to a Compute Engine instance may also provide the ability to execute code as that instance/Service account. -As per business needs, there could be multiple user-managed service accounts configured for a project. Granting the iam.serviceAccountUser role to a user for a project gives the user access to all service accounts in the project, including service accounts that may be created in the future. This can result into elevation of privileges by using service accounts and corresponding Compute Engine instances. + Based on business needs, there could be multiple user-managed service accounts configured for a project. Granting the iam.serviceAccountUser or iam.serviceAccountTokenCreator roles to a user for a project gives the user access to all service accounts in the project, including service accounts that may be created in the future. This can result in elevation of privileges by using service accounts and corresponding Compute Engine instances. -In order to implement least privileges best practices, IAM users should not be assigned Service Account User role at project level. Instead iam.serviceAccountUser role should be assigned to a user for a specific service account giving a user access to the service account." + In order to implement least privileges best practices, IAM users should not be assigned the Service Account User or Service Account Token Creator roles at the project level. Instead, these roles should be assigned to a user for a specific service account, giving that user access to the service account. The Service Account User allows a user to bind a service account to a long-running job service, whereas the Service Account Token Creator role allows a user to directly impersonate (or assert) the identity of a service account." tag cis_scored: true tag cis_level: 1 @@ -49,6 +48,7 @@ ref 'GCP Docs', url: 'https://cloud.google.com/iam/docs/granting-roles-to-service-accounts' ref 'GCP Docs', url: 'https://cloud.google.com/iam/docs/understanding-roles' ref 'GCP Docs', url: 'https://cloud.google.com/iam/docs/granting-changing-revoking-access' + ref 'GCP Docs', url: 'https://console.cloud.google.com/iam-admin/iam' describe "[#{gcp_project_id}] A project-level binding of ServiceAccountUser" do subject { iam_bindings_cache.iam_bindings['roles/iam.serviceAccountUser'] } diff --git a/controls/1.07-iam.rb b/controls/1.07-iam.rb index 9913b89..acb8167 100644 --- a/controls/1.07-iam.rb +++ b/controls/1.07-iam.rb @@ -28,12 +28,12 @@ title "[#{control_abbrev.upcase}] Ensure user-managed/external keys for service accounts are rotated every 90 days or less" - desc 'Service Account keys consist of a key ID (Private_key_Id) and Private key, which are used to sign programmatic requests that you make to Google cloud services accessible to that particular Service account. It is recommended that all Service Account keys are regularly rotated.' - desc 'rationale', "Rotating Service Account keys will reduce the window of opportunity for an access key that is associated with a compromised or terminated account to be used. Service Account keys should be rotated to ensure that data cannot be accessed with an old key which might have been lost, cracked, or stolen. + desc 'Service Account keys consist of a key ID (Private_key_Id) and Private key, which are used to sign programmatic requests users make to Google cloud services accessible to that particular service account. It is recommended that all Service Account keys are regularly rotated.' + desc 'rationale', "Rotating Service Account keys will reduce the window of opportunity for an access key that is associated with a compromised or terminated account to be used. Service Account keys should be rotated to ensure that data cannot be accessed with an old key that might have been lost, cracked, or stolen. -Each service account is associated with a key pair, which is managed by Google Cloud Platform (GCP). It is used for service-to-service authentication within GCP. Google rotates the keys daily. + Each service account is associated with a key pair managed by Google Cloud Platform (GCP). It is used for service-to-service authentication within GCP. Google rotates the keys daily. -GCP provides option to create one or more user-managed (also called as external key pairs) key pairs for use from outside GCP (for example, for use with Application Default Credentials). When a new key pair is created, user is enforced download the private key (which is not retained by Google). With external keys, users are responsible for security of the private key and other management operations such as key rotation. External keys can be managed by the IAM API, gcloud command-line tool, or the Service Accounts page in the Google Cloud Platform Console. GCP facilitates up to 10 external service account keys per service account to facilitate key rotation." + GCP provides the option to create one or more user-managed (also called external key pairs) key pairs for use from outside GCP (for example, for use with Application Default Credentials). When a new key pair is created, the user is required to download the private key (which is not retained by Google). With external keys, users are responsible for keeping the private key secure and other management operations such as key rotation. External keys can be managed by the IAM API, gcloud command-line tool, or the Service Accounts page in the Google Cloud Platform Console. GCP facilitates up to 10 external service account keys per service account to facilitate key rotation." tag cis_scored: true tag cis_level: 1 @@ -48,15 +48,22 @@ ref 'GCP Docs', url: 'https://cloud.google.com/iam/docs/service-accounts' service_account_cache.service_account_emails.each do |sa_email| - if service_account_cache.service_account_keys[sa_email].key_names.count > 1 + # First, get all user-managed keys for the current service account + user_managed_keys_for_sa = service_account_cache.service_account_keys[sa_email].where { key_type == 'USER_MANAGED' } + + # Then, filter for only the enabled ones among them (where 'disabled' attribute is false) + enabled_user_managed_keys = user_managed_keys_for_sa.where { !disabled } + + if enabled_user_managed_keys.count > 0 impact 'medium' - describe "[#{gcp_project_id}] ServiceAccount Keys for #{sa_email} older than #{sa_key_older_than_seconds} seconds" do - subject { service_account_cache.service_account_keys[sa_email].where { (Time.now - sa_key_older_than_seconds > valid_after_time) } } + describe "[#{gcp_project_id}] Enabled user-managed ServiceAccount Keys for #{sa_email} older than #{sa_key_older_than_seconds} seconds" do + # Now, for the actual check: filter the *enabled* keys for those older than the specified time + subject { enabled_user_managed_keys.where { (Time.now - sa_key_older_than_seconds > valid_after_time) } } it { should_not exist } end else - describe "[#{gcp_project_id}] ServiceAccount [#{sa_email}] does not have user-managed keys. This test is Not Applicable." do - skip "[#{gcp_project_id}] ServiceAccount [#{sa_email}] does not have user-managed keys." + describe "[#{gcp_project_id}] ServiceAccount [#{sa_email}] does not have enabled user-managed keys. This test is Not Applicable." do + skip "[#{gcp_project_id}] ServiceAccount [#{sa_email}] does not have enabled user-managed keys." end end end diff --git a/controls/1.08-iam.rb b/controls/1.08-iam.rb index 5d6d16e..57a8fdb 100644 --- a/controls/1.08-iam.rb +++ b/controls/1.08-iam.rb @@ -27,12 +27,13 @@ title "[#{control_abbrev.upcase}] Ensure that Separation of duties is enforced while assigning service account related roles to users" - desc "It is recommended that the principle of 'Separation of Duties' is enforced while assigning service account related roles to users." - desc 'rationale', "Built-in/Predefined IAM role Service Account admin allows user/identity to create, delete, manage service account(s). Built-in/Predefined IAM role Service Account User allows user/identity (with adequate privileges on Compute and App Engine) to assign service account(s) to Apps/Compute Instances. + desc "It is recommended that the principle of 'Separation of Duties' is enforced while assigning service-account related roles to users." + desc 'rationale', "The built-in/predefined IAM role Service Account admin allows the user/identity to create, delete, and manage service account(s). The built-in/predefined IAM role Service Account User allows the user/identity (with adequate privileges on Compute and App Engine) to assign service account(s) to Apps/Compute Instances. -Separation of duties is the concept of ensuring that one individual does not have all necessary permissions to be able to complete a malicious action. In Cloud IAM - service accounts, this could be an action such as using a service account to access resources that user should not normally have access to. Separation of duties is a business control typically used in larger organizations, meant to help avoid security or privacy incidents and errors. It is considered best practice. + Separation of duties is the concept of ensuring that one individual does not have all necessary permissions to be able to complete a malicious action. In Cloud IAM - service accounts, this could be an action such as using a service account to access resources that user should not normally have access to. + Separation of duties is a business control typically used in larger organizations, meant to help avoid security or privacy incidents and errors. It is considered best practice. -Any user(s) should not have Service Account Admin and Service Account User, both roles assigned at a time." + No user should have Service Account Admin and Service Account User roles assigned at the same time." tag cis_scored: false tag cis_level: 2 diff --git a/controls/1.09-iam.rb b/controls/1.09-iam.rb index 6a7e57b..e9fcb58 100644 --- a/controls/1.09-iam.rb +++ b/controls/1.09-iam.rb @@ -37,7 +37,10 @@ tag nist: ['AC-3'] ref 'CIS Benchmark', url: cis_url.to_s - ref 'GCP Docs', url: 'https://cloud.google.com/kms/docs/key-rotation#frequency_of_key_rotation' + ref 'GCP Docs', url: 'https://cloud.google.com/sdk/gcloud/reference/kms/keys/remove-iam-policy-binding' + ref 'GCP Docs', url: 'https://cloud.google.com/sdk/gcloud/reference/kms/keys/set-iam-policy' + ref 'GCP Docs', url: 'https://cloud.google.com/sdk/gcloud/reference/kms/keys/get-iam-policy' + ref 'GCP Docs', url: 'https://cloud.google.com/kms/docs/object-hierarchy#key_resource_id' # Get all "normal" regions and add dual/multi regions locations = google_compute_regions(project: gcp_project_id).region_names diff --git a/controls/1.10-iam.rb b/controls/1.10-iam.rb index 761e908..426afc1 100644 --- a/controls/1.10-iam.rb +++ b/controls/1.10-iam.rb @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -title 'Ensure Encryption keys are rotated within a period of 90 days' +title 'Ensure KMS Encryption Keys Are Rotated Within a Period of 90 Days' gcp_project_id = input('gcp_project_id') cis_version = input('cis_version') @@ -24,17 +24,15 @@ control "cis-gcp-#{control_id}-#{control_abbrev}" do impact 'none' - title "[#{control_abbrev.upcase}] Ensure Encryption keys are rotated within a period of 90 days" + title "[#{control_abbrev.upcase}] Ensure KMS Encryption Keys Are Rotated Within a Period of 90 Days" - desc "Google Cloud Key Management Service (KMS) stores cryptographic keys in a hierarchical structure designed for useful and elegant access control management. + desc "Google Cloud Key Management Service stores cryptographic keys in a hierarchical structure designed for useful and elegant access control management. -Automatic cryptographic key rotation is only available for symmetric keys. Cloud KMS does not support automatic rotation of asymmetric keys so such keys are out of scope for this control. More information can be found in the GCP documentation references of this control. - -The format for the rotation schedule depends on the client library that is used. For the gcloud command-line tool, the next rotation time must be in ISO or RFC3339 format, and the rotation period must be in the form INTEGER[UNIT], where units can be one of seconds (s), minutes (m), hours (h) or days (d)." + The format for the rotation schedule depends on the client library that is used. For the gcloud command-line tool, the next rotation time must be in ISO or RFC3339 format, and the rotation period must be in the form INTEGER[UNIT], where units can be one of seconds (s), minutes (m), hours (h) or days (d)." desc 'rationale', "Set a key rotation period and starting time. A key can be created with a specified rotation period, which is the time between when new key versions are generated automatically. A key can also be created with a specified next rotation time. A key is a named object representing a cryptographic key used for a specific purpose. The key material, the actual bits used for encryption, can change over time as new key versions are created. -A key is used to protect some corpus of data. You could encrypt a collection of files with the same key, and people with decrypt permissions on that key would be able to decrypt those files. Hence it's necessary to make sure rotation period is set to specific time." + A key is used to protect some corpus of data. A collection of files could be encrypted with the same key and people with decrypt permissions on that key would be able to decrypt those files. Therefore, it's necessary to make sure the rotation period is set to a specific time." tag cis_scored: true tag cis_level: 1 @@ -45,7 +43,7 @@ ref 'CIS Benchmark', url: cis_url.to_s ref 'GCP Docs', url: 'https://cloud.google.com/kms/docs/key-rotation#frequency_of_key_rotation' - ref 'GCP Docs', url: 'https://cloud.google.com/kms/docs/key-rotation#asymmetric' + ref 'GCP Docs', url: 'https://cloud.google.com/kms/docs/re-encrypt-data' # Get all "normal" regions and add dual/multi regions locations = google_compute_regions(project: gcp_project_id).region_names diff --git a/controls/1.11-iam.rb b/controls/1.11-iam.rb index d5ae196..8632969 100644 --- a/controls/1.11-iam.rb +++ b/controls/1.11-iam.rb @@ -27,11 +27,13 @@ desc "It is recommended that the principle of 'Separation of Duties' is enforced while assigning KMS related roles to users." - desc 'rationale', "Built-in/Predefined IAM role Cloud KMS Admin allows user/identity to create, delete, and manage service account(s). Built-in/Predefined IAM role Cloud KMS CryptoKey Encrypter/Decrypter allows user/identity (with adequate privileges on concerned resources) to encrypt and decrypt data at rest using encryption key(s). Built-in/Predefined IAM role Cloud KMS CryptoKey Encrypter allows user/identity (with adequate privileges on concerned resources) to encrypt data at rest using encryption key(s). Builtin/Predefined IAM role Cloud KMS CryptoKey Decrypter allows user/identity (with adequate privileges on concerned resources) to decrypt data at rest using encryption key(s). + desc 'rationale', "The built-in/predefined IAM role Cloud KMS Admin allows the user/identity to create, delete, and manage service account(s). The built-in/predefined IAM role Cloud KMS CryptoKey Encrypter/Decrypter allows the user/identity (with adequate privileges on concerned resources) to encrypt and decrypt data at rest using an encryption key(s). -Separation of duties is the concept of ensuring that one individual does not have all necessary permissions to be able to complete a malicious action. In Cloud KMS, this could be an action such as using a key to access and decrypt data that that user should not normally have access to. Separation of duties is a business control typically used in larger organizations, meant to help avoid security or privacy incidents and errors. It is considered best practice. + The built-in/predefined IAM role Cloud KMS CryptoKey Encrypter allows the user/identity (with adequate privileges on concerned resources) to encrypt data at rest using an encryption key(s). The built-in/predefined IAM role Cloud KMS CryptoKey Decrypter allows the user/identity (with adequate privileges on concerned resources) to decrypt data at rest using an encryption key(s). -Any user(s) should not have Cloud KMS Admin and any of the Cloud KMS CryptoKey Encrypter/Decrypter, Cloud KMS CryptoKey Encrypter, Cloud KMS CryptoKey Decrypter roles assigned at a time." + Separation of duties is the concept of ensuring that one individual does not have all necessary permissions to be able to complete a malicious action. In Cloud KMS, this could be an action such as using a key to access and decrypt data a user should not normally have access to. Separation of duties is a business control typically used in larger organizations, meant to help avoid security or privacy incidents and errors. It is considered best practice. + + No user(s) should have Cloud KMS Admin and any of the Cloud KMS CryptoKey Encrypter/Decrypter, Cloud KMS CryptoKey Encrypter, Cloud KMS CryptoKey Decrypter roles assigned at the same time." tag cis_scored: true tag cis_level: 2 diff --git a/controls/1.12-iam.rb b/controls/1.12-iam.rb index 56be34d..70adca3 100644 --- a/controls/1.12-iam.rb +++ b/controls/1.12-iam.rb @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -title 'Ensure API keys are not created for a project' +title 'Ensure API Keys Only Exist for Active Services' gcp_project_id = input('gcp_project_id') cis_version = input('cis_version') @@ -23,17 +23,13 @@ control "cis-gcp-#{control_id}-#{control_abbrev}" do impact 'medium' - title "[#{control_abbrev.upcase}] Ensure API keys are not created for a project" + title "[#{control_abbrev.upcase}] Ensure API Keys Only Exist for Active Services" - desc 'Keys are insecure because they can be viewed publicly, such as from within a browser, or they can be accessed on a device where the key resides. It is recommended to use standard authentication flow instead.' - desc 'rationale', "Security risks involved in using API-Keys are below: - -- API keys are a simple encrypted strings -- API keys do not identify the user or the application making the API request -- API keys are typically accessible to clients, making it easy to discover and steal an API key - -To avoid security risk by using API keys, it is recommended to use standard authentication -flow instead." + desc 'API Keys should only be used for services in cases where other authentication methods are unavailable. Unused keys with their permissions in tact may still exist within a project. Keys are insecure because they can be viewed publicly, such as from within a browser, or they can be accessed on a device where the key resides. It is recommended to use standard authentication flow instead.' + desc 'rationale', "To avoid the security risk in using API keys, it is recommended to use standard authentication flow instead. Security risks involved in using API-Keys appear below: + - API keys are simple encrypted strings + - API keys do not identify the user or the application making the API request + - API keys are typically accessible to clients, making it easy to discover and steal an API key" tag cis_scored: false tag cis_level: 2 @@ -44,7 +40,9 @@ ref 'CIS Benchmark', url: cis_url.to_s ref 'GCP Docs', url: 'https://cloud.google.com/docs/authentication/api-keys' - + ref 'GCP Docs', url: 'https://cloud.google.com/sdk/gcloud/reference/services/api-keys/list' + ref 'GCP Docs', url: 'https://cloud.google.com/docs/authentication' + ref 'GCP Docs', url: 'https://cloud.google.com/sdk/gcloud/reference/services/api-keys/delete' describe 'This control is not scored' do skip 'This control is not scored' end diff --git a/controls/1.13-iam.rb b/controls/1.13-iam.rb index 2575b08..a29dd79 100644 --- a/controls/1.13-iam.rb +++ b/controls/1.13-iam.rb @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -title 'Ensure API keys are restricted to use by only specified Hosts and Apps' +title 'Ensure API Keys Are Restricted To Use by Only Specified Hosts and Apps' gcp_project_id = input('gcp_project_id') cis_version = input('cis_version') @@ -23,21 +23,18 @@ control "cis-gcp-#{control_id}-#{control_abbrev}" do impact 'medium' - title "[#{control_abbrev.upcase}] Ensure API keys are restricted to use by only specified Hosts and Apps" + title "[#{control_abbrev.upcase}] Ensure API Keys Are Restricted To Use by Only Specified Hosts and Apps" - desc 'Unrestricted keys are insecure because they can be viewed publicly, such as from within a browser, or they can be accessed on a device where the key resides. It is recommended to restrict API key usage only from trusted hosts, HTTP referrers and apps.' - desc 'rationale', "Security risks involved in using API-Keys are below: - -- API keys are a simple encrypted strings + desc 'API Keys should only be used for services in cases where other authentication methods are unavailable. In this case, unrestricted keys are insecure because they can be viewed publicly, such as from within a browser, or they can be accessed on a device where the key resides. It is recommended to restrict API key usage to trusted hosts, HTTP referrers and apps. It is recommended to use the more secure standard authentication flow instead.' + desc 'rationale', "Security risks involved in using API-Keys appear below: +- API keys are simple encrypted strings - API keys do not identify the user or the application making the API request - API keys are typically accessible to clients, making it easy to discover and steal an API key - -Because of this Google recommend using the standard authentication flow instead. However, there are limited cases where API keys are more appropriate. For example, if there is a mobile application that needs to use the Google Cloud Translation API, but doesn't otherwise need a back-end server, API keys are the simplest way to authenticate to that API. - -In order to reduce attack vector, API-Keys can be restricted only to the trusted hosts, HTTP referrers and applications." +In light of these potential risks, Google recommends using the standard authentication flow instead of API keys. However, there are limited cases where API keys are more appropriate. For example, if there is a mobile application that needs to use the Google Cloud Translation API, but doesn't otherwise need a backend server, API keys are the simplest way to authenticate to that API. +In order to reduce attack vectors, API-Keys can be restricted only to trusted hosts, HTTP referrers and applications." tag cis_scored: false - tag cis_level: 1 + tag cis_level: 2 tag cis_gcp: control_id.to_s tag cis_version: cis_version.to_s tag project: gcp_project_id.to_s @@ -45,6 +42,8 @@ ref 'CIS Benchmark', url: cis_url.to_s ref 'GCP Docs', url: 'https://cloud.google.com/docs/authentication/api-keys' + ref 'GCP Docs', url: 'https://cloud.google.com/sdk/gcloud/reference/services/api-keys/list' + ref 'GCP Docs', url: 'https://cloud.google.com/sdk/gcloud/reference/services/api-keys/update' describe 'This control is not scored' do skip 'This control is not scored' diff --git a/controls/1.14-iam.rb b/controls/1.14-iam.rb index 39096ce..27371a8 100644 --- a/controls/1.14-iam.rb +++ b/controls/1.14-iam.rb @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -title 'Ensure API keys are restricted to only APIs that application needs access' +title 'Ensure API Keys Are Restricted to Only APIs That Application Needs Access' gcp_project_id = input('gcp_project_id') cis_version = input('cis_version') @@ -38,7 +38,7 @@ restricted to use (call) only APIs required by an application." tag cis_scored: false - tag cis_level: 1 + tag cis_level: 2 tag cis_gcp: control_id.to_s tag cis_version: cis_version.to_s tag project: gcp_project_id.to_s diff --git a/controls/1.15-iam.rb b/controls/1.15-iam.rb index 64ab4d7..4acba71 100644 --- a/controls/1.15-iam.rb +++ b/controls/1.15-iam.rb @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -title 'Ensure API keys are rotated every 90 days' +title 'Ensure API Keys Are Rotated Every 90 Days' gcp_project_id = input('gcp_project_id') cis_version = input('cis_version') @@ -25,19 +25,20 @@ title "[#{control_abbrev.upcase}] Ensure API keys are rotated every 90 days" - desc 'It is recommended to rotate API keys every 90 days.' - desc 'rationale', "Security risks involved in using API-Keys are below: + desc 'API Keys should only be used for services in cases where other authentication methods are unavailable. If they are in use it is recommended to rotate API keys every 90 days.' + desc 'rationale', "Security risks involved in using API-Keys are listed below: + - API keys are simple encrypted strings + - API keys do not identify the user or the application making the API request + - API keys are typically accessible to clients, making it easy to discover and steal an API key -- API keys are a simple encrypted strings -- API keys do not identify the user or the application making the API request -- API keys are typically accessible to clients, making it easy to discover and steal an API key + Because of these potential risks, Google recommends using the standard authentication flow instead of API Keys. However, there are limited cases where API keys are more appropriate. For example, if there is a mobile application that needs to use the Google Cloud Translation API, but doesn't otherwise need a backend server, API keys are the simplest way to authenticate to that API. -Because of this Google recommend using the standard authentication flow instead. However, there are limited cases where API keys are more appropriate. For example, if there is a mobile application that needs to use the Google Cloud Translation API, but doesn't otherwise need a backend server, API keys are the simplest way to authenticate to that API. + Once a key is stolen, it has no expiration, meaning it may be used indefinitely unless the project owner revokes or regenerates the key. Rotating API keys will reduce the window of opportunity for an access key that is associated with a compromised or terminated account to be used. -Once the key is stolen, it has no expiration, so it may be used indefinitely, unless the project owner revokes or regenerates the key. Rotating API keys will reduce the window of opportunity for an access key that is associated with a compromised or terminated account to be used. API keys should be rotated to ensure that data cannot be accessed with an old key which might have been lost, cracked, or stolen." + API keys should be rotated to ensure that data cannot be accessed with an old key that might have been lost, cracked, or stolen." tag cis_scored: false - tag cis_level: 1 + tag cis_level: 2 tag cis_gcp: control_id.to_s tag cis_version: cis_version.to_s tag project: gcp_project_id.to_s diff --git a/controls/1.16-iam.rb b/controls/1.16-iam.rb new file mode 100644 index 0000000..87aeacf --- /dev/null +++ b/controls/1.16-iam.rb @@ -0,0 +1,41 @@ +# Copyright 2019 The inspec-gcp-cis-benchmark Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title 'Ensure Essential Contacts is Configured for Organization' + +gcp_project_id = input('gcp_project_id') +cis_version = input('cis_version') +cis_url = input('cis_url') +control_id = '1.16' +control_abbrev = 'iam' + +control "cis-gcp-#{control_id}-#{control_abbrev}" do + impact 'low' + title "[#{control_abbrev.upcase}] Ensure Essential Contacts is Configured for Organization" + desc 'It is recommended that Essential Contacts is configured to designate email addresses for Google Cloud services to notify of important technical or security information.' + desc 'rationale', 'Many Google Cloud services, such as Cloud Billing, send out notifications to share important information with Google Cloud users. By default, these notifications are sent to members with certain Identity and Access Management (IAM) roles. With Essential Contacts, you can customize who receives notifications by providing your own list of contacts.' + + tag cis_scored: false + tag cis_level: 1 + tag cis_gcp: control_id.to_s + tag cis_version: cis_version.to_s + tag project: gcp_project_id.to_s + tag nist: ['CP-2'] + + ref 'CIS Benchmark', url: cis_url.to_s + ref 'GCP Docs', url: 'https://cloud.google.com/resource-manager/docs/managing-notification-contacts' + describe 'This control is not scored' do + skip 'This control is not scored' + end +end diff --git a/controls/1.17-iam.rb b/controls/1.17-iam.rb new file mode 100644 index 0000000..c64f0f9 --- /dev/null +++ b/controls/1.17-iam.rb @@ -0,0 +1,42 @@ +# Copyright 2025 The inspec-gcp-cis-benchmark Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title 'Ensure Secrets are Not Stored in Cloud Functions Environment Variables by Using Secret Manager' + +gcp_project_id = input('gcp_project_id') +cis_version = input('cis_version') +cis_url = input('cis_url') +control_id = '1.17' +control_abbrev = 'iam' + +control "cis-gcp-#{control_id}-#{control_abbrev}" do + impact 'medium' + title "[#{control_abbrev.upcase}] Ensure Secrets are Not Stored in Cloud Functions Environment Variables by Using Secret Manager" + desc 'Google Cloud Functions allow you to host serverless code that is executed when an event is triggered, without the requiring the management a host operating system. These functions can also store environment variables to be used by the code that may contain authentication or other information that needs to remain confidential.' + desc 'rationale', 'It is recommended to use the Secret Manager, because environment variables are stored unencrypted, and accessible for all users who have access to the code.' + + tag cis_scored: false + tag cis_level: 1 + tag cis_gcp: control_id.to_s + tag cis_version: cis_version.to_s + tag project: gcp_project_id.to_s + tag nist: ['SC-28'] + + ref 'CIS Benchmark', url: cis_url.to_s + ref 'GCP Docs', url: 'https://cloud.google.com/functions/docs/configuring/env-var#managing_secrets' + ref 'GCP Docs', url: 'https://cloud.google.com/secret-manager/docs/overview' + describe 'This control is not scored' do + skip 'This control is not scored' + end +end diff --git a/controls/2.01-logging.rb b/controls/2.01-logging.rb index 453f344..3a337b9 100644 --- a/controls/2.01-logging.rb +++ b/controls/2.01-logging.rb @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -title 'Ensure that Cloud Audit Logging is configured properly across all services and all users from a project ' +title 'Ensure That Cloud Audit Logging Is Configured Properly' gcp_project_id = input('gcp_project_id') cis_version = input('cis_version') @@ -23,11 +23,9 @@ control "cis-gcp-#{control_id}-#{control_abbrev}" do impact 'low' - title "[#{control_abbrev.upcase}] Ensure that Cloud Audit Logging is configured properly across all services and a -ll users from a project " + title "[#{control_abbrev.upcase}] Ensure That Cloud Audit Logging Is Configured Properly" - desc "It is recommended that Cloud Audit Logging is configured to track all Admin activities and -read, write access to user data." + desc 'It is recommended that Cloud Audit Logging is configured to track all admin activities and read, write access to user data.' desc 'rationale', "Cloud Audit Logging maintains two audit logs for each project and organization: Admin Activity nd Data Access. diff --git a/controls/2.02-logging.rb b/controls/2.02-logging.rb index daa79ee..19699ad 100644 --- a/controls/2.02-logging.rb +++ b/controls/2.02-logging.rb @@ -25,8 +25,8 @@ title "[#{control_abbrev.upcase}] Ensure that sinks are configured for all Log entries" - desc 'It is recommended to create sink which will export copies of all the log entries.' - desc 'rationale', 'Log entries are held in Stackdriver Logging for a limited time known as the retention period. After that, the entries are deleted. To keep log entries longer, sink can export them outside of Stackdriver Logging. Exporting involves writing a filter that selects the log entries to export, and choosing a destination in Cloud Storage, BigQuery, or Cloud Pub/Sub. The filter and destination are held in an object called a sink. To ensure all log entries are exported using sink ensure that there is no filter configured for a sink. Sinks can be created in projects, organizations, folders, and billing accounts.' + desc 'It is recommended to create a sink that will export copies of all the log entries. This can help aggregate logs from multiple projects and export them to a Security Information and Event Management (SIEM).' + desc 'rationale', 'Log entries are held in Cloud Logging. To aggregate logs, export them to a SIEM. To keep them longer, it is recommended to set up a log sink. Exporting involves writing a filter that selects the log entries to export, and choosing a destination in Cloud Storage, BigQuery, or Cloud Pub/Sub. The filter and destination are held in an object called a sink. To ensure all log entries are exported to sinks, ensure that there is no filter configured for a sink. Sinks can be created in projects, organizations, folders, and billing accounts.' tag cis_scored: true tag cis_level: 1 @@ -41,6 +41,8 @@ ref 'GCP Docs', url: 'https://cloud.google.com/logging/docs/export/' ref 'GCP Docs', url: 'https://cloud.google.com/logging/docs/export/using_exported_logs' ref 'GCP Docs', url: 'https://cloud.google.com/logging/docs/export/configure_export_v2' + ref 'GCP Docs', url: 'https://cloud.google.com/logging/docs/export/aggregated_exports' + ref 'GCP Docs', url: 'https://cloud.google.com/sdk/gcloud/reference/beta/logging/sinks/list' empty_filter_sinks = [] google_logging_project_sinks(project: gcp_project_id).names.each do |sink_name| diff --git a/controls/2.03-logging.rb b/controls/2.03-logging.rb index fd2fa21..2d8bdcc 100644 --- a/controls/2.03-logging.rb +++ b/controls/2.03-logging.rb @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -title 'Ensure that retention policies on log buckets are configured using Bucket Lock' +title 'Ensure That Retention Policies on Cloud Storage Buckets Used for Exporting Logs Are Configured Using Bucket Lock' gcp_project_id = input('gcp_project_id') cis_version = input('cis_version') @@ -23,15 +23,15 @@ control "cis-gcp-#{control_id}-#{control_abbrev}" do impact 'low' - title "[#{control_abbrev.upcase}] Ensure that retention policies on log buckets are configured using Bucket Lock" + title "[#{control_abbrev.upcase}] Ensure That Retention Policies on Cloud Storage Buckets Used for Exporting Logs Are Configured Using Bucket Lock" - desc 'It is recommended to set up retention policies and configure Bucket Lock on all storage buckets that are used as log sinks.' - desc 'rationale', "Logs can be exported by creating one or more sinks that include a log filter and a destination. As Stackdriver Logging receives new log entries, they are compared against each sink. If a log entry matches a sink's filter, then a copy of the log entry is written to the destination. + desc 'Enabling retention policies on log buckets will protect logs stored in cloud storage buckets from being overwritten or accidentally deleted. It is recommended to set up retention policies and configure Bucket Lock on all storage buckets that are used as log sinks.' + desc 'rationale', "Logs can be exported by creating one or more sinks that include a log filter and a destination. As Cloud Logging receives new log entries, they are compared against each sink. If a log entry matches a sink's filter, then a copy of the log entry is written to the destination. -Sinks can be configured to export logs in storage buckets. It is recommended to configure a data retention policy for these cloud storage buckets and to lock the data retention policy; thus permanently preventing the policy from being reduced or removed. This way, if the system is ever compromised by an attacker or a malicious insider who wants to cover their tracks, the activity logs are definitely preserved for forensics and security investigations." + Sinks can be configured to export logs in storage buckets. It is recommended to configure a data retention policy for these cloud storage buckets and to lock the data retention policy; thus permanently preventing the policy from being reduced or removed. This way, if the system is ever compromised by an attacker or a malicious insider who wants to cover their tracks, the activity logs are definitely preserved for forensics and security investigations." tag cis_scored: true - tag cis_level: 1 + tag cis_level: 2 tag cis_gcp: control_id.to_s tag cis_version: cis_version.to_s tag project: gcp_project_id.to_s @@ -39,6 +39,7 @@ ref 'CIS Benchmark', url: cis_url.to_s ref 'GCP Docs', url: 'https://cloud.google.com/storage/docs/bucket-lock' + ref 'GCP Docs', url: 'https://cloud.google.com/storage/docs/using-bucket-lock' if google_logging_project_sinks(project: gcp_project_id).where(destination: /storage.googleapis.com/).destinations.empty? describe "[#{gcp_project_id}] does not have logging sinks configured." do diff --git a/controls/2.04-logging.rb b/controls/2.04-logging.rb index 07a1b74..f0fa9d9 100644 --- a/controls/2.04-logging.rb +++ b/controls/2.04-logging.rb @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -title 'Ensure log metric filter and alerts exists for Project Ownership assignments/changes' +title 'Ensure Log Metric Filter and Alerts Exist for Project Ownership Assignments/Changes' gcp_project_id = input('gcp_project_id') cis_version = input('cis_version') @@ -23,22 +23,22 @@ control "cis-gcp-#{control_id}-#{control_abbrev}" do impact 'low' - title "[#{control_abbrev.upcase}] Ensure log metric filter and alerts exists for Project Ownership assignments/changes" + title "[#{control_abbrev.upcase}] Ensure Log Metric Filter and Alerts Exist for Project Ownership Assignments/Changes" - desc "In order to prevent unnecessarily project ownership assignments to users/serviceaccounts and further misuses of project and resources, all roles/Owner assignments should be monitored. + desc "In order to prevent unnecessary project ownership assignments to users/service-accounts and further misuses of projects and resources, all roles/Owner assignments should be monitored. -Members (users/Service-Accounts) with role assignment to primitive role roles/owner are Project Owners. + Members (users/Service-Accounts) with role assignment to primitive role roles/owner are Project Owners. -Project Owner has all the privileges on a project it belongs to. These can be summarized as below: + Project Owner has all the privileges on a project it belongs to. These can be summarized as below: -- All viewer permissions on All GCP Services part within the project -- Permissions for actions that modify state of All GCP Services within the -project -- Manage roles and permissions for a project and all resources within the -project -- Set up billing for a project + - All viewer permissions on All GCP Services part within the project + - Permissions for actions that modify state of All GCP Services within the + project + - Manage roles and permissions for a project and all resources within the + project + - Set up billing for a project -Granting owner role to a member (user/Service-Account) will allow members to modify the IAM policy. Therefore grant the owner role only if the member has a legitimate purpose to manage the IAM policy. This is because as project IAM policy contains sensitive access control data and having a minimal set of users manage it will simplify any auditing that you may have to do." + Granting owner role to a member (user/Service-Account) will allow members to modify the IAM policy. Therefore grant the owner role only if the member has a legitimate purpose to manage the IAM policy. This is because as project IAM policy contains sensitive access control data and having a minimal set of users manage it will simplify any auditing that you may have to do." desc 'rationale', "Project Ownership Having highest level of privileges on a project, to avoid misuse of project resources project ownership assignment/change actions mentioned should be monitored and alerted to concerned recipients. - Sending project ownership Invites diff --git a/controls/2.05-logging.rb b/controls/2.05-logging.rb index a05b512..17263e6 100644 --- a/controls/2.05-logging.rb +++ b/controls/2.05-logging.rb @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -title 'Ensure log metric filter and alerts exists for Audit Configuration Changes' +title 'Ensure That the Log Metric Filter and Alerts Exist for Audit Configuration Changes' gcp_project_id = input('gcp_project_id') cis_version = input('cis_version') @@ -23,10 +23,14 @@ control "cis-gcp-#{control_id}-#{control_abbrev}" do impact 'low' - title "[#{control_abbrev.upcase}] Ensure log metric filter and alerts exists for Audit Configuration Changes" + title "[#{control_abbrev.upcase}] Ensure That the Log Metric Filter and Alerts Exist for Audit Configuration Changes" - desc "Google Cloud Platform services write audit log entries to Admin Activity and Data Access logs to helps answer the questions of 'who did what, where, and when?' within Google Cloud Platform projects. Cloud Audit logging records information includes the identity of the API caller, the time of the API call, the source IP address of the API caller, the request parameters, and the response elements returned by the GCP services. Cloud Audit logging provides a history of AWS API calls for an account, including API calls made via the Console, SDKs, command line tools, and other GCP services." - desc 'rationale', 'Admin activity and Data access logs produced by Cloud audit logging enables security analysis, resource change tracking, and compliance auditing. Configuring metric filter and alerts for Audit Configuration Changes ensures recommended state of audit configuration and hence, all the activities in project are audit-able at any point in time.' + desc "Google Cloud Platform (GCP) services write audit log entries to the Admin Activity and Data Access logs to help answer the questions of, 'who did what, where, and when?' within GCP projects. + + Cloud audit logging records information includes the identity of the API caller, the time of the API call, the source IP address of the API caller, the request parameters, and the response elements returned by GCP services. Cloud audit logging provides a history of GCP API calls for an account, including API calls made via the console, SDKs, command-line tools, and other GCP services." + desc 'rationale', 'Admin activity and data access logs produced by cloud audit logging enable security analysis, resource change tracking, and compliance auditing. + + Configuring the metric filter and alerts for audit configuration changes ensures the recommended state of audit configuration is maintained so that all activities in the project are audit-able at any point in time.' tag cis_scored: true tag cis_level: 1 diff --git a/controls/2.06-logging.rb b/controls/2.06-logging.rb index 21786cf..b0874dd 100644 --- a/controls/2.06-logging.rb +++ b/controls/2.06-logging.rb @@ -25,8 +25,8 @@ title "[#{control_abbrev.upcase}] Ensure log metric filter and alerts exists for Custom Role changes" - desc 'It is recommended that a metric filter and alarm be established for changes IAM Role creation, deletion and updating activities.' - desc 'rationale', 'Google Cloud Identity and Access Management (Cloud IAM) provides predefined roles that give granular access to specific Google Cloud Platform resources and prevent unwanted access to other resources. However to cater organization specific needs, Cloud IAM also provides ability to create custom roles. Project Owner and administrators with Organization Role Administrator role or the IAM Role Administrator role can create custom roles. Monitoring role creation, deletion and updating activities will help in identifying over-privileged role at early stages.' + desc 'It is recommended that a metric filter and alarm be established for changes to Identity and Access Management (IAM) role creation, deletion and updating activities.' + desc 'rationale', 'Google Cloud IAM provides predefined roles that give granular access to specific Google Cloud Platform resources and prevent unwanted access to other resources. However, to cater to organization-specific needs, Cloud IAM also provides the ability to create custom roles. Project owners and administrators with the Organization Role Administrator role or the IAM Role Administrator role can create custom roles. Monitoring role creation, deletion and updating activities will help in identifying any over-privileged role at early stages.' tag cis_scored: true tag cis_level: 1 diff --git a/controls/2.07-logging.rb b/controls/2.07-logging.rb index d64764b..0cad90a 100644 --- a/controls/2.07-logging.rb +++ b/controls/2.07-logging.rb @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -title 'Ensure log metric filter and alerts exists for VPC Network Firewall rule changes' +title 'Ensure That the Log Metric Filter and Alerts Exist for VPC Network Firewall Rule Changes' gcp_project_id = input('gcp_project_id') cis_version = input('cis_version') @@ -23,13 +23,13 @@ control "cis-gcp-#{control_id}-#{control_abbrev}" do impact 'low' - title "[#{control_abbrev.upcase}] Ensure log metric filter and alerts exists for VPC Network Firewall rule changes" + title "[#{control_abbrev.upcase}] Ensure That the Log Metric Filter and Alerts Exist for VPC Network Firewall Rule Changes" - desc 'It is recommended that a metric filter and alarm be established for VPC Network Firewall rule changes.' - desc 'rationale', 'Monitoring for Create or Update firewall rule events gives insight network access changes and may reduce the time it takes to detect suspicious activity.' + desc 'It is recommended that a metric filter and alarm be established for Virtual Private Cloud (VPC) Network Firewall rule changes.' + desc 'rationale', 'Monitoring for Create or Update Firewall rule events gives insight to network access changes and may reduce the time it takes to detect suspicious activity.' tag cis_scored: true - tag cis_level: 1 + tag cis_level: 2 tag cis_gcp: control_id.to_s tag cis_version: cis_version.to_s tag project: gcp_project_id.to_s diff --git a/controls/2.08-logging.rb b/controls/2.08-logging.rb index 2338b67..c191cc0 100644 --- a/controls/2.08-logging.rb +++ b/controls/2.08-logging.rb @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -title 'Ensure log metric filter and alerts exists for VPC network route changes' +title 'Ensure That the Log Metric Filter and Alerts Exist for VPC Network Route Changes' gcp_project_id = input('gcp_project_id') cis_version = input('cis_version') @@ -23,15 +23,15 @@ control "cis-gcp-#{control_id}-#{control_abbrev}" do impact 'low' - title "[#{control_abbrev.upcase}] Ensure log metric filter and alerts exists for VPC network route changes " + title "[#{control_abbrev.upcase}] Ensure That the Log Metric Filter and Alerts Exist for VPC Network Route Changes" - desc 'It is recommended that a metric filter and alarm be established for VPC network route changes.' - desc 'rationale', "Google Cloud Platform (GCP) routes define the paths network traffic takes from a VM instance to another destinations. The other destination can be inside your VPC network (such as another VM) or outside of it. Every route consists of a destination and a next hop. Traffic whose destination IP is within the destination range is sent to the next hop for delivery. + desc 'It is recommended that a metric filter and alarm be established for Virtual Private Cloud (VPC) network route changes.' + desc 'rationale', "Google Cloud Platform (GCP) routes define the paths network traffic takes from a VM instance to another destination. The other destination can be inside the organization VPC network (such as another VM) or outside of it. Every route consists of a destination and a next hop. Traffic whose destination IP is within the destination range is sent to the next hop for delivery. -Monitoring changes to route tables will help ensure that all VPC traffic flows through an expected path." + Monitoring changes to route tables will help ensure that all VPC traffic flows through an expected path." tag cis_scored: true - tag cis_level: 1 + tag cis_level: 2 tag cis_gcp: control_id.to_s tag cis_version: cis_version.to_s tag project: gcp_project_id.to_s @@ -43,6 +43,8 @@ ref 'GCP Docs', url: 'https://cloud.google.com/monitoring/alerts/' ref 'GCP Docs', url: 'https://cloud.google.com/logging/docs/reference/tools/gcloud-logging' ref 'GCP Docs', url: 'https://cloud.google.com/storage/docs/access-control/iam' + ref 'GCP Docs', url: 'https://cloud.google.com/sdk/gcloud/reference/beta/logging/metrics/create' + ref 'GCP Docs', url: 'https://cloud.google.com/sdk/gcloud/reference/alpha/monitoring/policies/create' log_filter = 'resource.type=global AND jsonPayload.event_subtype="compute.routes.delete" OR jsonPayload.event_subtype="compute.routes.insert"' describe "[#{gcp_project_id}] VPC Route changes filter" do diff --git a/controls/2.09-logging.rb b/controls/2.09-logging.rb index 722ae1e..1934344 100644 --- a/controls/2.09-logging.rb +++ b/controls/2.09-logging.rb @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -title 'Ensure log metric filter and alerts exists for VPC network changes' +title 'Ensure That the Log Metric Filter and Alerts Exist for VPC Network Changes' gcp_project_id = input('gcp_project_id') cis_version = input('cis_version') @@ -23,15 +23,15 @@ control "cis-gcp-#{control_id}-#{control_abbrev}" do impact 'low' - title "[#{control_abbrev.upcase}] Ensure log metric filter and alerts exists for VPC network changes " + title "[#{control_abbrev.upcase}] Ensure That the Log Metric Filter and Alerts Exist for VPC Network Changes" - desc 'It is recommended that a metric filter and alarm be established for VPC network changes.' - desc 'rationale', "It is possible to have more than 1 VPC within an project, in addition it is also possible to create a peer connection between 2 VPCs enabling network traffic to route between VPCs. + desc 'It is recommended that a metric filter and alarm be established for Virtual Private Cloud (VPC) network changes.' + desc 'rationale', "It is possible to have more than one VPC within a project. In addition, it is also possible to create a peer connection between two VPCs enabling network traffic to route between VPCs. -Monitoring changes to VPC will help ensure VPC traffic flow is not getting impacted." + Monitoring changes to a VPC will help ensure VPC traffic flow is not getting impacted." tag cis_scored: true - tag cis_level: 1 + tag cis_level: 2 tag cis_gcp: control_id.to_s tag cis_version: cis_version.to_s tag project: gcp_project_id.to_s diff --git a/controls/2.10-logging.rb b/controls/2.10-logging.rb index bc185a8..e48f11a 100644 --- a/controls/2.10-logging.rb +++ b/controls/2.10-logging.rb @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -title 'Ensure log metric filter and alerts exists for Cloud Storage IAM permission changes' +title 'Ensure That the Log Metric Filter and Alerts Exist for Cloud Storage IAM Permission Changes' gcp_project_id = input('gcp_project_id') cis_version = input('cis_version') @@ -23,13 +23,13 @@ control "cis-gcp-#{control_id}-#{control_abbrev}" do impact 'low' - title "[#{control_abbrev.upcase}] Ensure log metric filter and alerts exists for Cloud Storage IAM permission changes" + title "[#{control_abbrev.upcase}] Ensure That the Log Metric Filter and Alerts Exist for Cloud Storage IAM Permission Changes" desc 'It is recommended that a metric filter and alarm be established for Cloud Storage Bucket IAM changes.' - desc 'rationale', 'Monitoring changes to Cloud Storage bucket permissions may reduce time to detect and correct permissions on sensitive Cloud Storage bucket and objects inside the bucket.' + desc 'rationale', 'Monitoring changes to cloud storage bucket permissions may reduce the time needed to detect and correct permissions on sensitive cloud storage buckets and objects inside the bucket.' tag cis_scored: true - tag cis_level: 1 + tag cis_level: 2 tag cis_gcp: control_id.to_s tag cis_version: cis_version.to_s tag project: gcp_project_id.to_s diff --git a/controls/2.11-logging.rb b/controls/2.11-logging.rb index 9704bf6..6381b55 100644 --- a/controls/2.11-logging.rb +++ b/controls/2.11-logging.rb @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -title ' Ensure log metric filter and alerts exists for SQL instance configuration changes' +title 'Ensure That the Log Metric Filter and Alerts Exist for SQL Instance Configuration Changes' gcp_project_id = input('gcp_project_id') cis_version = input('cis_version') @@ -25,16 +25,14 @@ title "[#{control_abbrev.upcase}] Ensure log metric filter and alerts exists for SQL instance configuration changes" - desc 'It is recommended that a metric filter and alarm be established for SQL Instance configuration changes.' - desc 'rationale', "Monitoring changes to Sql Instance configuration changes may reduce time to detect and correct misconfigurations done on sql server. - -Below are the few of configurable Options which may impact security posture of a SQL Instance: - -- Enable auto backups and high availability: Misconfiguration may adversely impact Business continuity, Disaster Recovery and High Availability -- Authorize networks : Misconfiguration may increase exposure to the untrusted networks" + desc 'It is recommended that a metric filter and alarm be established for SQL instance configuration changes.' + desc 'rationale', "Monitoring changes to SQL instance configuration changes may reduce the time needed to detect and correct misconfigurations done on the SQL server. + Below are a few of the configurable options which may the impact security posture of an SQL instance: + - Enable auto backups and high availability: Misconfiguration may adversely impact business continuity, disaster recovery, and high availability + - Authorize networks: Misconfiguration may increase exposure to untrusted networks" tag cis_scored: true - tag cis_level: 1 + tag cis_level: 2 tag cis_gcp: control_id.to_s tag cis_version: cis_version.to_s tag project: gcp_project_id.to_s diff --git a/controls/2.12-logging.rb b/controls/2.12-logging.rb new file mode 100644 index 0000000..7f98ec2 --- /dev/null +++ b/controls/2.12-logging.rb @@ -0,0 +1,46 @@ +# Copyright 2019 The inspec-gcp-cis-benchmark Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title 'Ensure That Cloud DNS Logging Is Enabled for All VPC Networks' + +gcp_project_id = input('gcp_project_id') +cis_version = input('cis_version') +cis_url = input('cis_url') +control_id = '2.12' +control_abbrev = 'logging' + +control "cis-gcp-#{control_id}-#{control_abbrev}" do + impact 'low' + + title "[#{control_abbrev.upcase}] Ensure That Cloud DNS Logging Is Enabled for All VPC Networks" + + desc 'Cloud DNS logging records the queries from the name servers within your VPC to Stackdriver. Logged queries can come from Compute Engine VMs, GKE containers, or other GCP resources provisioned within the VPC.' + desc 'rationale', "Security monitoring and forensics cannot depend solely on IP addresses from VPC flow logs, especially when considering the dynamic IP usage of cloud resources, HTTP virtual host routing, and other technology that can obscure the DNS name used by a client from the IP address. Monitoring of Cloud DNS logs provides visibility to DNS names requested by the clients within the VPC. These logs can be monitored for anomalous domain names, evaluated against threat intelligence, and + + Note: For full capture of DNS, firewall must block egress UDP/53 (DNS) and TCP/443 (DNS over HTTPS) to prevent client from using external DNS name server for resolution." + + tag cis_scored: false + tag cis_level: 1 + tag cis_gcp: control_id.to_s + tag cis_version: cis_version.to_s + tag project: gcp_project_id.to_s + tag nist: %w[] # Add relevant NIST controls if any in future + + ref 'CIS Benchmark', url: cis_url.to_s + ref 'GCP Docs', url: 'https://cloud.google.com/dns/docs/monitoring' + + describe 'This control is not scored' do + skip 'This control is not scored' + end +end diff --git a/controls/2.13-logging.rb b/controls/2.13-logging.rb new file mode 100644 index 0000000..b0afbe4 --- /dev/null +++ b/controls/2.13-logging.rb @@ -0,0 +1,47 @@ +# Copyright 2019 The inspec-gcp-cis-benchmark Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title 'Ensure Cloud Asset Inventory Is Enabled' + +gcp_project_id = input('gcp_project_id') +cis_version = input('cis_version') +cis_url = input('cis_url') +control_id = '2.13' +control_abbrev = 'logging' # As per existing structure, though control is about Asset Inventory + +control "cis-gcp-#{control_id}-#{control_abbrev}" do + impact 'low' + + title "[#{control_abbrev.upcase}] Ensure Cloud Asset Inventory Is Enabled" + + desc "GCP Cloud Asset Inventory is services that provides a historical view of GCP resources and IAM policies through a time-series database. The information recorded includes metadata on Google Cloud resources, metadata on policies set on Google Cloud projects or resources, and runtime information gathered within a Google Cloud resource. + + Cloud Asset Inventory Service (CAIS) API enablement is not required for operation of the service, but rather enables the mechanism for searching/exporting CAIS asset data directly." + desc 'rationale', "The GCP resources and IAM policies captured by GCP Cloud Asset Inventory enables security analysis, resource change tracking, and compliance auditing. + + It is recommended GCP Cloud Asset Inventory be enabled for all GCP projects." + + tag cis_scored: false + tag cis_level: 1 + tag cis_gcp: control_id.to_s + tag cis_version: cis_version.to_s + tag project: gcp_project_id.to_s + tag nist: %w[] # Add relevant NIST controls if any in future + + ref 'CIS Benchmark', url: cis_url.to_s + ref 'GCP Docs', url: 'https://cloud.google.com/asset-inventory/docs' + describe 'This control is not scored' do + skip 'This control is not scored' + end +end diff --git a/controls/2.14-logging.rb b/controls/2.14-logging.rb new file mode 100644 index 0000000..31d5721 --- /dev/null +++ b/controls/2.14-logging.rb @@ -0,0 +1,49 @@ +# Copyright 2019 The inspec-gcp-cis-benchmark Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title "Ensure 'Access Transparency' is 'Enabled'" + +gcp_project_id = input('gcp_project_id') +cis_version = input('cis_version') +cis_url = input('cis_url') +# org_id = input('org_id') # Potentially for future use if InSpec resource becomes available +control_id = '2.14' +control_abbrev = 'logging' + +control "cis-gcp-#{control_id}-#{control_abbrev}" do + impact 'medium' + + title "[#{control_abbrev.upcase}] Ensure 'Access Transparency' is 'Enabled'" + + desc 'GCP Access Transparency provides audit logs for all actions that Google personnel take in your Google Cloud resources.' + desc 'rationale', "Controlling access to your information is one of the foundations of information security. Given that Google Employees do have access to your organizations' projects for support reasons, you should have logging in place to view who, when, and why your information is being accessed. To use Access Transparency your organization will need to have at one of the following support level: Premium, Enterprise, Platinum, or Gold. There will be subscription costs associated with support, as well as increased storage costs for storing the logs." + + tag cis_scored: false + tag cis_level: 2 + tag cis_gcp: control_id.to_s + tag cis_version: cis_version.to_s + tag project: gcp_project_id.to_s + tag nist: %w[] # Add relevant NIST controls if any in future + + ref 'CIS Benchmark', url: cis_url.to_s + ref 'GCP Docs', url: 'https://cloud.google.com/cloud-provider-access-management/access-transparency/docs/overview' + ref 'GCP Docs', url: 'https://cloud.google.com/cloud-provider-access-management/access-transparency/docs/enable' + ref 'GCP Docs', url: 'https://cloud.google.com/cloud-provider-access-management/access-transparency/docs/reading-logs' + ref 'GCP Docs', url: 'https://cloud.google.com/cloud-provider-access-management/access-transparency/docs/reading-logs#justification_reason_codes' + ref 'GCP Docs', url: 'https://cloud.google.com/cloud-provider-access-management/access-transparency/docs/supported-services' + + describe 'This control is not scored' do + skip 'This control is not scored' + end +end diff --git a/controls/2.15-logging.rb b/controls/2.15-logging.rb new file mode 100644 index 0000000..e318a54 --- /dev/null +++ b/controls/2.15-logging.rb @@ -0,0 +1,48 @@ +# Copyright 2019 The inspec-gcp-cis-benchmark Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title "Ensure 'Access Approval' is 'Enabled'" + +gcp_project_id = input('gcp_project_id') +cis_version = input('cis_version') +cis_url = input('cis_url') +control_id = '2.15' +control_abbrev = 'logging' # Retaining 'logging' as per existing convention for this section + +control "cis-gcp-#{control_id}-#{control_abbrev}" do + impact 'medium' + + title "[#{control_abbrev.upcase}] Ensure 'Access Approval' is 'Enabled'" + + desc "GCP Access Approval enables you to require your organizations' explicit approval whenever Google support try to access your projects. You can then select users within your organization who can approve these requests through giving them a security role in IAM. All access requests display which Google Employee requested them in an email or Pub/Sub message that you can choose to Approve. This adds an additional control and logging of who in your organization approved/denied these requests." + desc 'rationale', "Controlling access to your information is one of the foundations of information security. Google Employees do have access to your organizations' projects for support reasons. With Access Approval, organizations can then be certain that their information is accessed by only approved Google Personnel. Note: To use Access Approval your organization will need have enabled Access Transparency and have at one of the following support level: Enhanced or Premium. There will be subscription costs associated with these support levels, as well as increased storage costs for storing the logs. There may also be a potential delay in support times if approval is not granted promptly." + + tag cis_scored: false + tag cis_level: 2 + tag cis_gcp: control_id.to_s + tag cis_version: cis_version.to_s + tag project: gcp_project_id.to_s + tag nist: %w[] # Add relevant NIST controls if any in future + + ref 'CIS Benchmark', url: cis_url.to_s + ref 'GCP Docs', url: 'https://cloud.google.com/cloud-provider-access-management/access-approval/docs' + ref 'GCP Docs', url: 'https://cloud.google.com/cloud-provider-access-management/access-approval/docs/overview' + ref 'GCP Docs', url: 'https://cloud.google.com/cloud-provider-access-management/access-approval/docs/quickstart-custom-key' + ref 'GCP Docs', url: 'https://cloud.google.com/cloud-provider-access-management/access-approval/docs/supported-services' + ref 'GCP Docs', url: 'https://cloud.google.com/cloud-provider-access-management/access-approval/docs/view-historical-requests' + + describe 'This control is not scored' do + skip 'This control is not scored' + end +end diff --git a/controls/2.16-logging.rb b/controls/2.16-logging.rb new file mode 100644 index 0000000..1f32f14 --- /dev/null +++ b/controls/2.16-logging.rb @@ -0,0 +1,45 @@ +# Copyright 2019 The inspec-gcp-cis-benchmark Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title 'Ensure Logging is enabled for HTTP(S) Load Balancer' + +gcp_project_id = input('gcp_project_id') +cis_version = input('cis_version') +cis_url = input('cis_url') +control_id = '2.16' +control_abbrev = 'logging' + +control "cis-gcp-#{control_id}-#{control_abbrev}" do + impact 'medium' + + title "[#{control_abbrev.upcase}] Ensure Logging is enabled for HTTP(S) Load Balancer" + + desc 'Logging enabled on a HTTPS Load Balancer will show all network traffic and its destination.' + desc 'rationale', 'Logging will allow you to view HTTPS network traffic to your web applications. On high use systems with a high percentage sample rate, the logging file may grow to high capacity in a short amount of time. Ensure that the sample rate is set appropriately so that storage costs are not exorbitant.' + + tag cis_scored: false + tag cis_level: 2 + tag cis_gcp: control_id.to_s + tag cis_version: cis_version.to_s + tag project: gcp_project_id.to_s + tag nist: %w[] # Add relevant NIST controls if any in future + + ref 'CIS Benchmark', url: cis_url.to_s + ref 'GCP Docs', url: 'https://cloud.google.com/load-balancing/' + ref 'GCP Docs', url: 'https://cloud.google.com/load-balancing/docs/https/https-logging-monitoring#gcloud:-global-mode' + ref 'GCP Docs', url: 'https://cloud.google.com/sdk/gcloud/reference/compute/backend-services/' + describe 'This control is not scored' do + skip 'This control is not scored' + end +end diff --git a/controls/3.01-networking.rb b/controls/3.01-networking.rb index 505bc82..d2e7ec8 100644 --- a/controls/3.01-networking.rb +++ b/controls/3.01-networking.rb @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -title 'Ensure the default network does not exist in a project' +title 'Ensure That the Default Network Does Not Exist in a Project' gcp_project_id = input('gcp_project_id') cis_version = input('cis_version') @@ -23,10 +23,19 @@ control "cis-gcp-#{control_id}-#{control_abbrev}" do impact 'medium' - title "[#{control_abbrev.upcase}] Ensure the default network does not exist in a project" + title "[#{control_abbrev.upcase}] Ensure That the Default Network Does Not Exist in a Project" desc 'To prevent use of default network, a project should not have a default network.' - desc 'rationale', 'The default network has automatically created firewall rules and has pre-fabricated network configuration. Based on your security and networking requirements, you should create your network and delete the default network.' + desc 'rationale', "The default network has a preconfigured network configuration and automatically generates the following insecure firewall rules: + - default-allow-internal: Allows ingress connections for all protocols and ports among instances in the network. + - default-allow-ssh: Allows ingress connections on TCP port 22(SSH) from any source to any instance in the network. + - default-allow-rdp: Allows ingress connections on TCP port 3389(RDP) from any source to any instance in the network. + - default-allow-icmp: Allows ingress ICMP traffic from any source to any instance in the network. + These automatically created firewall rules do not get audit logged by default. + + Furthermore, the default network is an auto mode network, which means that its subnets use the same predefined range of IP addresses, and as a result, it's not possible to use Cloud VPN or VPC Network Peering with the default network. + + Based on organization security and networking requirements, the organization should create a new network and delete the default network." tag cis_scored: true tag cis_level: 2 @@ -39,7 +48,9 @@ ref 'GCP Docs', url: 'https://cloud.google.com/compute/docs/networking#firewall_rules' ref 'GCP Docs', url: 'https://cloud.google.com/compute/docs/reference/latest/networks/insert' ref 'GCP Docs', url: 'https://cloud.google.com/compute/docs/reference/latest/networks/delete' - + ref 'GCP Docs', url: 'https://cloud.google.com/vpc/docs/firewall-rules-logging' + ref 'GCP Docs', url: 'https://cloud.google.com/vpc/docs/vpc#default-network' + ref 'GCP Docs', url: 'https://cloud.google.com/sdk/gcloud/reference/compute/networks/delete' describe "[#{gcp_project_id}] Subnets" do subject { google_compute_networks(project: gcp_project_id) } its('network_names') { should_not include 'default' } diff --git a/controls/3.02-networking.rb b/controls/3.02-networking.rb index ae6f9b1..52e8a77 100644 --- a/controls/3.02-networking.rb +++ b/controls/3.02-networking.rb @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -title 'Ensure legacy networks do not exists for a project' +title 'Ensure Legacy Networks Do Not Exist for Older Projects' gcp_project_id = input('gcp_project_id') cis_version = input('cis_version') @@ -23,10 +23,10 @@ control "cis-gcp-#{control_id}-#{control_abbrev}" do impact 'medium' - title "[#{control_abbrev.upcase}] Ensure legacy networks does not exists for a project" + title "[#{control_abbrev.upcase}] Ensure Legacy Networks Do Not Exist for Older Projects" - desc 'In order to prevent use of legacy networks, a project should not have a legacy network configured.' - desc 'rationale', 'Legacy networks have a single network IPv4 prefix range and a single gateway IP address for the whole network. The network is global in scope and spans all cloud regions. You cannot create subnetworks in a legacy network or switch from legacy to auto or custom subnet networks. Legacy networks can thus have an impact for high network traffic projects and subject to the single point of contention or failure.' + desc 'In order to prevent use of legacy networks, a project should not have a legacy network configured. As of now, Legacy Networks are gradually being phased out, and you can no longer create projects with them. This recommendation is to check older projects to ensure that they are not using Legacy Networks.' + desc 'rationale', 'Legacy networks have a single network IPv4 prefix range and a single gateway IP address for the whole network. The network is global in scope and spans all cloud regions. Subnetworks cannot be created in a legacy network and are unable to switch from legacy to auto or custom subnet networks. Legacy networks can have an impact for high network traffic projects and are subject to a single point of contention or failure.' tag cis_scored: true tag cis_level: 1 @@ -37,7 +37,7 @@ ref 'CIS Benchmark', url: cis_url.to_s ref 'GCP Docs', url: 'https://cloud.google.com/compute/docs/networking#creating_a_legacy_network' - ref 'GCP Docs', url: 'https://cloud.google.com/compute/docs/networking#legacy_non-subnet_network' + ref 'GCP Docs', url: 'https://cloud.google.com/vpc/docs/using-legacy#deleting_a_legacy_network' network_names = google_compute_networks(project: gcp_project_id).network_names diff --git a/controls/3.03-networking.rb b/controls/3.03-networking.rb index e71940f..7879fe3 100644 --- a/controls/3.03-networking.rb +++ b/controls/3.03-networking.rb @@ -25,8 +25,8 @@ title "[#{control_abbrev.upcase}] Ensure that DNSSEC is enabled for Cloud DNS" - desc 'Cloud DNS is a fast, reliable and cost-effective Domain Name System that powers millions of domains on the internet. DNSSEC in Cloud DNS enables domain owners to take easy steps to protect their domains against DNS hijacking and man-in-the-middle and other attacks.' - desc 'rationale', 'Domain Name System Security Extensions (DNSSEC) adds security to the Domain Name System (DNS) protocol by enabling DNS responses to be validated. Having a trustworthy Domain Name System (DNS) that translates a domain name like www.example.com into its associated IP address is an increasingly important building block of today’s web-based applications. Attackers can hijack this process of domain/IP lookup and redirect users to a malicious site through DNS hijacking and man-in-the-middle attacks. DNSSEC helps mitigate the risk of such attacks by cryptographically signing DNS records. As a result, it prevents attackers from issuing fake DNS responses that may misdirect browsers to nefarious websites.' + desc 'Cloud Domain Name System (DNS) is a fast, reliable and cost-effective domain name system that powers millions of domains on the internet. Domain Name System Security Extensions (DNSSEC) in Cloud DNS enables domain owners to take easy steps to protect their domains against DNS hijacking and man-in-the-middle and other attacks.' + desc 'rationale', 'Domain Name System Security Extensions (DNSSEC) adds security to the DNS protocol by enabling DNS responses to be validated. Having a trustworthy DNS that translates a domain name like www.example.com into its associated IP address is an increasingly important building block of today’s web-based applications. Attackers can hijack this process of domain/IP lookup and redirect users to a malicious site through DNS hijacking and man-in-the-middle attacks. DNSSEC helps mitigate the risk of such attacks by cryptographically signing DNS records. As a result, it prevents attackers from issuing fake DNS responses that may misdirect browsers to nefarious websites.' tag cis_scored: true tag cis_level: 1 diff --git a/controls/3.04-networking.rb b/controls/3.04-networking.rb index 49d08dd..0014bf8 100644 --- a/controls/3.04-networking.rb +++ b/controls/3.04-networking.rb @@ -25,10 +25,12 @@ title "[#{control_abbrev.upcase}] Ensure that RSASHA1 is not used for key-signing key in Cloud DNS DNSSEC" - desc 'DNSSEC algorithm numbers in this registry may be used in CERT RRs. Zone signing (DNSSEC) and transaction security mechanisms (SIG(0) and TSIG) make use of particular subsets of these algorithms. The algorithm used for key signing should be recommended one and it should not be weak.' - desc 'rationale', 'DNSSEC algorithm numbers in this registry may be used in CERT RRs. Zonesigning (DNSSEC) and transaction security mechanisms (SIG(0) and TSIG) make use of particular subsets of these algorithms. The algorithm used for key signing should be recommended one and it should not be weak. + desc 'NOTE: Currently, the SHA1 algorithm has been removed from general use by Google, and, if being used, needs to be whitelisted on a project basis by Google and will also, therefore, require a Google Cloud support contract. -When enabling DNSSEC for a managed zone, or creating a managed zone with DNSSEC, you can select the DNSSEC signing algorithms and the denial-of-existence type. Changing the DNSSEC settings is only effective for a managed zone if DNSSEC is not already enabled. If you need to change the settings for a managed zone where it has been enabled, you can turn DNSSEC off and then re-enable it with different settings.' + DNSSEC algorithm numbers in this registry may be used in CERT RRs. Zone signing (DNSSEC) and transaction security mechanisms (SIG(0) and TSIG) make use of particular subsets of these algorithms. The algorithm used for key signing should be a recommended one and it should be strong.' + desc 'rationale', 'Domain Name System Security Extensions (DNSSEC) algorithm numbers in this registry may be used in CERT RRs. Zonesigning (DNSSEC) and transaction security mechanisms (SIG(0) and TSIG) make use of particular subsets of these algorithms. + + The algorithm used for key signing should be a recommended one and it should be strong. When enabling DNSSEC for a managed zone, or creating a managed zone with DNSSEC, the user can select the DNSSEC signing algorithms and the denial-of-existence type. Changing the DNSSEC settings is only effective for a managed zone if DNSSEC is not already enabled. If there is a need to change the settings for a managed zone where it has been enabled, turn DNSSEC off and then re-enable it with different settings.' tag cis_scored: false tag cis_level: 1 diff --git a/controls/3.05-networking.rb b/controls/3.05-networking.rb index 257928d..07f9e01 100644 --- a/controls/3.05-networking.rb +++ b/controls/3.05-networking.rb @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -title 'Ensure that RSASHA1 is not used for zone-signing key in Cloud DNS DNSSEC' +title 'Ensure That RSASHA1 Is Not Used for the Zone-Signing Key in Cloud DNS DNSSEC' gcp_project_id = input('gcp_project_id') cis_version = input('cis_version') @@ -23,12 +23,14 @@ control "cis-gcp-#{control_id}-#{control_abbrev}" do impact 'none' - title "[#{control_abbrev.upcase}] Ensure that RSASHA1 is not used for zone-signing key in Cloud DNS DNSSEC" + title "[#{control_abbrev.upcase}] Ensure That RSASHA1 Is Not Used for the Zone-Signing Key in Cloud DNS DNSSEC" - desc 'DNSSEC algorithm numbers in this registry may be used in CERT RRs. Zone signing (DNSSEC) and transaction security mechanisms (SIG(0) and TSIG) make use of particular subsets of these algorithms. The algorithm used for key signing should be recommended one and it should not be weak.' - desc 'rationale', "DNSSEC algorithm numbers in this registry may be used in CERT RRs. Zonesigning (DNSSEC) and transaction security mechanisms (SIG(0) and TSIG) make use of particular subsets of these algorithms. + desc 'NOTE: Currently, the SHA1 algorithm has been removed from general use by Google, and, if being used, needs to be whitelisted on a project basis by Google and will also, therefore, require a Google Cloud support contract. -The algorithm used for key signing should be recommended one and it should not be weak. When enabling DNSSEC for a managed zone, or creating a managed zone with DNSSEC, you can select the DNSSEC signing algorithms and the denial-of-existence type. Changing the DNSSEC settings is only effective for a managed zone if DNSSEC is not already enabled. If you need to change the settings for a managed zone where it has been enabled, you can turn DNSSEC off and then re-enable it with different settings." + DNSSEC algorithm numbers in this registry may be used in CERT RRs. Zone signing (DNSSEC) and transaction security mechanisms (SIG(0) and TSIG) make use of particular subsets of these algorithms. The algorithm used for key signing should be a recommended one and it should be strong.' + desc 'rationale', "DNSSEC algorithm numbers in this registry may be used in CERT RRs. Zone signing (DNSSEC) and transaction security mechanisms (SIG(0) and TSIG) make use of particular subsets of these algorithms. + + The algorithm used for key signing should be a recommended one and it should be strong. When enabling DNSSEC for a managed zone, or creating a managed zone with DNSSEC, the DNSSEC signing algorithms and the denial-of-existence type can be selected. Changing the DNSSEC settings is only effective for a managed zone if DNSSEC is not already enabled. If the need exists to change the settings for a managed zone where it has been enabled, turn DNSSEC off and then re-enable it with different settings." tag cis_scored: false tag cis_level: 1 diff --git a/controls/3.06-networking.rb b/controls/3.06-networking.rb index deace35..f9ddcf3 100644 --- a/controls/3.06-networking.rb +++ b/controls/3.06-networking.rb @@ -25,8 +25,10 @@ title "[#{control_abbrev.upcase}] Ensure that SSH access is restricted from the internet" - desc 'GCP Firewall Rules are specific to a VPC Network. Each rule either allows or denies traffic when its conditions are met. Its conditions allow you to specify the type of traffic, such as ports and protocols, and the source or destination of the traffic, including IP addresses, subnets, and instances. Firewall rules are defined at the VPC network level, and are specific to the network in which they are defined. The rules themselves cannot be shared among networks. Firewall rules only support IPv4 traffic. When specifying a source for an ingress rule or a destination for an egress rule by address, you can only use an IPv4 address or IPv4 block in CIDR notation. Generic (0.0.0.0/0) incoming traffic from internet to VPC or VM instance using SSH on Port 22 can be avoided.' - desc 'rationale', 'GCP Firewall Rules within a VPC Network. These rules apply to outgoing (egress) traffic from instances and incoming (ingress) traffic to instances in the network. Egress and ingress traffic are controlled even if the traffic stays within the network (for example, instance-to-instance communication). For an instance to have outgoing Internet access, the network must have a valid Internet gateway route or custom route whose destination IP is specified. This route simply defines the path to the Internet, to avoid the most general (0.0.0.0/0) destination IP Range specified from Internet through SSH with default Port 22. We need to restrict generic access from Internet to specific IP Range.' + desc 'GCP Firewall Rules are specific to a VPC Network. Each rule either allows or denies traffic when its conditions are met. Its conditions allow the user to specify the type of traffic, such as ports and protocols, and the source or destination of the traffic, including IP addresses, subnets, and instances. + + Firewall rules are defined at the VPC network level and are specific to the network in which they are defined. The rules themselves cannot be shared among networks. Firewall rules only support IPv4 traffic. When specifying a source for an ingress rule or a destination for an egress rule by address, only an IPv4 address or IPv4 block in CIDR notation can be used. Generic (0.0.0.0/0) incoming traffic from the internet to VPC or VM instance using SSH on Port 22 can be avoided.' + desc 'rationale', 'GCP Firewall Rules within a VPC Network apply to outgoing (egress) traffic from instances and incoming (ingress) traffic to instances in the network. Egress and ingress traffic flows are controlled even if the traffic stays within the network (for example, instance-to-instance communication). For an instance to have outgoing Internet access, the network must have a valid Internet gateway route or custom route whose destination IP is specified. This route simply defines the path to the Internet, to avoid the most general (0.0.0.0/0) destination IP Range specified from the Internet through SSH with the default Port 22. Generic access from the Internet to a specific IP Range needs to be restricted.' tag cis_scored: true tag cis_level: 2 @@ -37,6 +39,7 @@ ref 'CIS Benchmark', url: cis_url.to_s ref 'GCP Docs', url: 'https://cloud.google.com/vpc/docs/firewalls#blockedtraffic' + ref 'GCP Docs', url: 'https://cloud.google.com/blog/products/identity-security/cloud-iap-enables-context-aware-access-to-vms-via-ssh-and-rdp-without-bastion-hosts' google_compute_firewalls(project: gcp_project_id).where(firewall_direction: 'INGRESS').firewall_names.each do |firewall_name| describe "[#{gcp_project_id}] #{firewall_name}" do diff --git a/controls/3.07-networking.rb b/controls/3.07-networking.rb index f17abc3..dfbd7b8 100644 --- a/controls/3.07-networking.rb +++ b/controls/3.07-networking.rb @@ -25,8 +25,10 @@ title "[#{control_abbrev.upcase}] Ensure that RDP access is restricted from the internet" - desc 'GCP Firewall Rules are specific to a VPC Network. Each rule either allows or denies traffic when its conditions are met. Its conditions allow you to specify the type of traffic, such as ports and protocols, and the source or destination of the traffic, including IP addresses, subnets, and instances. Firewall rules are defined at the VPC network level, and are specific to the network in which they are defined. The rules themselves cannot be shared among networks. Firewall rules only support IPv4 traffic. When specifying a source for an ingress rule or a destination for an egress rule by address, you can only use an IPv4 address or IPv4 block in CIDR notation. Generic (0.0.0.0/0) incoming traffic from internet to VPC or VM instance using RDP on Port 3389 can be avoided.' - desc 'rationale', 'GCP Firewall Rules within a VPC Network. These rules apply to outgoing (egress) traffic from instances and incoming (ingress) traffic to instances in the network. Egress and ingress traffic are controlled even if the traffic stays within the network (for example, instance-to-instance communication). For an instance to have outgoing Internet access, the network must have a valid Internet gateway route or custom route whose destination IP is specified. This route simply defines the path to the Internet, to avoid the most general (0.0.0.0/0) destination IP Range specified from Internet through RDP with default Port 3389. We need to restrict generic access from Internet to specific IP Range.' + desc 'GCP Firewall Rules are specific to a VPC Network. Each rule either allows or denies traffic when its conditions are met. Its conditions allow users to specify the type of traffic, such as ports and protocols, and the source or destination of the traffic, including IP addresses, subnets, and instances. + + Firewall rules are defined at the VPC network level and are specific to the network in which they are defined. The rules themselves cannot be shared among networks. Firewall rules only support IPv4 traffic. When specifying a source for an ingress rule or a destination for an egress rule by address, an IPv4 address or IPv4 block in CIDR notation can be used. Generic (0.0.0.0/0) incoming traffic from the Internet to a VPC or VM instance using RDP on Port 3389 can be avoided.' + desc 'rationale', 'GCP Firewall Rules within a VPC Network. These rules apply to outgoing (egress) traffic from instances and incoming (ingress) traffic to instances in the network. Egress and ingress traffic flows are controlled even if the traffic stays within the network (for example, instance-to-instance communication). For an instance to have outgoing Internet access, the network must have a valid Internet gateway route or custom route whose destination IP is specified. This route simply defines the path to the Internet, to avoid the most general (0.0.0.0/0) destination IP Range specified from the Internet through RDP with the default Port 3389. Generic access from the Internet to a specific IP Range should be restricted.' tag cis_scored: true tag cis_level: 2 @@ -37,6 +39,7 @@ ref 'CIS Benchmark', url: cis_url.to_s ref 'GCP Docs', url: 'https://cloud.google.com/vpc/docs/firewalls#blockedtraffic' + ref 'GCP Docs', url: 'https://cloud.google.com/blog/products/identity-security/cloud-iap-enables-context-aware-access-to-vms-via-ssh-and-rdp-without-bastion-hosts' google_compute_firewalls(project: gcp_project_id).where(firewall_direction: 'INGRESS').firewall_names.each do |firewall_name| describe "[#{gcp_project_id}] #{firewall_name}" do diff --git a/controls/3.08-networking.rb b/controls/3.08-networking.rb index 20ee14a..a4d7dd9 100644 --- a/controls/3.08-networking.rb +++ b/controls/3.08-networking.rb @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -title 'Ensure VPC Flow logs is enabled for every subnet in VPC Network' +title 'Ensure that VPC Flow Logs is Enabled for Every Subnet in a VPC Network' gcp_project_id = input('gcp_project_id') cis_version = input('cis_version') @@ -23,22 +23,23 @@ control "cis-gcp-#{control_id}-#{control_abbrev}" do impact 'low' - title "[#{control_abbrev.upcase}] Ensure VPC Flow logs is enabled for every subnet in VPC Network" + title "[#{control_abbrev.upcase}] Ensure that VPC Flow Logs is Enabled for Every Subnet in a VPC Network" - desc "Flow Logs is a feature that enables you to capture information about the IP traffic going to and from network interfaces in your VPC Subnets. After you've created a flow log, you can view and retrieve its data in Stackdriver Logging. It is recommended that Flow Logs be enabled for every business critical VPC subnet." - desc 'rationale', "VPC networks and subnetworks provide logically isolated and secure network partitions where you can launch GCP resources. When Flow Logs is enabled for a subnet, VMs within subnet starts reporting on all TCP and UDP flows. Each VM samples the TCP and UDP flows it sees, inbound and outbound, whether the flow is to or from another VM, a host in your on-premises datacenter, a Google service, or a host on the Internet. If two GCP VMs are communicating, and both are in subnets that have VPC Flow Logs enabled, both VMs report the flows. + desc "Flow Logs is a feature that enables users to capture information about the IP traffic going to and from network interfaces in the organization's VPC Subnets. Once a flow log is created, the user can view and retrieve its data in Stackdriver Logging. It is recommended that Flow Logs be enabled for every business-critical VPC subnet." + desc 'rationale', "VPC networks and subnetworks not reserved for internal HTTP(S) load balancing provide logically isolated and secure network partitions where GCP resources can be launched. When Flow Logs are enabled for a subnet, VMs within that subnet start reporting on all Transmission Control Protocol (TCP) and User Datagram Protocol (UDP) flows. Each VM samples the TCP and UDP flows it sees, inbound and outbound, whether the flow is to or from another VM, a host in the on-premises datacenter, a Google service, or a host on the Internet. If two GCP VMs are communicating, and both are in subnets that have VPC Flow Logs enabled, both VMs report the flows. -Flow Logs supports following use cases: + Flow Logs supports the following use cases: + - Network monitoring + - Understanding network usage and optimizing network traffic expenses + - Network forensics + - Real-time security analysis -- Network monitoring -- Understanding network usage and optimizing network traffic expenses -- Network forensics -- Real-time security analysis + Flow Logs provide visibility into network traffic for each VM inside the subnet and can be used to detect anomalous traffic or provide insight during security workflows. -Flow Logs provide visibility into network traffic for each VM inside the subnet and can be used to detect anomalous traffic or insight during security workflows." + The Flow Logs must be configured such that all network traffic is logged, the interval of logging is granular to provide detailed information on the connections, no logs are filtered, and metadata to facilitate investigations are included." tag cis_scored: true - tag cis_level: 1 + tag cis_level: 2 tag cis_gcp: control_id.to_s tag cis_version: cis_version.to_s tag project: gcp_project_id.to_s diff --git a/controls/3.09-networking.rb b/controls/3.09-networking.rb index 5073dc5..9b42194 100644 --- a/controls/3.09-networking.rb +++ b/controls/3.09-networking.rb @@ -12,8 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -title 'Ensure no HTTPS or SSL proxy load balancers permit SSL policies with -weak cipher suites' +title 'Ensure No HTTPS or SSL Proxy Load Balancers Permit SSL Policies With Weak Cipher Suites' gcp_project_id = input('gcp_project_id') cis_version = input('cis_version') @@ -27,10 +26,10 @@ control "cis-gcp-#{control_id}-#{control_abbrev}" do impact 'low' - title "[#{control_abbrev.upcase}] Ensure no HTTPS or SSL proxy load balancers permit SSL policies with weak cipher suites" + title "[#{control_abbrev.upcase}] Ensure No HTTPS or SSL Proxy Load Balancers Permit SSL Policies With Weak Cipher Suites" - desc 'Secure Sockets Layer (SSL) policies determine what port Transport Layer Security (TLS) features clients are permitted to use when connecting to load balancers. To prevent usage of insecure features, SSL policies should use (a) at least TLS 1.2 with the MODERN profile; or (b) the RESTRICTED profile, because it effectively requires clients to use TLS 1.2 regardless of the chosen minimum TLS version; or (3) a CUSTOM profile that does not support any of the following features: TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_256_GCM_SHA384, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_3DES_EDE_CBC_SHA' - desc 'rationale', 'Load balancers are used to efficiently distribute traffic across multiple servers. Both SSL proxy and HTTPS load balancers are external load balancers, meaning they distribute traffic from the Internet to a GCP network. GCP customers can configure load balancer SSL policies with a minimum TLS version (1.0, 1.1, or 1.2) that clients can use to establish a connection, along with a profile (Compatible, Modern, Restricted, or Custom) that specifies permissible cipher suites. To comply with users using outdated protocols, GCP load balancers can be configured to permit insecure cipher suites. In fact, the GCP default SSL policy uses a minimum TLS versionls of 1.0 and a Compatible profile, which allows the widest range of insecure cipher suites. As a result, it is easy for customers to configure a load balancer without even knowing that they are permitting outdated cipher suites.' + desc 'Secure Sockets Layer (SSL) policies determine what port Transport Layer Security (TLS) features clients are permitted to use when connecting to load balancers. To prevent usage of insecure features, SSL policies should use (a) at least TLS 1.2 with the MODERN profile; or (b) the RESTRICTED profile, because it effectively requires clients to use TLS 1.2 regardless of the chosen minimum TLS version; or (3) a CUSTOM profile that does not support any of the following features: TLS_RSA_WITH_AES_128_GCM_SHA256 TLS_RSA_WITH_AES_256_GCM_SHA384 TLS_RSA_WITH_AES_128_CBC_SHA TLS_RSA_WITH_AES_256_CBC_SHA TLS_RSA_WITH_3DES_EDE_CBC_SHA' + desc 'rationale', 'Load balancers are used to efficiently distribute traffic across multiple servers. Both SSL proxy and HTTPS load balancers are external load balancers, meaning they distribute traffic from the Internet to a GCP network. GCP customers can configure load balancer SSL policies with a minimum TLS version (1.0, 1.1, or 1.2) that clients can use to establish a connection, along with a profile (Compatible, Modern, Restricted, or Custom) that specifies permissible cipher suites. To comply with users using outdated protocols, GCP load balancers can be configured to permit insecure cipher suites. In fact, the GCP default SSL policy uses a minimum TLS version of 1.0 and a Compatible profile, which allows the widest range of insecure cipher suites. As a result, it is easy for customers to configure a load balancer without even knowing that they are permitting outdated cipher suites.' tag cis_scored: false tag cis_level: 1 diff --git a/controls/3.10-networking.rb b/controls/3.10-networking.rb new file mode 100644 index 0000000..af9d8a8 --- /dev/null +++ b/controls/3.10-networking.rb @@ -0,0 +1,45 @@ +# Copyright 2025 The inspec-gcp-cis-benchmark Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title 'Use Identity Aware Proxy (IAP) to Ensure Only Traffic From Google IP Addresses are Allowed' + +gcp_project_id = input('gcp_project_id') +cis_version = input('cis_version') +cis_url = input('cis_url') +control_id = '3.10' +control_abbrev = 'networking' + +control "cis-gcp-#{control_id}-#{control_abbrev}" do + impact 'medium' + title "[#{control_abbrev.upcase}] Use Identity Aware Proxy (IAP) to Ensure Only Traffic From Google IP Addresses are Allowed" + desc 'IAP authenticates the user requests to your apps via a Google single sign in. You can then manage these users with permissions to control access. It is recommended to use both IAP permissions and firewalls to restrict this access to your apps with sensitive information.' + desc 'rationale', 'IAP ensure that access to VMs is controlled by authenticating incoming requests. Access to your apps and the VMs should be restricted by firewall rules that allow only the proxy IAP IP addresses contained in the 35.235.240.0/20 subnet. Otherwise, unauthenticated requests can be made to your apps. To ensure that load balancing works correctly health checks should also be allowed.' + + tag cis_scored: false + tag cis_level: 2 + tag cis_gcp: control_id.to_s + tag cis_version: cis_version.to_s + tag project: gcp_project_id.to_s + tag nist: ['AC-1'] + + ref 'CIS Benchmark', url: cis_url.to_s + ref 'GCP Docs', url: 'https://cloud.google.com/iap' + ref 'GCP Docs', url: 'https://cloud.google.com/iap/docs/load-balancer-howto' + ref 'GCP Docs', url: 'https://cloud.google.com/load-balancing/docs/health-checks' + ref 'GCP Docs', url: 'https://cloud.google.com/blog/products/identity-security/cloud-iap-enables-context-aware-access-to-vms-via-ssh-and-rdp-without-bastion-hosts' + + describe 'This control is not scored' do + skip 'This control is not scored' + end +end diff --git a/controls/4.09-vms.rb b/controls/4.09-vms.rb new file mode 100644 index 0000000..0c84352 --- /dev/null +++ b/controls/4.09-vms.rb @@ -0,0 +1,64 @@ +# Copyright 2019 The inspec-gcp-cis-benchmark Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title 'Ensure That Compute Instances Do Not Have Public IP Addresses (Automated)' + +gcp_project_id = input('gcp_project_id') +cis_version = input('cis_version') +cis_url = input('cis_url') +control_id = '4.09' +control_abbrev = 'vms' + +control "cis-gcp-#{control_id}-#{control_abbrev}" do + impact 'medium' + + title "[#{control_abbrev.upcase}] Ensure That Compute Instances Do Not Have Public IP Addresses (Automated)" + + desc 'Compute instances should not be configured to have external IP addresses. Removing the external IP address from your Compute instance may cause some applications to stop working.' + desc 'rationale', "To reduce your attack surface, Compute instances should not have public IP addresses. Instead, instances should be configured behind load balancers, to minimize the instance's exposure to the internet." + + tag cis_scored: true + tag cis_level: 2 + tag cis_gcp: control_id.to_s + tag cis_version: cis_version.to_s + tag project: gcp_project_id.to_s + tag nist: %w[] # Add relevant NIST controls if any in future + + ref 'CIS Benchmark', url: cis_url.to_s + ref 'GCP Docs', url: 'https://cloud.google.com/load-balancing/docs/backend-service#backends_and_external_ip_addresses' + ref 'GCP Docs', url: 'https://cloud.google.com/compute/docs/instances/connecting-advanced#sshbetweeninstances' + ref 'GCP Docs', url: 'https://cloud.google.com/compute/docs/instances/connecting-to-instance' + ref 'GCP Docs', url: 'https://cloud.google.com/compute/docs/ip-addresses/reserve-static-external-ip-address#unassign_ip' + ref 'GCP Docs', url: 'https://cloud.google.com/resource-manager/docs/organization-policy/org-policy-constraints' + ref 'Organization Policy', url: 'https://console.cloud.google.com/orgpolicies/compute-vmExternalIpAccess' + + # control_id and control_abbrev are already defined above + + instances_found = false + google_compute_zones(project: gcp_project_id).zone_names.each do |zone_name| + google_compute_instances(project: gcp_project_id, zone: zone_name).where { instance_name !~ /^gke-/ }.where(status: 'RUNNING').instance_names.each do |instance_name| + instances_found = true + describe "[#{gcp_project_id}] Instance: #{instance_name} in Zone: #{zone_name}" do + subject { google_compute_instance(project: gcp_project_id, zone: zone_name, name: instance_name) } + its('public_ip_configured?') { should be false } + end + end + end + + unless instances_found + describe "[#{control_abbrev.upcase}] #{control_id} - No Non-GKE Running Instances Found" do + skip 'No non-GKE running compute instances were found in the project, this control is Not Applicable.' + end + end +end diff --git a/controls/4.10-vms.rb b/controls/4.10-vms.rb new file mode 100644 index 0000000..ac1ab4a --- /dev/null +++ b/controls/4.10-vms.rb @@ -0,0 +1,45 @@ +# Copyright 2019 The inspec-gcp-cis-benchmark Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title 'Ensure That App Engine Applications Enforce HTTPS Connections (Manual)' + +gcp_project_id = input('gcp_project_id') +cis_version = input('cis_version') +cis_url = input('cis_url') +control_id = '4.10' +control_abbrev = 'vms' # Though App Engine, keeping consistent with section + +control "cis-gcp-#{control_id}-#{control_abbrev}" do + impact 'low' + + title "[#{control_abbrev.upcase}] Ensure That App Engine Applications Enforce HTTPS Connections (Manual)" + + desc 'In order to maintain the highest level of security all connections to an application should be secure by default.' + desc 'rationale', 'Insecure HTTP connections maybe subject to eavesdropping which can expose sensitive data. All connections to appengine will automatically be redirected to the HTTPS endpoint ensuring that all connections are secured by TLS.' + + tag cis_scored: false + tag cis_level: 2 + tag cis_gcp: control_id.to_s + tag cis_version: cis_version.to_s + tag project: gcp_project_id.to_s + tag nist: %w[] # Add relevant NIST controls if any in future + + ref 'CIS Benchmark', url: cis_url.to_s + ref 'GCP App Engine Docs', url: 'https://cloud.google.com/appengine/docs/standard/python3/config/appref' + ref 'GCP App Engine Docs (Flexible)', url: 'https://cloud.google.com/appengine/docs/flexible/nodejs/configuring-your-app-with-app-yaml' + + describe 'This control is not scored' do + skip 'This control is not scored' + end +end diff --git a/controls/4.11-vms.rb b/controls/4.11-vms.rb new file mode 100644 index 0000000..e44ecb8 --- /dev/null +++ b/controls/4.11-vms.rb @@ -0,0 +1,69 @@ +# Copyright 2019 The inspec-gcp-cis-benchmark Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title 'Ensure That Compute Instances Have Confidential Computing Enabled (Automated)' + +gcp_project_id = input('gcp_project_id') +cis_version = input('cis_version') +cis_url = input('cis_url') +supported_confidential_vm_types = input('supported_confidential_vm_types', value: /^(n2d|c2d|n3d)/, description: 'Regex for machine types supporting Confidential Computing. N2D, C2D (AMD SEV). N3D (AMD SEV-SNP). T2D (Intel TDX) might also be relevant if supported by InSpec resources.') +control_id = '4.11' +control_abbrev = 'vms' + +control "cis-gcp-#{control_id}-#{control_abbrev}" do + impact 'medium' + + title "[#{control_abbrev.upcase}] Ensure That Compute Instances Have Confidential Computing Enabled (Automated)" + + desc 'Google Cloud encrypts data at-rest and in-transit, but customer data must be decrypted for processing. Confidential Computing is a breakthrough technology that encrypts data in-use while it is being processed. Confidential Computing environments keep data encrypted in memory and elsewhere outside the central processing unit (CPU). Confidential VMs leverage hardware-based memory encryption technologies... Customer data will stay encrypted while it is used, indexed, queried, or trained on. Encryption keys are generated by and reside solely in dedicated hardware and are not exportable, enhancing isolation and security. Built-in hardware optimizations ensure Confidential Computing workloads experience minimal to no significant performance penalties.' + desc 'rationale', "Confidential Computing enables customers' sensitive code and other data encrypted in memory during processing. Google does not have access to the encryption keys. Confidential VM can help alleviate concerns about risk related to either dependency on Google infrastructure or Google insiders' access to customer data in the clear." + + tag cis_scored: true + tag cis_level: 2 + tag cis_gcp: control_id.to_s + tag cis_version: cis_version.to_s + tag project: gcp_project_id.to_s + tag nist: %w[] # Add relevant NIST controls if any in future + + ref 'CIS Benchmark', url: cis_url.to_s + ref 'GCP Docs', url: 'https://cloud.google.com/compute/confidential-vm/docs/creating-cvm-instance' + ref 'GCP Docs', url: 'https://cloud.google.com/compute/confidential-vm/docs/about-cvm' + ref 'GCP Docs', url: 'https://cloud.google.com/confidential-computing' + ref 'Google Cloud Blog', url: 'https://cloud.google.com/blog/products/identity-security/introducing-google-cloud-confidential-computing-with-confidential-vms' + ref 'Supported Configurations', url: 'https://cloud.google.com/confidential-computing/confidential-vm/docs/supported-configurations' + ref 'Pricing', url: 'https://cloud.google.com/compute/confidential-vm/pricing' + + # control_id and control_abbrev are already defined above + # supported_confidential_vm_types is defined as an input above + + instances_found = false + google_compute_zones(project: gcp_project_id).zone_names.each do |zone_name| + google_compute_instances(project: gcp_project_id, zone: zone_name) + .where(status: 'RUNNING') + .where { machine_type.match(supported_confidential_vm_types) } # Filter for machine types that *can* support it + .instance_names.each do |instance_name| + instances_found = true + describe "[#{gcp_project_id}] Confidential VM: #{instance_name} in Zone: #{zone_name}" do + subject { google_compute_instance(project: gcp_project_id, zone: zone_name, name: instance_name).confidential_instance_config } + its('enable_confidential_compute') { should be true } + end + end + end + + unless instances_found + describe "[#{control_abbrev.upcase}] #{control_id} - No Running Instances of Supported Machine Types Found" do + skip 'No running compute instances of machine types supporting Confidential Computing (e.g., N2D, C2D, N3D) were found in the project. This control is Not Applicable or requires manual verification for instances not matching the filter.' + end + end +end diff --git a/controls/4.12-vms.rb b/controls/4.12-vms.rb new file mode 100644 index 0000000..090da47 --- /dev/null +++ b/controls/4.12-vms.rb @@ -0,0 +1,58 @@ +# Copyright 2019 The inspec-gcp-cis-benchmark Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title 'Ensure the Latest Operating System Updates Are Installed On Your Virtual Machines in All Projects (Manual)' + +gcp_project_id = input('gcp_project_id') +cis_version = input('cis_version') +cis_url = input('cis_url') +control_id = '4.12' +control_abbrev = 'vms' + +control "cis-gcp-#{control_id}-#{control_abbrev}" do + impact 'medium' + + title "[#{control_abbrev.upcase}] Ensure the Latest Operating System Updates Are Installed On Your Virtual Machines in All Projects (Manual)" + + desc 'Google Cloud Virtual Machines have the ability via an OS Config agent API to periodically (about every 10 minutes) report OS inventory data. A patch compliance API periodically reads this data, and cross references metadata to determine if the latest updates are installed. This is not the only Patch Management solution available to your organization and you should weigh your needs before committing to using this method. Most Operating Systems require a restart or changing critical resources to apply the updates. Using the Google Cloud VM manager for its OS Patch management will incur additional costs for each VM managed by it.' + desc 'rationale', 'Keeping virtual machine operating systems up to date is a security best practice. Using this service will simplify this process.' + + tag cis_scored: false + tag cis_level: 2 + tag cis_gcp: control_id.to_s + tag cis_version: cis_version.to_s + tag project: gcp_project_id.to_s + tag nist: %w[] # Add relevant NIST controls if any in future + + ref 'CIS Benchmark', url: cis_url.to_s + ref 'GCP Docs - Manage OS', url: 'https://cloud.google.com/compute/docs/manage-os' + ref 'GCP Docs - OS Patch Management', url: 'https://cloud.google.com/compute/docs/os-patch-management' + ref 'GCP Docs - VM Manager', url: 'https://cloud.google.com/compute/docs/vm-manager' + ref 'GCP Docs - OS Details VM Manager', url: 'https://cloud.google.com/compute/docs/images/os-details#vm-manager' + ref 'GCP Docs - VM Manager Pricing', url: 'https://cloud.google.com/compute/docs/vm-manager#pricing' + ref 'GCP Docs - Verify Setup', url: 'https://cloud.google.com/compute/docs/troubleshooting/vm-manager/verify-setup' + ref 'GCP Docs - View OS Details', url: 'https://cloud.google.com/compute/docs/instances/view-os-details#view-data-tools' + ref 'GCP Docs - Create Patch Job', url: 'https://cloud.google.com/compute/docs/os-patch-management/create-patch-job' + ref 'GCP Docs - Setup NAT', url: 'https://cloud.google.com/nat/docs/set-up-network-address-translation' + ref 'GCP Docs - Private Google Access', url: 'https://cloud.google.com/vpc/docs/configure-private-google-access' + ref 'CIS Workbench Ref', url: 'https://workbench.cisecurity.org/sections/811638/recommendations/1334335' + ref 'GCP Docs - OS Agent Install', url: 'https://cloud.google.com/compute/docs/manage-os#agent-install' + ref 'GCP Docs - Verify SA Enabled', url: 'https://cloud.google.com/compute/docs/troubleshooting/vm-manager/verify-setup#service-account-enabled' + ref 'GCP Docs - OS Patch Dashboard', url: 'https://cloud.google.com/compute/docs/os-patch-management#use-dashboard' + ref 'GCP Docs - Verify Metadata Enabled', url: 'https://cloud.google.com/compute/docs/troubleshooting/vm-manager/verify-setup#metadata-enabled' + + describe 'This control is not scored' do + skip 'This control is not scored' + end +end diff --git a/controls/5.01-storage.rb b/controls/5.01-storage.rb index c41c746..b9ca7fd 100644 --- a/controls/5.01-storage.rb +++ b/controls/5.01-storage.rb @@ -25,8 +25,8 @@ title "[#{control_abbrev.upcase}] Ensure that Cloud Storage bucket is not anonymously or publicly accessible" - desc 'It is recommended that IAM policy on Cloud Storage bucket does not allows anonymous and/or public access.' - desc 'rationale', 'Allowing anonymous and/or public access grants permissions to anyone to access bucket content. Such access might not be desired if you are storing any sensitive data. Hence, ensure that anonymous and/or public access to a bucket is not allowed.' + desc 'It is recommended that IAM policy on Cloud Storage bucket does not allows anonymous or public access.' + desc 'rationale', 'Allowing anonymous or public access grants permissions to anyone to access bucket content. Such access might not be desired if you are storing any sensitive data. Hence, ensure that anonymous or public access to a bucket is not allowed.' tag cis_scored: true tag cis_level: 1 @@ -38,6 +38,7 @@ ref 'CIS Benchmark', url: cis_url.to_s ref 'GCP Docs', url: 'https://cloud.google.com/storage/docs/access-control/iam-reference' ref 'GCP Docs', url: 'https://cloud.google.com/storage/docs/access-control/making-data-public' + ref 'GCP Docs', url: 'https://cloud.google.com/storage/docs/gsutil/commands/iam' storage_buckets = google_storage_buckets(project: gcp_project_id).bucket_names diff --git a/controls/5.02-storage.rb b/controls/5.02-storage.rb index 7799fdc..3f3a174 100644 --- a/controls/5.02-storage.rb +++ b/controls/5.02-storage.rb @@ -26,22 +26,18 @@ title "[#{control_abbrev.upcase}] Ensure that Cloud Storage buckets have uniform bucket-level access enabled" desc 'It is recommended that uniform bucket-level access is enabled on Cloud Storage buckets.' - desc 'rationale', "It is recommended to use uniform bucket-level access to unify and simplify how you grant -access to your Cloud Storage resources. -Cloud Storage offers two systems for granting users permission to access your buckets and -objects: Cloud Identity and Access Management (Cloud IAM) and Access Control Lists -(ACLs). These systems act in parallel - in order for a user to access a Cloud Storage -resource, only one of the systems needs to grant the user permission. Cloud IAM is used -throughout Google Cloud and allows you to grant a variety of permissions at the bucket and -project levels. ACLs are used only by Cloud Storage and have limited permission options, -but they allow you to grant permissions on a per-object basis. + desc 'rationale', "It is recommended to use uniform bucket-level access to unify and simplify + how you grant access to your Cloud Storage resources. -In order to support a uniform permissioning system, Cloud Storage has uniform bucket- -level access. Using this feature disables ACLs for all Cloud Storage resources: access to + Cloud Storage offers two systems for granting users permission to access your buckets and objects: + Cloud Identity and Access Management (Cloud IAM) and Access Control Lists (ACLs).These systems act in parallel - + in order for a user to access a Cloud Storage resource, only one of the systems needs to grant the user permission. + Cloud IAM is used throughout Google Cloud and allows you to grant a variety of permissions at the bucket and project levels. + ACLs are used only by Cloud Storage and have limited permission options, but they allow you to grant permissions on a per-object basis. -Cloud Storage resources then is granted exclusively through Cloud IAM. Enabling uniform -bucket-level access guarantees that if a Storage bucket is not publicly accessible, no object -in the bucket is publicly accessible either." + In order to support a uniform permissioning system, Cloud Storage has uniform bucket-level access. Using this feature disables ACLs for + all Cloud Storage resources: access to Cloud Storage resources then is granted exclusively through Cloud IAM. Enabling uniform + bucket-level access guarantees that if a Storage bucket is not publicly accessible, no object in the bucket is publicly accessible either." tag cis_scored: true tag cis_level: 2 @@ -52,6 +48,8 @@ ref 'CIS Benchmark', url: cis_url.to_s ref 'GCP Docs', url: 'https://cloud.google.com/storage/docs/uniform-bucket-level-access' + ref 'GCP Docs', url: 'https://cloud.google.com/storage/docs/using-uniform-bucket-level-access' + ref 'GCP Docs', url: 'https://cloud.google.com/storage/docs/setting-org-policies#uniform-bucket' storage_buckets = google_storage_buckets(project: gcp_project_id).bucket_names diff --git a/controls/6.01-db.rb b/controls/6.01-db.rb index d121099..52719c4 100644 --- a/controls/6.01-db.rb +++ b/controls/6.01-db.rb @@ -32,9 +32,9 @@ title "[#{control_abbrev.upcase}] Ensure that MySql database instance does not allow anyone to connect with administrative privileges." - desc "It is recommended to set a password for the administrative user (root by default) to prevent unauthorized access to the SQL database Instances. - This recommendation is applicable only for MySql Instances. PostgreSQL does not offer any setting for No Password from cloud console." - desc 'rationale', 'At the time of MySql Instance creation, not providing a administrative password allows anyone to connect to the SQL database instance with administrative privileges. Root password should be set to ensure only authorized users have these privileges.' + desc "It is recommended to set a password for the administrative user (root by default) to prevent unauthorized access to the SQL database instances. + This recommendation is applicable only for MySQL Instances. PostgreSQL does not offer any setting for No Password from the cloud console." + desc 'rationale', 'At the time of MySQL Instance creation, not providing an administrative password allows anyone to connect to the SQL database instance with administrative privileges. The root password should be set to ensure only authorized users have these privileges.' tag cis_scored: true tag cis_level: 1 @@ -57,19 +57,15 @@ control "cis-gcp-#{sub_control_id}-#{control_abbrev}" do impact 'none' - title "[#{control_abbrev.upcase}] Ensure 'skip_show_database' database flag for Cloud SQL Mysql - instance is set to 'on'" + title "[#{control_abbrev.upcase}] Ensure 'Skip_show_database’ database flag for cloud SQL MySQL instance is set to 'On’" desc 'It is recommended to set skip_show_database database flag for Cloud SQL Mysql instance to on' - desc 'rationale', "'skip_show_database' database flag prevents people from using the SHOW DATABASES -statement if they do not have the SHOW DATABASES privilege. This can improve security if -you have concerns about users being able to see databases belonging to other users. Its -effect depends on the SHOW DATABASES privilege: If the variable value is ON, the SHOW -DATABASES statement is permitted only to users who have the SHOW DATABASES -privilege, and the statement displays all database names. If the value is OFF, SHOW -DATABASES is permitted to all users, but displays the names of only those databases for -which the user has the SHOW DATABASES or other privilege. This recommendation is -applicable to Mysql database instances." + desc 'rationale', "skip_show_database database flag prevents people from using the SHOW DATABASES statement if they do not + have the SHOW DATABASES privilege. This can improve security if you have concerns about users being able to see databases + belonging to other users. Its effect depends on the SHOW DATABASES privilege: If the variable value is ON, the SHOW DATABASES + statement is permitted only to users who have the SHOW DATABASES privilege, and the statement displays all database names. If + the value is OFF, SHOW DATABASES is permitted to all users, but displays the names of only those databases for which the user + has the SHOW DATABASES or other privilege. This recommendation is applicable to Mysql database instances." tag cis_scored: true tag cis_level: 1 @@ -80,6 +76,7 @@ ref 'CIS Benchmark', url: cis_url.to_s ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/mysql/flags' + ref 'GCP Docs', url: 'https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_skip_show_database' sql_instance_names.each do |db| if sql_cache.instance_objects[db].database_version.include? 'MYSQL' @@ -119,12 +116,14 @@ control "cis-gcp-#{sub_control_id}-#{control_abbrev}" do impact 'none' - title "[#{control_abbrev.upcase}] Ensure that the 'local_infile' database flag for a Cloud SQL Mysql instance is set to 'off'" + title "[#{control_abbrev.upcase}] Ensure that the 'Local_infile’ database flag for a cloud SQL MySQL instance is set to 'off’" desc 'It is recommended to set the local_infile database flag for a Cloud SQL MySQL instance to off.' desc 'rationale', "The local_infile flag controls the server-side LOCAL capability for LOAD DATA statements. Depending on the - local_infile setting, the server refuses or permits local data loading by clients that have LOCAL enabled on - the client side." + local_infile setting, the server refuses or permits local data loading by clients that have LOCAL enabled on the client side. + To explicitly cause the server to refuse LOAD DATA LOCAL statements (regardless of how client programs and libraries are configured + at build time or runtime), start mysqld with local_infile disabled. local_infile can also be set at runtime. + Due to security issues associated with the local_infile flag, it is recommended to disable it. This recommendation is applicable to MySQL database instances." tag cis_scored: true tag cis_level: 1 @@ -135,6 +134,8 @@ ref 'CIS Benchmark', url: cis_url.to_s ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/mysql/flags' + ref 'GCP Docs', url: 'https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_local_infile' + ref 'GCP Docs', url: 'https://dev.mysql.com/doc/refman/5.7/en/load-data-local.html' sql_instance_names.each do |db| if sql_cache.instance_objects[db].database_version.include? 'MYSQL' diff --git a/controls/6.02-db.rb b/controls/6.02-db.rb index cb7e1ba..4b45220 100644 --- a/controls/6.02-db.rb +++ b/controls/6.02-db.rb @@ -21,7 +21,6 @@ log_min_error_statement = input('log_min_error_statement') log_error_verbosity = input('log_error_verbosity') log_statement = input('log_statement') -log_hostname = input('log_hostname') control_id = '6.2' control_abbrev = 'db' @@ -33,87 +32,31 @@ control "cis-gcp-#{sub_control_id}-#{control_abbrev}" do impact 'none' - title "[#{control_abbrev.upcase}] Ensure that the 'log_checkpoints' database flag for Cloud SQL PostgreSQL instance is set to 'on'" + title "[#{control_abbrev.upcase}] Ensure 'Log_error_verbosity’ Database Flag for Cloud SQL PostgreSQL Instance Is Set to 'DEFAULT’ or Stricter" - desc 'Enabling log_checkpoints causes checkpoints and restart points to be logged in the server log. Some statistics are included in the log messages, including the number of buffers written and the time spent writing them. ' - desc 'rationale', 'Enable system logging to include detailed information such as an event source, date,user, timestamp, source addresses, destination addresses, and other useful elements.' + desc 'The log_error_verbosity flag controls the verbosity/details of messages logged. Valid values are: + • TERSE + • DEFAULT + • VERBOSE + TERSE excludes the logging of DETAIL, HINT, QUERY, and CONTEXT error information. + VERBOSE output includes the SQLSTATE error code, source code file name, function name, and line number that generated the error. + Ensure an appropriate value is set to \'DEFAULT\' or stricter.' - tag cis_scored: true - tag cis_level: 1 - tag cis_gcp: sub_control_id.to_s - tag cis_version: cis_version.to_s - tag project: gcp_project_id.to_s - tag nist: ['AU-3'] - - ref 'CIS Benchmark', url: cis_url.to_s - ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/postgres/flags#setting_a_database_flag' - - sql_instance_names.each do |db| - if sql_cache.instance_objects[db].database_version.include? 'POSTGRES' - impact 'medium' - if sql_cache.instance_objects[db].settings.database_flags.nil? - describe "[#{gcp_project_id} , #{db} ] does not any have database flags." do - subject { false } - it { should be true } - end - else - describe.one do - sql_cache.instance_objects[db].settings.database_flags.each do |flag| - next unless flag.name == 'log_checkpoints' - describe "[#{gcp_project_id} , #{db} ] should have a database flag 'log_checkpoints' set to 'on' " do - subject { flag } - its('name') { should cmp 'log_checkpoints' } - its('value') { should cmp 'on' } - end - end - end - end - else - describe "[#{gcp_project_id}] [#{db}] is not a PostgreSQL database. This test is Not Applicable." do - skip "[#{gcp_project_id}] [#{db}] is not a PostgreSQL database" - end - end - end - - if sql_instance_names.empty? - describe "[#{gcp_project_id}] does not have CloudSQL instances. This test is Not Applicable." do - skip "[#{gcp_project_id}] does not have CloudSQL instances." - end - end -end - -# 6.2.2 -sub_control_id = "#{control_id}.2" -control "cis-gcp-#{sub_control_id}-#{control_abbrev}" do - impact 'none' - - title "[#{control_abbrev.upcase}] Ensure 'log_error_verbosity' database flag for Cloud SQL - PostgreSQL instance is set to 'DEFAULT' or stricter" - - desc 'The log_error_verbosity flag controls the verbosity/details of messages logged. Valid - values are: - - TERSE - - DEFAULT - - VERBOSE - TERSE excludes the logging of DETAIL, HINT, QUERY, and CONTEXT error information. - VERBOSE output includes the SQLSTATE error code, source code file name, function name, - and line number that generated the error. - Ensure an appropriate value is set to \'DEFAULT\' or stricter.' - desc 'rationale', "Auditing helps in troubleshooting operational problems and also permits forensic analysis. - If log_error_verbosity is not set to the correct value, too many details or too few details - may be logged. This flag should be configured with a value of 'DEFAULT' or stricter. This - recommendation is applicable to PostgreSQL database instances." + desc 'rationale', 'Auditing helps in troubleshooting operational problems and also permits forensic analysis. If log_error_verbosity is not + set to the correct value, too many details or too few details may be logged. This flag should be configured with a value of \'DEFAULT\' + or stricter. + This recommendation is applicable to PostgreSQL database instances.' tag cis_scored: true - tag cis_level: 1 + tag cis_level: 2 tag cis_gcp: sub_control_id.to_s tag cis_version: cis_version.to_s tag project: gcp_project_id.to_s tag nist: ['AU-3'] ref 'CIS Benchmark', url: cis_url.to_s - ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/postgres/flags#setting_a_database_flag' - ref 'GCP Docs', url: 'https://www.postgresql.org/docs/9.6/runtime-config-logging.html#RUNTIME-CONFIG-LOGGING-WHAT' + ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/postgres/flags' + ref 'GCP Docs', url: 'https://www.postgresql.org/docs/current/runtime-config-logging.html#GUC-LOG-ERROR-VERBOSITY' sql_instance_names.each do |db| if sql_cache.instance_objects[db].database_version.include? 'POSTGRES' @@ -149,16 +92,18 @@ end end -# 6.2.3 -sub_control_id = "#{control_id}.3" +# 6.2.2 +sub_control_id = "#{control_id}.2" control "cis-gcp-#{sub_control_id}-#{control_abbrev}" do impact 'none' - title "[#{control_abbrev.upcase}] Ensure that the 'log_connections' database flag for Cloud SQL PostgreSQL instance is set to 'on'" + title "[#{control_abbrev.upcase}] Ensure that the 'Log_connections’ database flag for cloud SQL PostgreSQL instance is set to 'on’" - desc 'Enabling the log_connections setting causes each attempted connection to the server to be logged, along with successful completion of client authentication. ' - desc 'rationale', "PostgreSQL does not log attempted connections by default. Enabling the log_connections setting will create log entries for each attempted connection - as well as successful completion of client authentication which can be useful in troubleshooting issues and to determine any unusual connection attempts to the server." + desc 'Enabling the log_connections setting causes each attempted connection to the server to be logged, along with successful completion + of client authentication. This parameter cannot be changed after the session starts.' + desc 'rationale', "PostgreSQL does not log attempted connections by default. Enabling the log_connections setting will create log entries + for each attempted connection as well as successful completion of client authentication which can be useful in troubleshooting issues + and to determine any unusual connection attempts to the server. This recommendation is applicable to PostgreSQL database instances." tag cis_scored: true tag cis_level: 1 @@ -168,8 +113,8 @@ tag nist: ['AU-3'] ref 'CIS Benchmark', url: cis_url.to_s - ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/postgres/flags#setting_a_database_flag' - ref 'GCP Docs', url: 'https://www.postgresql.org/docs/9.6/runtime-config-logging.html#RUNTIME-CONFIG-LOGGING-WHAT' + ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/postgres/flags' + ref 'GCP Docs', url: 'https://www.postgresql.org/docs/current/runtime-config-logging.html#GUC-LOG-CONNECTIONS' sql_instance_names.each do |db| if sql_cache.instance_objects[db].database_version.include? 'POSTGRES' @@ -205,16 +150,18 @@ end end -# 6.2.4 -sub_control_id = "#{control_id}.4" +# 6.2.3 +sub_control_id = "#{control_id}.3" control "cis-gcp-#{sub_control_id}-#{control_abbrev}" do impact 'none' - title "[#{control_abbrev.upcase}] Ensure that the 'log_disconnections' database flag for Cloud SQL PostgreSQL instance is set to 'on'" + title "[#{control_abbrev.upcase}] Ensure that the 'Log_disconnections’ database flag for cloud SQL PostgreSQL instance is set to 'on’" desc 'Enabling the log_disconnections setting logs the end of each session, including the session duration.' desc 'rationale', "PostgreSQL does not log session details such as duration and session end by default. Enabling the log_disconnections - setting will create log entries at the end of each session which can be useful in troubleshooting issues and determine any unusual activity across a time period" + setting will create log entries at the end of each session which can be useful in troubleshooting issues and determine any unusual + activity across a time period. The log_disconnections and log_connections work hand in hand and generally, the pair would be enabled/disabled + together. This recommendation is applicable to PostgreSQL database instances." tag cis_scored: true tag cis_level: 1 @@ -224,8 +171,8 @@ tag nist: ['AU-3'] ref 'CIS Benchmark', url: cis_url.to_s - ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/postgres/flags#setting_a_database_flag' - ref 'GCP Docs', url: 'https://www.postgresql.org/docs/9.6/runtime-config-logging.html#RUNTIME-CONFIG-LOGGING-WHAT' + ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/postgres/flags' + ref 'GCP Docs', url: 'https://www.postgresql.org/docs/current/runtime-config-logging.html#GUC-LOG-DISCONNECTIONS' sql_instance_names.each do |db| if sql_cache.instance_objects[db].database_version.include? 'POSTGRES' @@ -261,24 +208,29 @@ end end -# 6.2.5 -sub_control_id = "#{control_id}.5" +# 6.2.4 +sub_control_id = "#{control_id}.4" control "cis-gcp-#{sub_control_id}-#{control_abbrev}" do impact 'none' - title "[#{control_abbrev.upcase}] Ensure 'log_duration' database flag for Cloud SQL PostgreSQL instance is set to 'on' " - - desc 'Enabling the log_duration setting causes the duration of each completed statement to be - logged. This does not logs the text of the query and thus behaves different from the - log_min_duration_statement flag. This parameter cannot be changed after session start.' - desc 'rationale', "Monitoring the time taken to execute the queries can be crucial in identifying any resource - hogging queries and assessing the performance of the server. Further steps such as load - balancing and use of optimized queries can be taken to ensure the performance and - stability of the server. This recommendation is applicable to PostgreSQL database - instances." + title "[#{control_abbrev.upcase}] Ensure 'Log_statement’ database flag for cloud SQL PostgreSQL instance is set appropriately" + + desc "The value of log_statement flag determined the SQL statements that are logged. Valid values are: + • none + • ddl + • mod + • all + The value ddl logs all data definition statements. The value mod logs all ddl statements, plus data-modifying statements. + The statements are logged after a basic parsing is done and statement type is determined, thus this does not logs statements + with errors. When using extended query protocol, logging occurs after an Execute message is received and values of the Bind parameters are included. + A value of 'ddl' is recommended unless otherwise directed by your organizations logging policy." + desc 'rationale', "Auditing helps in forensic analysis. If log_statement is not set to the correct value, too many statements may be + logged leading to issues in finding the relevant information from the logs, or too few statements may be logged with relevant information + missing from the logs. Setting log_statement to align with your organizations security and logging policies facilitates later auditing and + review of database activities. This recommendation is applicable to PostgreSQL database instances." tag cis_scored: true - tag cis_level: 1 + tag cis_level: 2 tag cis_gcp: sub_control_id.to_s tag cis_version: cis_version.to_s tag project: gcp_project_id.to_s @@ -286,7 +238,7 @@ ref 'CIS Benchmark', url: cis_url.to_s ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/postgres/flags' - ref 'GCP Docs', url: 'https://www.postgresql.org/docs/9.6/runtime-config-logging.html#GUC-LOG-MIN-DURATION-STATEMENT' + ref 'GCP Docs', url: 'https://www.postgresql.org/docs/current/runtime-config-logging.html#GUC-LOG-STATEMENT' sql_instance_names.each do |db| if sql_cache.instance_objects[db].database_version.include? 'POSTGRES' @@ -322,90 +274,33 @@ end end -# 6.2.6 -sub_control_id = "#{control_id}.6" -control "cis-gcp-#{sub_control_id}-#{control_abbrev}" do - impact 'none' - - title "[#{control_abbrev.upcase}] Ensure that the 'log_lock_waits' database flag for Cloud SQL PostgreSQL instance is set to 'on'" - - desc 'Enabling the log_lock_waits flag for a PostgreSQL instance creates a log for any session waits that take longer than the alloted deadlock_timeout time to acquire a lock.' - desc 'rationale', "The deadlock timeout defines the time to wait on a lock before checking for any conditions. Frequent run overs on deadlock timeout can be an indication of an - underlying issue. Logging such waits on locks by enabling the log_lock_waits flag can be used to identify poor performance due to locking delays or if a - specially-crafted SQL is attempting to starve resources through holding locks for excessive amounts of time." - - tag cis_scored: true - tag cis_level: 1 - tag cis_gcp: sub_control_id.to_s - tag cis_version: cis_version.to_s - tag project: gcp_project_id.to_s - tag nist: ['AU-3'] - - ref 'CIS Benchmark', url: cis_url.to_s - ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/postgres/flags#setting_a_database_flag' - ref 'GCP Docs', url: 'https://www.postgresql.org/docs/9.6/runtime-config-logging.html#RUNTIME-CONFIG-LOGGING-WHAT' - - sql_instance_names.each do |db| - if sql_cache.instance_objects[db].database_version.include? 'POSTGRES' - impact 'medium' - if sql_cache.instance_objects[db].settings.database_flags.nil? - describe "[#{gcp_project_id} , #{db} ] does not any have database flags." do - subject { false } - it { should be true } - end - else - describe.one do - sql_cache.instance_objects[db].settings.database_flags.each do |flag| - next unless flag.name == 'log_lock_waits' - describe "[#{gcp_project_id} , #{db} ] should have a database flag 'log_lock_waits' set to 'on' " do - subject { flag } - its('name') { should cmp 'log_lock_waits' } - its('value') { should cmp 'on' } - end - end - end - end - else - describe "[#{gcp_project_id}] [#{db}] is not a PostgreSQL database. This test is Not Applicable." do - skip "[#{gcp_project_id}] [#{db}] is not a PostgreSQL database" - end - end - end - - if sql_instance_names.empty? - describe "[#{gcp_project_id}] does not have CloudSQL instances. This test is Not Applicable." do - skip "[#{gcp_project_id}] does not have CloudSQL instances." - end - end -end - -# 6.2.7 -sub_control_id = "#{control_id}.7" +# 6.2.4 +sub_control_id = "#{control_id}.4" control "cis-gcp-#{sub_control_id}-#{control_abbrev}" do impact 'none' title "[#{control_abbrev.upcase}] Ensure 'log_statement' database flag for Cloud SQL PostgreSQL instance is set appropriately" desc 'The value of log_statement flag determined the SQL statements that are logged. Valid - values are: - - none - - ddl - - mod - - all - The value ddl logs all data definition statements. The value mod logs all ddl statements, plus - data-modifying statements. - The statements are logged after a basic parsing is done and statement type is determined, - thus this does not logs statements with errors. When using extended query protocol, - logging occurs after an Execute message is received and values of the Bind parameters are - included. - A value of \'ddl\' is recommended unless otherwise directed by your organization\'s logging - policy.' + values are: + - none + - ddl + - mod + - all + The value ddl logs all data definition statements. The value mod logs all ddl statements, plus + data-modifying statements. + The statements are logged after a basic parsing is done and statement type is determined, + thus this does not logs statements with errors. When using extended query protocol, + logging occurs after an Execute message is received and values of the Bind parameters are + included. + A value of \'ddl\' is recommended unless otherwise directed by your organization\'s logging + policy.' desc 'rationale', 'Auditing helps in troubleshooting operational problems and also permits forensic analysis. - If log_statement is not set to the correct value, too many statements may be logged leading - to issues in finding the relevant information from the logs, or too few statements may be - logged with relevant information missing from the logs. Setting log_statement to align with - your organization\'s security and logging policies facilitates later auditing and review of - database activities. This recommendation is applicable to PostgreSQL database instances.' + If log_statement is not set to the correct value, too many statements may be logged leading + to issues in finding the relevant information from the logs, or too few statements may be + logged with relevant information missing from the logs. Setting log_statement to align with + your organization\'s security and logging policies facilitates later auditing and review of + database activities. This recommendation is applicable to PostgreSQL database instances.' tag cis_scored: true tag cis_level: 1 tag cis_gcp: sub_control_id.to_s @@ -451,338 +346,21 @@ end end -# 6.2.8 -sub_control_id = "#{control_id}.8" -control "cis-gcp-#{sub_control_id}-#{control_abbrev}" do - impact 'none' - - title "[#{control_abbrev.upcase}] Ensure 'log_hostname' database flag for Cloud SQL PostgreSQL instance is set appropriately" - - desc 'PostgreSQL logs only the IP address of the connecting hosts. The log_hostname flag - controls the logging of hostnames in addition to the IP addresses logged. The performance - hit is dependent on the configuration of the environment and the host name resolution - setup. This parameter can only be set in the postgresql.conf file or on the server - command line.' - desc 'rationale', 'Logging hostnames can incur overhead on server performance as for each statement - logged, DNS resolution will be required to convert IP address to hostname. Depending on - the setup, this may be non-negligible. Additionally, the IP addresses that are logged can be - resolved to their DNS names later when reviewing the logs excluding the cases where - dynamic hostnames are used. This recommendation is applicable to PostgreSQL database - instances.' - tag cis_scored: true - tag cis_level: 1 - tag cis_gcp: sub_control_id.to_s - tag cis_version: cis_version.to_s - tag project: gcp_project_id.to_s - tag nist: ['AU-3'] - - ref 'CIS Benchmark', url: cis_url.to_s - ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/postgres/flags' - ref 'GCP Docs', url: 'https://www.postgresql.org/docs/current/runtime-config-logging.html#RUNTIME-CONFIG-LOGGING-WHAT' - - sql_instance_names.each do |db| - if sql_cache.instance_objects[db].database_version.include? 'POSTGRES' - impact 'medium' - if sql_cache.instance_objects[db].settings.database_flags.nil? - describe "[#{gcp_project_id} , #{db} ] does not any have database flags." do - subject { false } - it { should be true } - end - else - describe.one do - sql_cache.instance_objects[db].settings.database_flags.each do |flag| - next unless flag.name == 'log_hostname' - describe "[#{gcp_project_id} , #{db} ] should have a database flag 'log_hostname' set to #{log_hostname} " do - subject { flag } - its('name') { should cmp 'log_hostname' } - its('value') { should cmp log_hostname } - end - end - end - end - else - describe "[#{gcp_project_id}] [#{db}] is not a PostgreSQL database. This test is Not Applicable." do - skip "[#{gcp_project_id}] [#{db}] is not a PostgreSQL database" - end - end - end - - if sql_instance_names.empty? - describe "[#{gcp_project_id}] does not have CloudSQL instances. This test is Not Applicable." do - skip "[#{gcp_project_id}] does not have CloudSQL instances." - end - end -end - -# 6.2.9 -sub_control_id = "#{control_id}.9" -control "cis-gcp-#{sub_control_id}-#{control_abbrev}" do - impact 'none' - - title "[#{control_abbrev.upcase}] Ensure 'log_parser_stats' database flag for Cloud SQL PostgreSQL - instance is set to 'off'" - - desc 'The PostgreSQL planner/optimizer is responsible to parse and verify the syntax of each - query received by the server. If the syntax is correct a parse tree is built up else an error - is generated. The log_parser_stats flag controls the inclusion of parser performance - statistics in the PostgreSQL logs for each query.' - desc 'rationale', 'The log_parser_stats flag enables a crude profiling method for logging parser - performance statistics which even though can be useful for troubleshooting, it may - increase the amount of logs significantly and have performance overhead. This - recommendation is applicable to PostgreSQL database instances.' - tag cis_scored: true - tag cis_level: 1 - tag cis_gcp: sub_control_id.to_s - tag cis_version: cis_version.to_s - tag project: gcp_project_id.to_s - tag nist: ['AU-3'] - - ref 'CIS Benchmark', url: cis_url.to_s - ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/postgres/flags' - ref 'GCP Docs', url: 'https://www.postgresql.org/docs/current/runtime-config-logging.html#RUNTIME-CONFIG-LOGGING-WHAT' - ref 'GCP Docs', url: 'https://www.postgresql.org/docs/10/parser-stage.html' - - sql_instance_names.each do |db| - if sql_cache.instance_objects[db].database_version.include? 'POSTGRES' - impact 'medium' - if sql_cache.instance_objects[db].settings.database_flags.nil? - describe "[#{gcp_project_id} , #{db} ] does not any have database flags." do - subject { false } - it { should be true } - end - else - describe.one do - sql_cache.instance_objects[db].settings.database_flags.each do |flag| - next unless flag.name == 'log_parser_stats' - describe "[#{gcp_project_id} , #{db} ] should have a database flag 'log_parser_stats' set to 'off' " do - subject { flag } - its('name') { should cmp 'log_parser_stats' } - its('value') { should cmp 'off' } - end - end - end - end - else - describe "[#{gcp_project_id}] [#{db}] is not a PostgreSQL database. This test is Not Applicable." do - skip "[#{gcp_project_id}] [#{db}] is not a PostgreSQL database" - end - end - end - - if sql_instance_names.empty? - describe "[#{gcp_project_id}] does not have CloudSQL instances. This test is Not Applicable." do - skip "[#{gcp_project_id}] does not have CloudSQL instances." - end - end -end - -# 6.2.10 -sub_control_id = "#{control_id}.10" -control "cis-gcp-#{sub_control_id}-#{control_abbrev}" do - impact 'none' - - title "[#{control_abbrev.upcase}] Ensure 'log_planner_stats' database flag for Cloud SQL - PostgreSQL instance is set to 'off'" - - desc 'The same SQL query can be excuted in multiple ways and still produce different results. - The PostgreSQL planner/optimizer is responsible to create an optimal execution plan for - each query. The log_planner_stats flag controls the inclusion of PostgreSQL planner - performance statistics in the PostgreSQL logs for each query.' - desc 'rationale', 'The log_planner_stats flag enables a crude profiling method for logging PostgreSQL - planner performance statistics which even though can be useful for troubleshooting, it may - increase the amount of logs significantly and have performance overhead. This - recommendation is applicable to PostgreSQL database instances.' - tag cis_scored: true - tag cis_level: 1 - tag cis_gcp: sub_control_id.to_s - tag cis_version: cis_version.to_s - tag project: gcp_project_id.to_s - tag nist: ['AU-3'] - - ref 'CIS Benchmark', url: cis_url.to_s - ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/postgres/flags' - ref 'GCP Docs', url: 'https://www.postgresql.org/docs/9.6/runtime-config-statistics.html#RUNTIME-CONFIG-STATISTICS-MONITOR' - ref 'GCP Docs', url: 'https://www.postgresql.org/docs/9.5/planner-optimizer.html' - - sql_instance_names.each do |db| - if sql_cache.instance_objects[db].database_version.include? 'POSTGRES' - impact 'medium' - if sql_cache.instance_objects[db].settings.database_flags.nil? - describe "[#{gcp_project_id} , #{db} ] does not any have database flags." do - subject { false } - it { should be true } - end - else - describe.one do - sql_cache.instance_objects[db].settings.database_flags.each do |flag| - next unless flag.name == 'log_planner_stats' - describe "[#{gcp_project_id} , #{db} ] should have a database flag 'log_planner_stats' set to 'off' " do - subject { flag } - its('name') { should cmp 'log_planner_stats' } - its('value') { should cmp 'off' } - end - end - end - end - else - describe "[#{gcp_project_id}] [#{db}] is not a PostgreSQL database. This test is Not Applicable." do - skip "[#{gcp_project_id}] [#{db}] is not a PostgreSQL database" - end - end - end - - if sql_instance_names.empty? - describe "[#{gcp_project_id}] does not have CloudSQL instances. This test is Not Applicable." do - skip "[#{gcp_project_id}] does not have CloudSQL instances." - end - end -end - -# 6.2.11 -sub_control_id = "#{control_id}.11" -control "cis-gcp-#{sub_control_id}-#{control_abbrev}" do - impact 'medium' - - title "[#{control_abbrev.upcase}] Ensure 'log_executor_stats' database flag for Cloud SQL - PostgreSQL instance is set to 'off'" - - desc 'The PostgreSQL executor is responsible to execute the plan handed over by the PostgreSQL - planner. The executor processes the plan recursively to extract the required set of rows. - The log_executor_stats flag controls the inclusion of PostgreSQL executor performance - statistics in the PostgreSQL logs for each query.' - desc 'rationale', 'The log_executor_stats flag enables a crude profiling method for logging PostgreSQL - executor performance statistics which even though can be useful for troubleshooting, it - may increase the amount of logs significantly and have performance overhead. This - recommendation is applicable to PostgreSQL database instances.' - tag cis_scored: true - tag cis_level: 1 - tag cis_gcp: sub_control_id.to_s - tag cis_version: cis_version.to_s - tag project: gcp_project_id.to_s - tag nist: ['AU-3'] - - ref 'CIS Benchmark', url: cis_url.to_s - ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/postgres/flags' - ref 'GCP Docs', url: 'https://www.postgresql.org/docs/current/runtime-config-logging.html#RUNTIME-CONFIG-LOGGING-WHAT' - ref 'GCP Docs', url: 'https://www.postgresql.org/docs/8.2/executor.html' - - sql_instance_names.each do |db| - if sql_cache.instance_objects[db].database_version.include? 'POSTGRES' - impact 'medium' - if sql_cache.instance_objects[db].settings.database_flags.nil? - describe "[#{gcp_project_id} , #{db} ] does not any have database flags." do - subject { false } - it { should be true } - end - else - describe.one do - sql_cache.instance_objects[db].settings.database_flags.each do |flag| - next unless flag.name == 'log_executor_stats' - describe "[#{gcp_project_id} , #{db} ] should have a database flag 'log_executor_stats' set to 'off' " do - subject { flag } - its('name') { should cmp 'log_executor_stats' } - its('value') { should cmp 'off' } - end - end - end - end - else - describe "[#{gcp_project_id}] [#{db}] is not a PostgreSQL database. This test is Not Applicable." do - skip "[#{gcp_project_id}] [#{db}] is not a PostgreSQL database" - end - end - end - - if sql_instance_names.empty? - impact 'none' - describe "[#{gcp_project_id}] does not have CloudSQL instances. This test is Not Applicable." do - skip "[#{gcp_project_id}] does not have CloudSQL instances." - end - end -end - -# 6.2.12 -sub_control_id = "#{control_id}.12" -control "cis-gcp-#{sub_control_id}-#{control_abbrev}" do - impact 'none' - - title "[#{control_abbrev.upcase}] Ensure 'log_statement_stats' database flag for Cloud SQL - PostgreSQL instance is set to 'off'" - - desc 'The log_statement_stats flag controls the inclusion of end to end performance statistics - of a SQL query in the PostgreSQL logs for each query. This cannot be enabled with other - module statistics (log_parser_stats, log_planner_stats, log_executor_stats).' - desc 'rationale', 'The log_statement_stats flag enables a crude profiling method for logging end to end - performance statistics of a SQL query. This can be useful for troubleshooting but may - increase the amount of logs significantly and have performance overhead. This - recommendation is applicable to PostgreSQL database instances.' - tag cis_scored: true - tag cis_level: 1 - tag cis_gcp: sub_control_id.to_s - tag cis_version: cis_version.to_s - tag project: gcp_project_id.to_s - tag nist: ['AU-3'] - - ref 'CIS Benchmark', url: cis_url.to_s - ref 'GCP Docs', url: 'https://www.postgresql.org/docs/9.6/runtime-config-statistics.html#RUNTIME-CONFIG-STATISTICS-MONITOR' - ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/postgres/flags' - - sql_instance_names.each do |db| - if sql_cache.instance_objects[db].database_version.include? 'POSTGRES' - impact 'medium' - if sql_cache.instance_objects[db].settings.database_flags.nil? - describe "[#{gcp_project_id} , #{db} ] does not any have database flags." do - subject { false } - it { should be true } - end - else - describe.one do - sql_cache.instance_objects[db].settings.database_flags.each do |flag| - next unless flag.name == 'log_statement_stats' - describe "[#{gcp_project_id} , #{db} ] should have a database flag 'log_statement_stats' set to 'off' " do - subject { flag } - its('name') { should cmp 'log_statement_stats' } - its('value') { should cmp 'off' } - end - end - end - end - else - describe "[#{gcp_project_id}] [#{db}] is not a PostgreSQL database. This test is Not Applicable." do - skip "[#{gcp_project_id}] [#{db}] is not a PostgreSQL database" - end - end - end - - if sql_instance_names.empty? - describe "[#{gcp_project_id}] does not have CloudSQL instances. This test is Not Applicable." do - skip "[#{gcp_project_id}] does not have CloudSQL instances." - end - end -end - -# 6.2.13 -sub_control_id = "#{control_id}.13" +# 6.2.5 +sub_control_id = "#{control_id}.5" control "cis-gcp-#{sub_control_id}-#{control_abbrev}" do impact 'none' - title "[#{control_abbrev.upcase}] Ensure that the 'log_min_messages' database flag for Cloud SQL - PostgreSQL instance is set appropriately" + title "[#{control_abbrev.upcase}] Ensure that the 'Log_min_messages’ flag for a cloud SQL PostgreSQL instance is set at minimum to 'warning'" - desc 'The log_min_messages flag defines the minimum message severity level that is considered - as an error statement. Messages for error statements are logged with the SQL statement. - Valid values include DEBUG5, DEBUG4, DEBUG3, DEBUG2, DEBUG1, INFO, NOTICE, WARNING, ERROR, - LOG, FATAL, and PANIC. Each severity level includes the subsequent levels mentioned above. - Note: To effectively turn off logging failing statements, set this parameter to PANIC. - ERROR is considered the best practice setting. Changes should only be made in accordance - with the organization\'s logging policy.' - desc 'rationale', 'Auditing helps in troubleshooting operational problems and also permits forensic analysis. - If log_min_error_statement is not set to the correct value, messages may not be classified - as error messages appropriately. Considering general log messages as error messages - would make it difficult to find actual errors, while considering only stricter severity levels - as error messages may skip actual errors to log their SQL statements. The - log_min_messages flag should be set in accordance with the organization\'s logging policy. - This recommendation is applicable to PostgreSQL database instances.' + desc 'The log_min_messages flag defines the minimum message severity level that is considered as an error statement. Messages for error statements + are logged with the SQL statement. Valid values include (from lowest to highest severity) DEBUG5, DEBUG4, DEBUG3, DEBUG2, DEBUG1, INFO, NOTICE, + WARNING, ERROR, LOG, FATAL, and PANIC. Each severity level includes the subsequent levels mentioned above. ERROR is considered the best practice + setting. Changes should only be made in accordance with the organizations logging policy' + desc 'rationale', "Auditing helps in troubleshooting operational problems and also permits forensic analysis. If log_min_messages is not set to the + correct value, messages may not be classified as error messages appropriately. Setting the threshold to 'Warning' will log messages for + the most needed error messages. + This recommendation is applicable to PostgreSQL database instances." tag cis_scored: true tag cis_level: 1 tag cis_gcp: sub_control_id.to_s @@ -792,7 +370,7 @@ module statistics (log_parser_stats, log_planner_stats, log_executor_stats).' ref 'CIS Benchmark', url: cis_url.to_s ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/postgres/flags' - ref 'GCP Docs', url: 'https://www.postgresql.org/docs/9.6/runtime-config-logging.html#RUNTIME-CONFIG-LOGGING-WHEN' + ref 'GCP Docs', url: 'https://www.postgresql.org/docs/current/runtime-config-logging.html#GUC-LOG-MIN-MESSAGES' sql_instance_names.each do |db| if sql_cache.instance_objects[db].database_version.include? 'POSTGRES' @@ -828,26 +406,23 @@ module statistics (log_parser_stats, log_planner_stats, log_executor_stats).' end end -# 6.2.14 -sub_control_id = "#{control_id}.14" +# 6.2.6 +sub_control_id = "#{control_id}.6" control "cis-gcp-#{sub_control_id}-#{control_abbrev}" do impact 'none' - title "[#{control_abbrev.upcase}] Ensure 'log_min_error_statement' database flag for Cloud SQL - PostgreSQL instance is set to 'Error' or stricter" - - desc 'The log_min_error_statement flag defines the minimum message severity level that are - considered as an error statement. Messages for error statements are logged with the SQL - statement. Valid values include DEBUG5, DEBUG4, DEBUG3, DEBUG2, DEBUG1, INFO, NOTICE, - WARNING, ERROR, LOG, FATAL, and PANIC. Each severity level includes the subsequent levels - mentioned above. Ensure a value of ERROR or stricter is set.' - desc 'rationale', 'Auditing helps in troubleshooting operational problems and also permits forensic analysis. - If log_min_error_statement is not set to the correct value, messages may not be classified - as error messages appropriately. Considering general log messages as error messages - would make is difficult to find actual errors and considering only stricter severity levels as - error messages may skip actual errors to log their SQL statements. The - log_min_error_statement flag should be set to ERROR or stricter. This recommendation is - applicable to PostgreSQL database instances.' + title "[#{control_abbrev.upcase}] Ensure 'Log_min_error_statement’ database flag for cloud SQL PostgreSQL instance + is set to 'Error’ or stricter" + + desc 'The log_min_error_statement flag defines the minimum message severity level that are considered as an error statement. + Messages for error statements are logged with the SQL statement. Valid values include (from lowest to highest severity) DEBUG5, + DEBUG4, DEBUG3, DEBUG2, DEBUG1, INFO, NOTICE, WARNING, ERROR, LOG, FATAL, and PANIC. Each severity level includes the subsequent + levels mentioned above. Ensure a value of ERROR or stricter is set.' + desc 'rationale', 'Auditing helps in troubleshooting operational problems and also permits forensic analysis. If log_min_error_statement is + not set to the correct value, messages may not be classified as error messages appropriately. Considering general log messages as error + messages would make is difficult to find actual errors and considering only stricter severity levels as error messages may skip actual + errors to log their SQL statements. The log_min_error_statement flag should be set to ERROR or stricter. + This recommendation is applicable to PostgreSQL database instances.' tag cis_scored: true tag cis_level: 1 tag cis_gcp: sub_control_id.to_s @@ -857,7 +432,7 @@ module statistics (log_parser_stats, log_planner_stats, log_executor_stats).' ref 'CIS Benchmark', url: cis_url.to_s ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/postgres/flags' - ref 'GCP Docs', url: 'https://www.postgresql.org/docs/9.6/runtime-config-logging.html#RUNTIME-CONFIG-LOGGING-WHEN' + ref 'GCP Docs', url: 'https://www.postgresql.org/docs/current/runtime-config-logging.html#GUC-LOG-MIN-ERROR-STATEMENT' sql_instance_names.each do |db| if sql_cache.instance_objects[db].database_version.include? 'POSTGRES' @@ -894,23 +469,19 @@ module statistics (log_parser_stats, log_planner_stats, log_executor_stats).' end end -# 6.2.15 -sub_control_id = "#{control_id}.15" +# 6.2.7 +sub_control_id = "#{control_id}.7" control "cis-gcp-#{sub_control_id}-#{control_abbrev}" do impact 'none' - title "[#{control_abbrev.upcase}] Ensure that the 'log_temp_files' database flag for Cloud SQL - PostgreSQL instance is set to '0' (on)" - - desc 'PostgreSQL can create a temporary file for actions such as sorting, hashing and temporary - query results when these operations exceed work_mem. The log_temp_files flag controls - logging names and the file size when it is deleted. Configuring log_temp_files to 0 causes - all temporary file information to be logged, while positive values log only files whose size is - greater than or equal to the specified number of kilobytes. A value of -1 disables temporary - file information logging.' - desc 'rationale', 'If all temporary files are not logged, it may be more difficult to identify potential - performance issues that may be due to either poor application coding or deliberate - resource starvation attempts.' + title "[#{control_abbrev.upcase}] Ensure That the 'Log_min_duration_statement’ Database Flag for Cloud SQL PostgreSQL + Instance Is Set to '-1' (Disabled)" + + desc 'The log_min_duration_statement flag defines the minimum amount of execution time of a statement in milliseconds + where the total duration of the statement is logged. Ensure that log_min_duration_statement is disabled, i.e., a + value of -1 is set.' + desc 'rationale', 'Logging SQL statements may include sensitive information that should not be recorded in logs. This + recommendation is applicable to PostgreSQL database instances.' tag cis_scored: true tag cis_level: 1 tag cis_gcp: sub_control_id.to_s @@ -919,8 +490,8 @@ module statistics (log_parser_stats, log_planner_stats, log_executor_stats).' tag nist: ['AU-3'] ref 'CIS Benchmark', url: cis_url.to_s - ref 'GCP Docs', url: 'https://www.postgresql.org/docs/9.6/runtime-config-statistics.html#RUNTIME-CONFIG-STATISTICS-MONITOR' ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/postgres/flags' + ref 'GCP Docs', url: 'https://www.postgresql.org/docs/current/runtime-config-logging.html#GUC-LOG-MIN-DURATION-STATEMENT' sql_instance_names.each do |db| if sql_cache.instance_objects[db].database_version.include? 'POSTGRES' @@ -933,11 +504,11 @@ module statistics (log_parser_stats, log_planner_stats, log_executor_stats).' else describe.one do sql_cache.instance_objects[db].settings.database_flags.each do |flag| - next unless flag.name == 'log_temp_files' - describe "[#{gcp_project_id} , #{db} ] should have a database flag 'log_temp_files' set to '0' " do + next unless flag.name == 'log_min_duration_statement' + describe "[#{gcp_project_id} , #{db} ] should have a database flag 'log_min_duration_statement' set to '-1' " do subject { flag } - its('name') { should cmp 'log_temp_files' } - its('value') { should cmp '0' } + its('name') { should cmp 'log_min_duration_statement' } + its('value') { should cmp '-1' } end end end @@ -956,19 +527,23 @@ module statistics (log_parser_stats, log_planner_stats, log_executor_stats).' end end -# 6.2.16 -sub_control_id = "#{control_id}.16" +# 6.2.8 +sub_control_id = "#{control_id}.8" control "cis-gcp-#{sub_control_id}-#{control_abbrev}" do impact 'none' - title "[#{control_abbrev.upcase}] Ensure that the 'log_min_duration_statement' database flag for - Cloud SQL PostgreSQL instance is set to '-1' (disabled)" - - desc 'The log_min_duration_statement flag defines the minimum amount of execution time of a - statement in milliseconds where the total duration of the statement is logged. Ensure that - log_min_duration_statement is disabled, i.e., a value of -1 is set.' - desc 'rationale', 'Logging SQL statements may include sensitive information that should not be recorded in - logs. This recommendation is applicable to PostgreSQL database instances.' + title "[#{control_abbrev.upcase}] Ensure That 'cloudsql.enable_pgaudit' Database Flag for each Cloud Sql Postgresql + Instance Is Set to 'on' For Centralized Logging" + + desc 'Ensure cloudsql.enable_pgaudit database flag for Cloud SQL PostgreSQL instance is set to on to allow for centralized logging.' + desc 'rationale', 'As numerous other recommendations in this section consist of turning on flags for logging purposes, your organization + will need a way to manage these logs. You may have a solution already in place. If you do not, consider installing and enabling the + open source pgaudit extension within PostgreSQL and enabling its corresponding flag of cloudsql.enable_pgaudit. This flag and + installing the extension enables database auditing in PostgreSQL through the open-source pgAudit extension. This extension provides + detailed session and object logging to comply with government, financial, & ISO standards and provides auditing capabilities to + mitigate threats by monitoring security events on the instance. Enabling the flag and settings later in this recommendation will send + these logs to Google Logs Explorer so that you can access them in a central location. to This recommendation is applicable only to + PostgreSQL database instances.' tag cis_scored: true tag cis_level: 1 tag cis_gcp: sub_control_id.to_s @@ -977,8 +552,10 @@ module statistics (log_parser_stats, log_planner_stats, log_executor_stats).' tag nist: ['AU-3'] ref 'CIS Benchmark', url: cis_url.to_s - ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/postgres/flags' - ref 'GCP Docs', url: 'https://www.postgresql.org/docs/current/runtime-config-logging.html#RUNTIME-CONFIG-LOGGING-WHAT' + ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/postgres/flags#list-flags-postgres' + ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/postgres/pg-audit#enable-auditing-flag' + ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/postgres/pg-audit#customizing-database-audit-logging' + ref 'GCP Docs', url: 'https://cloud.google.com/logging/docs/audit/configure-data-access#config-console-enable' sql_instance_names.each do |db| if sql_cache.instance_objects[db].database_version.include? 'POSTGRES' @@ -991,11 +568,11 @@ module statistics (log_parser_stats, log_planner_stats, log_executor_stats).' else describe.one do sql_cache.instance_objects[db].settings.database_flags.each do |flag| - next unless flag.name == 'log_min_duration_statement' - describe "[#{gcp_project_id} , #{db} ] should have a database flag 'log_min_duration_statement' set to '-1' " do + next unless flag.name == 'enable_pgaudit' + describe "[#{gcp_project_id} , #{db} ] should have a database flag 'enable_pgaudit' set to 'on'" do subject { flag } - its('name') { should cmp 'log_min_duration_statement' } - its('value') { should cmp '-1' } + its('name') { should cmp 'enable_pgaudit' } + its('value') { should cmp 'on' } end end end diff --git a/controls/6.03-db.rb b/controls/6.03-db.rb index bfc1a1c..81ac649 100644 --- a/controls/6.03-db.rb +++ b/controls/6.03-db.rb @@ -28,15 +28,13 @@ control "cis-gcp-#{sub_control_id}-#{control_abbrev}" do impact 'none' - title "[#{control_abbrev.upcase}] Ensure 'external scripts enabled' database flag for Cloud SQL SQL Server instance is set to 'off'" + title "[#{control_abbrev.upcase}] Ensure 'external scripts enabled' Database Flag for Cloud SQL SQL Server Instance Is Set to 'off'" desc 'It is recommended to set external scripts enabled database flag for Cloud SQL SQL Server instance to off' - desc 'rationale', 'external scripts enabled enable the execution of scripts with certain remote language - extensions. This property is OFF by default. When Advanced Analytics Services is installed, - setup can optionally set this property to true. As the External Scripts Enabled feature - allows scripts external to SQL such as files located in an R library to be executed, which - could adversely affect the security of the system, hence this should be disabled.This - recommendation is applicable to SQL Server database instances.' + desc 'rationale', 'external scripts enabled enable the execution of scripts with certain remote language extensions. This property is + OFF by default. When Advanced Analytics Services is installed, setup can optionally set this property to true. As the External Scripts + Enabled feature allows scripts external to SQL such as files located in an R library to be executed, which could adversely affect the + security of the system, hence this should be disabled. This recommendation is applicable to SQL Server database instances.' tag cis_scored: true tag cis_level: 1 @@ -49,6 +47,7 @@ ref 'GCP Docs', url: 'https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/external-scripts-enabled-server-configuration-option?view=sql-server-ver15' ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/sqlserver/flags' ref 'GCP Docs', url: 'https://docs.microsoft.com/en-us/sql/advanced-analytics/concepts/security?view=sql-server-ver15' + ref 'GCP Docs', url: 'https://www.stigviewer.com/stig/ms_sql_server_2016_instance/2018-03-09/finding/V-79347' sql_instance_names.each do |db| if sql_cache.instance_objects[db].database_version.include? 'SQLSERVER' @@ -89,10 +88,17 @@ control "cis-gcp-#{sub_control_id}-#{control_abbrev}" do impact 'none' - title "[#{control_abbrev.upcase}] Ensure that the 'cross db ownership chaining' database flag for Cloud SQL Server instance is set to 'off'" + title "[#{control_abbrev.upcase}] Ensure 'cross db ownership chaining' Database Flag for Cloud SQL SQL Server Instance Is Set to 'off'" - desc 'It is recommended to set cross db ownership chaining database flag for Cloud SQL SQL Server instance to off. ' - desc 'rationale', 'Use the cross db ownership for chaining option to configure cross-database ownership chaining for an instance of Microsoft SQL Server. This server option allows you to control cross-database ownership chaining at the database level or to allow cross-database ownership chaining for all databases.Enabling cross db ownership is not recommended unless all of the databases hosted by the instance of SQL Server must participate in cross-database ownership chaining and you are aware of the security implications of this setting' + desc 'It is recommended to set cross db ownership chaining database flag for Cloud SQL SQL Server instance to off. + This flag is deprecated for all SQL Server versions in CGP. Going forward, you cant set its value to on. However, if you have this + flag enabled, we strongly recommend that you either remove the flag from your database or set it to off. For cross-database access, + use the Microsoft tutorial for signing stored procedures with a certificate.' + desc 'rationale', 'Use the cross db ownership for chaining option to configure cross-database ownership chaining for an instance of Microsoft + SQL Server. This server option allows you to control cross-database ownership chaining at the database level or to allow cross-database + ownership chaining for all databases. Enabling cross db ownership is not recommended unless all of the databases hosted by the instance + of SQL Server must participate in cross-database ownership chaining and you are aware of the security implications of this setting. This + recommendation is applicable to SQL Server database instances.' tag cis_scored: true tag cis_level: 1 @@ -145,20 +151,17 @@ control "cis-gcp-#{sub_control_id}-#{control_abbrev}" do impact 'none' - title "[#{control_abbrev.upcase}] Ensure 'user connections' database flag for Cloud SQL SQL Server instance is set as appropriate" - - desc 'It is recommended to set user connections database flag for Cloud SQL SQL Server - instance according organization-defined value.' - desc 'rationale', 'The user connections option specifies the maximum number of simultaneous user - connections that are allowed on an instance of SQL Server. The actual number of user - connections allowed also depends on the version of SQL Server that you are using, and also - the limits of your application or applications and hardware. SQL Server allows a maximum - of 32,767 user connections. Because user connections is a dynamic (self-configuring) - option, SQL Server adjusts the maximum number of user connections automatically as - needed, up to the maximum value allowable. For example, if only 10 users are logged in, 10 - user connection objects are allocated. In most cases, you do not have to change the value - for this option. The default is 0, which means that the maximum (32,767) user connections - are allowed. This recommendation is applicable to SQL Server database instances.' + title "[#{control_abbrev.upcase}] Ensure 'user Connections' Database Flag for Cloud SQL SQL Server Instance Is Set to a Non-limiting Value" + + desc 'It is recommended to check the user connections for a Cloud SQL SQL Server instance to ensure that it is not artificially limiting connections.' + desc 'rationale', 'The user connections option specifies the maximum number of simultaneous user connections that are allowed on an instance of SQL + Server. The actual number of user connections allowed also depends on the version of SQL Server that you are using, and also the limits of your + application or applications and hardware. SQL Server allows a maximum of 32,767 user connections. Because user connections is by default a + self-configuring value, with SQL Server adjusting the maximum number of user connections automatically as needed, up to the maximum value + allowable. For example, if only 10 users are logged in, 10 user connection objects are allocated. In most cases, you do not have to change the + value for this option. The default is 0, which means that the maximum (32,767) user connections are allowed. However if there is a number defined + here that limits connections, SQL Server will not allow anymore above this limit. If the connections are at the limit, any new requests will be + dropped, potentially causing lost data or outages for those using the database.' tag cis_scored: true tag cis_level: 1 @@ -211,18 +214,17 @@ control "cis-gcp-#{sub_control_id}-#{control_abbrev}" do impact 'none' - title "[#{control_abbrev.upcase}] Ensure 'user options' database flag for Cloud SQL SQL Server instance is not configured" + title "[#{control_abbrev.upcase}] Ensure 'user options' Database Flag for Cloud SQL SQL Server Instance Is Not Configured" - desc 'It is recommended that, user options database flag for Cloud SQL SQL Server instance - should not be configured.' - desc 'rationale', 'The user options option specifies global defaults for all users. A list of default query - processing options is established for the duration of a user\'s work session. The user - options option allows you to change the default values of the SET options (if the server\'s - default settings are not appropriate). - A user can override these defaults by using the SET statement. You can configure user - options dynamically for new logins. After you change the setting of user options, new login - sessions use the new setting; current login sessions are not affected. This recommendation - is applicable to SQL Server database instances.' + desc 'The user options option specifies global defaults for all users. A list of default query processing options is established + for the duration of a users work session. The user options option allows you to change the default values of the SET options (if the + servers default settings are not appropriate). + A user can override these defaults by using the SET statement. You can configure user options dynamically for new logins. After you + change the setting of user options, new login sessions use the new setting; current login sessions are not affected. This recommendation + is applicable to SQL Server database instances.' + desc 'rationale', 'It is recommended that, user options database flag for Cloud SQL SQL Server instance should not be configured. + A user can override these defaults set with user options by using the SET statement. Some of these features/options could adversely + affect the security of the system if enabled.' tag cis_scored: true tag cis_level: 1 @@ -274,19 +276,16 @@ control "cis-gcp-#{sub_control_id}-#{control_abbrev}" do impact 'none' - title "[#{control_abbrev.upcase}] Ensure 'remote access' database flag for Cloud SQL SQL Server instance is set to 'off'" + title "[#{control_abbrev.upcase}] Ensure 'remote access' Database Flag for Cloud SQL SQL Server Instance Is Set to 'off'" desc 'It is recommended to set remote access database flag for Cloud SQL SQL Server instance to off.' - desc 'rationale', 'The remote access option controls the execution of stored procedures from local or - remote servers on which instances of SQL Server are running. This default value for this - option is 1. This grants permission to run local stored procedures from remote servers or - remote stored procedures from the local server. To prevent local stored procedures from - being run from a remote server or remote stored procedures from being run on the local - server, this must be disabled. The Remote Access option controls the execution of local - stored procedures on remote servers or remote stored procedures on local server. \'Remote - access\' functionality can be abused to launch a Denial-of-Service (DoS) attack on remote - servers by off-loading query processing to a target, hence this should be disabled. This - recommendation is applicable to SQL Server database instances.' + desc 'rationale', 'The remote access option controls the execution of stored procedures from local or remote servers on + which instances of SQL Server are running. This default value for this option is 1. This grants permission to run local + stored procedures from remote servers or remote stored procedures from the local server. To prevent local stored procedures + from being run from a remote server or remote stored procedures from being run on the local server, this must be disabled. + The Remote Access option controls the execution of local stored procedures on remote servers or remote stored procedures on + local server. Remote access functionality can be abused to launch a Denial-of-Service (DoS) attack on remote servers by + off-loading query processing to a target, hence this should be disabled. This recommendation is applicable to SQL Server database instances.' tag cis_scored: true tag cis_level: 1 @@ -296,8 +295,8 @@ tag nist: ['CM-7'] ref 'CIS Benchmark', url: cis_url.to_s - ref 'GCP Docs', url: 'https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/configure-the-remote-access-server-configuration-option?view=sql-server-ver15' ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/sqlserver/flags' + ref 'GCP Docs', url: 'https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/configure-the-remote-access-server-configuration-option?view=sql-server-ver15' ref 'GCP Docs', url: 'https://www.stigviewer.com/stig/ms_sql_server_2016_instance/2018-03-09/finding/V-79337' sql_instance_names.each do |db| @@ -339,18 +338,17 @@ control "cis-gcp-#{sub_control_id}-#{control_abbrev}" do impact 'none' - title "[#{control_abbrev.upcase}] Ensure '3625 (trace flag)' database flag for Cloud SQL SQL Server instance is set to 'off'" + title "[#{control_abbrev.upcase}] Ensure '3625 (trace flag)' Database Flag for all Cloud SQL SQL Server Instances Is Set to 'on'" - desc 'It is recommended to set 3625 (trace flag) database flag for Cloud SQL SQL Server instance to off.' - desc 'rationale', 'Trace flags are frequently used to diagnose performance issues or to debug stored - procedures or complex computer systems, but they may also be recommended by - Microsoft Support to address behavior that is negatively impacting a specific workload. All - documented trace flags and those recommended by Microsoft Support are fully supported - in a production environment when used as directed. 3625(trace log) Limits the amount - of information returned to users who are not members of the sysadmin fixed server role, - by masking the parameters of some error messages using \'******\'. This can help prevent - disclosure of sensitive information, hence this is recommended to disable this flag. This - recommendation is applicable to SQL Server database instances.' + desc 'It is recommended to set 3625 (trace flag) database flag for Cloud SQL SQL Server instance to on.' + desc 'rationale', 'Microsoft SQL Trace Flags are frequently used to diagnose performance issues or to debug stored procedures or + complex computer systems, but they may also be recommended by Microsoft Support to address behavior that is negatively impacting a + specific workload. All documented trace flags and those recommended by Microsoft Support are fully supported in a production + environment when used as directed. 3625(trace log) Limits the amount of information returned to users who are not members of the + sysadmin fixed server role, by masking the parameters of some error messages using \'******\'. Setting this in a Google Cloud flag for + the instance allows for security through obscurity and prevents the disclosure of sensitive information, hence this is recommended to + set this flag globally to on to prevent the flag having been left off, or changed by bad actors. This recommendation is applicable to + SQL Server database instances.' tag cis_scored: true tag cis_level: 1 @@ -362,6 +360,7 @@ ref 'CIS Benchmark', url: cis_url.to_s ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/sqlserver/flags' ref 'GCP Docs', url: 'https://docs.microsoft.com/en-us/sql/t-sql/database-console-commands/dbcc-traceon-trace-flags-transact-sql?view=sql-server-ver15#trace-flags' + ref 'GCP Docs', url: 'https://github.com/ktaranov/sqlserver-kit/blob/master/SQL%20Server%20Trace%20Flag.md' sql_instance_names.each do |db| if sql_cache.instance_objects[db].database_version.include? 'SQLSERVER' @@ -403,10 +402,20 @@ control "cis-gcp-#{sub_control_id}-#{control_abbrev}" do impact 'none' - title "[#{control_abbrev.upcase}] Ensure that the 'contained database authentication' database flag for Cloud SQL server instance is set to 'off'" - - desc 'It is recommended to set contained database authentication database flag for Cloud SQL on the SQL Server instance is set to off.' - desc 'rationale', 'A contained database includes all database settings and metadata required to define the database and has no configuration dependencies on the instance of the Database Engine where the database is installed. Users can connect to the database without authenticating a login at the Database Engine level. Isolating the database from the Database Engine makes it possible to easily move the database to another instance of SQL Server.' + title "[#{control_abbrev.upcase}] Ensure 'contained database authentication' Database Flag for Cloud SQL SQL Server Instance Is Set to 'off'" + + desc 'A contained database includes all database settings and metadata required to define the database and has no configuration dependencies + on the instance of the Database Engine where the database is installed. Users can connect to the database without authenticating a login + at the Database Engine level. Isolating the database from the Database Engine makes it possible to easily move the database to another + instance of SQL Server. Contained databases have some unique threats that should be understood and mitigated by SQL Server Database Engine + administrators. Most of the threats are related to the USER WITH PASSWORD authentication process, which moves the authentication boundary + from the Database Engine level to the database level, hence this is recommended not to enable this flag. This recommendation is applicable + to SQL Server database instances.' + desc 'rationale', 'When contained databases are enabled, database users with the ALTER ANY USER permission, such as members of the db_owner and + db_accessadmin database roles, can grant access to databases and by doing so, grant access to the instance of SQL Server. This means that + control over access to the server is no longer limited to members of the sysadmin and securityadmin fixed server role, and logins with the + server level CONTROL SERVER and ALTER ANY LOGIN permission. + It is recommended to set contained database authentication database flag for Cloud SQL on the SQL Server instance to off.' tag cis_scored: true tag cis_level: 1 diff --git a/controls/6.04-db.rb b/controls/6.04-db.rb index dbd7aef..c38f425 100644 --- a/controls/6.04-db.rb +++ b/controls/6.04-db.rb @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -title 'Ensure that Cloud SQL database instance requires all incoming connections to use SSL' +title 'Ensure That the Cloud SQL Database Instance Requires All Incoming Connections To Use SSL' gcp_project_id = input('gcp_project_id') cis_version = input('cis_version') @@ -26,10 +26,10 @@ control "cis-gcp-#{control_id}-#{control_abbrev}" do impact 'high' - title "[#{control_abbrev.upcase}] Ensure that Cloud SQL database instance requires all incoming connections to use SSL" + title "[#{control_abbrev.upcase}] Ensure That the Cloud SQL Database Instance Requires All Incoming Connections To Use SSL" desc 'It is recommended to enforce all incoming connections to SQL database instance to use SSL.' - desc 'rationale', 'SQL database connections if successfully trapped (MITM); can reveal sensitive data like credentials, database queries, query outputs etc. For security, it is recommended to always use SSL encryption when connecting to your instance. This recommendation is applicable for Postgresql, MySql generation 1 and MySql generation 2 Instances.' + desc 'rationale', 'SQL database connections if successfully trapped (MITM); can reveal sensitive data like credentials, database queries, query outputs etc. For security, it is recommended to always use SSL encryption when connecting to your instance. This recommendation is applicable for Postgresql, MySql generation 1, MySql generation 2 and SQL Server 2017 instances.' tag cis_scored: true tag cis_level: 1 @@ -39,7 +39,7 @@ tag nist: %w[SC-1 SC-8] ref 'CIS Benchmark', url: cis_url.to_s - ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/postgres/configure-ssl-instance' + ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/postgres/configure-ssl-instance/' if sql_instance_names.empty? impact 'none' diff --git a/controls/6.05-db.rb b/controls/6.05-db.rb index 32689a1..d484325 100644 --- a/controls/6.05-db.rb +++ b/controls/6.05-db.rb @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -title 'Database Server should accept connections only from trusted Network(s)/IP(s) and restrict access from the world' +title 'Ensure That Cloud SQL Database Instances Do Not Implicitly Whitelist All Public IP Addresses' gcp_project_id = input('gcp_project_id') cis_version = input('cis_version') @@ -26,10 +26,12 @@ control "cis-gcp-#{control_id}-#{control_abbrev}" do impact 'medium' - title "[#{control_abbrev.upcase}] Database Server should accept connections only from trusted Network(s)/IP(s) and restrict access from the world" + title "[#{control_abbrev.upcase}] Ensure That Cloud SQL Database Instances Do Not Implicitly Whitelist All Public IP Addresses" - desc 'Database Server should accept connections only from trusted Network(s)/IP(s) and restrict access from the world.' - desc 'rationale', 'To minimize attack surface on a Database server instance, only trusted/known and required IP(s) should be white-listed to connect to it.' + desc 'Database Server should accept connections only from trusted Network(s)/IP(s) and restrict access from public IP addresses.' + desc 'rationale', 'To minimize attack surface on a Database server instance, only trusted/known and required IP(s) should be + white-listed to connect to it. An authorized network should not have IPs/networks configured to \'0.0.0.0/0\' which will allow + access to the instance from anywhere in the world. Note that authorized networks apply only to instances with public IPs.' tag cis_scored: true tag cis_level: 1 @@ -40,6 +42,9 @@ ref 'CIS Benchmark', url: cis_url.to_s ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/mysql/configure-ip' + ref 'GCP Docs', url: 'https://console.cloud.google.com/iam-admin/orgpolicies/sql-restrictAuthorizedNetworks' + ref 'GCP Docs', url: 'https://cloud.google.com/resource-manager/docs/organization-policy/org-policy-constraints' + ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/mysql/connection-org-policy' if sql_instance_names.empty? impact 'none' diff --git a/controls/6.06-db.rb b/controls/6.06-db.rb index 4f1810b..1fb5d4c 100644 --- a/controls/6.06-db.rb +++ b/controls/6.06-db.rb @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -title 'Ensure that Cloud SQL database instances do not have public IPs' +title 'Ensure That Cloud SQL Database Instances Do Not Have Public IPs' gcp_project_id = input('gcp_project_id') cis_version = input('cis_version') @@ -25,10 +25,11 @@ control "cis-gcp-#{control_id}-#{control_abbrev}" do impact 'high' - title "[#{control_abbrev.upcase}] Ensure that Cloud SQL database instances do not have public IPs" + title "[#{control_abbrev.upcase}] Ensure That Cloud SQL Database Instances Do Not Have Public IPs" desc 'It is recommended to configure Second Generation Sql instance to use private IPs instead of public IPs.' - desc 'rationale', "To lower the organization's attack surface, Cloud SQL databases should not have public IPs. Private IPs provide improved network security and lower latency for your application." + desc 'rationale', "To lower the organization's attack surface, Cloud SQL databases should not have public IPs. + Private IPs provide improved network security and lower latency for your application." tag cis_scored: true tag cis_level: 2 @@ -39,6 +40,9 @@ ref 'CIS Benchmark', url: cis_url.to_s ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/mysql/configure-private-ip' + ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/mysql/private-ip' + ref 'GCP Docs', url: 'https://cloud.google.com/resource-manager/docs/organization-policy/org-policy-constraints' + ref 'GCP Docs', url: 'https://console.cloud.google.com/iam-admin/orgpolicies/sql-restrictPublicIp' if sql_cache.instance_names.empty? impact 'none' diff --git a/controls/6.07-db.rb b/controls/6.07-db.rb index b7d9951..9bff569 100644 --- a/controls/6.07-db.rb +++ b/controls/6.07-db.rb @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -title 'Ensure that Cloud SQL database instances are configured with automated backups' +title 'Ensure That Cloud SQL Database Instances Are Configured With Automated Backups' gcp_project_id = input('gcp_project_id') cis_version = input('cis_version') @@ -25,12 +25,13 @@ control "cis-gcp-#{control_id}-#{control_abbrev}" do impact 'medium' - title "[#{control_abbrev.upcase}] Ensure that Cloud SQL database instances are configured with automated backups" + title "[#{control_abbrev.upcase}] Ensure That Cloud SQL Database Instances Are Configured With Automated Backups" desc 'It is recommended to have all SQL database instances set to enable automated backups.' - desc 'rationale', 'Backups provide a way to restore a Cloud SQL instance to recover lost data or recover from a problem - with that instance. Automated backups need to be set for any instance that contains data that should - be protected from loss or damage' + desc 'rationale', 'Backups provide a way to restore a Cloud SQL instance to recover lost data or recover from a + problem with that instance. Automated backups need to be set for any instance that contains data that should be + protected from loss or damage. This recommendation is applicable for SQL Server, PostgreSql, MySql generation 1 + and MySql generation 2 instances.' tag cis_scored: true tag cis_level: 1 @@ -41,7 +42,11 @@ ref 'CIS Benchmark', url: cis_url.to_s ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/mysql/backup-recovery/backups' + ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/postgres/backup-recovery/backups' + ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/sqlserver/backup-recovery/backups' + ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/mysql/backup-recovery/backing-up' ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/postgres/backup-recovery/backing-up' + ref 'GCP Docs', url: 'https://cloud.google.com/sql/docs/sqlserver/backup-recovery/backing-up' if sql_cache.instance_names.empty? impact 'none' diff --git a/controls/7.01-bq.rb b/controls/7.01-bq.rb index d5e34ec..8328194 100644 --- a/controls/7.01-bq.rb +++ b/controls/7.01-bq.rb @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -title 'Ensure that BigQuery datasets are not anonymously or publicly accessible' +title 'Ensure That BigQuery datasets are not anonymously or publicly accessible' gcp_project_id = input('gcp_project_id') cis_version = input('cis_version') @@ -23,7 +23,7 @@ control "cis-gcp-#{control_id}-#{control_abbrev}" do impact 'high' - title "[#{control_abbrev.upcase}] Ensure that BigQuery datasets are not anonymously or publicly accessible" + title "[#{control_abbrev.upcase}] Ensure That BigQuery datasets are not anonymously or publicly accessible" desc 'It is recommended that the IAM policy on BigQuery datasets does not allow anonymous and/or public access.' desc 'rationale', 'Granting permissions to allUsers or allAuthenticatedUsers allows anyone to access the dataset. Such access might not be desirable if sensitive data is being stored in the dataset. Therefore, ensure that anonymous and/or public access to a dataset is not allowed.' @@ -36,8 +36,7 @@ tag nist: ['AC-3'] ref 'CIS Benchmark', url: cis_url.to_s - ref 'GCP Docs', url: 'https://cloud.google.com/storage/docs/access-control/iam-reference' - ref 'GCP Docs', url: 'https://cloud.google.com/storage/docs/access-control/making-data-public' + ref 'GCP Docs', url: 'https://cloud.google.com/bigquery/docs/dataset-access-controls' if google_bigquery_datasets(project: gcp_project_id).ids.empty? impact 'none' diff --git a/controls/7.02-bq.rb b/controls/7.02-bq.rb index 581c8c8..00a6b4f 100644 --- a/controls/7.02-bq.rb +++ b/controls/7.02-bq.rb @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -title 'Ensure that all BigQuery Tables are encrypted with Customermanaged encryption key (CMEK)' +title 'Ensure that all BigQuery tables are encrypted with customer-managed encryption key (CMEK)' gcp_project_id = input('gcp_project_id') cis_version = input('cis_version') @@ -23,27 +23,20 @@ control "cis-gcp-#{control_id}-#{control_abbrev}" do impact 'high' - title "[#{control_abbrev.upcase}] Ensure that all BigQuery Tables are encrypted with Customermanaged encryption key (CMEK)" + title "[#{control_abbrev.upcase}] Ensure that all BigQuery tables are encrypted with customer-managed encryption key (CMEK)" - desc 'BigQuery by default encrypts the data as rest by employing Envelope Encryption using - Google managed cryptographic keys. The data is encrypted using the data encryption - keys and data encryption keys themselves are further encrypted using key encryption - keys. This is seamless and do not require any additional input from the user. However, if - you want to have greater control, Customer-managed encryption keys (CMEK) can be used - as encryption key management solution for BigQuery Data Sets. If CMEK is used, the CMEK - is used to encrypt the data encryption keys instead of using google-managed encryption - keys.' - desc 'rationale', 'BigQuery by default encrypts the data as rest by employing Envelope Encryption using - Google managed cryptographic keys. This is seamless and does not require any additional - input from the user. - For greater control over the encryption, customer-managed encryption keys (CMEK) can - be used as encryption key management solution for BigQuery tables. The CMEK is used to - encrypt the data encryption keys instead of using google-managed encryption keys. - BigQuery stores the table and CMEK association and the encryption/decryption is done - automatically. - Applying the Default Customer-managed keys on BigQuery data sets ensures that all the - new tables created in the future will be encrypted using CMEK but existing tables need to - be updated to use CMEK individually.' + desc 'BigQuery by default encrypts the data as rest by employing Envelope Encryption using Google managed cryptographic keys. + The data is encrypted using the data encryption keys and data encryption keys themselves are further encrypted using key + encryption keys. This is seamless and do not require any additional input from the user. However, if you want to have greater + control, Customer-managed encryption keys (CMEK) can be used as encryption key management solution for BigQuery Data Sets. + If CMEK is used, the CMEK is used to encrypt the data encryption keys instead of using google-managed encryption keys.' + desc 'rationale', 'BigQuery by default encrypts the data as rest by employing Envelope Encryption using Google managed + cryptographic keys. This is seamless and does not require any additional input from the user. For greater control over + the encryption, customer-managed encryption keys (CMEK) can be used as encryption key management solution for BigQuery tables. + The CMEK is used to encrypt the data encryption keys instead of using google-managed encryption keys. BigQuery stores the + table and CMEK association and the encryption/decryption is done automatically. Applying the Default Customer-managed keys on + BigQuery data sets ensures that all the new tables created in the future will be encrypted using CMEK but existing tables need + to be updated to use CMEK individually.' tag cis_scored: true tag cis_level: 2 diff --git a/controls/7.03-bq.rb b/controls/7.03-bq.rb index 9a20e9c..8a10fb3 100644 --- a/controls/7.03-bq.rb +++ b/controls/7.03-bq.rb @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -title 'Ensure that a Default Customer-managed encryption key (CMEK) is specified for all BigQuery Data Sets' +title 'Ensure that a default customer-managed encryption key (CMEK) is specified for all BigQuery data sets' gcp_project_id = input('gcp_project_id') cis_version = input('cis_version') @@ -23,22 +23,17 @@ control "cis-gcp-#{control_id}-#{control_abbrev}" do impact 'high' - title "[#{control_abbrev.upcase}] Ensure that a Default Customer-managed encryption key (CMEK) is - specified for all BigQuery Data Sets" - - desc 'BigQuery by default encrypts the data as rest by employing Envelope Encryption using - Google managed cryptographic keys. The data is encrypted using the data encryption - keys and data encryption keys themselves are further encrypted using key encryption - keys. This is seamless and do not require any additional input from the user. However, if - you want to have greater control, Customer-managed encryption keys (CMEK) can be used - as encryption key management solution for BigQuery Data Sets.' - desc 'rationale', 'BigQuery by default encrypts the data as rest by employing Envelope Encryption using - Google managed cryptographic keys. This is seamless and does not require any additional - input from the user. - For greater control over the encryption, customer-managed encryption keys (CMEK) can - be used as encryption key management solution for BigQuery Data Sets. Setting a Default - Customer-managed encryption key (CMEK) for a data set ensure any tables created in - future will use the specified CMEK if none other is provided.' + title "[#{control_abbrev.upcase}] Ensure that a default customer-managed encryption key (CMEK) is specified for all BigQuery data sets" + + desc 'BigQuery by default encrypts the data as rest by employing Envelope Encryption using Google managed cryptographic keys. The data + is encrypted using the data encryption keys and data encryption keys themselves are further encrypted using key encryption keys. This is + seamless and do not require any additional input from the user. However, if you want to have greater control, Customer-managed encryption + keys (CMEK) can be used as encryption key management solution for BigQuery Data Sets.' + desc 'rationale', 'BigQuery by default encrypts the data as rest by employing Envelope Encryption using Google managed cryptographic keys. + This is seamless and does not require any additional input from the user. + For greater control over the encryption, customer-managed encryption keys (CMEK) can be used as encryption key management solution for + BigQuery Data Sets. Setting a Default Customer-managed encryption key (CMEK) for a data set ensure any tables created in future will use + the specified CMEK if none other is provided.' tag cis_scored: true tag cis_level: 2 diff --git a/controls/7.04-bq.rb b/controls/7.04-bq.rb new file mode 100644 index 0000000..d7d4f01 --- /dev/null +++ b/controls/7.04-bq.rb @@ -0,0 +1,53 @@ +# Copyright 2019 The inspec-gcp-cis-benchmark Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title 'Ensure all data in BigQuery has been classified' + +gcp_project_id = input('gcp_project_id') +cis_version = input('cis_version') +cis_url = input('cis_url') +control_id = '7.4' +control_abbrev = 'storage' + +control "cis-gcp-#{control_id}-#{control_abbrev}" do + impact 'medium' + title "[#{control_abbrev.upcase}] Ensure all data in BigQuery has been classified" + desc 'BigQuery tables can contain sensitive data that for security purposes should be discovered, monitored, + classified, and protected. Google Clouds Sensitive Data Protection tools can automatically provide data classification + of all BigQuery data across an organization.' + desc 'rationale', 'Using a cloud service or 3rd party software to continuously monitor and automate the process of data + discovery and classification for BigQuery tables is an important part of protecting the data. + Sensitive Data Protection is a fully managed data protection and data privacy platform that uses machine learning and pattern + matching to discover and classify sensitive data in Google Cloud.' + + tag cis_scored: false + tag cis_level: 2 + tag cis_gcp: control_id.to_s + tag cis_version: cis_version.to_s + tag project: gcp_project_id.to_s + tag nist: ['AC-2'] + + ref 'CIS Benchmark', url: cis_url.to_s + + ref 'GCP Docs', url: 'https://cloud.google.com/dlp/docs/data-profiles' + ref 'GCP Docs', url: 'https://cloud.google.com/dlp/docs/analyze-data-profiles' + ref 'GCP Docs', url: 'https://cloud.google.com/dlp/docs/data-profiles-remediation' + ref 'GCP Docs', url: 'https://cloud.google.com/dlp/docs/send-profiles-to-scc' + ref 'GCP Docs', url: 'https://cloud.google.com/dlp/docs/profile-org-folder#chronicle' + ref 'GCP Docs', url: 'https://cloud.google.com/dlp/docs/profile-org-folder#publish-pubsub' + + describe 'This control is not scored' do + skip 'This control is not scored' + end +end diff --git a/controls/8.01-dp.rb b/controls/8.01-dp.rb new file mode 100644 index 0000000..12e8e9b --- /dev/null +++ b/controls/8.01-dp.rb @@ -0,0 +1,95 @@ +# Copyright 2019 The inspec-gcp-cis-benchmark Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title 'Ensure that Dataproc Cluster is encrypted using Customer-Managed Encryption Key' + +gcp_project_id = input('gcp_project_id') +cis_version = input('cis_version') +cis_url = input('cis_url') +control_id = '8.1' +control_abbrev = 'dataproc' + +control "cis-gcp-#{control_id}-#{control_abbrev}" do + impact 'medium' + title "[#{control_abbrev.upcase}] Ensure that Dataproc Cluster is encrypted using Customer-Managed Encryption Key" + desc 'When you use Dataproc, cluster and job data is stored on Persistent Disks (PDs) associated with the Compute Engine VMs in your cluster + and in a Cloud Storage staging bucket. This PD and bucket data is encrypted using a Google-generated data encryption key (DEK) and key + encryption key (KEK). The CMEK feature allows you to create, use, and revoke the key encryption key (KEK). Google still controls the data + encryption key (DEK).' + desc 'rationale', 'Cloud services offer the ability to protect data related to those services using encryption keys managed by the customer within + Cloud KMS. These encryption keys are called customer-managed encryption keys (CMEK). When you protect data in Google Cloud services with CMEK, + the CMEK key is within your control.' + + tag cis_scored: true + tag cis_level: 2 + tag cis_gcp: control_id.to_s + tag cis_version: cis_version.to_s + tag project: gcp_project_id.to_s + tag nist: ['SC-28'] + + ref 'CIS Benchmark', url: cis_url.to_s + ref 'GCP Docs', url: 'https://cloud.google.com/docs/security/encryption/default-encryption' + + # Fetch all available compute regions + # This uses a helper method or a direct gcloud command execution + # You might need to add a helper function in your libraries or execute a shell command + # For simplicity, let's assume you have a way to get all regions. + # A common way is to use a custom InSpec resource or shell out to gcloud. + + # Example using a placeholder for dynamic region discovery (you'd implement this) + # In a real scenario, you might use `command('gcloud compute regions list --format="value(name)"').stdout.strip.split("\n")` + # but directly executing `gcloud` within an InSpec resource's `initialize` or `filter` + # methods can be tricky due to execution context and dependencies. + # A better approach is to define a custom InSpec resource that wraps these gcloud calls. + # For now, let's manually list some common regions, or pass them as an input if fixed. + + # If you need to make this fully dynamic without hardcoding, you would likely need a + # custom InSpec resource that uses the Google Cloud SDK for Ruby to list regions. + # For demonstration, let's use a hardcoded list of common regions. + # In a real-world, dynamic scenario, you'd integrate a resource like `google_compute_regions` + # or run a `gcloud` command to get the list. + + all_gcp_regions = google_compute_regions(project: gcp_project_id).region_names + + found_clusters = [] + + all_gcp_regions.each do |region| + clusters_in_region = google_dataproc_clusters(project: gcp_project_id, region: region) + found_clusters.concat(clusters_in_region.cluster_names.map { |name| { name: name, region: region } }) if clusters_in_region.cluster_names.any? + rescue Inspec::Exceptions::ResourceFailed => e + # This will catch errors if the Dataproc API isn't enabled in a region, + # or if there are other region-specific issues. + # You can log this for debugging if needed: + # puts "Could not list clusters in region #{region}: #{e.message}" + end + + if found_clusters.empty? + describe "No Dataproc clusters found in project '#{gcp_project_id}' across all common regions." do + subject { found_clusters } + it { should be_empty } + end + else + found_clusters.each do |cluster_info| + cluster_name = cluster_info[:name] + cluster_region = cluster_info[:region] + cluster = google_dataproc_cluster(project: gcp_project_id, name: cluster_name, region: cluster_region) + + describe "[#{gcp_project_id}] Dataproc Cluster: #{cluster_name} in region #{cluster_region}" do + subject { cluster } + its('encryption_config.gce_pd_kms_key_name') { should_not be_nil } + its('encryption_config.gce_pd_kms_key_name') { should_not be_empty } + end + end + end +end diff --git a/inspec.yml b/inspec.yml index 54f2ab4..b3f3955 100644 --- a/inspec.yml +++ b/inspec.yml @@ -13,21 +13,20 @@ # limitations under the License. name: inspec-gcp-cis-benchmark -title: "InSpec GCP CIS 1.2 Benchmark" +title: "InSpec GCP CIS 4.0 Benchmark" maintainer: "Google Cloud Platform" copyright: "(c) 2022, Google, Inc." copyright_email: "copyright@google.com" license: "Apache-2.0" -summary: "Inspec Google Cloud Platform Center for Internet Security Benchmark v1.2 Profile" -version: 1.2.0-0 +summary: "Inspec Google Cloud Platform Center for Internet Security Benchmark v4.0 Profile" +version: 4.0.0-0 supports: - platform: gcp depends: - name: inspec-gcp-helpers - url: https://github.com/GoogleCloudPlatform/inspec-gcp-helpers/archive/1.0.9.tar.gz - + url: https://github.com/lxndrblz/inspec-gcp-helpers/archive/1.0.10.tar.gz inputs: # {{gcp_project_id}} # must be defined at runtime by the user @@ -39,7 +38,7 @@ inputs: - name: cis_version description: "The short version of the GCP CIS Benchmark" - value: "1.2" + value: "4.0" type: String - name: cis_url @@ -97,4 +96,4 @@ inputs: - name: user_connections description: "(Cloud SQL SQL Server) The user connections option specifies the maximum number of simultaneous user connections that are allowed on an instance of SQL Server. (see control 6.3.3)." value: "0" - type: String + type: String \ No newline at end of file diff --git a/walkthrough.md b/walkthrough.md index 7d8d6a9..a589b17 100644 --- a/walkthrough.md +++ b/walkthrough.md @@ -23,7 +23,7 @@ Continue on to the next step to start setting up your tutorial. ## Installing InSpec -InSpec is distributed as a Docker image. All you need to do is pull the image from the repository and create a function to run Inspec: +InSpec is distributed as a Docker image. All you need to do is pull the image from the repository and create a function to run InSpec: ```bash docker pull chef/inspec:4.26.15