From 6bb465729b2111378c5a9d1eb9e91d91951748c1 Mon Sep 17 00:00:00 2001 From: "mariano.pizarro" Date: Fri, 20 May 2022 10:19:32 -0300 Subject: [PATCH] feat: Support GCP NIST Access Control Configuration, Policies and DNS Best Practices --- src/gcp/nist-800-53-rev4/README.md | 19 +- .../rules/gcp-nist-800-53-rev4-1.1.ts | 109 ++ .../rules/gcp-nist-800-53-rev4-1.2.ts | 126 ++ .../rules/gcp-nist-800-53-rev4-1.3.ts | 128 ++ .../rules/gcp-nist-800-53-rev4-1.4.ts | 123 ++ .../rules/gcp-nist-800-53-rev4-1.5.ts | 114 ++ .../rules/gcp-nist-800-53-rev4-1.6.ts | 78 + .../rules/gcp-nist-800-53-rev4-1.7.ts | 71 + .../rules/gcp-nist-800-53-rev4-2.1.ts | 70 + .../rules/gcp-nist-800-53-rev4-2.2.ts | 76 + .../rules/gcp-nist-800-53-rev4-2.3.ts | 79 + src/gcp/nist-800-53-rev4/rules/index.ts | 22 +- .../tests/nist-800-53-rev4-1.x.test.ts | 1466 +++++++++++++++++ .../tests/nist-800-53-rev4-2.x.test.ts | 180 ++ .../tests/nist-800-53-rev4.test.ts | 18 - 15 files changed, 2654 insertions(+), 25 deletions(-) create mode 100644 src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-1.1.ts create mode 100644 src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-1.2.ts create mode 100644 src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-1.3.ts create mode 100644 src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-1.4.ts create mode 100644 src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-1.5.ts create mode 100644 src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-1.6.ts create mode 100644 src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-1.7.ts create mode 100644 src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-2.1.ts create mode 100644 src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-2.2.ts create mode 100644 src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-2.3.ts create mode 100644 src/gcp/nist-800-53-rev4/tests/nist-800-53-rev4-1.x.test.ts create mode 100644 src/gcp/nist-800-53-rev4/tests/nist-800-53-rev4-2.x.test.ts delete mode 100644 src/gcp/nist-800-53-rev4/tests/nist-800-53-rev4.test.ts diff --git a/src/gcp/nist-800-53-rev4/README.md b/src/gcp/nist-800-53-rev4/README.md index e784b489..642b3981 100644 --- a/src/gcp/nist-800-53-rev4/README.md +++ b/src/gcp/nist-800-53-rev4/README.md @@ -51,10 +51,17 @@ Policy Pack based on the [800-53 Rev. 4](https://csrc.nist.gov/publications/deta } ``` - + +| Rule | Description | +| ------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| AWS NIST 1.1 | Compute instances should not use the default service account | +| AWS NIST 1.2 | Compute instances should not use the default service account with full access to all Cloud APIs | +| AWS NIST 1.3 | Compute instance "block-project-ssh-keys should be enabled | +| AWS NIST 1.4 | Compute instances should not have public IP addresses | +| AWS NIST 1.5 | Compute instances "Enable connecting to serial ports" should not be enabled | +| AWS NIST 1.6 | SQL database instances should not permit access from 0.0.0.0/0 | +| AWS NIST 1.7 | SQL database instances should not have public IPs | +| AWS NIST 2.1 | DNS managed zone DNSSEC should be enabled | +| AWS NIST 2.2 | DNS managed zone DNSSEC key-signing keys should not use RSASHA1 | +| AWS NIST 2.3 | DNS managed zone DNSSEC zone-signing keys should not use RSASHA1 | diff --git a/src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-1.1.ts b/src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-1.1.ts new file mode 100644 index 00000000..c95da131 --- /dev/null +++ b/src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-1.1.ts @@ -0,0 +1,109 @@ +// GCP CIS 1.2.0 Rule equivalent 4.1 +export default { + id: 'gcp-nist-800-53-rev4-1.1', + title: + 'GCP NIST 1.1 Compute instances should not use the default service account', + description: `It is recommended to configure your instance to not use the default Compute Engine + service account because it has the Editor role on the project.`, + audit: `**From Console:** + + 1. Go to the *VM instances* page by visiting: https://console.cloud.google.com/compute/instances. + 2. Click on each instance name to go to its *VM instance details* page. + 3. Under the section *Service Account*, ensure that the default Compute Engine service account is not used. This account is named *[PROJECT_NUMBER]-compute@developer.gserviceaccount.com*. + + **From Command Line:** + + 1. List the instances in your project: + + gcloud compute instances list + + 2. Get the details on each instance: + + gcloud compute instances describe INSTANCE_NAME --zone ZONE + + 3. Ensure that the service account section does not have an email that matches the pattern used does not match the pattern *[PROJECT_NUMBER]-compute@developer.gserviceaccount.com*. + + **Exception:** + VMs created by GKE should be excluded. These VMs have names that start with *gke-* and + are labeled *goog-gke-node*.`, + rationale: `The default Compute Engine service account has the Editor role on the project, which allows read and write access to most Google Cloud Services. To defend against privilege escalations if your VM is compromised and prevent an attacker from gaining access to all of your project, it is recommended to not use the default Compute Engine service account. Instead, you should create a new service account and assigning only the permissions needed by your instance. + + The default Compute Engine service account is named *[PROJECT_NUMBER]- compute@developer.gserviceaccount.com*.`, + remediation: `**From Console:** + + 1. Go to the *VM instances* page by visiting:https://console.cloud.google.com/compute/instances. + 2. Click on the instance name to go to its *VM instance details* page. + 3. Click *STOP* and then click *EDIT*. + 4. Under the section *Service Account*, select a service account other than the default Compute Engine service account. You may first need to create a new service account. + 5. Click *Save* and then click *START*. + + **From Command Line:** + + 1. Stop the instance: + + gcloud compute instances stop INSTANCE_NAME + + 2. Update the instance: + + gcloud compute instances set-service-account INSTANCE_NAME --service-account=SERVICE_ACCOUNT + + 3. Restart the instance: + + gcloud compute instances start INSTANCE_NAME`, + references: [ + 'https://cloud.google.com/compute/docs/access/service-accounts', + 'https://cloud.google.com/compute/docs/access/create-enable-service-accounts-for-instances', + 'https://cloud.google.com/sdk/gcloud/reference/compute/instances/set-service-account', + ], + gql: `{ + querygcpVmInstance{ + __typename + id + project{ + id + } + name + labels{ + value + } + serviceAccounts{ + email + } + } + }`, + resource: 'querygcpVmInstance[*]', + severity: 'medium', + conditions: { + path: '@', + or: [ + { + path: '@', + and: [ + { + path: '[*].name', + match: /^gke-.*$/, + }, + { + path: '[*].labels', + array_any: { + path: '[*].value', + equal: 'goog-gke-node', + }, + }, + ], + }, + { + jq: `[{ "defaultEmail" : (.project[].id | split("/") | .[1] + "-compute@developer.gserviceaccount.com")} + .serviceAccounts[]] + | [.[] | select(.defaultEmail == .email) ] + | {"match" : (length > 0)}`, + path: '@', + and: [ + { + path: '@.match', + notEqual: true, + }, + ], + }, + ], + }, +} diff --git a/src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-1.2.ts b/src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-1.2.ts new file mode 100644 index 00000000..a657cf58 --- /dev/null +++ b/src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-1.2.ts @@ -0,0 +1,126 @@ +// GCP CIS 1.2.0 Rule equivalent 4.2 +export default { + id: 'gcp-nist-800-53-rev4-1.2', + title: + 'GCP NIST 1.2 Compute instances should not use the default service account with full access to all Cloud APIs', + description: `To support principle of least privileges and prevent potential privilege escalation it is + recommended that instances are not assigned to default service account Compute Engine + default service account with Scope Allow full access to all Cloud APIs.`, + audit: `**From Console:** + + 1. Go to the *VM instances* page by visiting: https://console.cloud.google.com/compute/instances. + 2. Click on each instance name to go to its *VM instance details* page. + 3. If the *Default Compute Engine service account* is selected under *Service Account*, ensure that *Cloud API access scopes* is not set to *Allow full access to all Cloud APIs*. + + **From Command Line:** + + 1. List Instances from project + + gcloud compute instances list + + 2. Get the details on each instance: + + gcloud compute instances describe INSTANCE_NAME --zone ZONE + + 3. Ensure that the instance is not configured to allow the https://www.googleapis.com/auth/cloud-platform scope for the default Compute Engine service account: + + serviceAccounts: + - email: [PROJECT_NUMBER]-compute@developer.gserviceaccount.com + scopes: + - https://www.googleapis.com/auth/cloud-platform + + **Exception:** Instances created by GKE should be excluded. These instances have names that + start with "gke-" and are labeled "goog-gke-node"`, + rationale: `Along with ability to optionally create, manage and use user managed custom service accounts, Google Compute Engine provides default service account *Compute Engine default service account* for an instances to access necessary cloud services. *Project Editor* role is assigned to *Compute Engine default service account* hence, This service account has almost all capabilities over all cloud services except billing. However, when *Compute Engine default service account* assigned to an instance it can operate in 3 scopes. + + 1. Allow default access: Allows only minimum access required to run an Instance (Least Privileges) + 2. Allow full access to all Cloud APIs: Allow full access to all the cloud APIs/Services (Too much access) + 3. Set access for each API: Allows Instance administrator to choose only those APIs that are needed to perform specific business functionality expected by instance + + When an instance is configured with *Compute Engine default service account* with Scope *Allow full access to all Cloud APIs*, based on IAM roles assigned to the user(s) accessing Instance, it may allow user to perform cloud operations/API calls that user is not supposed to perform leading to successful privilege escalation.`, + remediation: `**From Console:** + + 1. Go to the *VM instances* page by visiting: https://console.cloud.google.com/compute/instances. + 2. Click on the impacted VM instance. + 3. If the instance is not stopped, click the *Stop* button. Wait for the instance to be stopped. + 4. Next, click the *Edit* button. + 5. Scroll down to the *Service Account* section. + 6. Select a different service account or ensure that *Allow full access to all Cloud APIs* is not selected. + 7. Click the *Save* button to save your changes and then click *START*. + + **From Command Line:** + + 1. Stop the instance: + + gcloud compute instances stop INSTANCE_NAME + + 2. Update the instance: + + gcloud compute instances set-service-account INSTANCE_NAME --service- account=SERVICE_ACCOUNT --scopes [SCOPE1, SCOPE2...] + + 3. Restart the instance: + + gcloud compute instances start INSTANCE_NAME`, + references: [ + 'https://cloud.google.com/compute/docs/access/create-enable-service-accounts-for-instances', + 'https://cloud.google.com/compute/docs/access/service-accounts', + ], + gql: `{ + querygcpVmInstance{ + __typename + id + project{ + id + } + name + labels{ + value + } + serviceAccounts{ + email + scopes + } + } + }`, + resource: 'querygcpVmInstance[*]', + severity: 'medium', + conditions: { + path: '@', + or: [ + { + path: '@', + and: [ + { + path: '[*].name', + match: /^gke-.*$/, + }, + { + path: '[*].labels', + array_any: { + path: '[*].value', + equal: 'goog-gke-node', + }, + }, + ], + }, + { + jq: `[{ "defaultEmail" : (.project[].id | split("/") | .[1] + "-compute@developer.gserviceaccount.com")} + .serviceAccounts[]] + | [.[] | select(.defaultEmail == .email) ] + | {"match" : (length > 0), "scopes": .[].scopes} // {"match" : false, "scopes": []}`, + path: '@', + and: [ + { + path: '@.match', + notEqual: true, + }, + { + path: '[*].scopes', + array_all: { + notEqual: 'https://www.googleapis.com/auth/cloud-platform', + }, + }, + ], + }, + ], + }, +} diff --git a/src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-1.3.ts b/src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-1.3.ts new file mode 100644 index 00000000..6b9336cf --- /dev/null +++ b/src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-1.3.ts @@ -0,0 +1,128 @@ +// GCP CIS 1.2.0 Rule equivalent 4.3 +export default { + id: 'gcp-nist-800-53-rev4-1.3', + title: + 'GCP NIST 1.3 Compute instance "block-project-ssh-keys should be enabled', + description: `It is recommended to use Instance specific SSH key(s) instead of using common/shared + project-wide SSH key(s) to access Instances.`, + audit: `**From Console:** + + 1. Go to the *VM instances* page by visiting https://console.cloud.google.com/compute/instances. It will list all the instances in your project. + 2. For every instance, click on the name of the instance. + 3. Under *SSH Keys*, ensure *Block project-wide SSH keys* is selected. + + **From Command Line:** + + 1. List all instances in a project: + + gcloud compute instances list + + 2. For every instance, get the instance metadata: + + gcloud compute instances describe INSTANCE_NAME + + 3. Ensure key: *block-project-ssh-keys* set to *value*: '*true*'. + + **Exception:** + Instances created by GKE should be excluded. These instances have names that start with + "gke-" and are labeled "goog-gke-node".`, + rationale: 'Project-wide SSH keys are stored in Compute/Project-meta-data. Project wide SSH keys can be used to login into all the instances within project. Using project-wide SSH keys eases the SSH key management but if compromised, poses the security risk which can impact all the instances within project. It is recommended to use Instance specific SSH keys which can limit the attack surface if the SSH keys are compromised.', + remediation: `**From Console:** + + 1. Go to the *VM instances* page by visiting: https://console.cloud.google.com/compute/instances. It will list all the instances in your project. + 2. Click on the name of the Impacted instance + 3. Click *Edit* in the toolbar + 4. Under SSH Keys, go to the *Block project-wide SSH keys* checkbox + 5. To block users with project-wide SSH keys from connecting to this instance, select *Block project-wide SSH keys* + 6. Click *Save* at the bottom of the page + 7. Repeat steps for every impacted Instance + + **From Command Line:** + Block project-wide public SSH keys, set the metadata value to *TRUE*: + + gcloud compute instances add-metadata INSTANCE_NAME --metadata block-project- ssh-keys=TRUE`, + references: [ + 'https://cloud.google.com/compute/docs/instances/adding-removing-ssh-keys', + ], + gql: `{ + querygcpVmInstance{ + __typename + id + project{ + id + } + name + labels{ + value + } + serviceAccounts{ + email + scopes + } + metadata{ + items{ + key + value + } + } + } + }`, + resource: 'querygcpVmInstance[*]', + severity: 'medium', + conditions: { + path: '@', + or: [ + { + path: '@', + and: [ + { + path: '[*].name', + match: /^gke-.*$/, + }, + { + path: '[*].labels', + array_any: { + path: '[*].value', + equal: 'goog-gke-node', + }, + }, + ], + }, + { + path: '[*].metadata.items', + isEmpty: true + }, + { + and: [ + { + path: '[*].metadata.items', + array_any: { + and: [ + { + path: '[*].key', + equal: 'block-project-ssh-keys', + }, + { + path: '[*].value', + equal: 'true', + }, + ], + }, + }, + { + jq: `[{ "defaultEmail" : (.project[].id | split("/") | .[1] + "-compute@developer.gserviceaccount.com")} + .serviceAccounts[]] + | [.[] | select(.defaultEmail == .email) ] + | {"match" : (length > 0)} // {"match" : false}`, + path: '@', + and: [ + { + path: '@.match', + notEqual: true, + }, + ], + }, + ], + }, + ], + }, +} diff --git a/src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-1.4.ts b/src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-1.4.ts new file mode 100644 index 00000000..728e5b5c --- /dev/null +++ b/src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-1.4.ts @@ -0,0 +1,123 @@ +// GCP CIS 1.2.0 Rule equivalent 4.9 +export default { + id: 'gcp-nist-800-53-rev4-1.4', + title: + 'GCP NIST 1.4 Compute instances should not have public IP addresses', + description: + 'Compute instances should not be configured to have external IP addresses.', + audit: `**From Console:** + + 1. Go to the *VM instances* page by visiting: https://console.cloud.google.com/compute/instances. + 2. For every VM, ensure that there is no *External IP* configured. + + **From Command Line:** + + 1. List the instances in your project: + + gcloud compute instances list + + 2. For every instance, list its configuration: + + gcloud compute instances describe INSTANCE_NAME --zone=ZONE + + 3. The output should not contain an *accessConfigs* section under *networkInterfaces*. Note that the *natIP* value is present only for instances that are running or for instances that are stoped but have a static IP address. For instances that are stopped and are configured to have an ephemeral public IP address, the *natIP* field will not be present. Example output: + + networkInterfaces: + - accessConfigs: + - kind: compute#accessConfig + name: External NAT + networkTier: STANDARD + type: ONE_TO_ONE_NAT + + **Exception:** + + Instances created by GKE should be excluded because some of them have external IP + addresses and cannot be changed by editing the instance settings. Instances created by GKE + should be excluded. These instances have names that start with "gke-" and are labeled + "goog-gke-node".`, + rationale: 'To reduce your attack surface, Compute instances should not have public IP addresses. Instead, instances should be configured behind load balancers, to minimize the instance\'s exposure to the internet.', + remediation: `**From Console:** + + 1. Go to the *VM instances* page by visiting: https://console.cloud.google.com/compute/instances. + 2. Click on the instance name to go the the *Instance detail page*. + 3. Click *Edit*. + 4. For each Network interface, ensure that *External IP* is set to *None*. + 5. Click *Done* and then click *Save*. + + **From Command Line:** + + 1. Describe the instance properties: + + gcloud compute instances describe INSTANCE_NAME --zone=ZONE + + 2. Identify the access config name that contains the external IP address. This access config appears in the following format: + + networkInterfaces: + - accessConfigs: + - kind: compute#accessConfig + name: External NAT + natIP: 130.211.181.55 + type: ONE_TO_ONE_NAT + + 2. Delete the access config. + + gcloud compute instances delete-access-config INSTANCE_NAME --zone=ZONE -- access-config-name "ACCESS_CONFIG_NAME" + + + In the above example, the *ACCESS_CONFIG_NAME* is *External NAT*. The name of your access + config might be different. + + **Prevention:** + + You can configure the *Define allowed external IPs for VM instances* Organization Policy to prevent VMs from being configured with public IP addresses. Learn more at: https://console.cloud.google.com/orgpolicies/compute-vmExternalIpAccess`, + references: [ + 'https://cloud.google.com/load-balancing/docs/backend-service#backends_and_external_ip_addresses', + 'https://cloud.google.com/compute/docs/instances/connecting-advanced#sshbetweeninstances', + 'https://cloud.google.com/compute/docs/instances/connecting-to-instance', + 'https://cloud.google.com/compute/docs/ip-addresses/reserve-static-external-ip-address#unassign_ip', + 'https://cloud.google.com/resource-manager/docs/organization-policy/org-policy-constraints', + ], + gql: `{ + querygcpVmInstance { + id + __typename + name + networkInterfaces { + accessConfigs { + name + natIP + } + } + } + }`, + resource: 'querygcpVmInstance[*]', + severity: 'unknown', + conditions: { + not: { + and: [ + { + path: '@.name', + mismatch: /^gke-.*$/, + }, + { + path: '@.networkInterfaces', + array_any: { + path: '[*].accessConfigs', + array_any: { + and: [ + { + path: '[*].natIP', + notEqual: null, + }, + { + path: '[*].natIP', + notEqual: '', + }, + ], + }, + }, + }, + ], + }, + }, +} diff --git a/src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-1.5.ts b/src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-1.5.ts new file mode 100644 index 00000000..47f31d73 --- /dev/null +++ b/src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-1.5.ts @@ -0,0 +1,114 @@ +// GCP CIS 1.2.0 Rule equivalent 4.5 +export default { + id: 'gcp-nist-800-53-rev4-1.5', + title: + 'GCP NIST 1.5 Compute instances "Enable connecting to serial ports" should not be enabled', + description: `Interacting with a serial port is often referred to as the serial console, which is similar to + using a terminal window, in that input and output is entirely in text mode and there is no + graphical interface or mouse support. + + If you enable the interactive serial console on an instance, clients can attempt to connect to + that instance from any IP address. Therefore interactive serial console support should be + disabled.`, + audit: `**From Console:** + + 1. Login to Google Cloud console + 2. Go to Computer Engine + 3. Go to VM instances + 4. Click on the Specific VM + 5. Ensure *Enable connecting to serial ports* below *Remote access* block is + unselected. + + **From Command Line:** + Ensure the below command's output shows *null*: + + gcloud compute instances describe --zone= -- format="json(metadata.items[].key,metadata.items[].value)" + + or *key* and *value* properties from below command's json response are equal to *serial-port-enable* and *0* or *false* respectively. + + { + "metadata": { + "items": [ + { + "key": "serial-port-enable", + "value": "0" + } + ] + } + }`, + rationale: `A virtual machine instance has four virtual serial ports. Interacting with a serial port is similar to using a terminal window, in that input and output is entirely in text mode and there is no graphical interface or mouse support. The instance's operating system, BIOS, and other system-level entities often write output to the serial ports, and can accept input such as commands or answers to prompts. Typically, these system-level entities use the first serial port (port 1) and serial port 1 is often referred to as the serial console. + + The interactive serial console does not support IP-based access restrictions such as IP whitelists. If you enable the interactive serial console on an instance, clients can attempt to connect to that instance from any IP address. This allows anybody to connect to that instance if they know the correct SSH key, username, project ID, zone, and instance name. + + Therefore interactive serial console support should be disabled.`, + remediation: `**From Console:** + + 1. Login to Google Cloud console + 2. Go to Computer Engine + 3. Go to VM instances + 4. Click on the Specific VM + 5. Click *EDIT* + 6. Unselect *Enable connecting to serial ports* below *Remote access* block. + 7. Click *Save* + + **From Command Line:** + Use the below command to disable + + gcloud compute instances add-metadata INSTANCE_NAME --zone=ZONE -- metadata=serial-port-enable=false + + or + + gcloud compute instances add-metadata INSTANCE_NAME --zone=ZONE -- metadata=serial-port-enable=0 + + **Prevention:** + You can prevent VMs from having serial port access enable by *Disable VM serial port + access* organization policy: https://console.cloud.google.com/iam-admin/orgpolicies/compute-disableSerialPortAccess.`, + references: [ + 'https://cloud.google.com/compute/docs/instances/interacting-with-serial-console', + ], + gql: `{ + querygcpVmInstance{ + __typename + id + metadata{ + items{ + key + value + } + } + } + }`, + resource: 'querygcpVmInstance[*]', + severity: 'medium', + conditions: { + path: '@.metadata.items', + array_any: { + or: [ + { + and: [ + { + path: '[*].key', + equal: 'serial-port-enable', + }, + { + path: '[*].value', + equal: '0', + }, + ], + }, + { + and: [ + { + path: '[*].key', + equal: 'serial-port-enable', + }, + { + path: '[*].value', + equal: 'false', + }, + ], + }, + ], + }, + }, +} diff --git a/src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-1.6.ts b/src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-1.6.ts new file mode 100644 index 00000000..4902c93d --- /dev/null +++ b/src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-1.6.ts @@ -0,0 +1,78 @@ +// GCP CIS 1.2.0 Rule equivalent 6.5 +export default { + id: 'gcp-nist-800-53-rev4-1.6', + title: + 'GCP NIST 1.6 SQL database instances should not permit access from 0.0.0.0/0', + description: `Database Server should accept connections only from trusted Network(s)/IP(s) and + restrict access from the world.`, + audit: `**From Console:** + + 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting https://console.cloud.google.com/sql/instances. + 2. Click the instance name to open its *Instance details* page. + 3. Under the *Configuration* section click *Edit configurations*. + 4. Under *Configuration options* expand the *Connectivity* section. + 5. Ensure that no authorized network is configured to allow *0.0.0.0/0*. + + **From Command Line:** + + 1. List all Cloud SQL database Instances using the following command: + + gcloud sql instances list + + + 2. Get detailed configuration for every Cloud SQL database instance. + + gcloud sql instances describe INSTANCE_NAME + + Ensure that the section *settings: ipConfiguration : authorizedNetworks* does not have any parameter value containing *0.0.0.0/0*.`, + rationale: `To minimize attack surface on a Database server instance, only trusted/known and required IP(s) should be white-listed to connect to it. + + An authorized network should not have IPs/networks configured to *0.0.0.0/0* which will allow access to the instance from anywhere in the world. Note that authorized networks apply only to instances with public IPs.`, + remediation: `**From Console:** + + 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting https://console.cloud.google.com/sql/instances. + 2. Click the instance name to open its *Instance details* page. + 3. Under the *Configuration* section click *Edit configuration*s + 4. Under *Configuration options* expand the *Connectivity* section. + 5. Click the *delete* icon for the authorized network *0.0.0.0/0*. + 6. Click *Save* to update the instance. + + **From Command Line:** + + Update the authorized network list by dropping off any addresses. + + gcloud sql instances patch INSTANCE_NAME --authorized-networks=IP_ADDR1,IP_ADDR2... + + **Prevention:** + + To prevent new SQL instances from being configured to accept incoming connections from any IP addresses, set up a *Restrict Authorized Networks on Cloud SQL instances* Organization Policy at: https://console.cloud.google.com/iam-admin/orgpolicies/sql-restrictAuthorizedNetworks.`, + references: [ + 'https://cloud.google.com/sql/docs/mysql/configure-ip', + 'https://console.cloud.google.com/iam-admin/orgpolicies/sql-restrictAuthorizedNetworks', + 'https://cloud.google.com/resource-manager/docs/organization-policy/org-policy-constraints', + 'https://cloud.google.com/sql/docs/mysql/connection-org-policy', + ], + gql: `{ + querygcpSqlInstance { + id + __typename + name + settings { + ipConfiguration { + authorizedNetworks { + value + } + } + } + } + }`, + resource: 'querygcpSqlInstance[*]', + severity: 'high', + conditions: { + path: '@.settings.ipConfiguration.authorizedNetworks', + array_all: { + path: '[*].value', + notEqual: '0.0.0.0/0', + }, + }, +} diff --git a/src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-1.7.ts b/src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-1.7.ts new file mode 100644 index 00000000..98aeafb7 --- /dev/null +++ b/src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-1.7.ts @@ -0,0 +1,71 @@ +// GCP CIS 1.2.0 Rule equivalent 6.6 +export default { + id: 'gcp-nist-800-53-rev4-1.7', + title: + 'GCP NIST 1.7 SQL database instances should not have public IPs', + description: `It is recommended to configure Second Generation Sql instance to use private IPs instead of + public IPs.`, + audit: `**From Console:** + + 1. Go to the Cloud SQL Instances page in the Google Cloud Console: https://console.cloud.google.com/sql/instances + 2. Ensure that every instance has a private IP address and no public IP address configured. + + **From Command Line:** + + 1. List all Cloud SQL database instances using the following command: + + gcloud sql instances list + + 2. For every instance of type *instanceType: CLOUD_SQL_INSTANCE* with *backendType: SECOND_GEN*, get detailed configuration. Ignore instances of type *READ_REPLICA_INSTANCE* because these instances inherit their settings from the primary instance. Also, note that first generation instances cannot be configured to have a private IP address. + + gcloud sql instances describe INSTANCE_NAME + + 3. Ensure that the setting *ipAddresses* has an IP address configured of *type: PRIVATE* and has no IP address of type: PRIMARY. PRIMARY email addresses are public addresses. An instance can have both a private and public address at the same time. Note also that you cannot use private IP with First Generation instances.`, + rationale: 'To lower the organization\'s attack surface, Cloud SQL databases should not have public IPs. Private IPs provide improved network security and lower latency for your application.', + remediation: `**From Console:** + + 1. Go to the Cloud SQL Instances page in the Google Cloud Console: https://console.cloud.google.com/sql/instances + 2. Click the instance name to open its Instance details page. + 3. Select the *Connections* tab. + 4. Deselect the *Public IP* checkbox. + 5. Click *Save* to update the instance. + + **From Command Line:** + + 1. For every instance remove its public IP and assign a private IP instead: + + gcloud beta sql instances patch INSTANCE_NAME --network=VPC_NETWOR_NAME --no-assign-ip + + 2. Confirm the changes using the following command:: + + gcloud sql instances describe INSTANCE_NAME + + **Prevention:** + + To prevent new SQL instances from getting configured with public IP addresses, set up a *Restrict Public IP access on Cloud SQL instances* Organization policy at: https://console.cloud.google.com/iam-admin/orgpolicies/sql-restrictPublicIp.`, + references: [ + 'https://cloud.google.com/sql/docs/mysql/configure-private-ip', + 'https://cloud.google.com/sql/docs/mysql/private-ip', + 'https://cloud.google.com/resource-manager/docs/organization-policy/org-policy-constraints', + 'https://console.cloud.google.com/iam-admin/orgpolicies/sql-restrictPublicIp', + ], + gql: `{ + querygcpSqlInstance(filter:{instanceType:{eq: "CLOUD_SQL_INSTANCE"}, backendType:{eq: "SECOND_GEN"}}) { + id + __typename + name + ipAddresses{ + type + } + } + }`, + resource: 'querygcpSqlInstance[*]', + severity: 'unknown', + conditions: { + path: '@.ipAddresses', + array_all: { + path: '[*].type', + equal: 'PRIVATE', + }, + }, +} diff --git a/src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-2.1.ts b/src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-2.1.ts new file mode 100644 index 00000000..85c689d9 --- /dev/null +++ b/src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-2.1.ts @@ -0,0 +1,70 @@ +// GCP CIS 1.2.0 Rule equivalent 3.3 +export default { + id: 'gcp-nist-800-53-rev4-2.1', + title: 'GCP NIST 2.1 DNS managed zone DNSSEC should be enabled', + description: `Cloud Domain Name System (DNS) is a fast, reliable and cost-effective domain name system + that powers millions of domains on the internet. Domain Name System Security Extensions + (DNSSEC) in Cloud DNS enables domain owners to take easy steps to protect their domains + against DNS hijacking and man-in-the-middle and other attacks.`, + audit: `**From Console:** + + 1. Go to *Cloud DNS* by visiting https://console.cloud.google.com/net-services/dns/zones. + 2. For each zone of *Type Public*, ensure that *DNSSEC* is set to *On*. + + **From Command Line:** + + 1. List all the Managed Zones in a project: + + gcloud dns managed-zones list + + 2. For each zone of *VISIBILITY public*, get its metadata: + + gcloud dns managed-zones describe ZONE_NAME + + 3. Ensure that *dnssecConfig.state* property is *on*.`, + rationale: 'Domain Name System Security Extensions (DNSSEC) adds security to the DNS protocol by enabling DNS responses to be validated. Having a trustworthy DNS that translates a domain name like www.example.com into its associated IP address is an increasingly important building block of today’s web-based applications. Attackers can hijack this process of domain/IP lookup and redirect users to a malicious site through DNS hijacking and man-in- the-middle attacks. DNSSEC helps mitigate the risk of such attacks by cryptographically signing DNS records. As a result, it prevents attackers from issuing fake DNS responses that may misdirect browsers to nefarious websites.', + remediation: `**From Console:** + + 1. Go to *Cloud DNS* by visiting https://console.cloud.google.com/net-services/dns/zones. + 2. For each zone of *Type Public*, set *DNSSEC* to *On*. + + **From Command Line:** + Use the below command to enable *DNSSEC* for Cloud DNS Zone Name. + + gcloud dns managed-zones update ZONE_NAME --dnssec-state on`, + references: [ + 'https://cloudplatform.googleblog.com/2017/11/DNSSEC-now-available-in-Cloud-DNS.html', + 'https://cloud.google.com/dns/dnssec-config#enabling', + 'https://cloud.google.com/dns/dnssec', + ], + gql: `{ + querygcpDnsManagedZone { + id + __typename + visibility + dnssecConfigState + } + }`, + resource: 'querygcpDnsManagedZone[*]', + severity: 'medium', + conditions: { + or: [ + { + path: '@.visibility', + equal: 'private', + }, + { + and: [ + { + path: '@.visibility', + equal: 'public', + }, + { + path: '@.dnssecConfigState', + equal: 'on', + }, + ], + }, + ], + }, +} diff --git a/src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-2.2.ts b/src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-2.2.ts new file mode 100644 index 00000000..8f58e315 --- /dev/null +++ b/src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-2.2.ts @@ -0,0 +1,76 @@ +// GCP CIS 1.2.0 Rule equivalent 3.4 +export default { + id: 'gcp-nist-800-53-rev4-2.2', + title: + 'GCP NIST 2.2 DNS managed zone DNSSEC key-signing keys should not use RSASHA1', + description: `DNSSEC algorithm numbers in this registry may be used in CERT RRs. Zone signing + (DNSSEC) and transaction security mechanisms (SIG(0) and TSIG) make use of particular + subsets of these algorithms. The algorithm used for key signing should be a recommended + one and it should be strong.`, + audit: `Currently there is no support to audit this setting through console. + + **From Command Line:** + Ensure the property algorithm for keyType keySigning is not using *RSASHA1*. + + gcloud dns managed-zones describe ZONENAME --format="json(dnsName,dnssecConfig.state,dnssecConfig.defaultKeySpecs)"`, + rationale: `Domain Name System Security Extensions (DNSSEC) algorithm numbers in this registry may be used in CERT RRs. Zonesigning (DNSSEC) and transaction security mechanisms (SIG(0) and TSIG) make use of particular subsets of these algorithms. + + The algorithm used for key signing should be a recommended one and it should be strong. When enabling DNSSEC for a managed zone, or creating a managed zone with DNSSEC, the user can select the DNSSEC signing algorithms and the denial-of-existence type. Changing the DNSSEC settings is only effective for a managed zone if DNSSEC is not already enabled. If there is a need to change the settings for a managed zone where it has been enabled, turn DNSSEC off and then re-enable it with different settings.`, + remediation: `1. If it is necessary to change the settings for a managed zone where it has been enabled, NSSEC must be turned off and re-enabled with different settings. To turn off DNSSEC, run the following command: + + gcloud dns managed-zones update ZONE_NAME --dnssec-state off + + +2. To update key-signing for a reported managed DNS Zone, run the following command: + + gcloud dns managed-zones update ZONE_NAME --dnssec-state on --ksk-algorithm KSK_ALGORITHM --ksk-key-length KSK_KEY_LENGTH --zsk-algorithm ZSK_ALGORITHM - -zsk-key-length ZSK_KEY_LENGTH --denial-of-existence DENIAL_OF_EXISTENCE`, + references: [ + 'https://cloud.google.com/dns/dnssec-advanced#advanced_signing_options', + ], + gql: `{ + querygcpDnsManagedZone { + id + __typename + visibility + dnssecConfigDefaultKeySpecs { + keyType + algorithm + } + } + }`, + resource: 'querygcpDnsManagedZone[*]', + severity: 'medium', + conditions: { + or: [ + { + path: '@.visibility', + equal: 'private', + }, + { + and: [ + { + path: '@.visibility', + equal: 'public', + }, + { + not: { + path: '@.dnssecConfigDefaultKeySpecs', + array_any: { + and: [ + { + path: '[*].keyType', + equal: 'keySigning', + }, + { + path: '[*].algorithm', + equal: 'rsasha1', + }, + ], + }, + }, + }, + ], + }, + ], + }, +} diff --git a/src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-2.3.ts b/src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-2.3.ts new file mode 100644 index 00000000..044a5842 --- /dev/null +++ b/src/gcp/nist-800-53-rev4/rules/gcp-nist-800-53-rev4-2.3.ts @@ -0,0 +1,79 @@ +// GCP CIS 1.2.0 Rule equivalent 3.5 +export default { + id: 'gcp-nist-800-53-rev4-2.3', + title: + 'GCP NIST 2.3 DNS managed zone DNSSEC zone-signing keys should not use RSASHA1', + description: `DNSSEC algorithm numbers in this registry may be used in CERT RRs. Zone signing + (DNSSEC) and transaction security mechanisms (SIG(0) and TSIG) make use of particular + subsets of these algorithms. The algorithm used for key signing should be a recommended + one and it should be strong.`, + audit: `Currently there is no support to audit this setting through the console. + + **From Command Line:** + Ensure the property algorithm for keyType zone signing is not using RSASHA1. + + gcloud dns managed-zones describe --format="json(dnsName,dnssecConfig.state,dnssecConfig.defaultKeySpecs)"`, + rationale: `DNSSEC algorithm numbers in this registry may be used in CERT RRs. Zone signing (DNSSEC) and transaction security mechanisms (SIG(0) and TSIG) make use of particular subsets of these algorithms. + + The algorithm used for key signing should be a recommended one and it should be strong. When enabling DNSSEC for a managed zone, or creating a managed zone with DNSSEC, the DNSSEC signing algorithms and the denial-of-existence type can be selected. Changing the DNSSEC settings is only effective for a managed zone if DNSSEC is not already enabled. If the need exists to change the settings for a managed zone where it has been enabled, turn DNSSEC off and then re-enable it with different settings.`, + remediation: `1. If the need exists to change the settings for a managed zone where it has been + enabled, DNSSEC must be turned off and then re-enabled with different settings. To + turn off DNSSEC, run following command: + + gcloud dns managed-zones update ZONE_NAME --dnssec-state off + + +2. To update zone-signing for a reported managed DNS Zone, run the following + command: + + gcloud dns managed-zones update ZONE_NAME --dnssec-state on --ksk-algorithm KSK_ALGORITHM --ksk-key-length KSK_KEY_LENGTH --zsk-algorithm ZSK_ALGORITHM - -zsk-key-length ZSK_KEY_LENGTH --denial-of-existence DENIAL_OF_EXISTENCE`, + references: [ + 'https://cloud.google.com/dns/dnssec-advanced#advanced_signing_options', + ], + gql: `{ + querygcpDnsManagedZone { + id + __typename + visibility + dnssecConfigDefaultKeySpecs { + keyType + algorithm + } + } + }`, + resource: 'querygcpDnsManagedZone[*]', + severity: 'medium', + conditions: { + or: [ + { + path: '@.visibility', + equal: 'private', + }, + { + and: [ + { + path: '@.visibility', + equal: 'public', + }, + { + not: { + path: '@.dnssecConfigDefaultKeySpecs', + array_any: { + and: [ + { + path: '[*].keyType', + equal: 'zoneSigning', + }, + { + path: '[*].algorithm', + equal: 'rsasha1', + }, + ], + }, + }, + }, + ], + }, + ], + }, +} diff --git a/src/gcp/nist-800-53-rev4/rules/index.ts b/src/gcp/nist-800-53-rev4/rules/index.ts index 44842b4f..65a61b1a 100644 --- a/src/gcp/nist-800-53-rev4/rules/index.ts +++ b/src/gcp/nist-800-53-rev4/rules/index.ts @@ -1,3 +1,23 @@ +import Gcp_NIST_800_53_11 from './gcp-nist-800-53-rev4-1.1' +import Gcp_NIST_800_53_12 from './gcp-nist-800-53-rev4-1.2' +import Gcp_NIST_800_53_13 from './gcp-nist-800-53-rev4-1.3' +import Gcp_NIST_800_53_14 from './gcp-nist-800-53-rev4-1.4' +import Gcp_NIST_800_53_15 from './gcp-nist-800-53-rev4-1.5' +import Gcp_NIST_800_53_16 from './gcp-nist-800-53-rev4-1.6' +import Gcp_NIST_800_53_17 from './gcp-nist-800-53-rev4-1.7' +import Gcp_NIST_800_53_21 from './gcp-nist-800-53-rev4-2.1' +import Gcp_NIST_800_53_22 from './gcp-nist-800-53-rev4-2.2' +import Gcp_NIST_800_53_23 from './gcp-nist-800-53-rev4-2.3' + export default [ - // TODO: Add rules to export + Gcp_NIST_800_53_11, + Gcp_NIST_800_53_12, + Gcp_NIST_800_53_13, + Gcp_NIST_800_53_14, + Gcp_NIST_800_53_15, + Gcp_NIST_800_53_16, + Gcp_NIST_800_53_17, + Gcp_NIST_800_53_21, + Gcp_NIST_800_53_22, + Gcp_NIST_800_53_23. ] \ No newline at end of file diff --git a/src/gcp/nist-800-53-rev4/tests/nist-800-53-rev4-1.x.test.ts b/src/gcp/nist-800-53-rev4/tests/nist-800-53-rev4-1.x.test.ts new file mode 100644 index 00000000..97774375 --- /dev/null +++ b/src/gcp/nist-800-53-rev4/tests/nist-800-53-rev4-1.x.test.ts @@ -0,0 +1,1466 @@ +import cuid from 'cuid' +import CloudGraph, { Rule, Result, Engine } from '@cloudgraph/sdk' + +import Gcp_NIST_800_53_11 from '../rules/gcp-nist-800-53-rev4-1.1' +import Gcp_NIST_800_53_12 from '../rules/gcp-nist-800-53-rev4-1.2' +import Gcp_NIST_800_53_13 from '../rules/gcp-nist-800-53-rev4-1.3' +import Gcp_NIST_800_53_14 from '../rules/gcp-nist-800-53-rev4-1.4' +import Gcp_NIST_800_53_15 from '../rules/gcp-nist-800-53-rev4-1.5' +import Gcp_NIST_800_53_16 from '../rules/gcp-nist-800-53-rev4-1.6' +import Gcp_NIST_800_53_17 from '../rules/gcp-nist-800-53-rev4-1.7' + +export interface DatabaseFlagsItem { + name: string + value: string | null +} + +export interface AuthorizedNetwork { + value: string +} + +export interface IpConfiguration { + requireSsl?: boolean | null + authorizedNetworks?: AuthorizedNetwork[] +} + +export interface BackupConfiguration { + enabled: boolean | null + startTime: string | null +} + +export interface Settings { + databaseFlags: DatabaseFlagsItem[] + ipConfiguration?: IpConfiguration + backupConfiguration?: BackupConfiguration +} + +export interface IpAddress { + type: string +} + +export interface ServiceAccount { + email: string + scopes?: string[] +} + +export interface Label { + value: string +} + +export interface Project { + id: string +} + +export interface MetadataItem { + key: string + value: string +} + +export interface Metadata { + items: MetadataItem[] +} + +export interface DiskEncryptionKey { + sha256: string | null +} + +export interface Disk { + diskEncryptionKey: DiskEncryptionKey | null +} + +export interface AccessConfigs { + natIP: string | null +} + +export interface NetworkInterfaces { + accessConfigs: AccessConfigs[] +} + +export interface ShieldedInstanceConfig { + enableIntegrityMonitoring: boolean + enableVtpm: boolean +} + +export interface ConfidentialInstanceConfig { + enableConfidentialCompute: boolean +} + +export interface QuerygcpVmInstance { + id: string + name?: string + shieldedInstanceConfig?: ShieldedInstanceConfig + confidentialInstanceConfig?: ConfidentialInstanceConfig + networkInterfaces?: NetworkInterfaces[] + canIpForward?: boolean + project?: Project[] + labels?: Label[] + metadata?: Metadata + serviceAccounts?: ServiceAccount[] + disks?: Disk[] +} + +export interface ComputeProject { + commonInstanceMetadata: Metadata +} + +export interface QuerygcpProject { + id: string + computeProject?: ComputeProject[] + vmInstances?: QuerygcpVmInstance[] +} + +export interface QuerygcpSqlInstance { + id?: string + name: string + settings: Settings + ipAddresses?: IpAddress[] +} + +export interface NIST1xQueryResponse { + querygcpVmInstance?: QuerygcpVmInstance[] + querygcpProject?: QuerygcpProject[] + querygcpSqlInstance?: QuerygcpSqlInstance[] +} + +describe('GCP NIST 800-53: Rev. 4', () => { + let rulesEngine: Engine + beforeAll(() => { + rulesEngine = new CloudGraph.RulesEngine({ providerName: 'gcp', entityName: 'NIST'} ) + }) + + describe('GCP NIST 1.1 Compute instances should not use the default service account', () => { + const getTest41RuleFixture = ( + name: string, + projects: Project[], + labels: Label[], + serviceAccounts: ServiceAccount[] + ): NIST1xQueryResponse => { + return { + querygcpVmInstance: [ + { + id: cuid(), + name, + project: projects, + labels, + serviceAccounts, + }, + ], + } + } + + const test41Rule = async ( + data: NIST1xQueryResponse, + expectedResult: Result + ): Promise => { + // Act + const [processedRule] = await rulesEngine.processRule( + Gcp_NIST_800_53_11 as Rule, + { ...data } + ) + + // Asserts + expect(processedRule.result).toBe(expectedResult) + } + + test('No Security Issue when the vm name starts with "gke-", it has a "goog-gke-node" label and the service account is the default compute service account', async () => { + const projectId = 123456789 + const name = 'gke-test' + const projects: Project[] = [{ id: `projects/${projectId}` }] + const labels: Label[] = [ + { + value: 'goog-gke-node', + }, + ] + const serviceAccounts: ServiceAccount[] = [ + { + email: `${projectId}-compute@developer.gserviceaccount.com`, + }, + ] + const data: NIST1xQueryResponse = getTest41RuleFixture( + name, + projects, + labels, + serviceAccounts + ) + await test41Rule(data, Result.PASS) + }) + + test('No Security Issue when the vm name starts with "gke-", it does NOT have a "goog-gke-node" label but the service account is NOT the default compute service account', async () => { + const name = 'gke-test' + const projects: Project[] = [{ id: 'projects/dummy-id' }] + const labels: Label[] = [] + const serviceAccounts: ServiceAccount[] = [ + { + email: 'dummy-compute@test.com', + }, + ] + const data: NIST1xQueryResponse = getTest41RuleFixture( + name, + projects, + labels, + serviceAccounts + ) + await test41Rule(data, Result.PASS) + }) + + test('No Security Issue when the vm name does NOT start with "gke-", it has a "goog-gke-node" label but the service account is NOT the default compute service account', async () => { + const name = 'dummy' + const projects: Project[] = [{ id: 'projects/dummy-id' }] + const labels: Label[] = [ + { + value: 'goog-gke-node', + }, + ] + const serviceAccounts: ServiceAccount[] = [ + { + email: 'dummy-compute@test.com', + }, + ] + const data: NIST1xQueryResponse = getTest41RuleFixture( + name, + projects, + labels, + serviceAccounts + ) + await test41Rule(data, Result.PASS) + }) + + test('No Security Issue when the vm name does NOT start with "gke-", it does NOT have a "goog-gke-node" label but the service account is NOT the default compute service account', async () => { + const name = 'dummy' + const projects: Project[] = [{ id: 'projects/dummy-id' }] + const labels: Label[] = [ + { + value: 'dummy-label', + }, + ] + const serviceAccounts: ServiceAccount[] = [ + { + email: 'dummy-compute@test.com', + }, + ] + const data: NIST1xQueryResponse = getTest41RuleFixture( + name, + projects, + labels, + serviceAccounts + ) + await test41Rule(data, Result.PASS) + }) + + test('Security Issue when the vm name does NOT start with "gke-", it does NOT have a "goog-gke-node" label and the service account is the default compute service account', async () => { + const projectId = 123456789 + const name = 'dummy' + const projects: Project[] = [{ id: `projects/${projectId}` }] + const labels: Label[] = [ + { + value: 'dummy-label', + }, + ] + const serviceAccounts: ServiceAccount[] = [ + { + email: `${projectId}-compute@developer.gserviceaccount.com`, + }, + ] + const data: NIST1xQueryResponse = getTest41RuleFixture( + name, + projects, + labels, + serviceAccounts + ) + await test41Rule(data, Result.FAIL) + }) + + test('Security Issue when the vm name does start with "gke-", it does NOT have a "goog-gke-node" label and the service account is the default compute service account', async () => { + const name = 'gke-test' + const labels: Label[] = [ + { + value: 'dummy-label', + }, + ] + const projectId = 123456789 + const projects: Project[] = [{ id: `projects/${projectId}` }] + const serviceAccounts: ServiceAccount[] = [ + { + email: `${projectId}-compute@developer.gserviceaccount.com`, + }, + ] + const data: NIST1xQueryResponse = getTest41RuleFixture( + name, + projects, + labels, + serviceAccounts + ) + await test41Rule(data, Result.FAIL) + }) + + test('Security Issue when the vm name does start with "gke-", it does NOT have any label and the service account is the default compute service account', async () => { + const name = 'gke-test' + const labels: Label[] = [] + const projectId = 123456789 + const projects: Project[] = [{ id: `projects/${projectId}` }] + const serviceAccounts: ServiceAccount[] = [ + { + email: `${projectId}-compute@developer.gserviceaccount.com`, + }, + ] + const data: NIST1xQueryResponse = getTest41RuleFixture( + name, + projects, + labels, + serviceAccounts + ) + await test41Rule(data, Result.FAIL) + }) + + test('Security Issue when the vm name does NOT start with "gke-", it does have a "goog-gke-node" label and the service account is the default compute service account', async () => { + const name = 'dummy' + const labels: Label[] = [ + { + value: 'dummy-label', + }, + { + value: 'goog-gke-node', + }, + ] + const projectId = 123456789 + const projects: Project[] = [{ id: `projects/${projectId}` }] + const serviceAccounts: ServiceAccount[] = [ + { + email: `${projectId}-compute@developer.gserviceaccount.com`, + }, + ] + const data: NIST1xQueryResponse = getTest41RuleFixture( + name, + projects, + labels, + serviceAccounts + ) + await test41Rule(data, Result.FAIL) + }) + }) + + describe('GCP NIST 1.2 Compute instances should not use the default service account with full access to all Cloud APIs', () => { + const getTest42RuleFixture = ( + name: string, + projects: Project[], + labels: Label[], + serviceAccounts: ServiceAccount[] + ): NIST1xQueryResponse => { + return { + querygcpVmInstance: [ + { + id: cuid(), + name, + project: projects, + labels, + serviceAccounts, + }, + ], + } + } + + const test42Rule = async ( + data: NIST1xQueryResponse, + expectedResult: Result + ): Promise => { + // Act + const [processedRule] = await rulesEngine.processRule( + Gcp_NIST_800_53_12 as Rule, + { ...data } + ) + + // Asserts + expect(processedRule.result).toBe(expectedResult) + } + + test(`No Security Issue when the vm name starts with "gke-", + it has a "goog-gke-node" label + and the service account is the default compute service account + but it does NOT have the "cloud-platform" scope`, async () => { + const projectId = 123456789 + const name = 'gke-test' + const projects: Project[] = [{ id: `projects/${projectId}` }] + const labels: Label[] = [ + { + value: 'goog-gke-node', + }, + ] + const serviceAccounts: ServiceAccount[] = [ + { + email: `${projectId}-compute@developer.gserviceaccount.com`, + scopes: ['https://www.googleapis.com/auth/cloud-platform'], + }, + ] + const data: NIST1xQueryResponse = getTest42RuleFixture( + name, + projects, + labels, + serviceAccounts + ) + await test42Rule(data, Result.PASS) + }) + + test(`No Security Issue when the vm name starts with "gke-", + it does NOT have a "goog-gke-node" label, + the service account is NOT the default compute service account, + and it has the "cloud-platform" scope`, async () => { + const name = 'gke-test' + const projects: Project[] = [{ id: 'projects/dummy-id' }] + const labels: Label[] = [] + const serviceAccounts: ServiceAccount[] = [ + { + email: 'dummy-compute@test.com', + scopes: [], + }, + ] + const data: NIST1xQueryResponse = getTest42RuleFixture( + name, + projects, + labels, + serviceAccounts + ) + await test42Rule(data, Result.PASS) + }) + + test('No Security Issue when the vm name does NOT start with "gke-", it has a "goog-gke-node" label but the service account is NOT the default compute service account', async () => { + const name = 'dummy' + const projects: Project[] = [{ id: 'projects/dummy-id' }] + const labels: Label[] = [ + { + value: 'goog-gke-node', + }, + ] + const serviceAccounts: ServiceAccount[] = [ + { + email: 'dummy-compute@test.com', + scopes: ['https://www.googleapis.com/auth/cloud-platform'], + }, + ] + const data: NIST1xQueryResponse = getTest42RuleFixture( + name, + projects, + labels, + serviceAccounts + ) + await test42Rule(data, Result.PASS) + }) + + test(`No Security Issue when the vm name does NOT start with "gke-", + it does NOT have a "goog-gke-node" label, + the service account is NOT the default compute service account + and it has the "cloud-platform" scope`, async () => { + const name = 'dummy' + const projects: Project[] = [{ id: 'projects/dummy-id' }] + const labels: Label[] = [ + { + value: 'dummy-label', + }, + ] + const serviceAccounts: ServiceAccount[] = [ + { + email: 'dummy-compute@test.com', + scopes: ['https://www.googleapis.com/auth/cloud-platform'], + }, + ] + const data: NIST1xQueryResponse = getTest42RuleFixture( + name, + projects, + labels, + serviceAccounts + ) + await test42Rule(data, Result.PASS) + }) + + test(`Security Issue when the vm name does NOT start with "gke-", + it does NOT have a "goog-gke-node" label, + the service account is the default compute service account, + and it has the "cloud-platform" scope`, async () => { + const projectId = 123456789 + const name = 'dummy' + const projects: Project[] = [{ id: `projects/${projectId}` }] + const labels: Label[] = [ + { + value: 'dummy-label', + }, + ] + const serviceAccounts: ServiceAccount[] = [ + { + email: `${projectId}-compute@developer.gserviceaccount.com`, + scopes: ['https://www.googleapis.com/auth/cloud-platform'], + }, + ] + const data: NIST1xQueryResponse = getTest42RuleFixture( + name, + projects, + labels, + serviceAccounts + ) + await test42Rule(data, Result.FAIL) + }) + + test(`Security Issue when the vm name does start with "gke-", + it does NOT have a "goog-gke-node" label, + the service account is the default compute service account, + and it has the "cloud-platform" scope`, async () => { + const name = 'gke-test' + const labels: Label[] = [ + { + value: 'dummy-label', + }, + ] + const projectId = 123456789 + const projects: Project[] = [{ id: `projects/${projectId}` }] + const serviceAccounts: ServiceAccount[] = [ + { + email: `${projectId}-compute@developer.gserviceaccount.com`, + scopes: ['https://www.googleapis.com/auth/cloud-platform'], + }, + ] + const data: NIST1xQueryResponse = getTest42RuleFixture( + name, + projects, + labels, + serviceAccounts + ) + await test42Rule(data, Result.FAIL) + }) + + test(`Security Issue when the vm name does start with "gke-", + it does NOT have any label, + the service account is the default compute service account, + and it has the "cloud-platform" scope`, async () => { + const name = 'gke-test' + const labels: Label[] = [] + const projectId = 123456789 + const projects: Project[] = [{ id: `projects/${projectId}` }] + const serviceAccounts: ServiceAccount[] = [ + { + email: `${projectId}-compute@developer.gserviceaccount.com`, + scopes: ['https://www.googleapis.com/auth/cloud-platform'], + }, + ] + const data: NIST1xQueryResponse = getTest42RuleFixture( + name, + projects, + labels, + serviceAccounts + ) + await test42Rule(data, Result.FAIL) + }) + + test(`Security Issue when the vm name does NOT start with "gke-", + it does have a "goog-gke-node" label + the service account is the default compute service account + and it has the "cloud-platform" scope`, async () => { + const name = 'dummy' + const labels: Label[] = [ + { + value: 'dummy-label', + }, + { + value: 'goog-gke-node', + }, + ] + const projectId = 123456789 + const projects: Project[] = [{ id: `projects/${projectId}` }] + const serviceAccounts: ServiceAccount[] = [ + { + email: `${projectId}-compute@developer.gserviceaccount.com`, + scopes: ['https://www.googleapis.com/auth/cloud-platform'], + }, + ] + const data: NIST1xQueryResponse = getTest42RuleFixture( + name, + projects, + labels, + serviceAccounts + ) + await test42Rule(data, Result.FAIL) + }) + }) + + describe('GCP NIST 1.3 Compute instance "block-project-ssh-keys should be enabled', () => { + const getTest43RuleFixture = ( + name: string, + projects: Project[], + labels: Label[], + serviceAccounts: ServiceAccount[], + metadataItems: MetadataItem[] + ): NIST1xQueryResponse => { + return { + querygcpVmInstance: [ + { + id: cuid(), + name, + project: projects, + labels, + serviceAccounts, + metadata: { + items: metadataItems, + }, + }, + ], + } + } + + const test43Rule = async ( + data: NIST1xQueryResponse, + expectedResult: Result + ): Promise => { + // Act + const [processedRule] = await rulesEngine.processRule( + Gcp_NIST_800_53_13 as Rule, + { ...data } + ) + + // Asserts + expect(processedRule.result).toBe(expectedResult) + } + + test(`No Security Issue when the vm name starts with "gke-", + it has a "goog-gke-node" label + and the service account is the default compute service account + and it does have the "block-project-ssh-keys" set to true`, async () => { + const projectId = 123456789 + const name = 'gke-test' + const projects: Project[] = [{ id: `projects/${projectId}` }] + const labels: Label[] = [ + { + value: 'goog-gke-node', + }, + ] + const serviceAccounts: ServiceAccount[] = [ + { + email: `${projectId}-compute@developer.gserviceaccount.com`, + }, + ] + const metadataItems: MetadataItem[] = [ + { + key: 'block-project-ssh-keys', + value: 'true', + }, + ] + const data: NIST1xQueryResponse = getTest43RuleFixture( + name, + projects, + labels, + serviceAccounts, + metadataItems + ) + await test43Rule(data, Result.PASS) + }) + + test(`No Security Issue when the vm name starts with "gke-", + it does NOT have a "goog-gke-node" label, + the service account is NOT the default compute service account, + and it does have the "block-project-ssh-keys" set to true`, async () => { + const name = 'gke-test' + const projects: Project[] = [{ id: 'projects/dummy-id' }] + const labels: Label[] = [] + const serviceAccounts: ServiceAccount[] = [ + { + email: 'dummy-compute@test.com', + }, + ] + const metadataItems: MetadataItem[] = [ + { + key: 'block-project-ssh-keys', + value: 'true', + }, + ] + const data: NIST1xQueryResponse = getTest43RuleFixture( + name, + projects, + labels, + serviceAccounts, + metadataItems + ) + await test43Rule(data, Result.PASS) + }) + + test(`No Security Issue when the vm name does NOT start with "gke-", + it has a "goog-gke-node" label, + the service account is NOT the default compute service account + and it does have the "block-project-ssh-keys" set to true`, async () => { + const name = 'dummy' + const projects: Project[] = [{ id: 'projects/dummy-id' }] + const labels: Label[] = [ + { + value: 'goog-gke-node', + }, + ] + const serviceAccounts: ServiceAccount[] = [ + { + email: 'dummy-compute@test.com', + }, + ] + const metadataItems: MetadataItem[] = [ + { + key: 'block-project-ssh-keys', + value: 'true', + }, + ] + const data: NIST1xQueryResponse = getTest43RuleFixture( + name, + projects, + labels, + serviceAccounts, + metadataItems + ) + await test43Rule(data, Result.PASS) + }) + + test(`No Security Issue when the vm name does NOT start with "gke-", + it does NOT have a "goog-gke-node" label, + the service account is NOT the default compute service account + and it does have the "block-project-ssh-keys" set to true`, async () => { + const name = 'dummy' + const projects: Project[] = [{ id: 'projects/dummy-id' }] + const labels: Label[] = [ + { + value: 'dummy-label', + }, + ] + const serviceAccounts: ServiceAccount[] = [ + { + email: 'dummy-compute@test.com', + }, + ] + const metadataItems: MetadataItem[] = [ + { + key: 'block-project-ssh-keys', + value: 'true', + }, + ] + const data: NIST1xQueryResponse = getTest43RuleFixture( + name, + projects, + labels, + serviceAccounts, + metadataItems + ) + await test43Rule(data, Result.PASS) + }) + + test(`No Security Issue when the vm name does NOT start with "gke-", + it does NOT have a "goog-gke-node" label, + the service account is NOT the default compute service account + and it not have metadata`, async () => { + const name = 'dummy' + const projects: Project[] = [{ id: 'projects/dummy-id' }] + const labels: Label[] = [ + { + value: 'dummy-label', + }, + ] + const serviceAccounts: ServiceAccount[] = [ + { + email: 'dummy-compute@test.com', + }, + ] + const metadataItems: MetadataItem[] = [] + const data: NIST1xQueryResponse = getTest43RuleFixture( + name, + projects, + labels, + serviceAccounts, + metadataItems + ) + await test43Rule(data, Result.PASS) + }) + + test(`Security Issue when the vm name does NOT start with "gke-", + it does NOT have a "goog-gke-node" label, + the service account is the default compute service account, + and it does have the "block-project-ssh-keys" set to true`, async () => { + const projectId = 123456789 + const name = 'dummy' + const projects: Project[] = [{ id: `projects/${projectId}` }] + const labels: Label[] = [ + { + value: 'dummy-label', + }, + ] + const serviceAccounts: ServiceAccount[] = [ + { + email: `${projectId}-compute@developer.gserviceaccount.com`, + }, + ] + const metadataItems: MetadataItem[] = [ + { + key: 'block-project-ssh-keys', + value: 'true', + }, + ] + const data: NIST1xQueryResponse = getTest43RuleFixture( + name, + projects, + labels, + serviceAccounts, + metadataItems + ) + await test43Rule(data, Result.FAIL) + }) + + test(`Security Issue when the vm name does start with "gke-", + it does NOT have a "goog-gke-node" label, + the service account is the default compute service account, + and it does have the "block-project-ssh-keys" set to true`, async () => { + const name = 'gke-test' + const labels: Label[] = [ + { + value: 'dummy-label', + }, + ] + const projectId = 123456789 + const projects: Project[] = [{ id: `projects/${projectId}` }] + const serviceAccounts: ServiceAccount[] = [ + { + email: `${projectId}-compute@developer.gserviceaccount.com`, + }, + ] + const metadataItems: MetadataItem[] = [ + { + key: 'block-project-ssh-keys', + value: 'true', + }, + ] + const data: NIST1xQueryResponse = getTest43RuleFixture( + name, + projects, + labels, + serviceAccounts, + metadataItems + ) + await test43Rule(data, Result.FAIL) + }) + + test(`Security Issue when the vm name does start with "gke-", + it does NOT have any label, + the service account is the default compute service account, + and it does have the "block-project-ssh-keys" set to true`, async () => { + const name = 'gke-test' + const labels: Label[] = [] + const projectId = 123456789 + const projects: Project[] = [{ id: `projects/${projectId}` }] + const serviceAccounts: ServiceAccount[] = [ + { + email: `${projectId}-compute@developer.gserviceaccount.com`, + }, + ] + const metadataItems: MetadataItem[] = [ + { + key: 'block-project-ssh-keys', + value: 'true', + }, + ] + const data: NIST1xQueryResponse = getTest43RuleFixture( + name, + projects, + labels, + serviceAccounts, + metadataItems + ) + await test43Rule(data, Result.FAIL) + }) + + test(`Security Issue when the vm name does NOT start with "gke-", + it does have a "goog-gke-node" label + the service account is the default compute service account + and it does have the "block-project-ssh-keys" set to true`, async () => { + const name = 'dummy' + const labels: Label[] = [ + { + value: 'dummy-label', + }, + { + value: 'goog-gke-node', + }, + ] + const projectId = 123456789 + const projects: Project[] = [{ id: `projects/${projectId}` }] + const serviceAccounts: ServiceAccount[] = [ + { + email: `${projectId}-compute@developer.gserviceaccount.com`, + }, + ] + const metadataItems: MetadataItem[] = [ + { + key: 'block-project-ssh-keys', + value: 'true', + }, + ] + const data: NIST1xQueryResponse = getTest43RuleFixture( + name, + projects, + labels, + serviceAccounts, + metadataItems + ) + await test43Rule(data, Result.FAIL) + }) + + test(`Security Issue when the vm name does NOT start with "gke-", + it does NOT have a "goog-gke-node" label, + the service account is NOT default compute service account, + and it does have the "block-project-ssh-keys" set to false`, async () => { + const projectId = 123456789 + const name = 'dummy' + const projects: Project[] = [{ id: `projects/${projectId}` }] + const labels: Label[] = [ + { + value: 'dummy-label', + }, + ] + const serviceAccounts: ServiceAccount[] = [ + { + email: 'dummy@test.com', + }, + ] + const metadataItems: MetadataItem[] = [ + { + key: 'block-project-ssh-keys', + value: 'false', + }, + ] + const data: NIST1xQueryResponse = getTest43RuleFixture( + name, + projects, + labels, + serviceAccounts, + metadataItems + ) + await test43Rule(data, Result.FAIL) + }) + + test(`Security Issue when the vm name does start with "gke-", + it does NOT have a "goog-gke-node" label, + the service account is NOT the default compute service account, + and it does have the "block-project-ssh-keys" set to false`, async () => { + const name = 'gke-test' + const labels: Label[] = [ + { + value: 'dummy-label', + }, + ] + const projectId = 123456789 + const projects: Project[] = [{ id: `projects/${projectId}` }] + const serviceAccounts: ServiceAccount[] = [ + { + email: 'dummy@test.com', + }, + ] + const metadataItems: MetadataItem[] = [ + { + key: 'block-project-ssh-keys', + value: 'false', + }, + ] + const data: NIST1xQueryResponse = getTest43RuleFixture( + name, + projects, + labels, + serviceAccounts, + metadataItems + ) + await test43Rule(data, Result.FAIL) + }) + + test(`Security Issue when the vm name does start with "gke-", + it does NOT have any label, + the service account is NOT the default compute service account, + and it does have the "block-project-ssh-keys" set to false`, async () => { + const name = 'gke-test' + const labels: Label[] = [] + const projectId = 123456789 + const projects: Project[] = [{ id: `projects/${projectId}` }] + const serviceAccounts: ServiceAccount[] = [ + { + email: 'dummy@test.com', + }, + ] + const metadataItems: MetadataItem[] = [ + { + key: 'block-project-ssh-keys', + value: 'false', + }, + ] + const data: NIST1xQueryResponse = getTest43RuleFixture( + name, + projects, + labels, + serviceAccounts, + metadataItems + ) + await test43Rule(data, Result.FAIL) + }) + + test(`Security Issue when the vm name does NOT start with "gke-", + it does have a "goog-gke-node" label + the service account is NOT the default compute service account + and it does have the "block-project-ssh-keys" set to false`, async () => { + const name = 'dummy' + const labels: Label[] = [ + { + value: 'dummy-label', + }, + { + value: 'goog-gke-node', + }, + ] + const projectId = 123456789 + const projects: Project[] = [{ id: `projects/${projectId}` }] + const serviceAccounts: ServiceAccount[] = [ + { + email: 'dummy@test.com', + }, + ] + const metadataItems: MetadataItem[] = [ + { + key: 'block-project-ssh-keys', + value: 'false', + }, + ] + const data: NIST1xQueryResponse = getTest43RuleFixture( + name, + projects, + labels, + serviceAccounts, + metadataItems + ) + await test43Rule(data, Result.FAIL) + }) + + test(`Security Issue when the vm name does NOT start with "gke-", + it does NOT have a "goog-gke-node" label, + the service account is NOT default compute service account, + and the "block-project-ssh-keys" key is not present`, async () => { + const projectId = 123456789 + const name = 'dummy' + const projects: Project[] = [{ id: `projects/${projectId}` }] + const labels: Label[] = [ + { + value: 'dummy-label', + }, + ] + const serviceAccounts: ServiceAccount[] = [ + { + email: 'dummy@test.com', + }, + ] + const metadataItems: MetadataItem[] = [ + { + key: 'dummy-ssh-keys', + value: 'false', + }, + ] + const data: NIST1xQueryResponse = getTest43RuleFixture( + name, + projects, + labels, + serviceAccounts, + metadataItems + ) + await test43Rule(data, Result.FAIL) + }) + + test(`Security Issue when the vm name does start with "gke-", + it does NOT have a "goog-gke-node" label, + the service account is NOT the default compute service account, + and the "block-project-ssh-keys" key is not present`, async () => { + const name = 'gke-test' + const labels: Label[] = [ + { + value: 'dummy-label', + }, + ] + const projectId = 123456789 + const projects: Project[] = [{ id: `projects/${projectId}` }] + const serviceAccounts: ServiceAccount[] = [ + { + email: 'dummy@test.com', + }, + ] + const metadataItems: MetadataItem[] = [ + { + key: 'dummy-ssh-keys', + value: 'false', + }, + ] + const data: NIST1xQueryResponse = getTest43RuleFixture( + name, + projects, + labels, + serviceAccounts, + metadataItems + ) + await test43Rule(data, Result.FAIL) + }) + + test(`Security Issue when the vm name does start with "gke-", + it does NOT have any label, + the service account is NOT the default compute service account, + and the "block-project-ssh-keys" key is not present`, async () => { + const name = 'gke-test' + const labels: Label[] = [] + const projectId = 123456789 + const projects: Project[] = [{ id: `projects/${projectId}` }] + const serviceAccounts: ServiceAccount[] = [ + { + email: 'dummy@test.com', + }, + ] + const metadataItems: MetadataItem[] = [ + { + key: 'dummy-ssh-keys', + value: 'false', + }, + ] + const data: NIST1xQueryResponse = getTest43RuleFixture( + name, + projects, + labels, + serviceAccounts, + metadataItems + ) + await test43Rule(data, Result.FAIL) + }) + + test(`Security Issue when the vm name does NOT start with "gke-", + it does have a "goog-gke-node" label + the service account is NOT the default compute service account + and the "block-project-ssh-keys" key is not present`, async () => { + const name = 'dummy' + const labels: Label[] = [ + { + value: 'dummy-label', + }, + { + value: 'goog-gke-node', + }, + ] + const projectId = 123456789 + const projects: Project[] = [{ id: `projects/${projectId}` }] + const serviceAccounts: ServiceAccount[] = [ + { + email: 'dummy@test.com', + }, + ] + const metadataItems: MetadataItem[] = [ + { + key: 'dummy-ssh-keys', + value: 'false', + }, + ] + const data: NIST1xQueryResponse = getTest43RuleFixture( + name, + projects, + labels, + serviceAccounts, + metadataItems + ) + await test43Rule(data, Result.FAIL) + }) + }) + + describe('GCP NIST 1.4 Compute instances should not have public IP addresses', () => { + const test49Rule = async ( + instanceName: string, + natIP: string | null, + expectedResult: Result + ): Promise => { + // Arrange + const data: NIST1xQueryResponse = { + querygcpVmInstance: [ + { + id: cuid(), + name: instanceName, + networkInterfaces: [ + { + accessConfigs: [ + { + natIP, + }, + ], + }, + ], + }, + ], + } + + // Act + const [processedRule] = await rulesEngine.processRule( + Gcp_NIST_800_53_14 as Rule, + { ...data } + ) + + // Asserts + expect(processedRule.result).toBe(expectedResult) + } + + test('No Security Issue when there is an inbound rule with an instance cretaed by GKE with natIP', async () => { + await test49Rule('gke-instance-1', '34.69.30.133', Result.PASS) + }) + + test('No Security Issue when there is an inbound rule with an instance cretaed by GKE without natIp', async () => { + await test49Rule('gke-instance-1', '34.69.30.133', Result.PASS) + }) + + test('No Security Issue when there is an inbound rule with a random instance without natIP', async () => { + await test49Rule('instance-1', null, Result.PASS) + }) + + test('Security Issue when there is an inbound rule with a random instance with natIP', async () => { + await test49Rule('instance-1', '34.69.30.133', Result.FAIL) + }) + }) + + describe('GCP NIST 1.5 Compute instances "Enable connecting to serial ports" should not be enabled', () => { + const getTest45RuleFixture = ( + metadataItems: MetadataItem[] + ): NIST1xQueryResponse => { + return { + querygcpVmInstance: [ + { + id: cuid(), + name: 'dummy-project-name', + project: [], + labels: [], + serviceAccounts: [], + metadata: { + items: metadataItems, + }, + }, + ], + } + } + + const test45Rule = async ( + data: NIST1xQueryResponse, + expectedResult: Result + ): Promise => { + // Act + const [processedRule] = await rulesEngine.processRule( + Gcp_NIST_800_53_15 as Rule, + { ...data } + ) + + // Asserts + expect(processedRule.result).toBe(expectedResult) + } + + test('No Security Issue when ¨block-project-ssh-keys¨ is set to false', async () => { + const metadataItems: MetadataItem[] = [ + { + key: 'serial-port-enable', + value: 'false', + }, + ] + const data: NIST1xQueryResponse = getTest45RuleFixture(metadataItems) + await test45Rule(data, Result.PASS) + }) + + test('No Security Issue when ¨serial-port-enable¨ is set to 0', async () => { + const metadataItems: MetadataItem[] = [ + { + key: 'serial-port-enable', + value: '0', + }, + ] + const data: NIST1xQueryResponse = getTest45RuleFixture(metadataItems) + await test45Rule(data, Result.PASS) + }) + + test('Security Security Issue when ¨serial-port-enable¨ is set to true', async () => { + const metadataItems: MetadataItem[] = [ + { + key: 'serial-port-enable', + value: 'true', + }, + ] + const data: NIST1xQueryResponse = getTest45RuleFixture(metadataItems) + await test45Rule(data, Result.FAIL) + }) + + test('Security Security Issue when ¨serial-port-enable¨ is set to 1', async () => { + const metadataItems: MetadataItem[] = [ + { + key: 'serial-port-enable', + value: 'true', + }, + ] + const data: NIST1xQueryResponse = getTest45RuleFixture(metadataItems) + await test45Rule(data, Result.FAIL) + }) + + test('Security Security Issue when ¨serial-port-enable¨ is set to 1', async () => { + const metadataItems: MetadataItem[] = [ + { + key: 'serial-port-enable', + value: '1', + }, + ] + const data: NIST1xQueryResponse = getTest45RuleFixture(metadataItems) + await test45Rule(data, Result.FAIL) + }) + + test('Security Security Issue when metadata is empty', async () => { + const metadataItems: MetadataItem[] = [ + { + key: 'serial-port-enable', + value: '1', + }, + ] + const data: NIST1xQueryResponse = getTest45RuleFixture(metadataItems) + await test45Rule(data, Result.FAIL) + }) + + test('Security Security Issue when metadata does NOT contain ¨serial-port-enable¨ key', async () => { + const metadataItems: MetadataItem[] = [ + { + key: 'dummy-key', + value: 'false', + }, + ] + const data: NIST1xQueryResponse = getTest45RuleFixture(metadataItems) + await test45Rule(data, Result.FAIL) + }) + }) + + describe('GCP NIST 1.6 SQL database instances should not permit access from 0.0.0.0/0', () => { + const getRuleFixture = (): NIST1xQueryResponse => { + return { + querygcpSqlInstance: [ + { + id: cuid(), + name: 'test-sql-instance', + settings: { + ipConfiguration: { + authorizedNetworks: [ + { value: '192.168.0.0/24' }, + { value: '192.168.1.0/24' }, + ], + }, + databaseFlags: [], + }, + }, + ], + } + } + + const testRule = async ( + data: NIST1xQueryResponse, + expectedResult: Result + ): Promise => { + // Act + const [processedRule] = await rulesEngine.processRule( + Gcp_NIST_800_53_16 as Rule, + { ...data } + ) + + // Asserts + expect(processedRule.result).toBe(expectedResult) + } + + test("No Security Issue when authorizedNetworks is NOT set to '0.0.0.0/0'", async () => { + const data: NIST1xQueryResponse = getRuleFixture() + await testRule(data, Result.PASS) + }) + + test('No Security Issue when authorizedNetworks is empty', async () => { + const data: NIST1xQueryResponse = getRuleFixture() + const sqlInstance = data.querygcpSqlInstance?.[0] as QuerygcpSqlInstance + sqlInstance.settings = { + ipConfiguration: { + authorizedNetworks: [], + }, + databaseFlags: [], + } + await testRule(data, Result.PASS) + }) + + test("Security Issue when authorizedNetworks is set to '0.0.0.0/0'", async () => { + const data: NIST1xQueryResponse = getRuleFixture() + const sqlInstance = data.querygcpSqlInstance?.[0] as QuerygcpSqlInstance + sqlInstance.settings = { + ipConfiguration: { + authorizedNetworks: [{ value: '0.0.0.0/0' }], + }, + databaseFlags: [], + } + await testRule(data, Result.FAIL) + }) + }) + + describe('GCP NIST 1.7 SQL database instances should not have public IPs', () => { + const getRuleFixture = (): NIST1xQueryResponse => { + return { + querygcpSqlInstance: [ + { + id: cuid(), + name: 'test-sql-instance', + ipAddresses: [ + { + type: 'PRIVATE', + }, + ], + settings: { + databaseFlags: [], + }, + }, + ], + } + } + + const testRule = async ( + data: NIST1xQueryResponse, + expectedResult: Result + ): Promise => { + // Act + const [processedRule] = await rulesEngine.processRule( + Gcp_NIST_800_53_17 as Rule, + { ...data } + ) + + // Asserts + expect(processedRule.result).toBe(expectedResult) + } + + test('No Security Issue when ipAddresses are PRIVATE', async () => { + const data: NIST1xQueryResponse = getRuleFixture() + await testRule(data, Result.PASS) + }) + + test('No Security Issue when ipAddresses are empty', async () => { + const data: NIST1xQueryResponse = getRuleFixture() + const sqlInstance = data.querygcpSqlInstance?.[0] as QuerygcpSqlInstance + sqlInstance.ipAddresses = [] + await testRule(data, Result.PASS) + }) + + test('Security Issue when ipAddresses are PUBLIC', async () => { + const data: NIST1xQueryResponse = getRuleFixture() + const sqlInstance = data.querygcpSqlInstance?.[0] as QuerygcpSqlInstance + sqlInstance.ipAddresses = [ + { + type: 'PUBLIC', + }, + ] + await testRule(data, Result.FAIL) + }) + + test('Security Issue when ipAddresses are PRIVATE and PUBLIC', async () => { + const data: NIST1xQueryResponse = getRuleFixture() + const sqlInstance = data.querygcpSqlInstance?.[0] as QuerygcpSqlInstance + sqlInstance.ipAddresses = [ + { + type: 'PRIVATE', + }, + { + type: 'PUBLIC', + }, + ] + await testRule(data, Result.FAIL) + }) + }) +}) diff --git a/src/gcp/nist-800-53-rev4/tests/nist-800-53-rev4-2.x.test.ts b/src/gcp/nist-800-53-rev4/tests/nist-800-53-rev4-2.x.test.ts new file mode 100644 index 00000000..042dbeb5 --- /dev/null +++ b/src/gcp/nist-800-53-rev4/tests/nist-800-53-rev4-2.x.test.ts @@ -0,0 +1,180 @@ +import cuid from 'cuid' +import CloudGraph, { Rule, Result, Engine } from '@cloudgraph/sdk' + +import Gcp_NIST_800_53_21 from '../rules/gcp-nist-800-53-rev4-2.1' +import Gcp_NIST_800_53_22 from '../rules/gcp-nist-800-53-rev4-2.2' +import Gcp_NIST_800_53_23 from '../rules/gcp-nist-800-53-rev4-2.3' + +export interface DnssecConfigDefaultKeySpecs { + keyType: string + algorithm: string +} + +export interface QuerygcpDnsManagedZone { + id: string + visibility?: string + dnssecConfigState?: string + dnssecConfigDefaultKeySpecs?: DnssecConfigDefaultKeySpecs[] +} + +export interface NIST2xQueryResponse { + querygcpDnsManagedZone?: QuerygcpDnsManagedZone[] +} + +describe('GCP NIST 800-53: Rev. 4', () => { + let rulesEngine: Engine + beforeAll(() => { + rulesEngine = new CloudGraph.RulesEngine({ + providerName: 'gcp', + entityName: 'NIST', + }) + }) + + describe('GCP NIST 2.1 DNS managed zone DNSSEC should be enabled', () => { + const test33Rule = async ( + visibility: string, + dnssecConfigState: string, + expectedResult: Result + ): Promise => { + // Arrange + const data: NIST2xQueryResponse = { + querygcpDnsManagedZone: [ + { + id: cuid(), + visibility, + dnssecConfigState, + }, + ], + } + + // Act + const [processedRule] = await rulesEngine.processRule( + Gcp_NIST_800_53_21 as Rule, + { ...data } + ) + + // Asserts + expect(processedRule.result).toBe(expectedResult) + } + + test('No Security Issue when there is an inbound rule with visibility public and dnssecConfigState is enabled', async () => { + await test33Rule('public', 'on', Result.PASS) + }) + + test('No Security Issue when there is an inbound rule with visibility private and dnssecConfigState is not enabled', async () => { + await test33Rule('private', 'off', Result.PASS) + }) + + test('Security Issue when there is an inbound rule with visibility public and dnssecConfigState is not enabled', async () => { + await test33Rule('public', 'off', Result.FAIL) + }) + }) + + describe('GCP NIST 2.2 DNS managed zone DNSSEC key-signing keys should not use RSASHA1', () => { + const test34Rule = async ( + visibility: string, + keyType: string, + algorithm: string, + expectedResult: Result + ): Promise => { + // Arrange + const data: NIST2xQueryResponse = { + querygcpDnsManagedZone: [ + { + id: cuid(), + visibility, + dnssecConfigDefaultKeySpecs: [ + { + keyType: 'keySigning', + algorithm: 'rsasha512', + }, + { + keyType: 'keyTest', + algorithm: 'rsasha1', + }, + { + keyType, + algorithm, + }, + ], + }, + ], + } + + // Act + const [processedRule] = await rulesEngine.processRule( + Gcp_NIST_800_53_22 as Rule, + { ...data } + ) + + // Asserts + expect(processedRule.result).toBe(expectedResult) + } + + test('No Security Issue when there is an inbound rule with visibility public and keyType keySigning and algorithm type different to rsasha1', async () => { + await test34Rule('public', 'keySigning', 'rsasha256', Result.PASS) + }) + + test('No Security Issue when there is an inbound rule with visibility private and keyType keySigning and algorithm type rsasha1', async () => { + await test34Rule('private', 'keySigning', 'rsasha256', Result.PASS) + }) + + test('Security Issue when there is an inbound rule with visibility public and keyType keySigning and algorithm type rsasha1', async () => { + await test34Rule('public', 'keySigning', 'rsasha1', Result.FAIL) + }) + }) + + describe('GCP NIST 2.3 DNS managed zone DNSSEC zone-signing keys should not use RSASHA1', () => { + const test35Rule = async ( + visibility: string, + keyType: string, + algorithm: string, + expectedResult: Result + ): Promise => { + // Arrange + const data: NIST2xQueryResponse = { + querygcpDnsManagedZone: [ + { + id: cuid(), + visibility, + dnssecConfigDefaultKeySpecs: [ + { + keyType: 'zoneSigning', + algorithm: 'rsasha512', + }, + { + keyType: 'keyTest', + algorithm: 'rsasha1', + }, + { + keyType, + algorithm, + }, + ], + }, + ], + } + + // Act + const [processedRule] = await rulesEngine.processRule( + Gcp_NIST_800_53_23 as Rule, + { ...data } + ) + + // Asserts + expect(processedRule.result).toBe(expectedResult) + } + + test('No Security Issue when there is an inbound rule with visibility public and keyType zoneSigning and algorithm type different to rsasha1', async () => { + await test35Rule('public', 'zoneSigning', 'rsasha256', Result.PASS) + }) + + test('No Security Issue when there is an inbound rule with visibility private and keyType zoneSigning and algorithm type rsasha1', async () => { + await test35Rule('private', 'zoneSigning', 'rsasha256', Result.PASS) + }) + + test('Security Issue when there is an inbound rule with visibility public and keyType zoneSigning and algorithm type rsasha1', async () => { + await test35Rule('public', 'zoneSigning', 'rsasha1', Result.FAIL) + }) + }) +}) diff --git a/src/gcp/nist-800-53-rev4/tests/nist-800-53-rev4.test.ts b/src/gcp/nist-800-53-rev4/tests/nist-800-53-rev4.test.ts deleted file mode 100644 index 03166c72..00000000 --- a/src/gcp/nist-800-53-rev4/tests/nist-800-53-rev4.test.ts +++ /dev/null @@ -1,18 +0,0 @@ -import CloudGraph, { Rule, Result, Engine } from '@cloudgraph/sdk' - -describe('GCP NIST 800-53: Rev. 4', () => { - let rulesEngine: Engine - beforeAll(() => { - rulesEngine = new CloudGraph.RulesEngine({ - providerName: 'gcp', - entityName: 'NIST', - }) - }) - - // TODO: Change once we have real checks - describe('Dummy Check', () => { - test('Dummy Test', async () => { - expect('PASS').toBe(Result.PASS) - }) - }) -})