├── .gitignore ├── AUTHORS ├── .rubocop.yml ├── Gemfile ├── Dockerfile ├── CONTRIBUTING.md ├── controls ├── 2.4.rb ├── 2.3.rb ├── 2.2.rb ├── 3.1.rb ├── 6.4.rb ├── 8.6.rb ├── 6.3.rb ├── 6.2.rb ├── 8.5.rb ├── 3.6.rb ├── 4.1.rb ├── 2.1.rb ├── 7.1.rb ├── 8.7.rb ├── 7.2.rb ├── 1.2.rb ├── 10.5.rb ├── 1.1.rb ├── 10.2.rb ├── 3.5.rb ├── 1.3.rb └── 10.6.rb ├── README.md ├── cloudbuild.yml ├── inspec.yml └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | inspec.lock 2 | *.swp 3 | Gemfile.lock 4 | inputs.yml 5 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the list of inspec-gcp-pci-profile authors for copyright purposes. 2 | # 3 | # This does not necessarily list everyone who has contributed code, since in 4 | # some cases, their employer may be the copyright holder. To see the full list 5 | # of contributors, see the revision history in source control. 6 | Google LLC 7 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | --- 2 | AllCops: 3 | TargetRubyVersion: 2.6.3p62 4 | Exclude: 5 | - Gemfile 6 | 7 | Layout/LineLength: 8 | Enabled: false 9 | 10 | Naming/FileName: 11 | Enabled: true 12 | Regex: !ruby/regexp '/^.{3,100}$/' 13 | 14 | Metrics/BlockLength: 15 | Enabled: false 16 | 17 | Style/FrozenStringLiteralComment: 18 | Enabled: false 19 | 20 | Style/IdenticalConditionalBranches: 21 | Enabled: false 22 | 23 | Layout/EmptyLineAfterGuardClause: 24 | Enabled: false 25 | 26 | Style/StringLiterals: 27 | Enabled: false 28 | 29 | Style/RedundantInterpolation: 30 | Enabled: false -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The inspec-gcp-cis-benchmark Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | source 'https://rubygems.org' 16 | 17 | gem 'inspec', '~> 4' -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | FROM cincproject/auditor:4.22.0 16 | 17 | COPY . /share/. 18 | 19 | RUN gem install rubocop 20 | 21 | ENTRYPOINT ["cinc-auditor"] 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution; 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | 25 | ## Community Guidelines 26 | 27 | This project follows 28 | [Google's Open Source Community Guidelines](https://opensource.google/conduct/). 29 | -------------------------------------------------------------------------------- /controls/2.4.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The inspec-gcp-pci-profile Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | gcp_project_id = input('gcp_project_id') 16 | pci_version = input('pci_version') 17 | pci_url = input('pci_url') 18 | pci_section = '2.4' 19 | 20 | cai_inventory_bucket_name = input('cai_inventory_bucket_name') 21 | cai_inventory_file_path = input('cai_inventory_file_path') 22 | cai_inventory_age_seconds = input('cai_inventory_age_seconds') 23 | 24 | title "[PCI-DSS-#{pci_version}][#{pci_section}] Maintain an inventory of system components that are in scope for PCI DSS." 25 | 26 | # 2.4 27 | pci_req = "#{pci_section}" 28 | pci_req_title = "Maintain an inventory of system components that are in scope for PCI DSS." 29 | pci_req_guidance = "Maintaining a current list of all system components will enable an organization to accurately and efficiently define the scope of their environment for implementing PCI DSS controls. Without an inventory, some system components could be forgotten, and be inadvertently excluded from the organization’s configuration standards." 30 | pci_req_coverage = 'partial' 31 | 32 | control "pci-dss-#{pci_version}-#{pci_req}" do 33 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 34 | desc "#{pci_req_guidance}" 35 | impact 1.0 36 | 37 | tag project: "#{gcp_project_id}" 38 | tag standard: "pci-dss" 39 | tag pci_version: "#{pci_version}" 40 | tag pci_section: "#{pci_section}" 41 | tag pci_req: "#{pci_req}" 42 | tag coverage: "#{pci_req_coverage}" 43 | 44 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 45 | 46 | # Ensure the CAI inventory file on a bucket was last updated recently 47 | describe "[#{gcp_project_id}] The object for CAI inventory at #{cai_inventory_bucket_name}/#{cai_inventory_file_path}" do 48 | subject { google_storage_bucket_object(bucket: cai_inventory_bucket_name, object: cai_inventory_file_path) } 49 | it { should exist } 50 | its('time_updated') { should be >= (Time.now - cai_inventory_age_seconds) } 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /controls/2.3.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The inspec-gcp-pci-profile Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | gcp_project_id = input('gcp_project_id') 16 | pci_version = input('pci_version') 17 | pci_url = input('pci_url') 18 | pci_section = '2.3' 19 | 20 | title "[PCI-DSS-#{pci_version}][#{pci_section}] Encrypt all non-console administrative access using strong cryptography." 21 | 22 | # 2.3 23 | pci_req = "#{pci_section}" 24 | pci_req_title = "Encrypt all non-console administrative access using strong cryptography." 25 | pci_req_guidance = "If non-console (including remote) administration does not use secure authentication and encrypted communications, sensitive administrative or operational level information (like administrator’s IDs and passwords) can be revealed to an eavesdropper. A malicious individual could use this information to access the network, become administrator, and steal data. 26 | 27 | Clear-text protocols (such as HTTP, telnet, etc.) do not encrypt traffic or logon details, making it easy for an eavesdropper to intercept this information." 28 | pci_req_coverage = 'partial' 29 | 30 | control "pci-dss-#{pci_version}-#{pci_req}" do 31 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 32 | desc "#{pci_req_guidance}" 33 | impact 1.0 34 | 35 | tag project: "#{gcp_project_id}" 36 | tag standard: "pci-dss" 37 | tag pci_version: "#{pci_version}" 38 | tag pci_section: "#{pci_section}" 39 | tag pci_req: "#{pci_req}" 40 | tag coverage: "#{pci_req_coverage}" 41 | 42 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 43 | 44 | # Ensure telnet is not allowed by any non-GKE firewall rule 45 | google_compute_firewalls(project: gcp_project_id).where(firewall_direction: 'INGRESS').where { firewall_name !~ /^gke-/ }.firewall_names.each do |firewall_name| 46 | describe "[#{gcp_project_id}] #{firewall_name}" do 47 | subject { google_compute_firewall(project: gcp_project_id, name: firewall_name) } 48 | it "should not allow Telnet (tcp/23)" do 49 | subject.allow_port_protocol?('23', 'tcp').should_not eq(true) 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /controls/2.2.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The inspec-gcp-pci-profile Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | gcp_project_id = input('gcp_project_id') 16 | pci_version = input('pci_version') 17 | pci_url = input('pci_url') 18 | pci_section = '2.2' 19 | 20 | title "[PCI-DSS-#{pci_version}][#{pci_section}] Develop configuration standards for all system components. Assure that these standards address all known security vulnerabilities and are consistent with industry-accepted system hardening standards." 21 | 22 | # 2.2 23 | pci_req = "#{pci_section}" 24 | pci_req_title = "Develop configuration standards for all system components. Assure that these standards address all known security vulnerabilities and are consistent with industry-accepted system hardening standards." 25 | pci_req_guidance = "There are known weaknesses with many operating systems, databases, and enterprise applications, and there are also known ways to configure these systems to fix security vulnerabilities. To help those that are not security experts, a number of security organizations have established system-hardening guidelines and recommendations, which advise how to correct these weaknesses. 26 | 27 | Examples of sources for guidance on configuration standards include, but are not limited to: www.nist.gov, www.sans.org, and www.cisecurity.org, www.iso.org, and product vendors. 28 | 29 | System configuration standards must be kept up to date to ensure that newly identified weaknesses are corrected prior to a system being installed on the network." 30 | pci_req_coverage = 'partial' 31 | 32 | control "pci-dss-#{pci_version}-#{pci_req}" do 33 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 34 | desc "#{pci_req_guidance}" 35 | impact 1.0 36 | 37 | tag project: "#{gcp_project_id}" 38 | tag standard: "pci-dss" 39 | tag pci_version: "#{pci_version}" 40 | tag pci_section: "#{pci_section}" 41 | tag pci_req: "#{pci_req}" 42 | tag coverage: "#{pci_req_coverage}" 43 | 44 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 45 | 46 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] CIS GCP 1.0 Benchmark" do 47 | skip "Run separately." 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /controls/3.1.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The inspec-gcp-pci-profile Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | gcp_project_id = input('gcp_project_id') 16 | pci_version = input('pci_version') 17 | pci_url = input('pci_url') 18 | pci_section = '3.1' 19 | 20 | gcs_pii_buckets = input('gcs_pii_buckets') 21 | 22 | title "[PCI-DSS-#{pci_version}][#{pci_section}] Keep cardholder data storage to a minimum by implementing data retention and disposal policies, procedures and processes" 23 | 24 | # 3.1 25 | pci_req = "#{pci_section}" 26 | pci_req_title = "Keep cardholder data storage to a minimum by implementing data retention and disposal policies, procedures and processes" 27 | pci_req_guidance = "A formal data retention policy identifies what data needs to be retained, and where that data resides so it can be securely destroyed or deleted as soon as it is no longer needed. 28 | 29 | The only cardholder data that may be stored after authorization is the primary account number or PAN (rendered unreadable), expiration date, cardholder name, and service code. 30 | 31 | Understanding where cardholder data is located is necessary so it can be properly retained or disposed of when no longer needed. In order to define appropriate retention requirements, an entity first needs to understand their own business needs as well as any legal or regulatory obligations that apply to their industry, and/or that apply to the type of data being retained." 32 | pci_req_coverage = 'partial' 33 | 34 | control "pci-dss-#{pci_version}-#{pci_req}" do 35 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 36 | desc "#{pci_req_guidance}" 37 | impact 1.0 38 | 39 | tag project: "#{gcp_project_id}" 40 | tag standard: "pci-dss" 41 | tag pci_version: "#{pci_version}" 42 | tag pci_section: "#{pci_section}" 43 | tag pci_req: "#{pci_req}" 44 | tag coverage: "#{pci_req_coverage}" 45 | 46 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 47 | 48 | # Ensure each sensitive data GCS bucket has logging/versioning/retention policy set 49 | gcs_pii_buckets.each do |bucket| 50 | describe "[#{gcp_project_id}] Sensitive data bucket: #{bucket}" do 51 | subject { google_storage_bucket(name: bucket) } 52 | it { should exist } 53 | its('versioning.enabled') { should eq true } 54 | its('logging.log_bucket') { should_not eq nil } 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GCP PCI DSS v3.2.1 Inspec Profile 2 | 3 | This repository holds the [Google Cloud Platform (GCP)](https://cloud.google.com/) [PCI DSS v3.2.1](https://www.pcisecuritystandards.org/pci_security/) [Inspec](https://www.inspec.io/) Profile. 4 | 5 | ## Required Disclaimer 6 | 7 | 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 PCI-DSS requirements. This code is not certified by PCI-DSS. 8 | 9 | ## Coverage 10 | 11 | TBD 12 | 13 | ## Usage 14 | 15 | ### Profile Attributes 16 | 17 | * **gcp_project_id** - (Default: "", type: string) - The target GCP Project that must be specified. 18 | * **fw_change_control_id_regex** - (Default: 'CID:', type: string) - Non-GKE Firewall rules should have a description that matches this regex 19 | * **fw_override_control_id_regex** - (Default: 'AID:', type: string) - Firewall rules that allow insecure protocols needing an override should have a description that matches this regex 20 | * **cai_inventory_bucket_name** - (Default: "", type: string) - GCS Bucket name where the latest CAI export is stored 21 | * **cai_inventory_file_path** - (Default: "", type: string) - File path/name where the latest CAI export is stored inside the cai_inventory_bucket_name GCS bucket. 22 | * **gcs_pii_buckets** - (Default: "", type: list) - List of GCS buckets where PII is known to be stored. 23 | * **kms_admins_list** - (Default: "", type: list) - List of accounts/users/groups that should have KMS admin permissions 24 | * **kms_encrypters_list** - (Default: "", type: list) - List of accounts/users/groups that should have KMS encrypters permissions 25 | * **kms_decrypters_list** - (Default: "", type: list) - List of accounts/users/groups that should have KMS decrypters permissions 26 | * **kms_encrypterdecrypters_list** - (Default: "", type: list) - List of accounts/users/groups that should have KMS encrypter/decrypter permissions 27 | * **kms_regions_list** - (Default: "", type: list) - List of GCP regions ("global" is valid) where KMS keyrings can be present. 28 | ### CLI Example 29 | 30 | ``` 31 | $ cat attrs.yml 32 | gcp_project_id: "my-project-id" 33 | fw_change_control_id_regex: 'CID:' 34 | fw_override_control_id_regex: 'AID:' 35 | cai_inventory_bucket_name: "my-inventory-bucket-name" 36 | cai_inventory_file_path: "my-inventory-file-path" 37 | gcs_pii_buckets: 38 | - "my-pii-bucket-name1" 39 | - "my-pii-bucket-name2" 40 | kms_admins_list: 41 | - "serviceAccount:sa1@my-project-id.iam.gserviceaccount.com" 42 | kms_encrypters_list: 43 | - "serviceAccount:sa2@my-project-id.iam.gserviceaccount.com" 44 | kms_decrypters_list: 45 | - "serviceAccount:sa3@my-project-id.iam.gserviceaccount.com" 46 | kms_encrypterdecrypters_list: 47 | - "serviceAccount:sa4@my-project-id.iam.gserviceaccount.com" 48 | kms_regions_list: 49 | - "us-central1" 50 | - "global" 51 | ``` 52 | 53 | Example run: 54 | ``` 55 | $ inspec exec . -t gcp:// --attrs attrs.yml 56 | ``` 57 | -------------------------------------------------------------------------------- /controls/6.4.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The inspec-gcp-pci-profile Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | gcp_project_id = input('gcp_project_id') 16 | gcp_gke_locations = input('gcp_gke_locations') 17 | gce_zones = input('gce_zones') 18 | pci_version = input('pci_version') 19 | pci_url = input('pci_url') 20 | pci_section = '6.4' 21 | 22 | environment_label = input('environment_label') 23 | gke_clusters = GKECache(project: gcp_project_id, gke_locations: gcp_gke_locations).gke_clusters_cache 24 | gce_instances = GCECache(project: gcp_project_id, gce_zones: gce_zones).gce_instances_cache 25 | 26 | title "[PCI-DSS-#{pci_version}][#{pci_section}] Follow change control processes and procedures for all changes to system components" 27 | 28 | # 6.4.1 29 | pci_req = "#{pci_section}.1" 30 | pci_req_title = "Separate development/test environments from production environments, and enforce the separation with access controls." 31 | pci_req_guidance = "Due to the constantly changing state of development and test environments, they tend to be less secure than the production environment. Without adequate separation between environments, it may be possible for the production environment, and cardholder data, to be compromised due to less stringent security configurations and possible vulnerabilities in a test or development environment." 32 | pci_req_coverage = 'partial' 33 | 34 | control "pci-dss-#{pci_version}-#{pci_req}" do 35 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 36 | desc "#{pci_req_guidance}" 37 | impact 1.0 38 | 39 | tag project: "#{gcp_project_id}" 40 | tag standard: "pci-dss" 41 | tag pci_version: "#{pci_version}" 42 | tag pci_section: "#{pci_section}" 43 | tag pci_req: "#{pci_req}" 44 | tag coverage: "#{pci_req_coverage}" 45 | 46 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 47 | 48 | # GCE/GKE Instances have a label indicating the environment 49 | gce_instances.each do |instance| 50 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Instance: #{instance[:zone]}/#{instance[:name]}'s" do 51 | subject { google_compute_instance(project: gcp_project_id, zone: instance[:zone], name: instance[:name]) } 52 | it "should have an instance label key of #{environment_label}" do 53 | expect(subject.labels_keys).to include(/#{environment_label}/) 54 | end 55 | end 56 | end 57 | 58 | # One GKE cluster per project 59 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}]" do 60 | subject { gke_clusters } 61 | it "should have no more than one GKE cluster in this project" do 62 | expect(subject.length).to be <= 1 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /controls/8.6.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The inspec-gcp-pci-profile Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | gcp_project_id = input('gcp_project_id') 16 | gcp_gke_locations = input('gcp_gke_locations') 17 | pci_version = input('pci_version') 18 | pci_url = input('pci_url') 19 | pci_section = '8.6' 20 | 21 | gke_clusters = GKECache(project: gcp_project_id, gke_locations: gcp_gke_locations).gke_clusters_cache 22 | 23 | title "[PCI-DSS-#{pci_version}][#{pci_section}] Where other authentication mechanisms are used, authentication mechanisms must be assigned to an individual account and not shared among multiple accounts and physical and/or logical controls must be in place to ensure only the intended account can use that mechanism to gain access." 24 | 25 | # 8.6 26 | pci_req = "#{pci_section}" 27 | pci_req_title = "Where other authentication mechanisms are used, authentication mechanisms must be assigned to an individual account and not shared among multiple accounts and physical and/or logical controls must be in place to ensure only the intended account can use that mechanism to gain access." 28 | pci_req_guidance = "If user authentication mechanisms such as tokens, smart cards, and certificates can be used by multiple accounts, it may be impossible to identify the individual using the authentication mechanism. Having physical and/or logical controls (for example, a PIN, biometric data, or a password) to uniquely identify the user of the account will prevent unauthorized users from gaining access through use of a shared authentication mechanism." 29 | pci_req_coverage = 'partial' 30 | 31 | control "pci-dss-#{pci_version}-#{pci_req}" do 32 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 33 | desc "#{pci_req_guidance}" 34 | impact 1.0 35 | 36 | tag project: "#{gcp_project_id}" 37 | tag standard: "pci-dss" 38 | tag pci_version: "#{pci_version}" 39 | tag pci_section: "#{pci_section}" 40 | tag pci_req: "#{pci_req}" 41 | tag coverage: "#{pci_req_coverage}" 42 | 43 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 44 | 45 | # Ensure all GKE clusters do not have basic auth and have client certs auth disabled 46 | gke_clusters.each do |gke_cluster| 47 | describe "[#{gcp_project_id}] GKE Cluster #{gke_cluster[:location]}/#{gke_cluster[:cluster_name]}'s" do 48 | subject { google_container_cluster(project: gcp_project_id, location: gke_cluster[:location], name: gke_cluster[:cluster_name]) } 49 | it "Basic Authentication should be disabled" do 50 | subject.master_auth.username.should cmp(nil) 51 | end 52 | # master_auth.password should also be nil, but we don't want to put that sensitive info in the output 53 | it "Client Certificate Authentication should be disabled" do 54 | subject.master_auth.client_certificate.should cmp(nil) 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /controls/6.3.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The inspec-gcp-pci-profile Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | gcp_project_id = input('gcp_project_id') 16 | gcp_gke_locations = input('gcp_gke_locations') 17 | pci_version = input('pci_version') 18 | pci_url = input('pci_url') 19 | pci_section = '6.3' 20 | 21 | gke_clusters = GKECache(project: gcp_project_id, gke_locations: gcp_gke_locations).gke_clusters_cache 22 | 23 | title "[PCI-DSS-#{pci_version}][#{pci_section}] Develop internal and external software applications (including web-based administrative access to applications) securely" 24 | 25 | # 6.3.2 26 | pci_req = "#{pci_section}.2" 27 | pci_req_title = "Review custom code prior to release to production or customers in order to identify any potential coding vulnerability (using either manual or automated processes)" 28 | pci_req_guidance = "Security vulnerabilities in custom code are commonly exploited by malicious individuals to gain access to a network and compromise cardholder data. 29 | 30 | An individual knowledgeable and experienced in code-review techniques should be involved in the review process. Code reviews should be performed by someone other than the developer of the code to allow for an independent, objective review. Automated tools or processes may also be used in lieu of manual reviews, but keep in mind that it may be difficult or even impossible for an automated tool to identify some coding issues. 31 | 32 | Correcting coding errors before the code is deployed into a production environment or released to customers prevents the code exposing the environments to potential exploit. Faulty code is also far more difficult and expensive to address after it has been deployed or released into production environments. 33 | 34 | Including a formal review and signoff by management prior to release helps to ensure that code is approved and has been developed in accordance with policies and procedures." 35 | pci_req_coverage = 'partial' 36 | 37 | control "pci-dss-#{pci_version}-#{pci_req}" do 38 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 39 | desc "#{pci_req_guidance}" 40 | impact 1.0 41 | 42 | tag project: "#{gcp_project_id}" 43 | tag standard: "pci-dss" 44 | tag pci_version: "#{pci_version}" 45 | tag pci_section: "#{pci_section}" 46 | tag pci_req: "#{pci_req}" 47 | tag coverage: "#{pci_req_coverage}" 48 | 49 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 50 | 51 | # GKE Clusters have Binary Authorization enabled 52 | # Binary Auth should be configured to permit only allowed/signed images from known registries. 53 | gke_clusters.each do |gke_cluster| 54 | describe "[#{gcp_project_id}] Cluster #{gke_cluster[:location]}/#{gke_cluster[:cluster_name]}" do 55 | subject { google_container_cluster(project: gcp_project_id, location: gke_cluster[:location], name: gke_cluster[:cluster_name]) } 56 | its('binary_authorization.enabled') { should eq true } 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /controls/6.2.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The inspec-gcp-pci-profile Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | gcp_project_id = input('gcp_project_id') 16 | gcp_gke_locations = input('gcp_gke_locations') 17 | pci_version = input('pci_version') 18 | pci_url = input('pci_url') 19 | pci_section = '6.2' 20 | 21 | gke_clusters = GKECache(project: gcp_project_id, gke_locations: gcp_gke_locations).gke_clusters_cache 22 | 23 | title "[PCI-DSS-#{pci_version}][#{pci_section}] Ensure that all system components and software are protected from known vulnerabilities by installing applicable vendor supplied security patches. Install critical security patches within one month of release." 24 | 25 | # 6.2 26 | pci_req = "#{pci_section}" 27 | pci_req_title = "Ensure that all system components and software are protected from known vulnerabilities by installing applicable vendorsupplied security patches. Install critical security patches within one month of release." 28 | pci_req_guidance = "There is a constant stream of attacks using widely published exploits, often called \"zero day\" (an attack that exploits a previously unknown vulnerability), against otherwise secured systems. If the most recent patches are not implemented on critical systems as soon as possible, a malicious individual can use these exploits to attack or disable a system, or gain access to sensitive data. 29 | 30 | Prioritizing patches for critical infrastructure ensures that high-priority systems and devices are protected from vulnerabilities as soon as possible after a patch is released. Consider prioritizing patch installations such that security patches for critical or at-risk systems are installed within 30 days, and other lower-risk patches are installed within 2-3 months. 31 | 32 | This requirement applies to applicable patches for all installed software, including payment applications (both those that are PA-DSS validated and those that are not)." 33 | pci_req_coverage = 'partial' 34 | 35 | control "pci-dss-#{pci_version}-#{pci_req}" do 36 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 37 | desc "#{pci_req_guidance}" 38 | impact 1.0 39 | 40 | tag project: "#{gcp_project_id}" 41 | tag standard: "pci-dss" 42 | tag pci_version: "#{pci_version}" 43 | tag pci_section: "#{pci_section}" 44 | tag pci_req: "#{pci_req}" 45 | tag coverage: "#{pci_req_coverage}" 46 | 47 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 48 | 49 | # GKE Nodes have auto upgrade enabled and run COS 50 | gke_clusters.each do |gke_cluster| 51 | google_container_node_pools(project: gcp_project_id, location: gke_cluster[:location], cluster_name: gke_cluster[:cluster_name]).node_pool_names.each do |nodepoolname| 52 | describe "[#{gcp_project_id}] Cluster #{gke_cluster[:location]}/#{gke_cluster[:cluster_name]}, Node Pool: #{nodepoolname}" do 53 | subject { google_container_node_pool(project: gcp_project_id, location: gke_cluster[:location], cluster_name: gke_cluster[:cluster_name], nodepool_name: nodepoolname) } 54 | its('config.image_type') { should match(/COS/) } 55 | its('management.auto_upgrade') { should cmp true } 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /controls/8.5.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The inspec-gcp-pci-profile Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | gcp_project_id = input('gcp_project_id') 16 | gce_zones = input('gce_zones') 17 | pci_version = input('pci_version') 18 | pci_url = input('pci_url') 19 | pci_section = '8.5' 20 | 21 | gce_instances = GCECache(project: gcp_project_id, gce_zones: gce_zones).gce_instances_cache 22 | 23 | title "[PCI-DSS-#{pci_version}][#{pci_section}] Do not use group, shared, or generic IDs, passwords, or other authentication methods" 24 | 25 | # 8.5 26 | pci_req = "#{pci_section}" 27 | pci_req_title = "Do not use group, shared, or generic IDs, passwords, or other authentication methods" 28 | pci_req_guidance = "If multiple users share the same authentication credentials (for example, user account and password), it becomes impossible to trace system access and activities to an individual. This in turn prevents an entity from assigning accountability for, or having effective logging of, an individual’s actions, since a given action could have been performed by anyone in the group that has knowledge of the authentication credentials." 29 | pci_req_coverage = 'partial' 30 | 31 | control "pci-dss-#{pci_version}-#{pci_req}" do 32 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 33 | desc "#{pci_req_guidance}" 34 | impact 1.0 35 | 36 | tag project: "#{gcp_project_id}" 37 | tag standard: "pci-dss" 38 | tag pci_version: "#{pci_version}" 39 | tag pci_section: "#{pci_section}" 40 | tag pci_req: "#{pci_req}" 41 | tag coverage: "#{pci_req_coverage}" 42 | 43 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 44 | 45 | # Ensure the default compute service account is not attached to GCE/GKE instances 46 | gce_instances.each do |instance| 47 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Instance: #{instance[:zone]}/#{instance[:name]}'s" do 48 | subject { google_compute_instance(project: gcp_project_id, zone: instance[:zone], name: instance[:name]) } 49 | it "service account should not be the Default Compute Service Account" do 50 | expect(subject.service_accounts[0].email).not_to match(/-compute@developer.gserviceaccount.com$/) 51 | end 52 | end 53 | end 54 | 55 | # Ensure the Default SA is not attached to Editor 56 | iam_cache = IAMBindingsCache(project: gcp_project_id) 57 | iam_editor_bindings = iam_cache.iam_bindings['roles/editor'] 58 | unless iam_editor_bindings.nil? 59 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] The IAM Role 'roles/editor'" do 60 | subject { iam_editor_bindings } 61 | it "should not be bound to the default compute service account" do 62 | subject.members.should_not include(/-compute@developer.gserviceaccount.com/) 63 | end 64 | end 65 | end 66 | 67 | # Service Account User should not be bound at the project level 68 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Ensure the role iam.serviceAccountUser is not bound at the project level" do 69 | subject { google_project_iam_bindings(project: gcp_project_id).where(iam_binding_role: 'roles/iam.serviceAccountUser') } 70 | its('count') { should be_zero } 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /controls/3.6.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The inspec-gcp-pci-profile Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | gcp_project_id = input('gcp_project_id') 16 | pci_version = input('pci_version') 17 | pci_url = input('pci_url') 18 | pci_section = '3.6' 19 | 20 | kms_rotation_period_seconds = input('kms_rotation_period_seconds') 21 | 22 | title "[PCI-DSS-#{pci_version}][#{pci_section}] Fully document and implement all keymanagement processes and procedures for cryptographic keys used for encryption of cardholder data" 23 | 24 | # 3.6.4 25 | pci_req = "#{pci_section}.4" 26 | pci_req_title = "Cryptographic key changes for keys that have reached the end of their cryptoperiod." 27 | pci_req_guidance = "A cryptoperiod is the time span during which a particular cryptographic key can be used for its defined purpose. Considerations for defining the cryptoperiod include, but are not limited to, the strength of the underlying algorithm, size or length of the key, risk of key compromise, and the sensitivity of the data being encrypted. 28 | 29 | Periodic changing of encryption keys when the keys have reached the end of their cryptoperiod is imperative to minimize the risk of someone’s obtaining the encryption keys, and using them to decrypt data." 30 | pci_req_coverage = 'partial' 31 | 32 | control "pci-dss-#{pci_version}-#{pci_req}" do 33 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 34 | desc "#{pci_req_guidance}" 35 | impact 1.0 36 | 37 | tag project: "#{gcp_project_id}" 38 | tag standard: "pci-dss" 39 | tag pci_version: "#{pci_version}" 40 | tag pci_section: "#{pci_section}" 41 | tag pci_req: "#{pci_req}" 42 | tag coverage: "#{pci_req_coverage}" 43 | 44 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 45 | 46 | # Get all "normal" regions and add "global" and multi-regional locations 47 | locations = google_compute_regions(project: gcp_project_id).region_names 48 | locations << 'global' << 'asia' << 'europe' << 'us' << 'eur4' << 'nam4' 49 | 50 | kms_cache = KMSKeyCache(project: gcp_project_id, locations: locations) 51 | # Ensure KMS keys autorotate 90d or less 52 | locations.each do |location| 53 | kms_cache.kms_key_ring_names[location].each do |keyring| 54 | kms_cache.kms_crypto_keys[location][keyring].each do |keyname| 55 | key = google_kms_crypto_key(project: gcp_project_id, location: location, key_ring_name: keyring, name: keyname) 56 | next unless key.primary_state == "ENABLED" 57 | if key.rotation_period.nil? 58 | describe "[#{gcp_project_id}] #{key.crypto_key_name}" do 59 | subject { key } 60 | its('rotation_period') { should_not be_nil } 61 | end 62 | else 63 | rotation_period_int = key.rotation_period.delete_suffix('s').to_i 64 | describe "[#{gcp_project_id}] #{key.crypto_key_name}" do 65 | subject { key } 66 | it "should have a lower or equal rotation period than #{kms_rotation_period_seconds}" do 67 | expect(rotation_period_int).to be <= kms_rotation_period_seconds 68 | end 69 | its('next_rotation_time') { should be <= (Time.now + kms_rotation_period_seconds) } 70 | end 71 | end 72 | end 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /controls/4.1.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The inspec-gcp-pci-profile Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | gcp_project_id = input('gcp_project_id') 16 | pci_version = input('pci_version') 17 | pci_url = input('pci_url') 18 | pci_section = '4.1' 19 | 20 | title "[PCI-DSS-#{pci_version}][#{pci_section}] Use strong cryptography and security protocols to safeguard sensitive cardholder data during transmission over open, public networks" 21 | 22 | # 4.1 23 | pci_req = "#{pci_section}" 24 | pci_req_title = "Use strong cryptography and security protocols to safeguard sensitive cardholder data during transmission over open, public networks" 25 | pci_req_guidance = "Sensitive information must be encrypted during transmission over public networks, because it is easy and common for a malicious individual to intercept and/or divert data while in transit. 26 | 27 | Secure transmission of cardholder data requires using trusted keys/certificates, a secure protocol for transport, and proper encryption strength to encrypt cardholder data. Connection requests from systems that do not support the required encryption strength, and that would result in an insecure connection, should not be accepted. 28 | 29 | Note that some protocol implementations (such as SSL, SSH v1.0, and early TLS) have known vulnerabilities that an attacker can use to gain control of the affected system. Whichever security protocol is used, ensure it is configured to use only secure versions and configurations to prevent use of an insecure connection—for example, by using only trusted certificates and supporting only strong encryption (not supporting weaker, insecure protocols or methods). 30 | 31 | Verifying that certificates are trusted (for example, have not expired and are issued from a trusted source) helps ensure the integrity of the secure connection. " 32 | pci_req_coverage = 'partial' 33 | 34 | control "pci-dss-#{pci_version}-#{pci_req}" do 35 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 36 | desc "#{pci_req_guidance}" 37 | impact 1.0 38 | 39 | tag project: "#{gcp_project_id}" 40 | tag standard: "pci-dss" 41 | tag pci_version: "#{pci_version}" 42 | tag pci_section: "#{pci_section}" 43 | tag pci_req: "#{pci_req}" 44 | tag coverage: "#{pci_req_coverage}" 45 | 46 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 47 | 48 | # All load balancers have custom/strong TLS profiles set 49 | google_compute_target_https_proxies(project: gcp_project_id).names.each do |proxy| 50 | describe "[#{gcp_project_id}] HTTPS Proxy: #{proxy}" do 51 | subject { google_compute_target_https_proxy(project: gcp_project_id, name: proxy) } 52 | it "should have a custom SSL policy configured" do 53 | subject.ssl_policy.should_not cmp(nil) 54 | end 55 | end 56 | end 57 | # Ensure SSL Policies use strong TLS 58 | google_compute_ssl_policies(project: gcp_project_id).names.each do |policy| 59 | describe "[#{gcp_project_id}] SSL Policy: #{policy}" do 60 | subject { google_compute_ssl_policy(project: gcp_project_id, name: policy) } 61 | it "should minimally require TLS 1.2" do 62 | subject.min_tls_version.should cmp "TLS_1_2" 63 | end 64 | it "profile should be RESTRICTED" do 65 | subject.profile.should cmp "RESTRICTED" 66 | end 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /controls/2.1.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The inspec-gcp-pci-profile Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | gcp_project_id = input('gcp_project_id') 16 | gcp_gke_locations = input('gcp_gke_locations') 17 | gce_zones = input('gce_zones') 18 | pci_version = input('pci_version') 19 | pci_url = input('pci_url') 20 | pci_section = '2.1' 21 | 22 | gke_clusters = GKECache(project: gcp_project_id, gke_locations: gcp_gke_locations).gke_clusters_cache 23 | gce_instances = GCECache(project: gcp_project_id, gce_zones: gce_zones).gce_instances_cache 24 | 25 | title "[PCI-DSS-#{pci_version}][#{pci_section}] Always change vendor-supplied defaults and remove or disable unnecessary default accounts" 26 | 27 | # 2.1 28 | pci_req = "#{pci_section}" 29 | pci_req_title = "Always change vendor-supplied defaults and remove or disable unnecessary default accounts before installing a system on the network." 30 | pci_req_guidance = "Malicious individuals (external and internal to an organization) often use vendor default settings, account names, and passwords to compromise operating system software, applications, and the systems on which they are installed. Because these default settings are often published and are well known in hacker communities, changing these settings will leave systems less vulnerable to attack. Even if a default account is not intended to be used, changing the default password to a strong unique password and then disabling the account will prevent a malicious individual from re-enabling the account and gaining access with the default password. " 31 | pci_req_coverage = 'partial' 32 | 33 | control "pci-dss-#{pci_version}-#{pci_req}" do 34 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 35 | desc "#{pci_req_guidance}" 36 | impact 1.0 37 | 38 | tag project: "#{gcp_project_id}" 39 | tag standard: "pci-dss" 40 | tag pci_version: "#{pci_version}" 41 | tag pci_section: "#{pci_section}" 42 | tag pci_req: "#{pci_req}" 43 | tag coverage: "#{pci_req_coverage}" 44 | 45 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 46 | 47 | # Ensure the default compute service account is not attached to GCE/GKE instances 48 | gce_instances.each do |instance| 49 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Instance: #{instance[:zone]}/#{instance[:name]}'s" do 50 | subject { google_compute_instance(project: gcp_project_id, zone: instance[:zone], name: instance[:name]) } 51 | it "service account should not be the Default Compute Service Account" do 52 | expect(subject.service_accounts[0].email).not_to match(/-compute@developer.gserviceaccount.com$/) 53 | end 54 | end 55 | end 56 | 57 | # Ensure the Default SA is not attached to Editor 58 | iam_cache = IAMBindingsCache(project: gcp_project_id) 59 | describe "[#{gcp_project_id}] The IAM Role 'roles/editor'" do 60 | subject { iam_cache.iam_bindings['roles/editor'] } 61 | it "should not be bound to the default compute service account" do 62 | subject.members.should_not include(/-compute@developer.gserviceaccount.com/) 63 | end 64 | end 65 | 66 | # Ensure all GKE clusters do not have basic auth and have client certs auth disabled 67 | gke_clusters.each do |gke_cluster| 68 | describe "[#{gcp_project_id}] GKE Cluster #{gke_cluster[:location]}/#{gke_cluster[:cluster_name]}'s" do 69 | subject { google_container_cluster(project: gcp_project_id, location: gke_cluster[:location], name: gke_cluster[:cluster_name]) } 70 | it "Basic Authentication should be disabled" do 71 | subject.master_auth.username.should cmp(nil) 72 | end 73 | # master_auth.password should also be nil, but we don't want to put that sensitive info in the output 74 | it "Client Certificate Authentication should be disabled" do 75 | subject.master_auth.client_certificate.should cmp(nil) 76 | end 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /cloudbuild.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | substitutions: 16 | _GCP_PROJECT_ID: '' 17 | _REPORTS_BUCKET: '' 18 | _SA_ADMIN: '' 19 | _LOGGING_VIEWER: '' 20 | _LOGGING_BUCKET: '' 21 | 22 | timeout: 3000s 23 | steps: 24 | - id: 'Build Docker container' 25 | name: 'gcr.io/cloud-builders/docker' 26 | args: [ 'build', '-t', 'gcr.io/${_GCP_PROJECT_ID}/inspec-gcp-pci-profile:${_PR_NUMBER}', '.' ] 27 | 28 | - id: 'Verify InSpec controls' 29 | waitFor: ['Build Docker container'] 30 | name: gcr.io/${_GCP_PROJECT_ID}/inspec-gcp-pci-profile:${_PR_NUMBER} 31 | entrypoint: '/bin/sh' 32 | args: 33 | - '-c' 34 | - | 35 | cinc-auditor vendor /share/. 36 | cinc-auditor check /share/. 37 | 38 | - id: 'Rubocop' 39 | waitFor: ['Verify InSpec controls'] 40 | name: gcr.io/${_GCP_PROJECT_ID}/inspec-gcp-pci-profile:${_PR_NUMBER} 41 | entrypoint: '/bin/sh' 42 | args: 43 | - '-c' 44 | - | 45 | rubocop /share/. 46 | 47 | - id: 'Write input file' 48 | waitFor: ['Rubocop'] 49 | name: gcr.io/cloud-foundation-cicd/cft/developer-tools:0 50 | entrypoint: '/bin/sh' 51 | args: 52 | - '-c' 53 | - | 54 | cloud_build_service_account=`gcloud config get-value account` 55 | cat < /workspace/inputs.yml 56 | gcp_project_id: "${_GCP_PROJECT_ID}" 57 | gcp_gke_locations: 58 | - 'us-central1' 59 | gce_zones: 60 | - 'us-central1' 61 | - 'us-central1-a' 62 | - 'us-central1-b' 63 | - 'us-central1-c' 64 | - 'us-central1-d' 65 | - 'us-central1-e' 66 | - 'us-central1-f' 67 | cis_version: "asdf" 68 | cis_url: "asdf" 69 | fw_change_control_id_regex: 'CID:' 70 | fw_override_control_id_regex: 'CID:' 71 | logging_viewer_list: ["user:${_LOGGING_VIEWER}"] 72 | logging_admin_list: ["serviceAccount:${_SA_ADMIN}"] 73 | gcs_logging_buckets: ["${_LOGGING_BUCKET}"] 74 | cai_inventory_bucket_name: "${_LOGGING_BUCKET}" 75 | cai_inventory_file_path: "asdf.json" 76 | cai_inventory_age_seconds: 31536000 77 | gcs_pii_buckets: ["${_LOGGING_BUCKET}"] 78 | kms_regions_list: ['global', 'us-central1'] 79 | kms_admins_list: ["serviceAccount:${_SA_ADMIN}"] 80 | kms_encrypters_list: ["serviceAccount:${_SA_ADMIN}"] 81 | kms_decrypters_list: ["serviceAccount:${_SA_ADMIN}"] 82 | kms_encrypterdecrypters_list: ["serviceAccount:${_SA_ADMIN}"] 83 | kms_rotation_period_seconds: 31536000 84 | environment_label: 'env' 85 | memorystore_admins_list: ["serviceAccount:${_SA_ADMIN}"] 86 | cloudsql_admins_list: ["serviceAccount:${_SA_ADMIN}"] 87 | cloudsql_clients_list: ["serviceAccount:${_SA_ADMIN}"] 88 | bq_admins_list: ["serviceAccount:${_SA_ADMIN}"] 89 | spanner_admins_list: ["serviceAccount:${_SA_ADMIN}"] 90 | project_owners_list: [] 91 | EOF 92 | cat /workspace/inputs.yml 93 | 94 | - id: 'Run PCI Profile on in-scope project' 95 | waitFor: ['Write input file'] 96 | name: gcr.io/${_GCP_PROJECT_ID}/inspec-gcp-pci-profile:${_PR_NUMBER} 97 | entrypoint: '/bin/sh' 98 | args: 99 | - '-c' 100 | - | 101 | cinc-auditor exec /share/. -t gcp:// \ 102 | --input-file /workspace/inputs.yml \ 103 | --reporter cli json:/workspace/pci_report.json html:/workspace/pci_report.html | tee out.json 104 | 105 | - id: 'Test Compliance' 106 | waitFor: ['Run PCI Profile on in-scope project'] 107 | name: mitre/inspec_tools 108 | args: ['compliance', '-j', '/workspace/pci_report.json', '-i', '{failed.total.max: 1}'] 109 | 110 | - id: 'Store json Report' 111 | waitFor: ['Test Compliance'] 112 | name: gcr.io/cloud-builders/gsutil 113 | args: 114 | - cp 115 | - /workspace/pci_report.json 116 | - gs://${_REPORTS_BUCKET}/pci_report-${BUILD_ID}.json -------------------------------------------------------------------------------- /controls/7.1.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The inspec-gcp-pci-profile Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | gcp_project_id = input('gcp_project_id') 16 | gce_zones = input('gce_zones') 17 | pci_version = input('pci_version') 18 | pci_url = input('pci_url') 19 | pci_section = '7.1' 20 | 21 | project_owners_list = input('project_owners_list') 22 | gce_instances = GCECache(project: gcp_project_id, gce_zones: gce_zones).gce_instances_cache 23 | 24 | title "[PCI-DSS-#{pci_version}][#{pci_section}] Limit access to system components and cardholder data to only those individuals whose job requires such access." 25 | 26 | # 7.1.2 27 | pci_req = "#{pci_section}.2" 28 | pci_req_title = "Restrict access to privileged user IDs to least privileges necessary to perform job responsibilities." 29 | pci_req_guidance = "When assigning privileged IDs, it is important to assign individuals only the privileges they need to perform their job (the “least privileges”). For example, the database administrator or backup administrator should not be assigned the same privileges as the overall systems administrator." 30 | pci_req_coverage = 'partial' 31 | 32 | control "pci-dss-#{pci_version}-#{pci_req}" do 33 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 34 | desc "#{pci_req_guidance}" 35 | impact 1.0 36 | 37 | tag project: "#{gcp_project_id}" 38 | tag standard: "pci-dss" 39 | tag pci_version: "#{pci_version}" 40 | tag pci_section: "#{pci_section}" 41 | tag pci_req: "#{pci_req}" 42 | tag coverage: "#{pci_req_coverage}" 43 | 44 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 45 | 46 | # Ensure IAM Role bindings are to groups and not users directly 47 | iam_cache = IAMBindingsCache(project: gcp_project_id) 48 | iam_cache.iam_bindings.each_key do |role| 49 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] IAM Role #{role}" do 50 | subject { iam_cache.iam_bindings[role] } 51 | it "should not have any \"user:@\" bound" do 52 | subject.members.to_s.should_not match(/user:/) 53 | end 54 | end 55 | end 56 | 57 | # Ensure the list of owners is known 58 | describe "[[#{gcp_project_id}] Ensure Owners" do 59 | subject { iam_cache.iam_bindings['roles/owner'] } 60 | if iam_cache.iam_bindings['roles/owner'].nil? || iam_cache.iam_bindings['roles/owner'].members.empty? 61 | skip 'There are no Owners in the project' 62 | else 63 | it "matches the Owners allow list" do 64 | expect(subject.members).to cmp(project_owners_list) 65 | end 66 | end 67 | end 68 | 69 | # Ensure the default compute service account is not attached to GCE/GKE instances 70 | gce_instances.each do |instance| 71 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Instance: #{instance[:zone]}/#{instance[:name]}'s" do 72 | subject { google_compute_instance(project: gcp_project_id, zone: instance[:zone], name: instance[:name]) } 73 | it "service account should not be the Default Compute Service Account" do 74 | expect(subject.service_accounts[0].email).not_to match(/-compute@developer.gserviceaccount.com$/) 75 | end 76 | end 77 | end 78 | 79 | # Ensure that primitive roles (Owner/Editor/Viewer) are not bound to GCE or GKE node pools 80 | # Get the unique list of attached SAs to GCE/GKE instances 81 | attached_service_accounts = [] 82 | gce_instances.each do |instance| 83 | sa = google_compute_instance(project: gcp_project_id, zone: instance[:zone], name: instance[:name]).service_accounts 84 | attached_service_accounts << sa[0].email if sa.length.positive? 85 | end 86 | 87 | # The unique list of attached SAs to instances 88 | attached_service_accounts.uniq! 89 | # Search primitive role bindings for members that should not include these attached SAs 90 | google_project_iam_bindings(project: gcp_project_id).where { iam_binding_role == 'roles/editor' || iam_binding_role == 'roles/owner' || iam_binding_role == 'roles/viewer' }.iam_binding_roles.each do |role| 91 | attached_service_accounts.each do |sa_name| 92 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] ServiceAccount #{sa_name}" do 93 | subject { iam_cache.iam_bindings[role] } 94 | it "should not be bound to #{role}" do 95 | subject.members.should_not include "serviceAccount:#{sa_name}" 96 | end 97 | end 98 | end 99 | end 100 | end 101 | -------------------------------------------------------------------------------- /controls/8.7.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The inspec-gcp-pci-profile Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | gcp_project_id = input('gcp_project_id') 16 | pci_version = input('pci_version') 17 | pci_url = input('pci_url') 18 | pci_section = '8.7' 19 | 20 | memorystore_admins_list = input('memorystore_admins_list') 21 | cloudsql_admins_list = input('cloudsql_admins_list') 22 | cloudsql_clients_list = input('cloudsql_clients_list') 23 | bq_admins_list = input('bq_admins_list') 24 | spanner_admins_list = input('spanner_admins_list') 25 | 26 | title "[PCI-DSS-#{pci_version}][#{pci_section}] All access to any database containing cardholder data (including access by applications, administrators, and all other users) is restricted." 27 | 28 | # 8.7 29 | pci_req = "#{pci_section}" 30 | pci_req_title = "All access to any database containing cardholder data (including access by applications, administrators, and all other users) is restricted" 31 | pci_req_guidance = "Without user authentication for access to databases and applications, the potential for unauthorized or malicious access increases, and such access cannot be logged since the user has not been authenticated and is therefore not known to the system. Also, database access should be granted through programmatic methods only (for example, through stored procedures), rather than via direct access to the database by end users (except for DBAs, who may need direct access to the database for their administrative duties)." 32 | pci_req_coverage = 'partial' 33 | 34 | control "pci-dss-#{pci_version}-#{pci_req}" do 35 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 36 | desc "#{pci_req_guidance}" 37 | impact 1.0 38 | 39 | tag project: "#{gcp_project_id}" 40 | tag standard: "pci-dss" 41 | tag pci_version: "#{pci_version}" 42 | tag pci_section: "#{pci_section}" 43 | tag pci_req: "#{pci_req}" 44 | tag coverage: "#{pci_req_coverage}" 45 | 46 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 47 | 48 | iam_cache = IAMBindingsCache(project: gcp_project_id) 49 | 50 | # Ensure allowlisted memorystore user accounts only 51 | redis_admin_bindings = iam_cache.iam_bindings['roles/redis.admin'] 52 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Ensure MemoryStore Admins" do 53 | subject { redis_admin_bindings } 54 | if redis_admin_bindings.nil? || redis_admin_bindings.members.empty? 55 | skip 'There are no MemoryStore Admins in the project' 56 | else 57 | it "matches the MemoryStore Admins allow list" do 58 | expect(subject.members).to cmp(memorystore_admins_list) 59 | end 60 | end 61 | end 62 | 63 | # Ensure allowlisted cloudsql admin accounts only 64 | cloud_sql_admin_bindings = iam_cache.iam_bindings['roles/cloudsql.admin'] 65 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Ensure CloudSQL Admins" do 66 | subject { cloud_sql_admin_bindings } 67 | if cloud_sql_admin_bindings.nil? || cloud_sql_admin_bindings.members.empty? 68 | skip 'There are no Cloud SQL Admins in the project' 69 | else 70 | it "matches the Cloud SQL Admins allow list" do 71 | expect(subject.members).to cmp(cloudsql_admins_list) 72 | end 73 | end 74 | end 75 | 76 | # Ensure allowlisted cloudsql client accounts only 77 | cloud_sql_client_bindings = iam_cache.iam_bindings['roles/cloudsql.client'] 78 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Ensure CloudSQL Clients" do 79 | subject { cloud_sql_client_bindings } 80 | if cloud_sql_client_bindings.nil? || cloud_sql_client_bindings.members.empty? 81 | skip 'There are no Cloud SQL Clients in the project' 82 | else 83 | it "matches the CloudSQL client allow list" do 84 | expect(subject.members).to cmp(cloudsql_clients_list) 85 | end 86 | end 87 | end 88 | 89 | # Ensure allowlisted BigQuery admin accounts only 90 | bq_admin_bindings = iam_cache.iam_bindings['roles/bigquery.admin'] 91 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Ensure BigQuery Admins" do 92 | subject { bq_admin_bindings } 93 | if bq_admin_bindings.nil? || bq_admin_bindings.members.empty? 94 | skip 'There are no BigQuery Admins in the project' 95 | else 96 | it "matches the BigQuery Admins allow list" do 97 | expect(subject.members).to cmp(bq_admins_list) 98 | end 99 | end 100 | end 101 | 102 | # Ensure allowlisted Cloud Spanner admin accounts only 103 | spanner_admin_bindings = iam_cache.iam_bindings['roles/spanner.admin'] 104 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Ensure Cloud Spanner Admins" do 105 | subject { google_project_iam_binding(project: gcp_project_id, role: 'roles/spanner.admin') } 106 | if spanner_admin_bindings.nil? || spanner_admin_bindings.members.empty? 107 | skip 'There are no Cloud Spanner Admins in the project' 108 | else 109 | it "matches the Cloud Spanner Admins allow list" do 110 | expect(subject.members).to cmp(spanner_admins_list) 111 | end 112 | end 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /controls/7.2.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The inspec-gcp-pci-profile Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | gcp_project_id = input('gcp_project_id') 16 | gcp_gke_locations = input('gcp_gke_locations') 17 | gce_zones = input('gce_zones') 18 | pci_version = input('pci_version') 19 | pci_url = input('pci_url') 20 | pci_section = '7.2' 21 | 22 | gke_clusters = GKECache(project: gcp_project_id, gke_locations: gcp_gke_locations).gke_clusters_cache 23 | gce_instances = GCECache(project: gcp_project_id, gce_zones: gce_zones).gce_instances_cache 24 | 25 | title "[PCI-DSS-#{pci_version}][#{pci_section}] Establish an access control system(s) for systems components that restricts access based on a user’s need to know, and is set to “deny all” unless specifically allowed. " 26 | 27 | # 7.2.3 28 | pci_req = "#{pci_section}.3" 29 | pci_req_title = "Default “deny-all” setting." 30 | pci_req_guidance = "Confirm that the access control systems have a default “deny-all” setting." 31 | pci_req_coverage = 'partial' 32 | 33 | control "pci-dss-#{pci_version}-#{pci_req}" do 34 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 35 | desc "#{pci_req_guidance}" 36 | impact 1.0 37 | 38 | tag project: "#{gcp_project_id}" 39 | tag standard: "pci-dss" 40 | tag pci_version: "#{pci_version}" 41 | tag pci_section: "#{pci_section}" 42 | tag pci_req: "#{pci_req}" 43 | tag coverage: "#{pci_req_coverage}" 44 | 45 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 46 | 47 | # Default subnets are not in use and not legacy 48 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Subnets" do 49 | subject { google_compute_networks(project: gcp_project_id) } 50 | its('network_names') { should_not include 'default' } 51 | end 52 | 53 | # Ensure all networks do not have default FW rules 54 | google_compute_firewalls(project: gcp_project_id).where(firewall_direction: 'INGRESS').where { firewall_name =~ /^default-allow-/ }.firewall_names.each do |firewall_name| 55 | fw = google_compute_firewall(project: gcp_project_id, name: firewall_name) 56 | next if fw.disabled == true 57 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Firewall Rule: #{firewall_name}" do 58 | subject { fw } 59 | it { should_not exist } 60 | end 61 | end 62 | 63 | # Ensure the default compute service account is not attached to GCE/GKE instances 64 | gce_instances.each do |instance| 65 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Instance: #{instance[:zone]}/#{instance[:name]}'s" do 66 | subject { google_compute_instance(project: gcp_project_id, zone: instance[:zone], name: instance[:name]) } 67 | it "service account should not be the Default Compute Service Account" do 68 | expect(subject.service_accounts[0].email).not_to match(/-compute@developer.gserviceaccount.com$/) 69 | end 70 | end 71 | end 72 | 73 | # Ensure the Default SA is not attached to Editor 74 | iam_cache = IAMBindingsCache(project: gcp_project_id) 75 | iam_editor_bindings = iam_cache.iam_bindings['roles/editor'] 76 | unless iam_editor_bindings.nil? 77 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] The IAM Role 'roles/editor'" do 78 | subject { iam_editor_bindings } 79 | it "should not be bound to the default compute service account" do 80 | subject.members.should_not include(/-compute@developer.gserviceaccount.com/) 81 | end 82 | end 83 | end 84 | 85 | # GCE Instances should block ssh keys 86 | gce_instances.each do |instance| 87 | next if instance[:name] =~ /^gke-/ 88 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Instance: #{instance[:zone]}/#{instance[:name]}'s" do 89 | subject { google_compute_instance(project: gcp_project_id, zone: instance[:zone], name: instance[:name]) } 90 | its('block_project_ssh_keys') { should cmp true } 91 | end 92 | end 93 | 94 | # GKE Clusters should not use Legacy ABAC in favor of RBAC 95 | gke_clusters.each do |gke_cluster| 96 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] GKE Cluster #{gke_cluster[:location]}/#{gke_cluster[:cluster_name]}" do 97 | subject { google_container_cluster(project: gcp_project_id, location: gke_cluster[:location], name: gke_cluster[:cluster_name]) } 98 | it "should enable RBAC" do 99 | expect(subject.legacy_abac.enabled).not_to cmp(true) 100 | end 101 | end 102 | end 103 | 104 | # GCE/GKE Instances should not have an OAuth Scope of cloud-platform 105 | gce_instances.each do |instance| 106 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Instance: #{instance[:zone]}/#{instance[:name]}" do 107 | subject { google_compute_instance(project: gcp_project_id, zone: instance[:zone], name: instance[:name]) } 108 | it "should not have an OAuth Scope of 'cloud-platform'" do 109 | expect(subject.service_account_scopes).to_not include('https://www.googleapis.com/auth/cloud-platform') 110 | end 111 | end 112 | end 113 | 114 | # GCS Buckets should not have allUsers or allAuthenticatedUsers (All) set on any bucket role 115 | google_storage_buckets(project: gcp_project_id).bucket_names.each do |bucket| 116 | google_storage_bucket_iam_bindings(bucket: bucket).iam_binding_roles.each do |role| 117 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] GCS Bucket #{bucket}, Role: #{role}" do 118 | subject { google_storage_bucket_iam_binding(bucket: bucket, role: role) } 119 | its('members') { should_not include 'allUsers' } 120 | its('members') { should_not include 'allAuthenticatedUsers' } 121 | end 122 | end 123 | end 124 | end 125 | -------------------------------------------------------------------------------- /controls/1.2.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The inspec-gcp-pci-profile Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | gcp_project_id = input('gcp_project_id') 16 | pci_version = input('pci_version') 17 | pci_url = input('pci_url') 18 | pci_section = '1.2' 19 | 20 | allow_all_tcp_ports = input('allow_all_tcp_ports') 21 | allow_all_udp_ports = input('allow_all_udp_ports') 22 | 23 | title "[PCI-DSS-#{pci_version}][#{pci_section}] Build firewall and router configurations that restrict connections between untrusted networks and any system components in the cardholder data environment." 24 | 25 | # 1.2.1 26 | pci_req = "#{pci_section}.1" 27 | pci_req_title = "Restrict inbound and outbound traffic to that which is necessary for the cardholder data environment, and specifically deny all other traffic." 28 | pci_req_guidance = "Examination of all inbound and outbound connections allows for inspection and restriction of traffic based on the source and/or destination address, thus preventing unfiltered access between untrusted and trusted environments. This prevents malicious individuals from accessing the entity’s network via unauthorized IP addresses or from using services, protocols, or ports in an unauthorized manner (for example, to send data they've obtained from within the entity’s network out to an untrusted server). Implementing a rule that denies all inbound and outbound traffic that is not specifically needed helps to prevent inadvertent holes that would allow unintended and potentially harmful traffic in or out." 29 | pci_req_coverage = 'partial' 30 | 31 | control "pci-dss-#{pci_version}-#{pci_req}" do 32 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 33 | desc "#{pci_req_guidance}" 34 | impact 1.0 35 | 36 | tag project: "#{gcp_project_id}" 37 | tag standard: "pci-dss" 38 | tag pci_version: "#{pci_version}" 39 | tag pci_section: "#{pci_section}" 40 | tag pci_req: "#{pci_req}" 41 | tag coverage: "#{pci_req_coverage}" 42 | 43 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 44 | 45 | if google_compute_networks(project: gcp_project_id).count.zero? 46 | impact 'none' 47 | describe "[#{gcp_project_id}] The project does not have VPCs. This test is not applicable." do 48 | skip "[#{gcp_project_id}] The project does not have VPCs. This test is not applicable." 49 | end 50 | else 51 | # Explicit egress deny all rule in place 52 | egress_deny_all_fw_rules = [] 53 | google_compute_firewalls(project: gcp_project_id).where(firewall_direction: 'EGRESS').where { firewall_name !~ /^gke/ }.firewall_names.each do |firewall_name| 54 | fw = google_compute_firewall(project: gcp_project_id, name: firewall_name) 55 | egress_deny_all_fw_rules << firewall_name if fw.respond_to?('denied') && !fw.denied.nil? && fw.denied[0].ip_protocol == "all" 56 | end 57 | describe "[#{gcp_project_id}]" do 58 | it "has a deny all egress rule" do 59 | expect(egress_deny_all_fw_rules.count).to be >= 1 60 | end 61 | end 62 | 63 | # At least one egress rule in place 64 | egress_fw_rules = [] 65 | google_compute_firewalls(project: gcp_project_id).where(firewall_direction: 'EGRESS').where { firewall_name !~ /^gke/ }.firewall_names.each do |firewall_name| 66 | egress_fw_rules << firewall_name 67 | end 68 | describe "[#{gcp_project_id}]" do 69 | it "has at least one egress rule" do 70 | expect(egress_fw_rules.count).to be >= 1 71 | end 72 | end 73 | 74 | # Does not have an allow-all egress rule 75 | egress_allow_all_fw_rules = [] 76 | google_compute_firewalls(project: gcp_project_id).where(firewall_direction: 'EGRESS').where { firewall_name !~ /^gke/ }.firewall_names.each do |firewall_name| 77 | fw = google_compute_firewall(project: gcp_project_id, name: firewall_name) 78 | egress_allow_all_fw_rules << firewall_name if fw.respond_to?('allowed') \ 79 | && !fw.allowed.nil? \ 80 | && fw.allowed[0].ip_protocol == "all" 81 | end 82 | describe "[#{gcp_project_id}]" do 83 | it "does not have an allow all egress rule" do 84 | expect(egress_allow_all_fw_rules.count).to eq(0) 85 | end 86 | end 87 | 88 | # Specific egress TCP ports allowed to 0.0.0.0/0 89 | egress_allow_tcp_to_any_fw_rules = [] 90 | google_compute_firewalls(project: gcp_project_id).where(firewall_direction: 'EGRESS').where { firewall_name !~ /^gke/ }.firewall_names.each do |firewall_name| 91 | fw = google_compute_firewall(project: gcp_project_id, name: firewall_name) 92 | next unless fw.respond_to?('allowed') && !fw.allowed.nil? && fw.destination_ranges == ["0.0.0.0/0"] 93 | fw.allowed.each do |allow_item| 94 | egress_allow_tcp_to_any_fw_rules += allow_item.ports if allow_item.ip_protocol == "tcp" 95 | end 96 | end 97 | describe "[#{gcp_project_id}]" do 98 | it "should allow specific TCP ports outbound to 0.0.0.0/0" do 99 | expect(egress_allow_tcp_to_any_fw_rules).to eq(allow_all_tcp_ports) 100 | end 101 | end 102 | 103 | # Specific egress UDP ports allowed to 0.0.0.0/0 104 | egress_allow_udp_to_any_fw_rules = [] 105 | google_compute_firewalls(project: gcp_project_id).where(firewall_direction: 'EGRESS').where { firewall_name !~ /^gke/ }.firewall_names.each do |firewall_name| 106 | fw = google_compute_firewall(project: gcp_project_id, name: firewall_name) 107 | next unless fw.respond_to?('allowed') && !fw.allowed.nil? && fw.destination_ranges == ["0.0.0.0/0"] 108 | fw.allowed.each do |allow_item| 109 | egress_allow_udp_to_any_fw_rules += allow_item.ports if allow_item.ip_protocol == "udp" 110 | end 111 | end 112 | describe "[#{gcp_project_id}]" do 113 | it "should allow specific UDP ports outbound to 0.0.0.0/0" do 114 | expect(egress_allow_udp_to_any_fw_rules).to eq(allow_all_udp_ports) 115 | end 116 | end 117 | end 118 | end 119 | -------------------------------------------------------------------------------- /controls/10.5.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The inspec-gcp-pci-profile Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | gcp_project_id = input('gcp_project_id') 16 | gcp_gke_locations = input('gcp_gke_locations') 17 | pci_version = input('pci_version') 18 | pci_url = input('pci_url') 19 | pci_section = '10.5' 20 | 21 | gke_clusters = GKECache(project: gcp_project_id, gke_locations: gcp_gke_locations).gke_clusters_cache 22 | gcs_logging_buckets = input('gcs_logging_buckets') 23 | logging_viewer_list = input('logging_viewer_list') 24 | logging_admin_list = input('logging_admin_list') 25 | 26 | title "[PCI-DSS-#{pci_version}][#{pci_section}] Secure audit trails so they cannot be altered." 27 | 28 | # 10.5.1 29 | pci_req = "#{pci_section}.1" 30 | pci_req_title = "Limit viewing of audit trails to those with a job-related need." 31 | pci_req_guidance = "Adequate protection of the audit logs includes strong access control (limit access to logs based on “need to know” only), and use of physical or network segregation to make the logs harder to find and modify. 32 | 33 | Promptly backing up the logs to a centralized log server or media that is difficult to alter keeps the logs protected even if the system generating the logs becomes compromised." 34 | pci_req_coverage = 'partial' 35 | 36 | control "pci-dss-#{pci_version}-#{pci_req}" do 37 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 38 | desc "#{pci_req_guidance}" 39 | impact 1.0 40 | 41 | tag project: "#{gcp_project_id}" 42 | tag standard: "pci-dss" 43 | tag pci_version: "#{pci_version}" 44 | tag pci_section: "#{pci_section}" 45 | tag pci_req: "#{pci_req}" 46 | tag coverage: "#{pci_req_coverage}" 47 | 48 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 49 | 50 | # Ensure only a desired list of accounts have logging.viewer 51 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Ensure a allowlist of users/SAs/groups have access to logging viewer" do 52 | subject { google_project_iam_binding(project: gcp_project_id, role: 'roles/logging.viewer') } 53 | it "matches the Logging Viewer allow list" do 54 | expect(subject.members).to cmp(logging_viewer_list).or eq(nil).or cmp([]) 55 | end 56 | end 57 | end 58 | 59 | # 10.5.2 60 | pci_req = "#{pci_section}.2" 61 | pci_req_title = "Protect audit trail files from unauthorized modifications." 62 | pci_req_guidance = "Adequate protection of the audit logs includes strong access control (limit access to logs based on “need to know” only), and use of physical or network segregation to make the logs harder to find and modify. 63 | 64 | Promptly backing up the logs to a centralized log server or media that is difficult to alter keeps the logs protected even if the system generating the logs becomes compromised." 65 | pci_req_coverage = 'partial' 66 | 67 | control "pci-dss-#{pci_version}-#{pci_req}" do 68 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 69 | desc "#{pci_req_guidance}" 70 | impact 1.0 71 | 72 | tag project: "#{gcp_project_id}" 73 | tag standard: "pci-dss" 74 | tag pci_version: "#{pci_version}" 75 | tag pci_section: "#{pci_section}" 76 | tag pci_req: "#{pci_req}" 77 | tag coverage: "#{pci_req_coverage}" 78 | 79 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 80 | 81 | # Ensure only a desired list of accounts have logging.admin 82 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Ensure a allowlist of users/SAs/groups have access to logging Admin" do 83 | subject { google_project_iam_binding(project: gcp_project_id, role: 'roles/logging.admin') } 84 | it "matches the Logging Admin allow list" do 85 | expect(subject.members).to cmp(logging_admin_list).or eq(nil).or cmp([]) 86 | end 87 | end 88 | end 89 | 90 | # 10.5.4 91 | pci_req = "#{pci_section}.4" 92 | pci_req_title = " Write logs for external-facing technologies onto a secure, centralized, internal log server or media device." 93 | pci_req_guidance = "By writing logs from external-facing technologies such as wireless, firewalls, DNS, and mail servers, the risk of those logs being lost or altered is lowered, as they are more secure within the internal network. 94 | 95 | Logs may be written directly, or offloaded or copied from external systems, to the secure internal system or media. " 96 | pci_req_coverage = 'partial' 97 | 98 | control "pci-dss-#{pci_version}-#{pci_req}" do 99 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 100 | desc "#{pci_req_guidance}" 101 | impact 1.0 102 | 103 | tag project: "#{gcp_project_id}" 104 | tag standard: "pci-dss" 105 | tag pci_version: "#{pci_version}" 106 | tag pci_section: "#{pci_section}" 107 | tag pci_req: "#{pci_req}" 108 | tag coverage: "#{pci_req_coverage}" 109 | 110 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 111 | 112 | # Ensure that Stackdriver Logging is enabled on all GKE Clusters 113 | gke_clusters.each do |gke_cluster| 114 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Cluster #{gke_cluster[:location]}/#{gke_cluster[:cluster_name]}" do 115 | subject { google_container_cluster(project: gcp_project_id, location: gke_cluster[:location], name: gke_cluster[:cluster_name]) } 116 | its('logging_service') { should match(/^logging.googleapis.com/) } 117 | end 118 | end 119 | end 120 | 121 | # 10.5.5 122 | pci_req = "#{pci_section}.5" 123 | pci_req_title = "Use file-integrity monitoring or change-detection software on logs to ensure that existing log data cannot be changed without generating alerts" 124 | pci_req_guidance = "File-integrity monitoring or change-detection systems check for changes to critical files, and notify when such changes are noted. For fileintegrity monitoring purposes, an entity usually monitors files that don’t regularly change, but when changed indicate a possible compromise." 125 | pci_req_coverage = 'partial' 126 | 127 | control "pci-dss-#{pci_version}-#{pci_req}" do 128 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 129 | desc "#{pci_req_guidance}" 130 | impact 1.0 131 | 132 | tag project: "#{gcp_project_id}" 133 | tag standard: "pci-dss" 134 | tag pci_version: "#{pci_version}" 135 | tag pci_section: "#{pci_section}" 136 | tag pci_req: "#{pci_req}" 137 | tag coverage: "#{pci_req_coverage}" 138 | 139 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 140 | 141 | # Ensure each logging bucket has versioning policy set 142 | gcs_logging_buckets.each do |bucket| 143 | next if bucket.empty? 144 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Logging bucket: #{bucket}" do 145 | subject { google_storage_bucket(name: bucket) } 146 | it { should exist } 147 | its('versioning.enabled') { should eq true } 148 | end 149 | end 150 | end 151 | -------------------------------------------------------------------------------- /controls/1.1.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The inspec-gcp-pci-profile Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | gcp_project_id = input('gcp_project_id') 16 | pci_version = input('pci_version') 17 | pci_url = input('pci_url') 18 | pci_section = '1.1' 19 | 20 | fw_change_control_id_regex = input('fw_change_control_id_regex') 21 | fw_override_control_id_regex = input('fw_override_control_id_regex') 22 | fw_checked_insecure_tcp_ports = input('fw_checked_insecure_tcp_ports') 23 | fw_checked_insecure_udp_ports = input('fw_checked_insecure_udp_ports') 24 | dmz_login_ports = input('dmz_login_ports') 25 | 26 | title "[PCI-DSS-#{pci_version}][#{pci_section}] Establish and implement firewall and router configuration standards" 27 | 28 | # 1.1.1 29 | pci_req = "#{pci_section}.1" 30 | pci_req_title = "A formal process for approving and testing all network connections and changes to the firewall and router configurations" 31 | pci_req_guidance = "A documented and implemented process for approving and testing all connections and changes to the firewalls and routers will help prevent security problems caused by misconfiguration of the network, router, or firewall. 32 | Without formal approval and testing of changes, records of the changes might not be updated, which could lead to inconsistencies between network documentation and the actual configuration." 33 | pci_req_coverage = 'partial' 34 | 35 | control "pci-dss-#{pci_version}-#{pci_req}" do 36 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 37 | desc "#{pci_req_guidance}" 38 | impact 1.0 39 | 40 | tag project: "#{gcp_project_id}" 41 | tag standard: "pci-dss" 42 | tag pci_version: "#{pci_version}" 43 | tag pci_section: "#{pci_section}" 44 | tag pci_req: "#{pci_req}" 45 | tag coverage: "#{pci_req_coverage}" 46 | 47 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 48 | 49 | # Non-GKE Firewall Rules have description that includes the change control ID 50 | google_compute_firewalls(project: gcp_project_id).where { firewall_name !~ /^gke/ }.firewall_names.each do |firewall_name| 51 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] #{firewall_name}" do 52 | subject { google_compute_firewall(project: gcp_project_id, name: firewall_name) } 53 | its('description') { should match(/#{fw_change_control_id_regex}/) } 54 | end 55 | end 56 | end 57 | 58 | # 1.1.4 59 | pci_req = "#{pci_section}.4" 60 | pci_req_title = "Requirements for a firewall at each Internet connection and between any demilitarized zone (DMZ) and the internal network zone" 61 | pci_req_guidance = "Using a firewall on every Internet connection coming into (and out of) the network, and between any DMZ and the internal network, allows the organization to monitor and control access and minimizes the chances of a malicious individual obtaining access to the internal network via an unprotected connection." 62 | pci_req_coverage = 'partial' 63 | 64 | control "pci-dss-#{pci_version}-#{pci_req}" do 65 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 66 | desc "#{pci_req_guidance}" 67 | impact 1.0 68 | 69 | tag project: "#{gcp_project_id}" 70 | tag standard: "pci-dss" 71 | tag pci_version: "#{pci_version}" 72 | tag pci_section: "#{pci_section}" 73 | tag pci_req: "#{pci_req}" 74 | tag coverage: "#{pci_req_coverage}" 75 | 76 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 77 | 78 | google_compute_firewalls(project: gcp_project_id).where(firewall_direction: 'INGRESS').firewall_names.each do |firewall_name| 79 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] #{firewall_name}" do 80 | subject { google_compute_firewall(project: gcp_project_id, name: firewall_name) } 81 | dmz_login_ports.each do |port| 82 | it "should not allow #{port} from 0.0.0.0/0" do 83 | expect(subject.allow_port_protocol?(port, 'tcp') && (subject.allow_ip_ranges? ['0.0.0.0/0'])).to eq(false) 84 | end 85 | end 86 | end 87 | end 88 | end 89 | 90 | # 1.1.6 91 | pci_req = "#{pci_section}.6" 92 | pci_req_title = "Documentation of business justification and approval for use of all services, protocols, and ports allowed, including documentation of security features implemented for those protocols considered to be insecure." 93 | pci_req_guidance = "Compromises often happen due to unused or insecure service and ports, since these often have known vulnerabilities and many organizations don’t patch vulnerabilities for the services, protocols, and ports they don't use (even though the vulnerabilities are still present). By clearly defining and documenting the services, protocols, and ports that are necessary for business, organizations can ensure that all other services, protocols, and ports are disabled or removed. Approvals should be granted by personnel independent of the personnel managing the configuration. 94 | 95 | If insecure services, protocols, or ports are necessary for business, the risk posed by use of these protocols should be clearly understood and accepted by the organization, the use of the protocol should be justified, and the security features that allow these protocols to be used securely should be documented and implemented. If these insecure services, protocols, or ports are not necessary for business, they should be disabled or removed. For guidance on services, protocols, or ports considered to be insecure, refer to industry standards and guidance (e.g., NIST, ENISA, OWASP, etc.)." 96 | pci_req_coverage = 'partial' 97 | 98 | control "pci-dss-#{pci_version}-#{pci_req}" do 99 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 100 | desc "#{pci_req_guidance}" 101 | impact 1.0 102 | 103 | tag project: "#{gcp_project_id}" 104 | tag standard: "pci-dss" 105 | tag pci_version: "#{pci_version}" 106 | tag pci_section: "#{pci_section}" 107 | tag pci_req: "#{pci_req}" 108 | tag coverage: "#{pci_req_coverage}" 109 | 110 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 111 | 112 | # Firewall Rules for insecure ports have description that includes the overriding change control ID 113 | google_compute_firewalls(project: gcp_project_id).where { firewall_name !~ /^gke-/ }.firewall_names.each do |firewall_name| 114 | fwrule = google_compute_firewall(project: gcp_project_id, name: firewall_name) 115 | fw_checked_insecure_tcp_ports.each do |port| 116 | next unless fwrule.allow_port_protocol?("#{port}", 'tcp') 117 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Insecure port tcp/#{port} in #{firewall_name}" do 118 | subject { google_compute_firewall(project: gcp_project_id, name: firewall_name) } 119 | its('description') { should match(/#{fw_override_control_id_regex}/) } 120 | end 121 | end 122 | fw_checked_insecure_udp_ports.each do |port| 123 | next unless fwrule.allow_port_protocol?("#{port}", 'udp') 124 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Insecure port udp/#{port} in #{firewall_name}" do 125 | subject { google_compute_firewall(project: gcp_project_id, name: firewall_name) } 126 | its('description') { should match(/#{fw_override_control_id_regex}/) } 127 | end 128 | end 129 | end 130 | end 131 | -------------------------------------------------------------------------------- /inspec.yml: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # Copyright 2019 The inspec-gcp-pci-profile Authors 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | name: inspec-gcp-pci-3.2.1 17 | title: "Inspec GCP PCI-DSS v3.2.1" 18 | maintainer: 19 | copyright: Google 20 | copyright_email: copyright@google.com 21 | license: Apache-2.0 22 | summary: "Inspec Google Cloud Platform PCI-DSS v3.2.1 Profile" 23 | version: "3.2.1-7" 24 | supports: 25 | - platform: gcp 26 | depends: 27 | - name: inspec-gcp-helpers 28 | url: https://github.com/GoogleCloudPlatform/inspec-gcp-helpers/archive/v1.0.7.tar.gz 29 | inputs: 30 | - name: gcp_project_id 31 | description: 'The GCP project identifier.' 32 | type: string 33 | required: true 34 | value: 'replace-with-your-project-id' 35 | - name: pci_version 36 | description: 'The short version of the PCI DSS Standard' 37 | value: '3.2.1' 38 | type: string 39 | - name: pci_url 40 | description: 'The URL to the PCI-DSS 3.2.1 PDF' 41 | value: 'https://www.pcisecuritystandards.org/documents/PCI_DSS_v3-2-1.pdf' 42 | type: string 43 | - name: bucket_logging_ignore_regex 44 | description: 'Ignore this bucket by regex from requiring logging to be enabled' 45 | # example = "-logging" 46 | value: "replace-with-bucket-name-or-partial-match" 47 | type: string 48 | - name: fw_change_control_id_regex 49 | description: 'Firewall rule descriptions must match this string' 50 | # example = "CID:" 51 | value: "replace-with-change-control-id-regex" 52 | type: string 53 | - name: fw_override_control_id_regex 54 | description: 'Firewall rule descriptions must match this string' 55 | # example = "CID:" 56 | value: "replace-with-change-control-id-regex" 57 | type: string 58 | - name: cai_inventory_bucket_name 59 | description: 'The GCS bucket name where the CAI inventory is sent' 60 | value: "inventory-bucket-name" 61 | type: string 62 | - name: cai_inventory_file_path 63 | description: 'The path inside the GCS bucket where the latest CAI inventory file is stored.' 64 | value: "path/to/inventory.json" 65 | type: string 66 | - name: cai_inventory_age_seconds 67 | description: 'The allowed age in seconds of the inventory file' 68 | value: 86400 69 | type: numeric 70 | - name: gcs_pii_buckets 71 | description: 'The list of buckets that contain PII, PHI, etc' 72 | type: array 73 | value: 74 | - "my-bucket-name" 75 | - "my-other-bucket" 76 | - name: gcs_logging_buckets 77 | description: 'The list of buckets that contain log exports' 78 | type: array 79 | value: 80 | - "my-bucket-name" 81 | - name: logging_viewer_list 82 | description: 'The allowed list of Logging Viewer' 83 | type: array 84 | value: 85 | - "serviceAccount:test1@myproject.iam.gserviceaccount.com" 86 | - name: logging_admin_list 87 | description: 'The allowed list of Logging Admins' 88 | type: array 89 | value: 90 | - "serviceAccount:test1@myproject.iam.gserviceaccount.com" 91 | - name: project_owners_list 92 | description: 'The allowed list of Owners' 93 | type: array 94 | value: 95 | - "user:testuser@domain.com" 96 | - name: cloudsql_admins_list 97 | description: 'The allowed list of CloudSQL Admins' 98 | type: array 99 | value: 100 | - "serviceAccount:test1@myproject.iam.gserviceaccount.com" 101 | - "serviceAccount:test2@myproject.iam.gserviceaccount.com" 102 | - name: cloudsql_clients_list 103 | description: 'The allowed list of CloudSQL Users' 104 | type: array 105 | value: 106 | - "serviceAccount:test1@myproject.iam.gserviceaccount.com" 107 | - "serviceAccount:test2@myproject.iam.gserviceaccount.com" 108 | - name: memorystore_admins_list 109 | description: 'The allowed list of Memorystore Admins' 110 | type: array 111 | value: 112 | - "serviceAccount:test1@myproject.iam.gserviceaccount.com" 113 | - "serviceAccount:test2@myproject.iam.gserviceaccount.com" 114 | - name: bq_admins_list 115 | description: 'The allowed list of BigQuery Admins' 116 | type: array 117 | value: 118 | - "serviceAccount:test1@myproject.iam.gserviceaccount.com" 119 | - "serviceAccount:test2@myproject.iam.gserviceaccount.com" 120 | - name: spanner_admins_list 121 | description: 'The allowed list of Cloud Spanner Admins' 122 | type: array 123 | value: 124 | - "serviceAccount:test1@myproject.iam.gserviceaccount.com" 125 | - "serviceAccount:test2@myproject.iam.gserviceaccount.com" 126 | - name: kms_admins_list 127 | description: 'The allowed list of KMS Admins' 128 | type: array 129 | value: 130 | - "serviceAccount:test1@myproject.iam.gserviceaccount.com" 131 | - "serviceAccount:test2@myproject.iam.gserviceaccount.com" 132 | - name: kms_encrypters_list 133 | description: 'The allowed list of KMS Encrypters' 134 | type: array 135 | value: 136 | - "serviceAccount:test1@myproject.iam.gserviceaccount.com" 137 | - "serviceAccount:test2@myproject.iam.gserviceaccount.com" 138 | - name: kms_decrypters_list 139 | description: 'The allowed list of KMS Decrypters' 140 | type: array 141 | value: 142 | - "serviceAccount:test1@myproject.iam.gserviceaccount.com" 143 | - "serviceAccount:test2@myproject.iam.gserviceaccount.com" 144 | - name: kms_encrypterdecrypters_list 145 | description: 'The allowed list of KMS EncrypterDecrypters' 146 | type: array 147 | value: 148 | - "serviceAccount:test1@myproject.iam.gserviceaccount.com" 149 | - "serviceAccount:test2@myproject.iam.gserviceaccount.com" 150 | - name: kms_regions_list 151 | description: 'The allowed list of regions for KMS keys' 152 | type: array 153 | value: 154 | - "us-central1" 155 | - name: sa_key_older_than_seconds 156 | description: 'How many seconds SA keys should not be older than' 157 | value: 7776000 158 | type: numeric 159 | - name: kms_rotation_period_seconds 160 | description: 'How many seconds KMS Keys should be last rotated (1 year)' 161 | value: 31536000 162 | type: numeric 163 | - name: environment_label 164 | description: 'The label key designating the GCE/GKE instance environment' 165 | value: "env" 166 | type: string 167 | - name: allow_all_tcp_ports 168 | description: 'The allowed list of tcp ports egress to 0.0.0.0/0' 169 | type: array 170 | value: 171 | - "80" 172 | - "443" 173 | - name: allow_all_udp_ports 174 | description: 'The allowed list of udp ports egress to 0.0.0.0/0' 175 | type: array 176 | value: 177 | - "53" 178 | - name: gcp_gke_locations 179 | description: 'The list of regions and/or zone names where GKE clusters are running. An empty array searches all locations' 180 | type: array 181 | value: 182 | - "" 183 | - name: gce_zones 184 | description: 'The list of zone names where GCE instances are running. An empty array searches all locations' 185 | type: array 186 | value: 187 | - "" 188 | - name: dmz_login_ports 189 | description: 'The list of ports that are used to login to servers in the corporate network' 190 | type: array 191 | value: 192 | - '22' 193 | - '3389' 194 | - '10000' 195 | - name: fw_checked_insecure_tcp_ports 196 | description: 'The list of tcp ports that are required to have description that includes the overriding change control ID' 197 | type: array 198 | value: 199 | - '21' 200 | - '23' 201 | - '25' 202 | - '445' 203 | - '31337' 204 | - name: fw_checked_insecure_udp_ports 205 | description: 'The list of udp ports that are required to have description that includes the overriding change control ID' 206 | type: array 207 | value: 208 | - '53' 209 | - '69' 210 | - '161' 211 | - '162' 212 | -------------------------------------------------------------------------------- /controls/10.2.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The inspec-gcp-pci-profile Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | gcp_project_id = input('gcp_project_id') 16 | pci_version = input('pci_version') 17 | pci_url = input('pci_url') 18 | pci_section = '10.2' 19 | 20 | logging_viewer_list = input('logging_viewer_list') 21 | bucket_logging_ignore_regex = input('bucket_logging_ignore_regex') 22 | 23 | title "[PCI-DSS-#{pci_version}][#{pci_section}] Implement automated audit trails for all system components" 24 | 25 | # 10.2 26 | pci_req = "#{pci_section}" 27 | pci_req_title = "Implement automated audit trails for all system components" 28 | pci_req_guidance = "Generating audit trails of suspect activities alerts the system administrator, sends data to other monitoring mechanisms (like intrusion detection systems), and provides a history trail for postincident follow-up. Logging of the following events enables an organization to identify and trace potentially malicious activities" 29 | pci_req_coverage = 'partial' 30 | 31 | control "pci-dss-#{pci_version}-#{pci_req}" do 32 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 33 | desc "#{pci_req_guidance}" 34 | impact 1.0 35 | 36 | tag project: "#{gcp_project_id}" 37 | tag standard: "pci-dss" 38 | tag pci_version: "#{pci_version}" 39 | tag pci_section: "#{pci_section}" 40 | tag pci_req: "#{pci_req}" 41 | tag coverage: "#{pci_req_coverage}" 42 | 43 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 44 | 45 | # Subnets should have VPC flow logs enabled 46 | google_compute_regions(project: gcp_project_id).region_names.each do |region| 47 | google_compute_subnetworks(project: gcp_project_id, region: region).subnetwork_names.each do |subnet| 48 | subnet_obj = google_compute_subnetwork(project: gcp_project_id, region: region, name: subnet) 49 | next unless subnet_obj.methods.include?(:log_config) == true 50 | describe "[#{gcp_project_id}] #{region}/#{subnet}" do 51 | subject { subnet_obj } 52 | its('log_config.enable') { should be true } 53 | end 54 | end 55 | end 56 | 57 | # GCS Buckets should have logging enabled 58 | google_storage_buckets(project: gcp_project_id).bucket_names.each do |bucket| 59 | next if bucket =~ /#{bucket_logging_ignore_regex}/ 60 | describe "[#{gcp_project_id}] GCS Bucket #{bucket}" do 61 | subject { google_storage_bucket(name: bucket).logging } 62 | its('log_bucket') { should_not eq nil } 63 | end 64 | end 65 | end 66 | 67 | # 10.2.1 68 | pci_req = "#{pci_section}.1" 69 | pci_req_title = "All individual user accesses to cardholder data" 70 | pci_req_guidance = "Malicious individuals could obtain knowledge of a user account with access to systems in the CDE, or they could create a new, unauthorized account in order to access cardholder data. A record of all individual accesses to cardholder data can identify which accounts may have been compromised or misused." 71 | pci_req_coverage = 'partial' 72 | 73 | control "pci-dss-#{pci_version}-#{pci_req}" do 74 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 75 | desc "#{pci_req_guidance}" 76 | impact 1.0 77 | 78 | tag project: "#{gcp_project_id}" 79 | tag standard: "pci-dss" 80 | tag pci_version: "#{pci_version}" 81 | tag pci_section: "#{pci_section}" 82 | tag pci_req: "#{pci_req}" 83 | tag coverage: "#{pci_req_coverage}" 84 | 85 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 86 | 87 | # Project audit logging should be set with data_read/write logging enabled 88 | describe "[#{gcp_project_id}] The project audit logging configuration" do 89 | subject { google_project_logging_audit_config(project: gcp_project_id) } 90 | its('default_types') { should include 'DATA_READ' } 91 | its('default_types') { should include 'DATA_WRITE' } 92 | end 93 | end 94 | 95 | # 10.2.2 96 | pci_req = "#{pci_section}.2" 97 | pci_req_title = "All actions taken by any individual with root or administrative privileges" 98 | pci_req_guidance = "Accounts with increased privileges, such as the “administrator” or “root” account, have the potential to greatly impact the security or operational functionality of a system. Without a log of the activities performed, an organization is unable to trace any issues resulting from an administrative mistake or misuse of privilege back to the specific action and individual." 99 | pci_req_coverage = 'partial' 100 | 101 | control "pci-dss-#{pci_version}-#{pci_req}" do 102 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 103 | desc "#{pci_req_guidance}" 104 | impact 1.0 105 | 106 | tag project: "#{gcp_project_id}" 107 | tag standard: "pci-dss" 108 | tag pci_version: "#{pci_version}" 109 | tag pci_section: "#{pci_section}" 110 | tag pci_req: "#{pci_req}" 111 | tag coverage: "#{pci_req_coverage}" 112 | 113 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 114 | 115 | # Project Audit logging should exist 116 | describe "[#{gcp_project_id}] The project audit logging configuration for admin activity" do 117 | subject { google_project_logging_audit_config(project: gcp_project_id) } 118 | it { should exist } 119 | end 120 | end 121 | 122 | # 10.2.3 123 | pci_req = "#{pci_section}.3" 124 | pci_req_title = "Access to all audit trails" 125 | pci_req_guidance = "Malicious users often attempt to alter audit logs to hide their actions, and a record of access allows an organization to trace any inconsistencies or potential tampering of the logs to an individual account. Having access to logs identifying changes, additions, and deletions can help retrace steps made by unauthorized personnel. " 126 | pci_req_coverage = 'partial' 127 | 128 | control "pci-dss-#{pci_version}-#{pci_req}" do 129 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 130 | desc "#{pci_req_guidance}" 131 | impact 1.0 132 | 133 | tag project: "#{gcp_project_id}" 134 | tag standard: "pci-dss" 135 | tag pci_version: "#{pci_version}" 136 | tag pci_section: "#{pci_section}" 137 | tag pci_req: "#{pci_req}" 138 | tag coverage: "#{pci_req_coverage}" 139 | 140 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 141 | 142 | # Ensure only a desired list of accounts have logging.* 143 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Ensure a allowlist of users/SAs/groups have access to logging viewer" do 144 | subject { google_project_iam_binding(project: gcp_project_id, role: 'roles/logging.viewer') } 145 | it "matches the Logging Viewer allow list" do 146 | expect(subject.members).to cmp(logging_viewer_list).or eq(nil).or cmp([]) 147 | end 148 | end 149 | end 150 | 151 | # 10.2.6 152 | pci_req = "#{pci_section}.6" 153 | pci_req_title = "Initialization, stopping, or pausing of the audit logs" 154 | pci_req_guidance = "Turning the audit logs off (or pausing them) prior to performing illicit activities is a common practice for malicious users wishing to avoid detection. Initialization of audit logs could indicate that the log function was disabled by a user to hide their actions." 155 | pci_req_coverage = 'partial' 156 | 157 | control "pci-dss-#{pci_version}-#{pci_req}" do 158 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 159 | desc "#{pci_req_guidance}" 160 | impact 1.0 161 | 162 | tag project: "#{gcp_project_id}" 163 | tag standard: "pci-dss" 164 | tag pci_version: "#{pci_version}" 165 | tag pci_section: "#{pci_section}" 166 | tag pci_req: "#{pci_req}" 167 | tag coverage: "#{pci_req_coverage}" 168 | 169 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 170 | 171 | # Project Audit log should not have exempted members 172 | describe "[#{gcp_project_id}] The project audit logging configuration" do 173 | subject { google_project_logging_audit_config(project: gcp_project_id) } 174 | it { should_not have_default_exempted_members } 175 | end 176 | end 177 | -------------------------------------------------------------------------------- /controls/3.5.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The inspec-gcp-pci-profile Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | gcp_project_id = input('gcp_project_id') 16 | pci_version = input('pci_version') 17 | pci_url = input('pci_url') 18 | pci_section = '3.5' 19 | 20 | kms_regions_list = input('kms_regions_list') 21 | kms_admins_list = input('kms_admins_list') 22 | kms_encrypters_list = input('kms_encrypters_list') 23 | kms_decrypters_list = input('kms_decrypters_list') 24 | kms_encrypterdecrypters_list = input('kms_encrypterdecrypters_list') 25 | 26 | title "[PCI-DSS-#{pci_version}][#{pci_section}] Document and implement procedures to protect keys used to secure stored cardholder data against disclosure and misuse" 27 | 28 | # 3.5.2 29 | pci_req = "#{pci_section}.2" 30 | pci_req_title = "Restrict access to cryptographic keys to the fewest number of custodians necessary" 31 | pci_req_guidance = "There should be very few who have access to cryptographic keys (reducing the potential for rending cardholder data visible by unauthorized parties), usually only those who have key custodian responsibilities. " 32 | pci_req_coverage = 'partial' 33 | 34 | control "pci-dss-#{pci_version}-#{pci_req}" do 35 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 36 | desc "#{pci_req_guidance}" 37 | impact 1.0 38 | 39 | tag project: "#{gcp_project_id}" 40 | tag standard: "pci-dss" 41 | tag pci_version: "#{pci_version}" 42 | tag pci_section: "#{pci_section}" 43 | tag pci_req: "#{pci_req}" 44 | tag coverage: "#{pci_req_coverage}" 45 | 46 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 47 | 48 | # Get all "normal" regions and add "global" and multi-regional locations 49 | locations = google_compute_regions(project: gcp_project_id).region_names 50 | locations << 'global' << 'asia' << 'europe' << 'us' << 'eur4' << 'nam4' 51 | 52 | kms_cache = KMSKeyCache(project: gcp_project_id, locations: locations) 53 | 54 | keyrings = false 55 | locations.each do |location| 56 | if kms_cache.kms_key_ring_names[location].count.positive? 57 | keyrings = true 58 | break 59 | end 60 | end 61 | 62 | if keyrings 63 | iam_cache = IAMBindingsCache(project: gcp_project_id) 64 | kms_admin_bindings = iam_cache.iam_bindings['roles/cloudkms.admin'] 65 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Ensure KMS Admins" do 66 | subject { kms_admin_bindings } 67 | if kms_admin_bindings.nil? || kms_admin_bindings.members.empty? 68 | skip 'There are no Cloud KMS Admins in the project' 69 | else 70 | it "matches the KMS admins allow list" do 71 | expect(subject.members).to cmp(kms_admins_list) 72 | end 73 | end 74 | end 75 | 76 | kms_encrypters_bindings = iam_cache.iam_bindings['roles/cloudkms.cryptoKeyEncrypter'] 77 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Ensure KMS Encrypter are on a allowlist" do 78 | subject { kms_encrypters_bindings } 79 | if kms_encrypters_bindings.nil? || kms_encrypters_bindings.members.empty? 80 | skip 'There are no Cloud KMS Encrypters in the project' 81 | else 82 | it "matches the KMS Encrypters allow list" do 83 | expect(subject.members).to cmp(kms_encrypters_list) 84 | end 85 | end 86 | end 87 | 88 | kms_decrypters_bindings = iam_cache.iam_bindings['roles/cloudkms.cryptoKeyDecrypter'] 89 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Ensure KMS Decrypter are on a allowlist" do 90 | subject { kms_decrypters_bindings } 91 | if kms_decrypters_bindings.nil? || kms_decrypters_bindings.members.empty? 92 | skip 'There are no Cloud KMS Decrypters in the project' 93 | else 94 | it "matches the KMS Decrypters allow list" do 95 | expect(subject.members).to cmp(kms_decrypters_list) 96 | end 97 | end 98 | end 99 | 100 | kms_enc_dec_bindings = iam_cache.iam_bindings['roles/cloudkms.cryptoKeyEncrypterDecrypter'] 101 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Ensure KMS Encrypter/Decrypter are on a allowlist" do 102 | subject { kms_enc_dec_bindings } 103 | if kms_enc_dec_bindings.nil? || kms_enc_dec_bindings.members.empty? 104 | skip 'There are no Cloud KMS Encrypter/Decrypters in the project' 105 | else 106 | it "matches the KMS Encrypter/Decrypters allow list" do 107 | expect(subject.members).to cmp(kms_encrypterdecrypters_list) 108 | end 109 | end 110 | end 111 | end 112 | end 113 | 114 | # 3.5.3 115 | pci_req = "#{pci_section}.3" 116 | pci_req_title = "Store secret and private keys used to encrypt/decrypt cardholder data in a KMS" 117 | pci_req_guidance = "Cryptographic keys must be stored securely to prevent unauthorized or unnecessary access that could result in the exposure of cardholder data. 118 | 119 | It is not intended that the key-encrypting keys be encrypted, however they are to be protected against disclosure and misuse as defined in Requirement 3.5. If key-encrypting keys are used, storing the key-encrypting keys in physically and/or logically separate locations from the dataencrypting keys reduces the risk of unauthorized access to both keys." 120 | pci_req_coverage = 'partial' 121 | 122 | control "pci-dss-#{pci_version}-#{pci_req}" do 123 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 124 | desc "#{pci_req_guidance}" 125 | impact 1.0 126 | 127 | tag project: "#{gcp_project_id}" 128 | tag standard: "pci-dss" 129 | tag pci_version: "#{pci_version}" 130 | tag pci_section: "#{pci_section}" 131 | tag pci_req: "#{pci_req}" 132 | tag coverage: "#{pci_req_coverage}" 133 | 134 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 135 | 136 | # Get all "normal" regions and add "global" and multi-regional locations 137 | locations = google_compute_regions(project: gcp_project_id).region_names 138 | locations << 'global' << 'asia' << 'europe' << 'us' << 'eur4' << 'nam4' 139 | 140 | kms_cache = KMSKeyCache(project: gcp_project_id, locations: locations) 141 | # Ensure all KMS Keys in each Keyring are HSM-backed. 142 | locations.each do |location| 143 | kms_cache.kms_key_ring_names[location].each do |keyring| 144 | kms_cache.kms_crypto_keys[location][keyring].each do |keyname| 145 | key = google_kms_crypto_key(project: gcp_project_id, location: location, key_ring_name: keyring, name: keyname) 146 | next unless key.primary_state == "ENABLED" 147 | describe "[#{gcp_project_id}] #{key.crypto_key_name}" do 148 | subject { key } 149 | its('version_template.protection_level') { should match(/HSM/i) } 150 | end 151 | end 152 | end 153 | end 154 | end 155 | 156 | # 3.5.4 157 | pci_req = "#{pci_section}.4" 158 | pci_req_title = "Store cryptographic keys in the fewest possible locations." 159 | pci_req_guidance = "Storing cryptographic keys in the fewest locations helps an organization to keep track and monitor all key locations, and minimizes the potential for keys to be exposed to unauthorized parties." 160 | pci_req_coverage = 'partial' 161 | 162 | control "pci-dss-#{pci_version}-#{pci_req}" do 163 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 164 | desc "#{pci_req_guidance}" 165 | impact 1.0 166 | 167 | tag project: "#{gcp_project_id}" 168 | tag standard: "pci-dss" 169 | tag pci_version: "#{pci_version}" 170 | tag pci_section: "#{pci_section}" 171 | tag pci_req: "#{pci_req}" 172 | tag coverage: "#{pci_req_coverage}" 173 | 174 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 175 | 176 | # Get all "normal" regions and add "global" and multi-regional locations 177 | locations = google_compute_regions(project: gcp_project_id).region_names 178 | locations << 'global' << 'asia' << 'europe' << 'us' << 'eur4' << 'nam4' 179 | 180 | kms_cache = KMSKeyCache(project: gcp_project_id, locations: locations) 181 | # Ensure all KMS Keys are in the fewest locations possible 182 | keyring_locations = [] 183 | locations.each do |location| 184 | kms_cache.kms_key_ring_names[location].each do 185 | keyring_locations << location 186 | end 187 | end 188 | keyring_locations.sort!.uniq! unless keyring_locations.empty? 189 | 190 | describe "[#{gcp_project_id}] KMS Regions #{keyring_locations}" do 191 | subject { keyring_locations } 192 | it { should be_in kms_regions_list.sort } 193 | end 194 | end 195 | -------------------------------------------------------------------------------- /controls/1.3.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The inspec-gcp-pci-profile Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | gcp_project_id = input('gcp_project_id') 16 | gcp_gke_locations = input('gcp_gke_locations') 17 | gce_zones = input('gce_zones') 18 | pci_version = input('pci_version') 19 | pci_url = input('pci_url') 20 | pci_section = '1.3' 21 | 22 | gke_clusters = GKECache(project: gcp_project_id, gke_locations: gcp_gke_locations).gke_clusters_cache 23 | gce_instances = GCECache(project: gcp_project_id, gce_zones: gce_zones).gce_instances_cache 24 | fw_change_control_id_regex = input('fw_change_control_id_regex') 25 | 26 | title "[PCI-DSS-#{pci_version}][#{pci_section}] Prohibit direct public access between the Internet and any system component in the cardholder data environment." 27 | 28 | # 1.3.2 29 | pci_req = "#{pci_section}.2" 30 | pci_req_title = "Limit inbound Internet traffic to IP addresses within the DMZ." 31 | pci_req_guidance = "The DMZ is that part of the network that manages connections between the Internet (or other untrusted networks), and services that an organization needs to have available to the public (like a web server). 32 | 33 | This functionality is intended to prevent malicious individuals from accessing the organization's internal network from the Internet, or from using services, protocols, or ports in an unauthorized manner." 34 | pci_req_coverage = 'partial' 35 | 36 | control "pci-dss-#{pci_version}-#{pci_req}" do 37 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 38 | desc "#{pci_req_guidance}" 39 | impact 1.0 40 | 41 | tag project: "#{gcp_project_id}" 42 | tag standard: "pci-dss" 43 | tag pci_version: "#{pci_version}" 44 | tag pci_section: "#{pci_section}" 45 | tag pci_req: "#{pci_req}" 46 | tag coverage: "#{pci_req_coverage}" 47 | 48 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 49 | 50 | # Ensure ingress from 0.0.0.0 only to target tags or service accounts 51 | ingress_from_all_fw_rules = [] 52 | google_compute_firewalls(project: gcp_project_id).where(firewall_direction: 'INGRESS').where { firewall_name !~ /^gke/ }.firewall_names.each do |firewall_name| 53 | fw = google_compute_firewall(project: gcp_project_id, name: firewall_name) 54 | ingress_from_all_fw_rules << firewall_name if !fw.disabled \ 55 | && fw.respond_to?('source_ranges') \ 56 | && !fw.source_ranges.nil? \ 57 | && fw.allow_ip_range_list(['0.0.0.0/0']) 58 | end 59 | if ingress_from_all_fw_rules == [] 60 | describe "There are no applicable firewall rules" do 61 | skip 'There are no applicable firewall rules in this project' 62 | end 63 | else 64 | ingress_from_all_fw_rules.each do |fw_rule| 65 | fw = google_compute_firewall(project: gcp_project_id, name: fw_rule) 66 | if !fw.respond_to?('target_tags') && !fw.respond_to?('target_service_accounts') 67 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Ingress firewall rule #{fw_rule} that does not target tags or service accounts" do 68 | subject { fw } 69 | it { should_not exist } 70 | end 71 | end 72 | next unless fw.allow_port_protocol?("0", "all") 73 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Ingress firewall rule #{fw_rule} that allows all ports/protocols" do 74 | subject { fw } 75 | it { should_not exist } 76 | end 77 | end 78 | end 79 | end 80 | 81 | # 1.3.4 82 | pci_req = "#{pci_section}.4" 83 | pci_req_title = "Do not allow unauthorized outbound traffic from the cardholder data environment to the Internet." 84 | pci_req_guidance = "All traffic outbound from the cardholder data environment should be evaluated to ensure that it follows established, authorized rules. Connections should be inspected to restrict traffic to only authorized communications (for example by restricting source/destination addresses/ports, and/or blocking of content)." 85 | pci_req_coverage = 'partial' 86 | 87 | control "pci-dss-#{pci_version}-#{pci_req}" do 88 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 89 | desc "#{pci_req_guidance}" 90 | impact 1.0 91 | 92 | tag project: "#{gcp_project_id}" 93 | tag standard: "pci-dss" 94 | tag pci_version: "#{pci_version}" 95 | tag pci_section: "#{pci_section}" 96 | tag pci_req: "#{pci_req}" 97 | tag coverage: "#{pci_req_coverage}" 98 | 99 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 100 | 101 | if google_compute_networks(project: gcp_project_id).count.zero? 102 | impact 'none' 103 | describe "[#{gcp_project_id}] The project does not have VPCs. This test is not applicable." do 104 | skip "[#{gcp_project_id}] The project does not have VPCs. This test is not applicable." 105 | end 106 | else 107 | # Explicit egress deny all rule in place 108 | egress_deny_all_fw_rules = [] 109 | google_compute_firewalls(project: gcp_project_id).where(firewall_direction: 'EGRESS').where { firewall_name !~ /^gke/ }.firewall_names.each do |firewall_name| 110 | fw = google_compute_firewall(project: gcp_project_id, name: firewall_name) 111 | egress_deny_all_fw_rules << firewall_name if !fw.disabled \ 112 | && fw.respond_to?('denied') \ 113 | && !fw.denied.nil? \ 114 | && fw.denied[0].ip_protocol == "all" 115 | end 116 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}]" do 117 | it "has a deny all egress rule" do 118 | expect(egress_deny_all_fw_rules.count).to be >= 1 119 | end 120 | end 121 | end 122 | 123 | # Non-GKE Firewall egress Rules have description that includes the change control ID 124 | google_compute_firewalls(project: gcp_project_id).where(firewall_direction: 'EGRESS').where { firewall_name !~ /^gke/ }.firewall_names.each do |firewall_name| 125 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] #{firewall_name}'s" do 126 | subject { google_compute_firewall(project: gcp_project_id, name: firewall_name) } 127 | it "description should include a change control ID" do 128 | subject.description.should match(/#{fw_change_control_id_regex}/) 129 | end 130 | end 131 | end 132 | end 133 | 134 | # 1.3.6 135 | pci_req = "#{pci_section}.6" 136 | pci_req_title = "Place system components that store cardholder data (such as a database) in an internal network zone, segregated from the DMZ and other untrusted networks." 137 | pci_req_guidance = "If cardholder data is located within the DMZ, it is easier for an external attacker to access this information, since there are fewer layers to penetrate. Securing system components that store cardholder data in an internal network zone that is segregated from the DMZ and other untrusted networks by a firewall can prevent unauthorized network traffic from reaching the system component." 138 | pci_req_coverage = 'partial' 139 | 140 | control "pci-dss-#{pci_version}-#{pci_req}" do 141 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 142 | desc "#{pci_req_guidance}" 143 | impact 1.0 144 | 145 | tag project: "#{gcp_project_id}" 146 | tag standard: "pci-dss" 147 | tag pci_version: "#{pci_version}" 148 | tag pci_section: "#{pci_section}" 149 | tag pci_req: "#{pci_req}" 150 | tag coverage: "#{pci_req_coverage}" 151 | 152 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 153 | 154 | # GCE Instances should not have public IPs 155 | gce_instances.each do |instance| 156 | next if instance[:name] =~ /^gke-/ 157 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Instance: #{instance[:zone]}/#{instance[:name]}'s" do 158 | subject { google_compute_instance(project: gcp_project_id, zone: instance[:zone], name: instance[:name]) } 159 | it "should not have a public IP assigned" do 160 | expect(!subject.network_interfaces[0].respond_to?('access_configs') || subject.first_network_interface_type != "one_to_one_nat").to eq(true) 161 | end 162 | end 163 | end 164 | 165 | # GCS Buckets should not have allUsers or allAuthenticatedUsers (All) set on any bucket role 166 | google_storage_buckets(project: gcp_project_id).bucket_names.each do |bucket| 167 | google_storage_bucket_iam_bindings(bucket: bucket).iam_binding_roles.each do |role| 168 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] GCS Bucket #{bucket}, Role: #{role}" do 169 | subject { google_storage_bucket_iam_binding(bucket: bucket, role: role) } 170 | its('members') { should_not include 'allUsers' } 171 | its('members') { should_not include 'allAuthenticatedUsers' } 172 | end 173 | end 174 | end 175 | 176 | # GKE Clusters have private API and nodes 177 | gke_clusters.each do |gke_cluster| 178 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Cluster #{gke_cluster[:location]}/#{gke_cluster[:cluster_name]}" do 179 | subject { google_container_cluster(project: gcp_project_id, location: gke_cluster[:location], name: gke_cluster[:cluster_name]) } 180 | its('private_cluster_config.enable_private_endpoint') { should cmp true } 181 | its('private_cluster_config.enable_private_nodes') { should cmp true } 182 | end 183 | end 184 | 185 | # CloudSQL instances require SSL, are not allowed from 0.0.0.0/0, and use a private IP endpoint 186 | google_sql_database_instances(project: gcp_project_id).instance_names.each do |db| 187 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] CloudSQL #{db}" do 188 | subject { google_sql_database_instance(project: gcp_project_id, database: db) } 189 | it { should have_ip_configuration_require_ssl } 190 | its('authorized_networks') { should_not include '0.0.0.0/0' } 191 | it "should use a private IP address only" do 192 | expect(subject.settings.ip_configuration.respond_to?('private_network') && !subject.settings.ip_configuration.private_network.nil?).to eq(true) 193 | expect(subject.settings.ip_configuration.ipv4_enabled).to cmp(false) 194 | end 195 | end 196 | end 197 | end 198 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /controls/10.6.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The inspec-gcp-pci-profile Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | gcp_project_id = input('gcp_project_id') 16 | pci_version = input('pci_version') 17 | pci_url = input('pci_url') 18 | pci_section = '10.6' 19 | 20 | title "[PCI-DSS-#{pci_version}][#{pci_section}] Review logs and security events for all system components to identify anomalies or suspicious activity." 21 | 22 | # 10.6 23 | pci_req = "#{pci_section}" 24 | pci_req_title = "Review logs and security events for all system components to identify anomalies or suspicious activity." 25 | pci_req_guidance = "Many breaches occur over days or months before being detected. Regular log reviews by personnel or automated means can identify and proactively address unauthorized access to the cardholder data environment. 26 | 27 | The log review process does not have to be manual. The use of log harvesting, parsing, and alerting tools can help facilitate the process by identifying log events that need to be reviewed." 28 | pci_req_coverage = 'partial' 29 | 30 | control "pci-dss-#{pci_version}-#{pci_req}" do 31 | title "[PCI-DSS #{pci_version}][#{pci_req}] #{pci_req_title}" 32 | desc "#{pci_req_guidance}" 33 | impact 1.0 34 | 35 | tag project: "#{gcp_project_id}" 36 | tag standard: "pci-dss" 37 | tag pci_version: "#{pci_version}" 38 | tag pci_section: "#{pci_section}" 39 | tag pci_req: "#{pci_req}" 40 | tag coverage: "#{pci_req_coverage}" 41 | 42 | ref "PCI DSS #{pci_version}", url: "#{pci_url}" 43 | 44 | # Ensure project-level export sink is configured 45 | empty_filter_sinks = [] 46 | google_logging_project_sinks(project: gcp_project_id).names.each do |sink_name| 47 | empty_filter_sinks.push(sink_name) if google_logging_project_sink(project: gcp_project_id, 48 | name: sink_name).filter.nil? 49 | end 50 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Project level Log sink with an empty filter" do 51 | subject { empty_filter_sinks } 52 | it "is expected to exist" do 53 | expect(empty_filter_sinks.count).to be_positive 54 | end 55 | end 56 | 57 | # Ensure project ownership changes filter exists 58 | log_filter = "(protoPayload.serviceName=\"cloudresourcemanager.googleapis.com\") AND (ProjectOwnership OR projectOwnerInvitee) OR (protoPayload.serviceData.policyDelta.bindingDeltas.action=\"REMOVE\" AND protoPayload.serviceData.policyDelta.bindingDeltas.role=\"roles/owner\") OR (protoPayload.serviceData.policyDelta.bindingDeltas.action=\"ADD\" AND protoPayload.serviceData.policyDelta.bindingDeltas.role=\"roles/owner\")" 59 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Project Ownership changes filter" do 60 | subject { google_project_metrics(project: gcp_project_id).where(metric_filter: log_filter) } 61 | it { should exist } 62 | end 63 | 64 | # Ensure project ownership alert policy is configured 65 | google_project_metrics(project: gcp_project_id).where(metric_filter: log_filter).metric_types.each do |metrictype| 66 | filter = "metric.type=\"#{metrictype}\" resource.type=\"audited_resource\"" 67 | google_project_alert_policies(project: gcp_project_id).where { policy_filter_list.include? filter }.where(policy_enabled_state: true).policy_names.each do |policy| 68 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Project Ownership changes alert policy" do 69 | subject { google_project_alert_policy_condition(policy: policy, filter: filter) } 70 | it { should exist } 71 | its('aggregation_cross_series_reducer') { should eq 'REDUCE_COUNT' } 72 | its('aggregation_per_series_aligner') { should eq 'ALIGN_RATE' } 73 | its('condition_threshold_value') { should eq 0.001 } 74 | its('aggregation_alignment_period') { should eq '60s' } 75 | end 76 | end 77 | end 78 | 79 | # Ensure audit configuration changes filter exists 80 | log_filter = "protoPayload.methodName=\"SetIamPolicy\" AND protoPayload.serviceData.policyDelta.auditConfigDeltas:*" 81 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Audit configuration changes filter" do 82 | subject { google_project_metrics(project: gcp_project_id).where(metric_filter: log_filter) } 83 | it { should exist } 84 | end 85 | 86 | # Ensure audit configuration alert policy is configured 87 | google_project_metrics(project: gcp_project_id).where(metric_filter: log_filter).metric_types.each do |metrictype| 88 | filter = "metric.type=\"#{metrictype}\" resource.type=\"audited_resource\"" 89 | google_project_alert_policies(project: gcp_project_id).where { policy_filter_list.include? filter }.where(policy_enabled_state: true).policy_names.each do |policy| 90 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Audit configuration changes alert policy" do 91 | subject { google_project_alert_policy_condition(policy: policy, filter: filter) } 92 | it { should exist } 93 | its('aggregation_cross_series_reducer') { should eq 'REDUCE_COUNT' } 94 | its('aggregation_per_series_aligner') { should eq 'ALIGN_RATE' } 95 | its('condition_threshold_value') { should eq 0.001 } 96 | its('aggregation_alignment_period') { should eq '60s' } 97 | end 98 | end 99 | end 100 | 101 | # Ensure custom role alert metric exists 102 | log_filter = "resource.type=\"iam_role\" AND protoPayload.methodName=\"google.iam.admin.v1.CreateRole\" OR protoPayload.methodName=\"google.iam.admin.v1.DeleteRole\" OR protoPayload.methodName=\"google.iam.admin.v1.UpdateRole\"" 103 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Custom Role changes filter" do 104 | subject { google_project_metrics(project: gcp_project_id).where(metric_filter: log_filter) } 105 | it { should exist } 106 | end 107 | 108 | # Ensure custom role alert policy is configured 109 | google_project_metrics(project: gcp_project_id).where(metric_filter: log_filter).metric_types.each do |metrictype| 110 | filter = "metric.type=\"#{metrictype}\" resource.type=\"audited_resource\"" 111 | google_project_alert_policies(project: gcp_project_id).where { policy_filter_list.include? filter }.where(policy_enabled_state: true).policy_names.each do |policy| 112 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Custom Role changes alert policy" do 113 | subject { google_project_alert_policy_condition(policy: policy, filter: filter) } 114 | it { should exist } 115 | its('aggregation_cross_series_reducer') { should eq 'REDUCE_COUNT' } 116 | its('aggregation_per_series_aligner') { should eq 'ALIGN_RATE' } 117 | its('condition_threshold_value') { should eq 0.001 } 118 | its('aggregation_alignment_period') { should eq '60s' } 119 | end 120 | end 121 | end 122 | 123 | # Ensure VPC FW Rule Changes alert metric exists" do 124 | log_filter = "resource.type=\"gce_firewall_rule\" AND jsonPayload.event_subtype=\"compute.firewalls.patch\" OR jsonPayload.event_subtype=\"compute.firewalls.insert\"" 125 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] VPC FW Rule changes filter" do 126 | subject { google_project_metrics(project: gcp_project_id).where(metric_filter: log_filter) } 127 | it { should exist } 128 | end 129 | 130 | # Ensure VPC FW Rule alert policy is configured 131 | google_project_metrics(project: gcp_project_id).where(metric_filter: log_filter).metric_types.each do |metrictype| 132 | filter = "metric.type=\"#{metrictype}\" resource.type=\"audited_resource\"" 133 | google_project_alert_policies(project: gcp_project_id).where { policy_filter_list.include? filter }.where(policy_enabled_state: true).policy_names.each do |policy| 134 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] VPC FW Rule changes alert policy" do 135 | subject { google_project_alert_policy_condition(policy: policy, filter: filter) } 136 | it { should exist } 137 | its('aggregation_cross_series_reducer') { should eq 'REDUCE_COUNT' } 138 | its('aggregation_per_series_aligner') { should eq 'ALIGN_RATE' } 139 | its('condition_threshold_value') { should eq 0.001 } 140 | its('aggregation_alignment_period') { should eq '60s' } 141 | end 142 | end 143 | end 144 | 145 | # Ensure VPC Route Changes alert metric exists" do 146 | log_filter = "resource.type=\"gce_route\" AND jsonPayload.event_subtype=\"compute.routes.delete\" OR jsonPayload.event_subtype=\"compute.routes.insert\"" 147 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] VPC Route changes filter" do 148 | subject { google_project_metrics(project: gcp_project_id).where(metric_filter: log_filter) } 149 | it { should exist } 150 | end 151 | 152 | # Ensure VPC Route Changes alert policy is configured 153 | google_project_metrics(project: gcp_project_id).where(metric_filter: log_filter).metric_types.each do |metrictype| 154 | filter = "metric.type=\"#{metrictype}\" resource.type=\"audited_resource\"" 155 | google_project_alert_policies(project: gcp_project_id).where { policy_filter_list.include? filter }.where(policy_enabled_state: true).policy_names.each do |policy| 156 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] VPC Route changes alert policy" do 157 | subject { google_project_alert_policy_condition(policy: policy, filter: filter) } 158 | it { should exist } 159 | its('aggregation_cross_series_reducer') { should eq 'REDUCE_COUNT' } 160 | its('aggregation_per_series_aligner') { should eq 'ALIGN_RATE' } 161 | its('condition_threshold_value') { should eq 0.001 } 162 | its('aggregation_alignment_period') { should eq '60s' } 163 | end 164 | end 165 | end 166 | 167 | # Ensure VPC Network changes alert metric exists" do 168 | log_filter = "resource.type=gce_network AND jsonPayload.event_subtype=\"compute.networks.insert\" OR jsonPayload.event_subtype=\"compute.networks.patch\" OR jsonPayload.event_subtype=\"compute.networks.delete\" OR jsonPayload.event_subtype=\"compute.networks.removePeering\" OR jsonPayload.event_subtype=\"compute.networks.addPeering\"" 169 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] VPC Network changes filter" do 170 | subject { google_project_metrics(project: gcp_project_id).where(metric_filter: log_filter) } 171 | it { should exist } 172 | end 173 | 174 | # Ensure VPC Network alert policy is configured 175 | google_project_metrics(project: gcp_project_id).where(metric_filter: log_filter).metric_types.each do |metrictype| 176 | filter = "metric.type=\"#{metrictype}\" resource.type=\"audited_resource\"" 177 | google_project_alert_policies(project: gcp_project_id).where { policy_filter_list.include? filter }.where(policy_enabled_state: true).policy_names.each do |policy| 178 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] VPC Network changes alert policy" do 179 | subject { google_project_alert_policy_condition(policy: policy, filter: filter) } 180 | it { should exist } 181 | its('aggregation_cross_series_reducer') { should eq 'REDUCE_COUNT' } 182 | its('aggregation_per_series_aligner') { should eq 'ALIGN_RATE' } 183 | its('condition_threshold_value') { should eq 0.001 } 184 | its('aggregation_alignment_period') { should eq '60s' } 185 | end 186 | end 187 | end 188 | 189 | # Ensure Cloud Storage IAM changes alert metric exists" do 190 | log_filter = "resource.type=gcs_bucket AND protoPayload.methodName=\"storage.setIamPermissions\"" 191 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Cloud Storage changes filter" do 192 | subject { google_project_metrics(project: gcp_project_id).where(metric_filter: log_filter) } 193 | it { should exist } 194 | end 195 | 196 | # Ensure Cloud Storage IAM alert policy is configured 197 | google_project_metrics(project: gcp_project_id).where(metric_filter: log_filter).metric_types.each do |metrictype| 198 | filter = "metric.type=\"#{metrictype}\" resource.type=\"audited_resource\"" 199 | google_project_alert_policies(project: gcp_project_id).where { policy_filter_list.include? filter }.where(policy_enabled_state: true).policy_names.each do |policy| 200 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Cloud Storage changes alert policy" do 201 | subject { google_project_alert_policy_condition(policy: policy, filter: filter) } 202 | it { should exist } 203 | its('aggregation_cross_series_reducer') { should eq 'REDUCE_COUNT' } 204 | its('aggregation_per_series_aligner') { should eq 'ALIGN_RATE' } 205 | its('condition_threshold_value') { should eq 0.001 } 206 | its('aggregation_alignment_period') { should eq '60s' } 207 | end 208 | end 209 | end 210 | 211 | # Ensure Cloud SQL instance changes alert metrics exists" do 212 | log_filter = "protoPayload.methodName=\"cloudsql.instances.update\"" 213 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Cloud SQL changes filter" do 214 | subject { google_project_metrics(project: gcp_project_id).where(metric_filter: log_filter) } 215 | it { should exist } 216 | end 217 | 218 | # Ensure Cloud SQL alert policy is configured 219 | google_project_metrics(project: gcp_project_id).where(metric_filter: log_filter).metric_types.each do |metrictype| 220 | filter = "metric.type=\"#{metrictype}\" resource.type=\"audited_resource\"" 221 | google_project_alert_policies(project: gcp_project_id).where { policy_filter_list.include? filter }.where(policy_enabled_state: true).policy_names.each do |policy| 222 | describe "[#{pci_version}][#{pci_req}][#{gcp_project_id}] Cloud SQL changes alert policy" do 223 | subject { google_project_alert_policy_condition(policy: policy, filter: filter) } 224 | it { should exist } 225 | its('aggregation_cross_series_reducer') { should eq 'REDUCE_COUNT' } 226 | its('aggregation_per_series_aligner') { should eq 'ALIGN_RATE' } 227 | its('condition_threshold_value') { should eq 0.001 } 228 | its('aggregation_alignment_period') { should eq '60s' } 229 | end 230 | end 231 | end 232 | end 233 | --------------------------------------------------------------------------------