├── .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 |
--------------------------------------------------------------------------------