mirror of
https://github.com/ghndrx/prowler.git
synced 2026-02-10 06:45:08 +00:00
feat(gcp): add 12 new checks for CIS Framework (#2426)
Co-authored-by: Sergio Garcia <sergargar1@gmail.com>
This commit is contained in:
@@ -40,7 +40,7 @@ It contains hundreds of controls covering CIS, NIST 800, NIST CSF, CISA, RBI, Fe
|
||||
| Provider | Checks | Services | [Compliance Frameworks](https://docs.prowler.cloud/en/latest/tutorials/compliance/) | [Categories](https://docs.prowler.cloud/en/latest/tutorials/misc/#categories) |
|
||||
|---|---|---|---|---|
|
||||
| AWS | 281 | 54 -> `prowler aws --list-services` | 21 -> `prowler aws --list-compliance` | 6 -> `prowler aws --list-categories` |
|
||||
| GCP | 47 | 7 -> `prowler gcp --list-services` | CIS soon | 0 -> `prowler gcp --list-categories`|
|
||||
| GCP | 59 | 10 -> `prowler gcp --list-services` | CIS soon | 0 -> `prowler gcp --list-categories`|
|
||||
| Azure | 20 | 3 -> `prowler azure --list-services` | CIS soon | 1 -> `prowler azure --list-categories` |
|
||||
| Kubernetes | Planned | - | - | - |
|
||||
|
||||
|
||||
0
prowler/providers/gcp/services/apikeys/__init__.py
Normal file
0
prowler/providers/gcp/services/apikeys/__init__.py
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"Provider": "gcp",
|
||||
"CheckID": "apikeys_api_restrictions_configured",
|
||||
"CheckTitle": "Ensure API Keys Are Restricted to Only APIs That Application Needs Access",
|
||||
"CheckType": [],
|
||||
"ServiceName": "apikeys",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "API Key",
|
||||
"Description": "API Keys should only be used for services in cases where other authentication methods are unavailable. If they are in use it is recommended to rotate API keys every 90 days.",
|
||||
"Risk": "Google Cloud Platform (GCP) API keys are simple encrypted strings that don't identify the user or the application that performs the API request. GCP API keys are typically accessible to clients, as they can be viewed publicly from within a browser, making it easy to discover and capture API keys.",
|
||||
"RelatedUrl": "",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "",
|
||||
"NativeIaC": "",
|
||||
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudAPI/check-for-api-key-api-restrictions.html",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Ensure that the usage of your Google Cloud API keys is restricted to specific APIs such as Cloud Key Management Service (KMS) API, Cloud Storage API, Cloud Monitoring API and/or Cloud Logging API. All Google Cloud API keys that are being used for production applications should use API restrictions. In order to follow cloud security best practices and reduce the attack surface, Google Cloud API keys should be restricted to call only those APIs required by your application.",
|
||||
"Url": "https://cloud.google.com/docs/authentication/api-keys"
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_GCP
|
||||
from prowler.providers.gcp.services.apikeys.apikeys_client import apikeys_client
|
||||
|
||||
|
||||
class apikeys_api_restrictions_configured(Check):
|
||||
def execute(self) -> Check_Report_GCP:
|
||||
findings = []
|
||||
for key in apikeys_client.keys:
|
||||
report = Check_Report_GCP(self.metadata())
|
||||
report.project_id = key.project_id
|
||||
report.resource_id = key.id
|
||||
report.resource_name = key.name
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"API key {key.name} have restrictions configured."
|
||||
if key.restrictions == {} or any(
|
||||
[
|
||||
target.get("service") == "cloudapis.googleapis.com"
|
||||
for target in key.restrictions["apiTargets"]
|
||||
]
|
||||
):
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"API key {key.name} doens't have restrictions configured."
|
||||
)
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
4
prowler/providers/gcp/services/apikeys/apikeys_client.py
Normal file
4
prowler/providers/gcp/services/apikeys/apikeys_client.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from prowler.providers.gcp.lib.audit_info.audit_info import gcp_audit_info
|
||||
from prowler.providers.gcp.services.apikeys.apikeys_service import APIKeys
|
||||
|
||||
apikeys_client = APIKeys(gcp_audit_info)
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"Provider": "gcp",
|
||||
"CheckID": "apikeys_key_rotated_in_90_days",
|
||||
"CheckTitle": "Ensure API Keys Are Rotated Every 90 Days",
|
||||
"CheckType": [],
|
||||
"ServiceName": "apikeys",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "API Key",
|
||||
"Description": "API Keys should only be used for services in cases where other authentication methods are unavailable. If they are in use it is recommended to rotate API keys every 90 days.",
|
||||
"Risk": "Once a Google Cloud API key is compromised, it can be used indefinitely unless the project owner revokes or regenerates that key.",
|
||||
"RelatedUrl": "",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "",
|
||||
"NativeIaC": "",
|
||||
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudAPI/rotate-api-keys.html",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Ensure that all your Google Cloud API keys are regularly regenerated (rotated) in order to meet security and compliance requirements. By default, it is recommended to rotate keys every 90 days. Google Cloud Platform (GCP) API keys are simple, encrypted strings that can be used when calling specific APIs that don't need to access private user data. API keys are typically used to track API requests associated with your GCP project for quota and billing. Rotating GCP API keys will substantially reduce the window of opportunity for exploits and ensure that data can't be accessed with an outdated key that might have been lost, cracked, or stolen.",
|
||||
"Url": "https://cloud.google.com/docs/authentication/api-keys"
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from prowler.lib.check.models import Check, Check_Report_GCP
|
||||
from prowler.providers.gcp.services.apikeys.apikeys_client import apikeys_client
|
||||
|
||||
|
||||
class apikeys_key_rotated_in_90_days(Check):
|
||||
def execute(self) -> Check_Report_GCP:
|
||||
findings = []
|
||||
for key in apikeys_client.keys:
|
||||
report = Check_Report_GCP(self.metadata())
|
||||
report.project_id = key.project_id
|
||||
report.resource_id = key.id
|
||||
report.resource_name = key.name
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"API key {key.name} created in less than 90 days."
|
||||
if (
|
||||
datetime.now(timezone.utc)
|
||||
- datetime.strptime(key.creation_time, "%Y-%m-%dT%H:%M:%S.%f%z")
|
||||
).days > 90:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"API key {key.name} creation date have more than 90 days."
|
||||
)
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
60
prowler/providers/gcp/services/apikeys/apikeys_service.py
Normal file
60
prowler/providers/gcp/services/apikeys/apikeys_service.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.providers.gcp.gcp_provider import generate_client
|
||||
|
||||
|
||||
################## API Keys
|
||||
class APIKeys:
|
||||
def __init__(self, audit_info):
|
||||
self.service = "apikeys"
|
||||
self.api_version = "v2"
|
||||
self.project_ids = audit_info.project_ids
|
||||
self.default_project_id = audit_info.default_project_id
|
||||
self.client = generate_client(self.service, self.api_version, audit_info)
|
||||
self.keys = []
|
||||
self.__get_keys__()
|
||||
|
||||
def __get_keys__(self):
|
||||
for project_id in self.project_ids:
|
||||
try:
|
||||
request = (
|
||||
self.client.projects()
|
||||
.locations()
|
||||
.keys()
|
||||
.list(
|
||||
parent=f"projects/{project_id}/locations/global",
|
||||
)
|
||||
)
|
||||
while request is not None:
|
||||
response = request.execute()
|
||||
|
||||
for key in response.get("keys", []):
|
||||
self.keys.append(
|
||||
Key(
|
||||
name=key["displayName"],
|
||||
id=key["uid"],
|
||||
creation_time=key["createTime"],
|
||||
restrictions=key.get("restrictions", {}),
|
||||
project_id=project_id,
|
||||
)
|
||||
)
|
||||
|
||||
request = (
|
||||
self.client.projects()
|
||||
.locations()
|
||||
.keys()
|
||||
.list_next(previous_request=request, previous_response=response)
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
|
||||
class Key(BaseModel):
|
||||
name: str
|
||||
id: str
|
||||
creation_time: str
|
||||
restrictions: dict
|
||||
project_id: str
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"Provider": "gcp",
|
||||
"CheckID": "compute_block_project_wide_ssh_keys_disabled",
|
||||
"CheckTitle": "Ensure “Block Project-Wide SSH Keys” Is Enabled for VM Instances",
|
||||
"CheckType": [],
|
||||
"ServiceName": "compute",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "VMInstance",
|
||||
"Description": "It is recommended to use Instance specific SSH key(s) instead of using common/shared project-wide SSH key(s) to access Instances.",
|
||||
"Risk": "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.",
|
||||
"RelatedUrl": "",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "https://docs.bridgecrew.io/docs/bc_gcp_networking_8#cli-command",
|
||||
"NativeIaC": "",
|
||||
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/ComputeEngine/enable-block-project-wide-ssh-keys.html",
|
||||
"Terraform": "https://docs.bridgecrew.io/docs/bc_gcp_networking_8#cli-command"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "It is recommended to use Instance specific SSH keys which can limit the attack surface if the SSH keys are compromised.",
|
||||
"Url": "https://cloud.google.com/compute/docs/instances/adding-removing-ssh-keys"
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_GCP
|
||||
from prowler.providers.gcp.services.compute.compute_client import compute_client
|
||||
|
||||
|
||||
class compute_block_project_wide_ssh_keys_disabled(Check):
|
||||
def execute(self) -> Check_Report_GCP:
|
||||
findings = []
|
||||
for instance in compute_client.instances:
|
||||
report = Check_Report_GCP(self.metadata())
|
||||
report.project_id = instance.project_id
|
||||
report.resource_id = instance.id
|
||||
report.resource_name = instance.name
|
||||
report.location = instance.zone
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"The VM Instance {instance.name} is making use of common/shared project-wide SSH key(s)."
|
||||
if instance.metadata.get("items"):
|
||||
for item in instance.metadata["items"]:
|
||||
if (
|
||||
item["key"] == "block-project-ssh-keys"
|
||||
and item["value"] == "true"
|
||||
):
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"The VM Instance {instance.name} is not making use of common/shared project-wide SSH key(s)."
|
||||
break
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -9,7 +9,7 @@
|
||||
"Severity": "medium",
|
||||
"ResourceType": "VMInstance",
|
||||
"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.",
|
||||
"Risk": "",
|
||||
"Risk": "The default Compute Engine service account has the Editor role on the project, which allows read and write access to most Google Cloud Services. This can lead to a privilege escalations if your VM is compromised allowing an attacker gaining access to all of your project",
|
||||
"RelatedUrl": "",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
@@ -19,8 +19,8 @@
|
||||
"Terraform": "https://docs.bridgecrew.io/docs/bc_gcp_iam_1#terraform"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "",
|
||||
"Url": ""
|
||||
"Text": "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`.",
|
||||
"Url": "https://cloud.google.com/iam/docs/granting-changing-revoking-access"
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
@@ -28,4 +28,3 @@
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"Severity": "medium",
|
||||
"ResourceType": "VMInstance",
|
||||
"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`.",
|
||||
"Risk": "",
|
||||
"Risk": "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.",
|
||||
"RelatedUrl": "",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
@@ -19,8 +19,8 @@
|
||||
"Terraform": "https://docs.bridgecrew.io/docs/bc_gcp_iam_2#terraform"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "",
|
||||
"Url": ""
|
||||
"Text": "To enforce the principle of least privileges and prevent potential privilege escalation, ensure that your Google Compute Engine instances are not configured to use the default service account with the Cloud API access scope set to \"Allow full access to all Cloud APIs\". The principle of least privilege (POLP), also known as the principle of least authority, is the security concept of giving the user/system/service the minimal set of permissions required to successfully perform its tasks.",
|
||||
"Url": "https://cloud.google.com/iam/docs/granting-changing-revoking-access"
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
@@ -28,4 +28,3 @@
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"Provider": "gcp",
|
||||
"CheckID": "compute_encryption_with_csek_is_disabled",
|
||||
"CheckTitle": "Ensure VM Disks for Critical VMs Are Encrypted With Customer-Supplied Encryption Keys (CSEK)",
|
||||
"CheckType": [],
|
||||
"ServiceName": "compute",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "high",
|
||||
"ResourceType": "Disks",
|
||||
"Description": "Customer-Supplied Encryption Keys (CSEK) are a feature in Google Cloud Storage and Google Compute Engine. If you supply your own encryption keys, Google uses your key to protect the Google-generated keys used to encrypt and decrypt your data. By default, Google Compute Engine encrypts all data at rest. Compute Engine handles and manages this encryption for you without any additional actions on your part. However, if you wanted to control and manage this encryption yourself, you can provide your own encryption keys.",
|
||||
"Risk": "By default, Compute Engine service encrypts all data at rest. The cloud service manages this type of encryption without any additional actions from you and your application. However, if you want to fully control and manage instance disk encryption, you can provide your own encryption keys.",
|
||||
"RelatedUrl": "",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "https://docs.bridgecrew.io/docs/bc_gcp_general_x#cli-command",
|
||||
"NativeIaC": "",
|
||||
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/ComputeEngine/enable-encryption-with-csek.html",
|
||||
"Terraform": "https://docs.bridgecrew.io/docs/bc_gcp_general_x#terraform"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Ensure that the disks attached to your production Google Compute Engine instances are encrypted with Customer-Supplied Encryption Keys (CSEKs) in order to have complete control over the data-at-rest encryption and decryption process, and meet strict compliance requirements. These custom keys, also known as Customer-Supplied Encryption Keys (CSEKs), are used by Google Compute Engine to protect the Google-generated keys used to encrypt and decrypt your instance data. Compute Engine service does not store your CSEKs on its servers and cannot access your protected data unless you provide the required key.",
|
||||
"Url": "https://cloud.google.com/storage/docs/encryption/using-customer-supplied-keys"
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_GCP
|
||||
from prowler.providers.gcp.services.compute.compute_client import compute_client
|
||||
|
||||
|
||||
class compute_encryption_with_csek_is_disabled(Check):
|
||||
def execute(self) -> Check_Report_GCP:
|
||||
findings = []
|
||||
for instance in compute_client.instances:
|
||||
report = Check_Report_GCP(self.metadata())
|
||||
report.project_id = instance.project_id
|
||||
report.resource_id = instance.id
|
||||
report.resource_name = instance.name
|
||||
report.location = instance.zone
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"The VM Instance {instance.name} have the following unencrypted disks: '{', '.join([i[0] for i in instance.disks_encryption if not i[1]])}'"
|
||||
if all([i[1] for i in instance.disks_encryption]):
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"The VM Instance {instance.name} have every disk encrypted."
|
||||
)
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"Provider": "gcp",
|
||||
"CheckID": "compute_ip_forwarding_is_enabled",
|
||||
"CheckTitle": "Ensure That IP Forwarding Is Not Enabled on Instances",
|
||||
"CheckType": [],
|
||||
"ServiceName": "compute",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "VMInstance",
|
||||
"Description": "Compute Engine instance cannot forward a packet unless the source IP address of the packet matches the IP address of the instance. Similarly, GCP won't deliver a packet whose destination IP address is different than the IP address of the instance receiving the packet. However, both capabilities are required if you want to use instances to help route packets. Forwarding of data packets should be disabled to prevent data loss or information disclosure.",
|
||||
"Risk": "When the IP Forwarding feature is enabled on a virtual machine's network interface (NIC), it allows the VM to act as a router and receive traffic addressed to other destinations. ",
|
||||
"RelatedUrl": "",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "https://docs.bridgecrew.io/docs/bc_gcp_networking_12#cli-command",
|
||||
"NativeIaC": "",
|
||||
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/ComputeEngine/disable-ip-forwarding.html",
|
||||
"Terraform": "https://docs.bridgecrew.io/docs/bc_gcp_networking_12#terraform"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Ensure that IP Forwarding feature is not enabled at the Google Compute Engine instance level for security and compliance reasons, as instances with IP Forwarding enabled act as routers/packet forwarders. Because IP forwarding is rarely required, except when the virtual machine (VM) is used as a network virtual appliance, each Google Cloud VM instance should be reviewed in order to decide whether the IP forwarding is really needed for the verified instance. IP Forwarding is enabled at the VM instance level and applies to all network interfaces (NICs) attached to the instance. In addition, Instances created by GKE should be excluded from this recommendation because they need to have IP forwarding enabled and cannot be changed. Instances created by GKE have names that start with \"gke- \".",
|
||||
"Url": "https://cloud.google.com/compute/docs/instances/create-start-instance"
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_GCP
|
||||
from prowler.providers.gcp.services.compute.compute_client import compute_client
|
||||
|
||||
|
||||
class compute_ip_forwarding_is_enabled(Check):
|
||||
def execute(self) -> Check_Report_GCP:
|
||||
findings = []
|
||||
for instance in compute_client.instances:
|
||||
report = Check_Report_GCP(self.metadata())
|
||||
report.project_id = instance.project_id
|
||||
report.resource_id = instance.id
|
||||
report.resource_name = instance.name
|
||||
report.location = instance.zone
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"The IP Forwarding of VM Instance {instance.name} is not enabled"
|
||||
)
|
||||
if instance.ip_forward and instance.name[:4] != "gke-":
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"The IP Forwarding of VM Instance {instance.name} is enabled"
|
||||
)
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"Provider": "gcp",
|
||||
"CheckID": "compute_rdp_access_from_the_internet_allowed",
|
||||
"CheckTitle": "Ensure That RDP Access Is Restricted From the Internet",
|
||||
"CheckType": [],
|
||||
"ServiceName": "networking",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "critical",
|
||||
"ResourceType": "FirewallRule",
|
||||
"Description": "GCP `Firewall Rules` are specific to a `VPC Network`. Each rule either `allows` or `denies` traffic when its conditions are met. Its conditions allow users to specify the type of traffic, such as ports and protocols, and the source or destination of the traffic, including IP addresses, subnets, and instances. Firewall rules are defined at the VPC network level and are specific to the network in which they are defined. The rules themselves cannot be shared among networks. Firewall rules only support IPv4 traffic. When specifying a source for an ingress rule or a destination for an egress rule by address, an `IPv4` address or `IPv4 block in CIDR` notation can be used. Generic `(0.0.0.0/0)` incoming traffic from the Internet to a VPC or VM instance using `RDP` on `Port 3389` can be avoided.",
|
||||
"Risk": "Allowing unrestricted Remote Desktop Protocol (RDP) access can increase opportunities for malicious activities such as hacking, Man-In-The-Middle attacks (MITM) and Pass-The-Hash (PTH) attacks.",
|
||||
"RelatedUrl": "",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "https://docs.bridgecrew.io/docs/bc_gcp_networking_2#cli-command",
|
||||
"NativeIaC": "",
|
||||
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudVPC/unrestricted-rdp-access.html",
|
||||
"Terraform": "https://docs.bridgecrew.io/docs/bc_gcp_networking_2#terraform"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Ensure that Google Cloud Virtual Private Cloud (VPC) firewall rules do not allow unrestricted access (i.e. 0.0.0.0/0) on TCP port 3389 in order to restrict Remote Desktop Protocol (RDP) traffic to trusted IP addresses or IP ranges only and reduce the attack surface. TCP port 3389 is used for secure remote GUI login to Windows VM instances by connecting a RDP client application with an RDP server.",
|
||||
"Url": "https://cloud.google.com/vpc/docs/using-firewalls"
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_GCP
|
||||
from prowler.providers.gcp.services.compute.compute_client import compute_client
|
||||
|
||||
|
||||
class compute_rdp_access_from_the_internet_allowed(Check):
|
||||
def execute(self) -> Check_Report_GCP:
|
||||
findings = []
|
||||
for firewall in compute_client.firewalls:
|
||||
report = Check_Report_GCP(self.metadata())
|
||||
report.project_id = firewall.project_id
|
||||
report.resource_id = firewall.id
|
||||
report.resource_name = firewall.name
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Firewall {firewall.name} does not expose port 3389 (RDP) to the internet."
|
||||
opened_port = False
|
||||
for rule in firewall.allowed_rules:
|
||||
if rule["IPProtocol"] == "all":
|
||||
opened_port = True
|
||||
break
|
||||
elif rule["IPProtocol"] == "tcp":
|
||||
if rule.get("ports") is None:
|
||||
opened_port = True
|
||||
break
|
||||
else:
|
||||
for port in rule["ports"]:
|
||||
if port.find("-") != -1:
|
||||
lower, higher = port.split("-")
|
||||
if int(lower) <= 3389 and int(higher) >= 3389:
|
||||
opened_port = True
|
||||
break
|
||||
elif int(port) == 3389:
|
||||
opened_port = True
|
||||
break
|
||||
if (
|
||||
"0.0.0.0/0" in firewall.source_ranges
|
||||
and firewall.direction == "INGRESS"
|
||||
and opened_port
|
||||
):
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"Firewall {firewall.name} does exposes port 3389 (RDP) to the internet."
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -9,7 +9,7 @@
|
||||
"Severity": "medium",
|
||||
"ResourceType": "VMInstance",
|
||||
"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.",
|
||||
"Risk": "",
|
||||
"Risk": "If you enable the interactive serial console on your VM instance, clients can attempt to connect to your instance from any IP address and this allows anybody to access the instance if they know the user name, the SSH key, the project ID, and the instance name and zone.",
|
||||
"RelatedUrl": "",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
@@ -19,8 +19,8 @@
|
||||
"Terraform": "https://docs.bridgecrew.io/docs/bc_gcp_networking_11#terraform"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "",
|
||||
"Url": ""
|
||||
"Text": "Ensure that \"Enable connecting to serial ports\" configuration setting is disabled for all your production Google Compute Engine instances. A Google Cloud virtual machine (VM) instance has 4 virtual serial ports. On your VM instances, the operating system (OS), BIOS, and other system-level entities write often output data to the serial ports and can accept input, such as commands or answers, to prompts. Usually, these system-level entities use the first serial port (Port 1) and Serial Port 1 is often referred to as the interactive serial console. This interactive serial console does not support IP-based access restrictions such as IP address whitelists. To adhere to cloud security best practices and reduce the risk of unauthorized access, interactive serial console support should be disabled for all instances used in production.",
|
||||
"Url": "https://cloud.google.com/compute"
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
@@ -28,4 +28,3 @@
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
|
||||
@@ -12,12 +12,34 @@ class Compute:
|
||||
self.project_ids = audit_info.project_ids
|
||||
self.default_project_id = audit_info.default_project_id
|
||||
self.client = generate_client(self.service, self.api_version, audit_info)
|
||||
self.regions = set()
|
||||
self.zones = set()
|
||||
self.instances = []
|
||||
self.networks = []
|
||||
self.firewalls = []
|
||||
self.__get_regions__()
|
||||
self.__get_zones__()
|
||||
self.__get_instances__()
|
||||
self.__get_networks__()
|
||||
self.__get_firewalls__()
|
||||
|
||||
def __get_regions__(self):
|
||||
for project_id in self.project_ids:
|
||||
try:
|
||||
request = self.client.regions().list(project=project_id)
|
||||
while request is not None:
|
||||
response = request.execute()
|
||||
|
||||
for region in response.get("items", []):
|
||||
self.regions.add(region["name"])
|
||||
|
||||
request = self.client.regions().list_next(
|
||||
previous_request=request, previous_response=response
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
def __get_zones__(self):
|
||||
for project_id in self.project_ids:
|
||||
@@ -67,6 +89,18 @@ class Compute:
|
||||
"shieldedInstanceConfig"
|
||||
]["enableIntegrityMonitoring"],
|
||||
service_accounts=instance["serviceAccounts"],
|
||||
ip_forward=instance.get("canIpForward", False),
|
||||
disks_encryption=[
|
||||
(
|
||||
disk["deviceName"],
|
||||
True
|
||||
if disk.get("diskEncryptionKey", {}).get(
|
||||
"sha256"
|
||||
)
|
||||
else False,
|
||||
)
|
||||
for disk in instance["disks"]
|
||||
],
|
||||
project_id=project_id,
|
||||
)
|
||||
)
|
||||
@@ -85,7 +119,6 @@ class Compute:
|
||||
request = self.client.networks().list(project=project_id)
|
||||
while request is not None:
|
||||
response = request.execute()
|
||||
|
||||
for network in response.get("items", []):
|
||||
self.networks.append(
|
||||
Network(
|
||||
@@ -103,6 +136,33 @@ class Compute:
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
def __get_firewalls__(self):
|
||||
for project_id in self.project_ids:
|
||||
try:
|
||||
request = self.client.firewalls().list(project=project_id)
|
||||
while request is not None:
|
||||
response = request.execute()
|
||||
|
||||
for firewall in response.get("items", []):
|
||||
self.firewalls.append(
|
||||
Firewall(
|
||||
name=firewall["name"],
|
||||
id=firewall["id"],
|
||||
source_ranges=firewall["sourceRanges"],
|
||||
direction=firewall["direction"],
|
||||
allowed_rules=firewall.get("allowed", []),
|
||||
project_id=project_id,
|
||||
)
|
||||
)
|
||||
|
||||
request = self.client.firewalls().list_next(
|
||||
previous_request=request, previous_response=response
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
|
||||
class Instance(BaseModel):
|
||||
name: str
|
||||
@@ -114,9 +174,20 @@ class Instance(BaseModel):
|
||||
shielded_enabled_vtpm: bool
|
||||
shielded_enabled_integrity_monitoring: bool
|
||||
service_accounts: list
|
||||
ip_forward: bool
|
||||
disks_encryption: list
|
||||
|
||||
|
||||
class Network(BaseModel):
|
||||
name: str
|
||||
id: str
|
||||
project_id: str
|
||||
|
||||
|
||||
class Firewall(BaseModel):
|
||||
name: str
|
||||
id: str
|
||||
source_ranges: list
|
||||
direction: str
|
||||
allowed_rules: list
|
||||
project_id: str
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"Severity": "medium",
|
||||
"ResourceType": "VMInstance",
|
||||
"Description": "To defend against advanced threats and ensure that the boot loader and firmware on your VMs are signed and untampered, it is recommended that Compute instances are launched with Shielded VM enabled.",
|
||||
"Risk": "",
|
||||
"Risk": "Whithout shielded VM enabled is not possible to defend against advanced threats and ensure that the boot loader and firmware on your Google Compute Engine instances are signed and untampered.",
|
||||
"RelatedUrl": "",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
@@ -19,8 +19,8 @@
|
||||
"Terraform": "https://docs.bridgecrew.io/docs/bc_gcp_general_y#terraform"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "",
|
||||
"Url": ""
|
||||
"Text": "Ensure that your Google Compute Engine instances are configured to use Shielded VM security feature for protection against rootkits and bootkits.Google Compute Engine service can enable 3 advanced security components for Shielded VM instances: 1. Virtual Trusted Platform Module (vTPM) - this component validates the guest virtual machine (VM) pre-boot and boot integrity, and provides key generation and protection. 2. Integrity Monitoring - lets you monitor and verify the runtime boot integrity of your shielded VM instances using Google Cloud Operations reports (also known as Stackdriver reports). 3. Secure boot helps - this security component protects your VM instances against boot-level and kernel-level malware and rootkits. To defend against advanced threats and ensure that the boot loader and firmware on your Google Compute Engine instances are signed and untampered, it is strongly recommended that your production instances are launched with Shielded VM enabled.",
|
||||
"Url": "https://cloud.google.com/compute/docs/instances/modifying-shielded-vm"
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
@@ -28,4 +28,3 @@
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"Provider": "gcp",
|
||||
"CheckID": "compute_ssh_access_from_the_internet_allowed",
|
||||
"CheckTitle": "Ensure That SSH Access Is Restricted From the Internet",
|
||||
"CheckType": [],
|
||||
"ServiceName": "networking",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "critical",
|
||||
"ResourceType": "FirewallRule",
|
||||
"Description": "GCP `Firewall Rules` are specific to a `VPC Network`. Each rule either `allows` or `denies` traffic when its conditions are met. Its conditions allow the user to specify the type of traffic, such as ports and protocols, and the source or destination of the traffic, including IP addresses, subnets, and instances. Firewall rules are defined at the VPC network level and are specific to the network in which they are defined. The rules themselves cannot be shared among networks. Firewall rules only support IPv4 traffic. When specifying a source for an ingress rule or a destination for an egress rule by address, only an `IPv4` address or `IPv4 block in CIDR` notation can be used. Generic `(0.0.0.0/0)` incoming traffic from the internet to VPC or VM instance using `SSH` on `Port 22` can be avoided.",
|
||||
"Risk": "Exposing Secure Shell (SSH) port 22 to the Internet can increase opportunities for malicious activities such as hacking, Man-In-The-Middle attacks (MITM) and brute-force attacks.",
|
||||
"RelatedUrl": "",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "https://docs.bridgecrew.io/docs/bc_gcp_networking_1#cli-command",
|
||||
"NativeIaC": "",
|
||||
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudVPC/unrestricted-ssh-access.html",
|
||||
"Terraform": "https://docs.bridgecrew.io/docs/bc_gcp_networking_1#terraform"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Check your Google Cloud Virtual Private Cloud (VPC) firewall rules for inbound rules that allow unrestricted access (i.e. 0.0.0.0/0) on TCP port 22 and restrict the access to trusted IP addresses or IP ranges only in order to implement the principle of least privilege and reduce the attack surface. TCP port 22 is used for secure remote login by connecting an SSH client application with an SSH server. It is strongly recommended to configure your Google Cloud VPC firewall rules to limit inbound traffic on TCP port 22 to known IP addresses only.",
|
||||
"Url": "https://cloud.google.com/vpc/docs/using-firewalls"
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_GCP
|
||||
from prowler.providers.gcp.services.compute.compute_client import compute_client
|
||||
|
||||
|
||||
class compute_ssh_access_from_the_internet_allowed(Check):
|
||||
def execute(self) -> Check_Report_GCP:
|
||||
findings = []
|
||||
for firewall in compute_client.firewalls:
|
||||
report = Check_Report_GCP(self.metadata())
|
||||
report.project_id = firewall.project_id
|
||||
report.resource_id = firewall.id
|
||||
report.resource_name = firewall.name
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Firewall {firewall.name} does not expose port 22 (SSH) to the internet."
|
||||
opened_port = False
|
||||
for rule in firewall.allowed_rules:
|
||||
if rule["IPProtocol"] == "all":
|
||||
opened_port = True
|
||||
break
|
||||
elif rule["IPProtocol"] == "tcp":
|
||||
if rule.get("ports") is None:
|
||||
opened_port = True
|
||||
break
|
||||
else:
|
||||
for port in rule["ports"]:
|
||||
if port.find("-") != -1:
|
||||
lower, higher = port.split("-")
|
||||
if int(lower) <= 22 and int(higher) >= 22:
|
||||
opened_port = True
|
||||
break
|
||||
elif int(port) == 22:
|
||||
opened_port = True
|
||||
break
|
||||
if (
|
||||
"0.0.0.0/0" in firewall.source_ranges
|
||||
and firewall.direction == "INGRESS"
|
||||
and opened_port
|
||||
):
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"Firewall {firewall.name} does exposes port 22 (SSH) to the internet."
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
0
prowler/providers/gcp/services/dataproc/__init__.py
Normal file
0
prowler/providers/gcp/services/dataproc/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from prowler.providers.gcp.lib.audit_info.audit_info import gcp_audit_info
|
||||
from prowler.providers.gcp.services.dataproc.dataproc_service import Dataproc
|
||||
|
||||
dataproc_client = Dataproc(gcp_audit_info)
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"Provider": "gcp",
|
||||
"CheckID": "dataproc_encrypted_with_cmks_disabled",
|
||||
"CheckTitle": "Ensure that Dataproc Cluster is encrypted using Customer-Managed Encryption Key",
|
||||
"CheckType": [],
|
||||
"ServiceName": "dataproc",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "high",
|
||||
"ResourceType": "Cluster",
|
||||
"Description": "When you use Dataproc, cluster and job data is stored on Persistent Disks (PDs) associated with the Compute Engine VMs in your cluster and in a Cloud Storage staging bucket. This PD and bucket data is encrypted using a Google-generated data encryption key (DEK) and key encryption key (KEK). The CMEK feature allows you to create, use, and revoke the key encryption key (KEK). Google still controls the data encryption key (DEK).",
|
||||
"Risk": "The Dataproc cluster data is encrypted using a Google-generated Data Encryption Key (DEK) and a Key Encryption Key (KEK). If you need to control and manage your cluster data encryption yourself, you can use your own Customer-Managed Keys (CMKs). Cloud KMS Customer-Managed Keys can be implemented as an additional security layer on top of existing data encryption, and are often used in the enterprise world, where compliance and security controls are very strict.",
|
||||
"RelatedUrl": "",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "",
|
||||
"NativeIaC": "",
|
||||
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/Dataproc/enable-encryption-with-cmks-for-dataproc-clusters.html",
|
||||
"Terraform": "https://docs.bridgecrew.io/docs/ensure-gcp-dataproc-cluster-is-encrypted-with-customer-supplied-encryption-keys-cseks#terraform"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Ensure that your Google Cloud Dataproc clusters on Compute Engine are encrypted with Customer-Managed Keys (CMKs) in order to control the cluster data encryption/decryption process. You can create and manage your own Customer-Managed Keys (CMKs) with Cloud Key Management Service (Cloud KMS). Cloud KMS provides secure and efficient encryption key management, controlled key rotation, and revocation mechanisms.",
|
||||
"Url": "https://cloud.google.com/dataproc/docs/concepts/configuring-clusters/customer-managed-encryption"
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_GCP
|
||||
from prowler.providers.gcp.services.dataproc.dataproc_client import dataproc_client
|
||||
|
||||
|
||||
class dataproc_encrypted_with_cmks_disabled(Check):
|
||||
def execute(self) -> Check_Report_GCP:
|
||||
findings = []
|
||||
for cluster in dataproc_client.clusters:
|
||||
report = Check_Report_GCP(self.metadata())
|
||||
report.project_id = cluster.project_id
|
||||
report.resource_id = cluster.id
|
||||
report.resource_name = cluster.name
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Dataproc cluster {cluster.name} is encrypted with customer managed encryption keys."
|
||||
if cluster.encryption_config.get("gcePdKmsKeyName") is None:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"Dataproc cluster {cluster.name} is not encrypted with customer managed encryption keys."
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
62
prowler/providers/gcp/services/dataproc/dataproc_service.py
Normal file
62
prowler/providers/gcp/services/dataproc/dataproc_service.py
Normal file
@@ -0,0 +1,62 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.providers.gcp.gcp_provider import generate_client
|
||||
from prowler.providers.gcp.services.compute.compute_client import compute_client
|
||||
|
||||
|
||||
################## Dataproc
|
||||
class Dataproc:
|
||||
def __init__(self, audit_info):
|
||||
self.service = "dataproc"
|
||||
self.api_version = "v1"
|
||||
self.project_ids = audit_info.project_ids
|
||||
self.default_project_id = audit_info.default_project_id
|
||||
self.client = generate_client(self.service, self.api_version, audit_info)
|
||||
self.clusters = []
|
||||
self.__get_clusters__()
|
||||
|
||||
def __get_clusters__(self):
|
||||
for project_id in self.project_ids:
|
||||
try:
|
||||
for region in compute_client.regions:
|
||||
request = (
|
||||
self.client.projects()
|
||||
.regions()
|
||||
.clusters()
|
||||
.list(projectId=project_id, region=region)
|
||||
)
|
||||
while request is not None:
|
||||
response = request.execute()
|
||||
|
||||
for cluster in response.get("clusters", []):
|
||||
self.clusters.append(
|
||||
Cluster(
|
||||
name=cluster["clusterName"],
|
||||
id=cluster["clusterUuid"],
|
||||
encryption_config=cluster["config"][
|
||||
"encryptionConfig"
|
||||
],
|
||||
project_id=project_id,
|
||||
)
|
||||
)
|
||||
|
||||
request = (
|
||||
self.client.projects()
|
||||
.regions()
|
||||
.clusters()
|
||||
.list_next(
|
||||
previous_request=request, previous_response=response
|
||||
)
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
|
||||
class Cluster(BaseModel):
|
||||
name: str
|
||||
id: str
|
||||
encryption_config: dict
|
||||
project_id: str
|
||||
0
prowler/providers/gcp/services/dns/__init__.py
Normal file
0
prowler/providers/gcp/services/dns/__init__.py
Normal file
4
prowler/providers/gcp/services/dns/dns_client.py
Normal file
4
prowler/providers/gcp/services/dns/dns_client.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from prowler.providers.gcp.lib.audit_info.audit_info import gcp_audit_info
|
||||
from prowler.providers.gcp.services.dns.dns_service import DNS
|
||||
|
||||
dns_client = DNS(gcp_audit_info)
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"Provider": "gcp",
|
||||
"CheckID": "dns_dnssec_disabled",
|
||||
"CheckTitle": "Ensure That DNSSEC Is Enabled for Cloud DNS",
|
||||
"CheckType": [],
|
||||
"ServiceName": "dns",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "DNS_Zone",
|
||||
"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.",
|
||||
"Risk": "Attackers can hijack the process of domain/IP lookup and redirect users to malicious web content through DNS hijacking and Man-In-The-Middle (MITM) attacks.",
|
||||
"RelatedUrl": "",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "https://docs.bridgecrew.io/docs/bc_gcp_networking_5#cli-command",
|
||||
"NativeIaC": "",
|
||||
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudDNS/enable-dns-sec.html",
|
||||
"Terraform": "https://docs.bridgecrew.io/docs/bc_gcp_networking_5#terraform"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Ensure that DNSSEC security feature is enabled for all your Google Cloud DNS managed zones in order to protect your domains against spoofing and cache poisoning attacks. By default, DNSSEC is not enabled for Google Cloud public DNS managed zones. DNSSEC security feature helps mitigate the risk of such attacks by encrypting signing DNS records. As a result, it prevents attackers from issuing fake DNS responses that may misdirect web clients to fake, fraudulent or scam websites.",
|
||||
"Url": "https://cloud.google.com/dns/docs/dnssec-config"
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_GCP
|
||||
from prowler.providers.gcp.services.dns.dns_client import dns_client
|
||||
|
||||
|
||||
class dns_dnssec_disabled(Check):
|
||||
def execute(self) -> Check_Report_GCP:
|
||||
findings = []
|
||||
for managed_zone in dns_client.managed_zones:
|
||||
report = Check_Report_GCP(self.metadata())
|
||||
report.project_id = managed_zone.project_id
|
||||
report.resource_id = managed_zone.id
|
||||
report.resource_name = managed_zone.name
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"Cloud DNS {managed_zone.name} have DNSSEC enabled."
|
||||
)
|
||||
if not managed_zone.dnssec:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"Cloud DNS {managed_zone.name} doens't have DNSSEC enabled."
|
||||
)
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"Provider": "gcp",
|
||||
"CheckID": "dns_rsasha1_in_use_to_key_sign_in_dnssec",
|
||||
"CheckTitle": "Ensure That RSASHA1 Is Not Used for the Key-Signing Key in Cloud DNS DNSSEC",
|
||||
"CheckType": [],
|
||||
"ServiceName": "dns",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "DNS_Zone",
|
||||
"Description": "NOTE: Currently, the SHA1 algorithm has been removed from general use by Google, and, if being used, needs to be whitelisted on a project basis by Google and will also, therefore, require a Google Cloud support contract. 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.",
|
||||
"Risk": "SHA1 is considered weak and vulnerable to collision attacks.",
|
||||
"RelatedUrl": "",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "",
|
||||
"NativeIaC": "",
|
||||
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudDNS/dns-sec-key-signing-algorithm-in-use.html",
|
||||
"Terraform": "https://docs.bridgecrew.io/docs/bc_gcp_networking_6#terraform"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Ensure that Domain Name System Security Extensions (DNSSEC) feature is not using the deprecated RSASHA1 algorithm for the Key-Signing Key (KSK) associated with your DNS managed zone file. The algorithm used for DNSSEC signing should be a strong one, such as ECDSAP256SHA256 algorithm, as this is secure and widely deployed, and therefore it is a good choice for both DNSSEC validation and signing.",
|
||||
"Url": "https://cloud.google.com/dns/docs/dnssec-config"
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_GCP
|
||||
from prowler.providers.gcp.services.dns.dns_client import dns_client
|
||||
|
||||
|
||||
class dns_rsasha1_in_use_to_key_sign_in_dnssec(Check):
|
||||
def execute(self) -> Check_Report_GCP:
|
||||
findings = []
|
||||
for managed_zone in dns_client.managed_zones:
|
||||
report = Check_Report_GCP(self.metadata())
|
||||
report.project_id = managed_zone.project_id
|
||||
report.resource_id = managed_zone.id
|
||||
report.resource_name = managed_zone.name
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Cloud DNS {managed_zone.name} is not using RSASHA1 algorithm as key signing."
|
||||
if any(
|
||||
[
|
||||
key["algorithm"] == "rsasha1"
|
||||
for key in managed_zone.key_specs
|
||||
if key["keyType"] == "keySigning"
|
||||
]
|
||||
):
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"Cloud DNS {managed_zone.name} is using RSASHA1 algorithm as key signing."
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"Provider": "gcp",
|
||||
"CheckID": "dns_rsasha1_in_use_to_zone_sign_in_dnssec",
|
||||
"CheckTitle": "Ensure That RSASHA1 Is Not Used for the Zone-Signing Key in Cloud DNS DNSSEC",
|
||||
"CheckType": [],
|
||||
"ServiceName": "dns",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "DNS_Zone",
|
||||
"Description": "NOTE: Currently, the SHA1 algorithm has been removed from general use by Google, and, if being used, needs to be whitelisted on a project basis by Google and will also, therefore, require a Google Cloud support contract. 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.",
|
||||
"Risk": "SHA1 is considered weak and vulnerable to collision attacks.",
|
||||
"RelatedUrl": "",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "",
|
||||
"NativeIaC": "",
|
||||
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudDNS/dns-sec-zone-signing-algorithm-in-use.html",
|
||||
"Terraform": "https://docs.bridgecrew.io/docs/bc_gcp_networking_6#terraform"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Ensure that Domain Name System Security Extensions (DNSSEC) feature is not using the deprecated RSASHA1 algorithm for the Zone-Signing Key (ZSK) associated with your public DNS managed zone. The algorithm used for DNSSEC signing should be a strong one, such as RSASHA256, as this algorithm is secure and widely deployed, and therefore it is a good candidate for both DNSSEC validation and signing.",
|
||||
"Url": "https://cloud.google.com/dns/docs/dnssec-config"
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_GCP
|
||||
from prowler.providers.gcp.services.dns.dns_client import dns_client
|
||||
|
||||
|
||||
class dns_rsasha1_in_use_to_zone_sign_in_dnssec(Check):
|
||||
def execute(self) -> Check_Report_GCP:
|
||||
findings = []
|
||||
for managed_zone in dns_client.managed_zones:
|
||||
report = Check_Report_GCP(self.metadata())
|
||||
report.project_id = managed_zone.project_id
|
||||
report.resource_id = managed_zone.id
|
||||
report.resource_name = managed_zone.name
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Cloud DNS {managed_zone.name} is not using RSASHA1 algorithm as zone signing."
|
||||
if any(
|
||||
[
|
||||
key["algorithm"] == "rsasha1"
|
||||
for key in managed_zone.key_specs
|
||||
if key["keyType"] == "zoneSigning"
|
||||
]
|
||||
):
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"Cloud DNS {managed_zone.name} is using RSASHA1 algorithm as zone signing."
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
51
prowler/providers/gcp/services/dns/dns_service.py
Normal file
51
prowler/providers/gcp/services/dns/dns_service.py
Normal file
@@ -0,0 +1,51 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.providers.gcp.gcp_provider import generate_client
|
||||
|
||||
|
||||
################## DNS
|
||||
class DNS:
|
||||
def __init__(self, audit_info):
|
||||
self.service = "dns"
|
||||
self.api_version = "v1"
|
||||
self.project_ids = audit_info.project_ids
|
||||
self.default_project_id = audit_info.default_project_id
|
||||
self.client = generate_client(self.service, self.api_version, audit_info)
|
||||
self.managed_zones = []
|
||||
self.__get_managed_zones__()
|
||||
|
||||
def __get_managed_zones__(self):
|
||||
for project_id in self.project_ids:
|
||||
try:
|
||||
request = self.client.managedZones().list(project=project_id)
|
||||
while request is not None:
|
||||
response = request.execute()
|
||||
for managed_zone in response.get("managedZones"):
|
||||
self.managed_zones.append(
|
||||
ManagedZone(
|
||||
name=managed_zone["name"],
|
||||
id=managed_zone["id"],
|
||||
dnssec=managed_zone["dnssecConfig"]["state"] == "on",
|
||||
key_specs=managed_zone["dnssecConfig"][
|
||||
"defaultKeySpecs"
|
||||
],
|
||||
project_id=project_id,
|
||||
)
|
||||
)
|
||||
|
||||
request = self.client.managedZones().list_next(
|
||||
previous_request=request, previous_response=response
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
|
||||
class ManagedZone(BaseModel):
|
||||
name: str
|
||||
id: str
|
||||
dnssec: bool
|
||||
key_specs: list
|
||||
project_id: str
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"Provider": "gcp",
|
||||
"CheckID": "iam_no_service_roles_at_project_level",
|
||||
"CheckTitle": "Ensure That IAM Users Are Not Assigned the Service Account User or Service Account Token Creator Roles at Project Level",
|
||||
"CheckType": [],
|
||||
"ServiceName": "iam",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "IAM Policy",
|
||||
"Description": "It is recommended to assign the `Service Account User (iam.serviceAccountUser)` and `Service Account Token Creator (iam.serviceAccountTokenCreator)` roles to a user for a specific service account rather than assigning the role to a user at project level.",
|
||||
"Risk": "The Service Account User (iam.serviceAccountUser) role allows an IAM user to attach a service account to a long-running job service such as an App Engine App or Dataflow Job, whereas the Service Account Token Creator (iam.serviceAccountTokenCreator) role allows a user to directly impersonate the identity of a service account.",
|
||||
"RelatedUrl": "",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "https://docs.bridgecrew.io/docs/bc_gcp_iam_3#cli-command",
|
||||
"NativeIaC": "",
|
||||
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudIAM/check-for-iam-users-with-service-roles.html",
|
||||
"Terraform": "https://docs.bridgecrew.io/docs/bc_gcp_iam_3#terraform"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Ensure that the Service Account User and Service Account Token Creator roles are assigned to a user for a specific GCP service account rather than to a user at the GCP project level, in order to implement the principle of least privilege (POLP). The principle of least privilege (also known as the principle of minimal privilege) is the practice of providing every user the minimal amount of access required to perform its tasks. Google Cloud Platform (GCP) IAM users should not have assigned the Service Account User or Service Account Token Creator roles at the GCP project level. Instead, these roles should be allocated to a user associated with a specific service account, providing that user access to the service account only.",
|
||||
"Url": "https://cloud.google.com/iam/docs/granting-changing-revoking-access"
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_GCP
|
||||
from prowler.providers.gcp.services.cloudresourcemanager.cloudresourcemanager_client import (
|
||||
cloudresourcemanager_client,
|
||||
)
|
||||
|
||||
|
||||
class iam_no_service_roles_at_project_level(Check):
|
||||
def execute(self) -> Check_Report_GCP:
|
||||
findings = []
|
||||
failed_projects = set()
|
||||
for binding in cloudresourcemanager_client.bindings:
|
||||
report = Check_Report_GCP(self.metadata())
|
||||
report.project_id = binding.project_id
|
||||
report.resource_id = binding.role
|
||||
report.resource_name = binding.role
|
||||
if binding.role in [
|
||||
"roles/iam.serviceAccountUser",
|
||||
"roles/iam.serviceAccountTokenCreator",
|
||||
]:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"IAM Users assigned to service role '{binding.role}' at project level {binding.project_id}."
|
||||
failed_projects.add(binding.project_id)
|
||||
findings.append(report)
|
||||
|
||||
for project in cloudresourcemanager_client.project_ids:
|
||||
if project not in failed_projects:
|
||||
report = Check_Report_GCP(self.metadata())
|
||||
report.project_id = project
|
||||
report.resource_id = project
|
||||
report.resource_name = ""
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"No IAM Users assigned to service roles at project level {project}."
|
||||
findings.append(report)
|
||||
return findings
|
||||
@@ -0,0 +1,135 @@
|
||||
from re import search
|
||||
from unittest import mock
|
||||
|
||||
GCP_PROJECT_ID = "123456789012"
|
||||
|
||||
|
||||
class Test_apikeys_api_restrictions_configured:
|
||||
def test_apikeys_no_keys(self):
|
||||
apikeys_client = mock.MagicMock
|
||||
apikeys_client.keys = []
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.apikeys.apikeys_api_restrictions_configured.apikeys_api_restrictions_configured.apikeys_client",
|
||||
new=apikeys_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.apikeys.apikeys_api_restrictions_configured.apikeys_api_restrictions_configured import (
|
||||
apikeys_api_restrictions_configured,
|
||||
)
|
||||
|
||||
check = apikeys_api_restrictions_configured()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_one_compliant_key(self):
|
||||
from prowler.providers.gcp.services.apikeys.apikeys_service import Key
|
||||
|
||||
key = Key(
|
||||
name="test",
|
||||
id="123",
|
||||
creation_time="2023-06-01T11:21:41.627509Z",
|
||||
restrictions={
|
||||
"apiTargets": [
|
||||
{"service": "dns.googleapis.com"},
|
||||
{"service": "oslogin.googleapis.com"},
|
||||
]
|
||||
},
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
apikeys_client = mock.MagicMock
|
||||
apikeys_client.project_ids = [GCP_PROJECT_ID]
|
||||
apikeys_client.keys = [key]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.apikeys.apikeys_api_restrictions_configured.apikeys_api_restrictions_configured.apikeys_client",
|
||||
new=apikeys_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.apikeys.apikeys_api_restrictions_configured.apikeys_api_restrictions_configured import (
|
||||
apikeys_api_restrictions_configured,
|
||||
)
|
||||
|
||||
check = apikeys_api_restrictions_configured()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
f"API key {key.name} have restrictions configured.",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == key.id
|
||||
|
||||
def test_one_key_without_restrictions(self):
|
||||
from prowler.providers.gcp.services.apikeys.apikeys_service import Key
|
||||
|
||||
key = Key(
|
||||
name="test",
|
||||
id="123",
|
||||
creation_time="2022-06-05T11:21:41.627509Z",
|
||||
restrictions={},
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
apikeys_client = mock.MagicMock
|
||||
apikeys_client.project_ids = [GCP_PROJECT_ID]
|
||||
apikeys_client.keys = [key]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.apikeys.apikeys_api_restrictions_configured.apikeys_api_restrictions_configured.apikeys_client",
|
||||
new=apikeys_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.apikeys.apikeys_api_restrictions_configured.apikeys_api_restrictions_configured import (
|
||||
apikeys_api_restrictions_configured,
|
||||
)
|
||||
|
||||
check = apikeys_api_restrictions_configured()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
f"API key {key.name} doens't have restrictions configured.",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == key.id
|
||||
|
||||
def test_one_key_with_cloudapis_restriction(self):
|
||||
from prowler.providers.gcp.services.apikeys.apikeys_service import Key
|
||||
|
||||
key = Key(
|
||||
name="test",
|
||||
id="123",
|
||||
creation_time="2022-06-05T11:21:41.627509Z",
|
||||
restrictions={
|
||||
"apiTargets": [
|
||||
{"service": "dns.googleapis.com"},
|
||||
{"service": "oslogin.googleapis.com"},
|
||||
{"service": "cloudapis.googleapis.com"},
|
||||
]
|
||||
},
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
apikeys_client = mock.MagicMock
|
||||
apikeys_client.project_ids = [GCP_PROJECT_ID]
|
||||
apikeys_client.keys = [key]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.apikeys.apikeys_api_restrictions_configured.apikeys_api_restrictions_configured.apikeys_client",
|
||||
new=apikeys_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.apikeys.apikeys_api_restrictions_configured.apikeys_api_restrictions_configured import (
|
||||
apikeys_api_restrictions_configured,
|
||||
)
|
||||
|
||||
check = apikeys_api_restrictions_configured()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
f"API key {key.name} doens't have restrictions configured.",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == key.id
|
||||
@@ -0,0 +1,90 @@
|
||||
from re import search
|
||||
from unittest import mock
|
||||
|
||||
GCP_PROJECT_ID = "123456789012"
|
||||
|
||||
|
||||
class Test_apikeys_key_rotated_in_90_days:
|
||||
def test_apikeys_no_keys(self):
|
||||
apikeys_client = mock.MagicMock
|
||||
apikeys_client.keys = []
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.apikeys.apikeys_key_rotated_in_90_days.apikeys_key_rotated_in_90_days.apikeys_client",
|
||||
new=apikeys_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.apikeys.apikeys_key_rotated_in_90_days.apikeys_key_rotated_in_90_days import (
|
||||
apikeys_key_rotated_in_90_days,
|
||||
)
|
||||
|
||||
check = apikeys_key_rotated_in_90_days()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_one_compliant_key(self):
|
||||
from prowler.providers.gcp.services.apikeys.apikeys_service import Key
|
||||
|
||||
key = Key(
|
||||
name="test",
|
||||
id="123",
|
||||
creation_time="2023-06-01T11:21:41.627509Z",
|
||||
restrictions={},
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
apikeys_client = mock.MagicMock
|
||||
apikeys_client.project_ids = [GCP_PROJECT_ID]
|
||||
apikeys_client.keys = [key]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.apikeys.apikeys_key_rotated_in_90_days.apikeys_key_rotated_in_90_days.apikeys_client",
|
||||
new=apikeys_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.apikeys.apikeys_key_rotated_in_90_days.apikeys_key_rotated_in_90_days import (
|
||||
apikeys_key_rotated_in_90_days,
|
||||
)
|
||||
|
||||
check = apikeys_key_rotated_in_90_days()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
f"API key {key.name} created in less than 90 days.",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == key.id
|
||||
|
||||
def test_one_key_with_more_than_90_days(self):
|
||||
from prowler.providers.gcp.services.apikeys.apikeys_service import Key
|
||||
|
||||
key = Key(
|
||||
name="test",
|
||||
id="123",
|
||||
creation_time="2022-06-05T11:21:41.627509Z",
|
||||
restrictions={},
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
apikeys_client = mock.MagicMock
|
||||
apikeys_client.project_ids = [GCP_PROJECT_ID]
|
||||
apikeys_client.keys = [key]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.apikeys.apikeys_key_rotated_in_90_days.apikeys_key_rotated_in_90_days.apikeys_client",
|
||||
new=apikeys_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.apikeys.apikeys_key_rotated_in_90_days.apikeys_key_rotated_in_90_days import (
|
||||
apikeys_key_rotated_in_90_days,
|
||||
)
|
||||
|
||||
check = apikeys_key_rotated_in_90_days()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
f"API key {key.name} creation date have more than 90 days.",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == key.id
|
||||
@@ -0,0 +1,143 @@
|
||||
from re import search
|
||||
from unittest import mock
|
||||
|
||||
GCP_PROJECT_ID = "123456789012"
|
||||
|
||||
|
||||
class Test_compute_block_project_wide_ssh_keys_disabled:
|
||||
def test_compute_no_instances(self):
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.instances = []
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_block_project_wide_ssh_keys_disabled.compute_block_project_wide_ssh_keys_disabled.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_block_project_wide_ssh_keys_disabled.compute_block_project_wide_ssh_keys_disabled import (
|
||||
compute_block_project_wide_ssh_keys_disabled,
|
||||
)
|
||||
|
||||
check = compute_block_project_wide_ssh_keys_disabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_one_compliant_instance_with_block_project_ssh_keys_true(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Instance
|
||||
|
||||
instance = Instance(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
zone="us-central1-a",
|
||||
public_ip=True,
|
||||
metadata={"items": [{"key": "block-project-ssh-keys", "value": "true"}]},
|
||||
shielded_enabled_vtpm=True,
|
||||
shielded_enabled_integrity_monitoring=True,
|
||||
service_accounts=[],
|
||||
ip_forward=False,
|
||||
disks_encryption=[("disk1", False), ("disk2", False)],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.instances = [instance]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_block_project_wide_ssh_keys_disabled.compute_block_project_wide_ssh_keys_disabled.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_block_project_wide_ssh_keys_disabled.compute_block_project_wide_ssh_keys_disabled import (
|
||||
compute_block_project_wide_ssh_keys_disabled,
|
||||
)
|
||||
|
||||
check = compute_block_project_wide_ssh_keys_disabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
f"The VM Instance {instance.name} is not making use of common/shared project-wide SSH key",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == instance.id
|
||||
|
||||
def test_one_instance_without_metadata(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Instance
|
||||
|
||||
instance = Instance(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
zone="us-central1-a",
|
||||
public_ip=True,
|
||||
metadata={},
|
||||
shielded_enabled_vtpm=True,
|
||||
shielded_enabled_integrity_monitoring=True,
|
||||
service_accounts=[],
|
||||
ip_forward=False,
|
||||
disks_encryption=[("disk1", False), ("disk2", False)],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.instances = [instance]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_block_project_wide_ssh_keys_disabled.compute_block_project_wide_ssh_keys_disabled.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_block_project_wide_ssh_keys_disabled.compute_block_project_wide_ssh_keys_disabled import (
|
||||
compute_block_project_wide_ssh_keys_disabled,
|
||||
)
|
||||
|
||||
check = compute_block_project_wide_ssh_keys_disabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
f"The VM Instance {instance.name} is making use of common/shared project-wide SSH key",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == instance.id
|
||||
|
||||
def test_one_instance_with_block_project_ssh_keys_false(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Instance
|
||||
|
||||
instance = Instance(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
zone="us-central1-a",
|
||||
public_ip=True,
|
||||
metadata={"items": [{"key": "block-project-ssh-keys", "value": "false"}]},
|
||||
shielded_enabled_vtpm=True,
|
||||
shielded_enabled_integrity_monitoring=True,
|
||||
service_accounts=[],
|
||||
ip_forward=False,
|
||||
disks_encryption=[("disk1", False), ("disk2", False)],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.instances = [instance]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_block_project_wide_ssh_keys_disabled.compute_block_project_wide_ssh_keys_disabled.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_block_project_wide_ssh_keys_disabled.compute_block_project_wide_ssh_keys_disabled import (
|
||||
compute_block_project_wide_ssh_keys_disabled,
|
||||
)
|
||||
|
||||
check = compute_block_project_wide_ssh_keys_disabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
f"The VM Instance {instance.name} is making use of common/shared project-wide SSH key",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == instance.id
|
||||
@@ -32,6 +32,8 @@ class Test_compute_default_service_account_in_use:
|
||||
metadata={},
|
||||
shielded_enabled_vtpm=True,
|
||||
shielded_enabled_integrity_monitoring=True,
|
||||
ip_forward=False,
|
||||
disks_encryption=[("disk1", False), ("disk2", False)],
|
||||
service_accounts=[{"email": "custom@developer.gserviceaccount.com"}],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
@@ -73,6 +75,8 @@ class Test_compute_default_service_account_in_use:
|
||||
service_accounts=[
|
||||
{"email": f"{GCP_PROJECT_ID}-compute@developer.gserviceaccount.com"}
|
||||
],
|
||||
ip_forward=False,
|
||||
disks_encryption=[("disk1", False), ("disk2", False)],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
@@ -113,6 +117,8 @@ class Test_compute_default_service_account_in_use:
|
||||
service_accounts=[
|
||||
{"email": f"{GCP_PROJECT_ID}-compute@developer.gserviceaccount.com"}
|
||||
],
|
||||
ip_forward=False,
|
||||
disks_encryption=[("disk1", False), ("disk2", False)],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@ class Test_compute_default_service_account_in_use_with_full_api_access:
|
||||
service_accounts=[
|
||||
{"email": "123-compute@developer.gserviceaccount.com", "scopes": []}
|
||||
],
|
||||
ip_forward=False,
|
||||
disks_encryption=[("disk1", False), ("disk2", False)],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
@@ -78,6 +80,8 @@ class Test_compute_default_service_account_in_use_with_full_api_access:
|
||||
"scopes": ["https://www.googleapis.com/auth/cloud-platform"],
|
||||
}
|
||||
],
|
||||
ip_forward=False,
|
||||
disks_encryption=[("disk1", False), ("disk2", False)],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
@@ -121,6 +125,8 @@ class Test_compute_default_service_account_in_use_with_full_api_access:
|
||||
"scopes": ["https://www.googleapis.com/auth/cloud-platform"],
|
||||
}
|
||||
],
|
||||
ip_forward=False,
|
||||
disks_encryption=[("disk1", False), ("disk2", False)],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
from re import search
|
||||
from unittest import mock
|
||||
|
||||
GCP_PROJECT_ID = "123456789012"
|
||||
|
||||
|
||||
class Test_compute_encryption_with_csek_is_disabled:
|
||||
def test_compute_no_instances(self):
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.instances = []
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_encryption_with_csek_is_disabled.compute_encryption_with_csek_is_disabled.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_encryption_with_csek_is_disabled.compute_encryption_with_csek_is_disabled import (
|
||||
compute_encryption_with_csek_is_disabled,
|
||||
)
|
||||
|
||||
check = compute_encryption_with_csek_is_disabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_one_compliant_instance_with_all_encrypted_disks(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Instance
|
||||
|
||||
instance = Instance(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
zone="us-central1-a",
|
||||
public_ip=True,
|
||||
metadata={"items": [{"key": "block-project-ssh-keys", "value": "true"}]},
|
||||
shielded_enabled_vtpm=True,
|
||||
shielded_enabled_integrity_monitoring=True,
|
||||
service_accounts=[],
|
||||
ip_forward=False,
|
||||
disks_encryption=[("disk1", True), ("disk2", True)],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.instances = [instance]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_encryption_with_csek_is_disabled.compute_encryption_with_csek_is_disabled.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_encryption_with_csek_is_disabled.compute_encryption_with_csek_is_disabled import (
|
||||
compute_encryption_with_csek_is_disabled,
|
||||
)
|
||||
|
||||
check = compute_encryption_with_csek_is_disabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
f"The VM Instance {instance.name} have every disk encrypted.",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == instance.id
|
||||
|
||||
def test_one_instance_with_one_unecrypted_disk(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Instance
|
||||
|
||||
instance = Instance(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
zone="us-central1-a",
|
||||
public_ip=True,
|
||||
metadata={},
|
||||
shielded_enabled_vtpm=True,
|
||||
shielded_enabled_integrity_monitoring=True,
|
||||
service_accounts=[],
|
||||
ip_forward=False,
|
||||
disks_encryption=[("disk1", False), ("disk2", True)],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.instances = [instance]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_encryption_with_csek_is_disabled.compute_encryption_with_csek_is_disabled.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_encryption_with_csek_is_disabled.compute_encryption_with_csek_is_disabled import (
|
||||
compute_encryption_with_csek_is_disabled,
|
||||
)
|
||||
|
||||
check = compute_encryption_with_csek_is_disabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
f"The VM Instance {instance.name} have the following unencrypted disks: '{', '.join([i[0] for i in instance.disks_encryption if not i[1]])}'",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == instance.id
|
||||
|
||||
def test_one_instance_with_all_unencrypted_disks(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Instance
|
||||
|
||||
instance = Instance(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
zone="us-central1-a",
|
||||
public_ip=True,
|
||||
metadata={"items": [{"key": "block-project-ssh-keys", "value": "false"}]},
|
||||
shielded_enabled_vtpm=True,
|
||||
shielded_enabled_integrity_monitoring=True,
|
||||
service_accounts=[],
|
||||
ip_forward=False,
|
||||
disks_encryption=[("disk1", False), ("disk2", False)],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.instances = [instance]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_encryption_with_csek_is_disabled.compute_encryption_with_csek_is_disabled.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_encryption_with_csek_is_disabled.compute_encryption_with_csek_is_disabled import (
|
||||
compute_encryption_with_csek_is_disabled,
|
||||
)
|
||||
|
||||
check = compute_encryption_with_csek_is_disabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
f"The VM Instance {instance.name} have the following unencrypted disks: '{', '.join([i[0] for i in instance.disks_encryption if not i[1]])}'",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == instance.id
|
||||
@@ -0,0 +1,146 @@
|
||||
from re import search
|
||||
from unittest import mock
|
||||
|
||||
GCP_PROJECT_ID = "123456789012"
|
||||
|
||||
|
||||
class Test_compute_ip_forwarding_is_enabled:
|
||||
def test_compute_no_instances(self):
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.instances = []
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_ip_forwarding_is_enabled.compute_ip_forwarding_is_enabled.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_ip_forwarding_is_enabled.compute_ip_forwarding_is_enabled import (
|
||||
compute_ip_forwarding_is_enabled,
|
||||
)
|
||||
|
||||
check = compute_ip_forwarding_is_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_one_compliant_instance(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Instance
|
||||
|
||||
instance = Instance(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
zone="us-central1-a",
|
||||
public_ip=True,
|
||||
metadata={},
|
||||
shielded_enabled_vtpm=True,
|
||||
shielded_enabled_integrity_monitoring=True,
|
||||
service_accounts=[{"email": "123-compute@developer.gserviceaccount.com"}],
|
||||
ip_forward=False,
|
||||
disks_encryption=[("disk1", False), ("disk2", False)],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.instances = [instance]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_ip_forwarding_is_enabled.compute_ip_forwarding_is_enabled.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_ip_forwarding_is_enabled.compute_ip_forwarding_is_enabled import (
|
||||
compute_ip_forwarding_is_enabled,
|
||||
)
|
||||
|
||||
check = compute_ip_forwarding_is_enabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
f"The IP Forwarding of VM Instance {instance.name} is not enabled",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == instance.id
|
||||
|
||||
def test_one_compliant_instance_gke(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Instance
|
||||
|
||||
instance = Instance(
|
||||
name="gke-test",
|
||||
id="1234567890",
|
||||
zone="us-central1-a",
|
||||
public_ip=True,
|
||||
metadata={},
|
||||
shielded_enabled_vtpm=True,
|
||||
shielded_enabled_integrity_monitoring=True,
|
||||
service_accounts=[
|
||||
{"email": f"{GCP_PROJECT_ID}-compute@developer.gserviceaccount.com"}
|
||||
],
|
||||
ip_forward=True,
|
||||
disks_encryption=[("disk1", False), ("disk2", False)],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.instances = [instance]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_ip_forwarding_is_enabled.compute_ip_forwarding_is_enabled.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_ip_forwarding_is_enabled.compute_ip_forwarding_is_enabled import (
|
||||
compute_ip_forwarding_is_enabled,
|
||||
)
|
||||
|
||||
check = compute_ip_forwarding_is_enabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
f"The IP Forwarding of VM Instance {instance.name} is not enabled",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == instance.id
|
||||
|
||||
def test_instance_with_ip_forwarding_enabled(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Instance
|
||||
|
||||
instance = Instance(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
zone="us-central1-a",
|
||||
public_ip=True,
|
||||
metadata={},
|
||||
shielded_enabled_vtpm=True,
|
||||
shielded_enabled_integrity_monitoring=True,
|
||||
service_accounts=[
|
||||
{"email": f"{GCP_PROJECT_ID}-compute@developer.gserviceaccount.com"}
|
||||
],
|
||||
ip_forward=True,
|
||||
disks_encryption=[("disk1", False), ("disk2", False)],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.instances = [instance]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_ip_forwarding_is_enabled.compute_ip_forwarding_is_enabled.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_ip_forwarding_is_enabled.compute_ip_forwarding_is_enabled import (
|
||||
compute_ip_forwarding_is_enabled,
|
||||
)
|
||||
|
||||
check = compute_ip_forwarding_is_enabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
f"The IP Forwarding of VM Instance {instance.name} is enabled",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == instance.id
|
||||
@@ -0,0 +1,414 @@
|
||||
from re import search
|
||||
from unittest import mock
|
||||
|
||||
GCP_PROJECT_ID = "123456789012"
|
||||
|
||||
|
||||
class Test_compute_rdp_access_from_the_internet_allowed:
|
||||
def test_compute_no_instances(self):
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.firewalls = []
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_rdp_access_from_the_internet_allowed.compute_rdp_access_from_the_internet_allowed.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_rdp_access_from_the_internet_allowed.compute_rdp_access_from_the_internet_allowed import (
|
||||
compute_rdp_access_from_the_internet_allowed,
|
||||
)
|
||||
|
||||
check = compute_rdp_access_from_the_internet_allowed()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_one_compliant_rule_with_valid_port(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Firewall
|
||||
|
||||
firewall = Firewall(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
source_ranges=["0.0.0.0/0"],
|
||||
direction="INGRESS",
|
||||
allowed_rules=[{"IPProtocol": "tcp", "ports": ["443"]}],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.firewalls = [firewall]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_rdp_access_from_the_internet_allowed.compute_rdp_access_from_the_internet_allowed.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_rdp_access_from_the_internet_allowed.compute_rdp_access_from_the_internet_allowed import (
|
||||
compute_rdp_access_from_the_internet_allowed,
|
||||
)
|
||||
|
||||
check = compute_rdp_access_from_the_internet_allowed()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
f"Firewall {firewall.name} does not expose port 3389",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == firewall.id
|
||||
|
||||
def test_one_compliant_rule_with_valid_port_range(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Firewall
|
||||
|
||||
firewall = Firewall(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
source_ranges=["0.0.0.0/0"],
|
||||
direction="INGRESS",
|
||||
allowed_rules=[{"IPProtocol": "tcp", "ports": ["3300-3380"]}],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.firewalls = [firewall]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_rdp_access_from_the_internet_allowed.compute_rdp_access_from_the_internet_allowed.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_rdp_access_from_the_internet_allowed.compute_rdp_access_from_the_internet_allowed import (
|
||||
compute_rdp_access_from_the_internet_allowed,
|
||||
)
|
||||
|
||||
check = compute_rdp_access_from_the_internet_allowed()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
f"Firewall {firewall.name} does not expose port 3389",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == firewall.id
|
||||
|
||||
def test_one_compliant_rule_with_valid_source_range(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Firewall
|
||||
|
||||
firewall = Firewall(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
source_ranges=["127.0.0.1/32"],
|
||||
direction="INGRESS",
|
||||
allowed_rules=[{"IPProtocol": "tcp", "ports": ["3389"]}],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.firewalls = [firewall]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_rdp_access_from_the_internet_allowed.compute_rdp_access_from_the_internet_allowed.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_rdp_access_from_the_internet_allowed.compute_rdp_access_from_the_internet_allowed import (
|
||||
compute_rdp_access_from_the_internet_allowed,
|
||||
)
|
||||
|
||||
check = compute_rdp_access_from_the_internet_allowed()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
f"Firewall {firewall.name} does not expose port 3389",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == firewall.id
|
||||
|
||||
def test_one_compliant_rule_with_valid_protocol(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Firewall
|
||||
|
||||
firewall = Firewall(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
source_ranges=["0.0.0.0/0"],
|
||||
direction="INGRESS",
|
||||
allowed_rules=[{"IPProtocol": "udp", "ports": ["3389"]}],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.firewalls = [firewall]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_rdp_access_from_the_internet_allowed.compute_rdp_access_from_the_internet_allowed.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_rdp_access_from_the_internet_allowed.compute_rdp_access_from_the_internet_allowed import (
|
||||
compute_rdp_access_from_the_internet_allowed,
|
||||
)
|
||||
|
||||
check = compute_rdp_access_from_the_internet_allowed()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
f"Firewall {firewall.name} does not expose port 3389",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == firewall.id
|
||||
|
||||
def test_one_compliant_rule_with_valid_direction(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Firewall
|
||||
|
||||
firewall = Firewall(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
source_ranges=["0.0.0.0/0"],
|
||||
direction="EGRESS",
|
||||
allowed_rules=[{"IPProtocol": "tcp", "ports": ["3389"]}],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.firewalls = [firewall]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_rdp_access_from_the_internet_allowed.compute_rdp_access_from_the_internet_allowed.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_rdp_access_from_the_internet_allowed.compute_rdp_access_from_the_internet_allowed import (
|
||||
compute_rdp_access_from_the_internet_allowed,
|
||||
)
|
||||
|
||||
check = compute_rdp_access_from_the_internet_allowed()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
f"Firewall {firewall.name} does not expose port 3389",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == firewall.id
|
||||
|
||||
def test_one_non_compliant_rule_with_single_port(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Firewall
|
||||
|
||||
firewall = Firewall(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
source_ranges=["0.0.0.0/0"],
|
||||
direction="INGRESS",
|
||||
allowed_rules=[{"IPProtocol": "tcp", "ports": ["3389"]}],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.firewalls = [firewall]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_rdp_access_from_the_internet_allowed.compute_rdp_access_from_the_internet_allowed.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_rdp_access_from_the_internet_allowed.compute_rdp_access_from_the_internet_allowed import (
|
||||
compute_rdp_access_from_the_internet_allowed,
|
||||
)
|
||||
|
||||
check = compute_rdp_access_from_the_internet_allowed()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
f"Firewall {firewall.name} does exposes port 3389",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == firewall.id
|
||||
|
||||
def test_one_non_compliant_rule_with_port_range(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Firewall
|
||||
|
||||
firewall = Firewall(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
source_ranges=["0.0.0.0/0"],
|
||||
direction="INGRESS",
|
||||
allowed_rules=[{"IPProtocol": "tcp", "ports": ["3380-3390"]}],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.firewalls = [firewall]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_rdp_access_from_the_internet_allowed.compute_rdp_access_from_the_internet_allowed.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_rdp_access_from_the_internet_allowed.compute_rdp_access_from_the_internet_allowed import (
|
||||
compute_rdp_access_from_the_internet_allowed,
|
||||
)
|
||||
|
||||
check = compute_rdp_access_from_the_internet_allowed()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
f"Firewall {firewall.name} does exposes port 3389",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == firewall.id
|
||||
|
||||
def test_one_non_compliant_with_all_ports_allowed(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Firewall
|
||||
|
||||
firewall = Firewall(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
source_ranges=["0.0.0.0/0"],
|
||||
direction="INGRESS",
|
||||
allowed_rules=[{"IPProtocol": "tcp"}],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.firewalls = [firewall]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_rdp_access_from_the_internet_allowed.compute_rdp_access_from_the_internet_allowed.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_rdp_access_from_the_internet_allowed.compute_rdp_access_from_the_internet_allowed import (
|
||||
compute_rdp_access_from_the_internet_allowed,
|
||||
)
|
||||
|
||||
check = compute_rdp_access_from_the_internet_allowed()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
f"Firewall {firewall.name} does exposes port 3389",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == firewall.id
|
||||
|
||||
def test_one_non_compliant_with_all_protocols_allowed(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Firewall
|
||||
|
||||
firewall = Firewall(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
source_ranges=["0.0.0.0/0"],
|
||||
direction="INGRESS",
|
||||
allowed_rules=[{"IPProtocol": "all"}],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.firewalls = [firewall]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_rdp_access_from_the_internet_allowed.compute_rdp_access_from_the_internet_allowed.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_rdp_access_from_the_internet_allowed.compute_rdp_access_from_the_internet_allowed import (
|
||||
compute_rdp_access_from_the_internet_allowed,
|
||||
)
|
||||
|
||||
check = compute_rdp_access_from_the_internet_allowed()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
f"Firewall {firewall.name} does exposes port 3389",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == firewall.id
|
||||
|
||||
def test_one_non_compliant_with_2_rules(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Firewall
|
||||
|
||||
firewall = Firewall(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
source_ranges=["0.0.0.0/0"],
|
||||
direction="INGRESS",
|
||||
allowed_rules=[
|
||||
{"IPProtocol": "udp", "ports": ["3389"]},
|
||||
{"IPProtocol": "all"},
|
||||
],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.firewalls = [firewall]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_rdp_access_from_the_internet_allowed.compute_rdp_access_from_the_internet_allowed.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_rdp_access_from_the_internet_allowed.compute_rdp_access_from_the_internet_allowed import (
|
||||
compute_rdp_access_from_the_internet_allowed,
|
||||
)
|
||||
|
||||
check = compute_rdp_access_from_the_internet_allowed()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
f"Firewall {firewall.name} does exposes port 3389",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == firewall.id
|
||||
|
||||
def test_one_compliant_with_3_rules(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Firewall
|
||||
|
||||
firewall = Firewall(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
source_ranges=["0.0.0.0/0"],
|
||||
direction="INGRESS",
|
||||
allowed_rules=[
|
||||
{"IPProtocol": "udp", "ports": ["3389"]},
|
||||
{"IPProtocol": "tcp", "ports": ["23"]},
|
||||
{"IPProtocol": "udp"},
|
||||
],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.firewalls = [firewall]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_rdp_access_from_the_internet_allowed.compute_rdp_access_from_the_internet_allowed.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_rdp_access_from_the_internet_allowed.compute_rdp_access_from_the_internet_allowed import (
|
||||
compute_rdp_access_from_the_internet_allowed,
|
||||
)
|
||||
|
||||
check = compute_rdp_access_from_the_internet_allowed()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
f"Firewall {firewall.name} does not expose port 3389",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == firewall.id
|
||||
@@ -7,7 +7,7 @@ GCP_PROJECT_ID = "123456789012"
|
||||
class Test_compute_serial_ports_in_use:
|
||||
def test_compute_no_instances(self):
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_id = GCP_PROJECT_ID
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.instances = []
|
||||
|
||||
with mock.patch(
|
||||
@@ -34,11 +34,13 @@ class Test_compute_serial_ports_in_use:
|
||||
shielded_enabled_vtpm=True,
|
||||
shielded_enabled_integrity_monitoring=True,
|
||||
service_accounts=[],
|
||||
ip_forward=False,
|
||||
disks_encryption=[("disk1", False), ("disk2", False)],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_id = GCP_PROJECT_ID
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.instances = [instance]
|
||||
|
||||
with mock.patch(
|
||||
@@ -72,11 +74,13 @@ class Test_compute_serial_ports_in_use:
|
||||
shielded_enabled_vtpm=True,
|
||||
shielded_enabled_integrity_monitoring=True,
|
||||
service_accounts=[],
|
||||
ip_forward=False,
|
||||
disks_encryption=[("disk1", False), ("disk2", False)],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_id = GCP_PROJECT_ID
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.instances = [instance]
|
||||
|
||||
with mock.patch(
|
||||
@@ -110,11 +114,13 @@ class Test_compute_serial_ports_in_use:
|
||||
shielded_enabled_vtpm=True,
|
||||
shielded_enabled_integrity_monitoring=True,
|
||||
service_accounts=[],
|
||||
ip_forward=False,
|
||||
disks_encryption=[("disk1", False), ("disk2", False)],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_id = GCP_PROJECT_ID
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.instances = [instance]
|
||||
|
||||
with mock.patch(
|
||||
@@ -148,11 +154,13 @@ class Test_compute_serial_ports_in_use:
|
||||
shielded_enabled_vtpm=True,
|
||||
shielded_enabled_integrity_monitoring=True,
|
||||
service_accounts=[],
|
||||
ip_forward=False,
|
||||
disks_encryption=[("disk1", False), ("disk2", False)],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_id = GCP_PROJECT_ID
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.instances = [instance]
|
||||
|
||||
with mock.patch(
|
||||
@@ -186,11 +194,13 @@ class Test_compute_serial_ports_in_use:
|
||||
shielded_enabled_vtpm=True,
|
||||
shielded_enabled_integrity_monitoring=True,
|
||||
service_accounts=[],
|
||||
ip_forward=False,
|
||||
disks_encryption=[("disk1", False), ("disk2", False)],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_id = GCP_PROJECT_ID
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.instances = [instance]
|
||||
|
||||
with mock.patch(
|
||||
|
||||
@@ -34,6 +34,8 @@ class Test_compute_shielded_vm_enabled:
|
||||
shielded_enabled_vtpm=True,
|
||||
shielded_enabled_integrity_monitoring=True,
|
||||
service_accounts=[],
|
||||
ip_forward=False,
|
||||
disks_encryption=[("disk1", False), ("disk2", False)],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
@@ -72,6 +74,8 @@ class Test_compute_shielded_vm_enabled:
|
||||
shielded_enabled_vtpm=False,
|
||||
shielded_enabled_integrity_monitoring=True,
|
||||
service_accounts=[],
|
||||
ip_forward=False,
|
||||
disks_encryption=[("disk1", False), ("disk2", False)],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
@@ -110,6 +114,8 @@ class Test_compute_shielded_vm_enabled:
|
||||
shielded_enabled_vtpm=True,
|
||||
shielded_enabled_integrity_monitoring=False,
|
||||
service_accounts=[],
|
||||
ip_forward=False,
|
||||
disks_encryption=[("disk1", False), ("disk2", False)],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,414 @@
|
||||
from re import search
|
||||
from unittest import mock
|
||||
|
||||
GCP_PROJECT_ID = "123456789012"
|
||||
|
||||
|
||||
class Test_compute_ssh_access_from_the_internet_allowed:
|
||||
def test_compute_no_instances(self):
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.firewalls = []
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_ssh_access_from_the_internet_allowed.compute_ssh_access_from_the_internet_allowed.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_ssh_access_from_the_internet_allowed.compute_ssh_access_from_the_internet_allowed import (
|
||||
compute_ssh_access_from_the_internet_allowed,
|
||||
)
|
||||
|
||||
check = compute_ssh_access_from_the_internet_allowed()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_one_compliant_rule_with_valid_port(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Firewall
|
||||
|
||||
firewall = Firewall(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
source_ranges=["0.0.0.0/0"],
|
||||
direction="INGRESS",
|
||||
allowed_rules=[{"IPProtocol": "tcp", "ports": ["443"]}],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.firewalls = [firewall]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_ssh_access_from_the_internet_allowed.compute_ssh_access_from_the_internet_allowed.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_ssh_access_from_the_internet_allowed.compute_ssh_access_from_the_internet_allowed import (
|
||||
compute_ssh_access_from_the_internet_allowed,
|
||||
)
|
||||
|
||||
check = compute_ssh_access_from_the_internet_allowed()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
f"Firewall {firewall.name} does not expose port 22",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == firewall.id
|
||||
|
||||
def test_one_compliant_rule_with_valid_port_range(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Firewall
|
||||
|
||||
firewall = Firewall(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
source_ranges=["0.0.0.0/0"],
|
||||
direction="INGRESS",
|
||||
allowed_rules=[{"IPProtocol": "tcp", "ports": ["1-20"]}],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.firewalls = [firewall]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_ssh_access_from_the_internet_allowed.compute_ssh_access_from_the_internet_allowed.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_ssh_access_from_the_internet_allowed.compute_ssh_access_from_the_internet_allowed import (
|
||||
compute_ssh_access_from_the_internet_allowed,
|
||||
)
|
||||
|
||||
check = compute_ssh_access_from_the_internet_allowed()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
f"Firewall {firewall.name} does not expose port 22",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == firewall.id
|
||||
|
||||
def test_one_compliant_rule_with_valid_source_range(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Firewall
|
||||
|
||||
firewall = Firewall(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
source_ranges=["127.0.0.1/32"],
|
||||
direction="INGRESS",
|
||||
allowed_rules=[{"IPProtocol": "tcp", "ports": ["22"]}],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.firewalls = [firewall]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_ssh_access_from_the_internet_allowed.compute_ssh_access_from_the_internet_allowed.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_ssh_access_from_the_internet_allowed.compute_ssh_access_from_the_internet_allowed import (
|
||||
compute_ssh_access_from_the_internet_allowed,
|
||||
)
|
||||
|
||||
check = compute_ssh_access_from_the_internet_allowed()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
f"Firewall {firewall.name} does not expose port 22",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == firewall.id
|
||||
|
||||
def test_one_compliant_rule_with_valid_protocol(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Firewall
|
||||
|
||||
firewall = Firewall(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
source_ranges=["0.0.0.0/0"],
|
||||
direction="INGRESS",
|
||||
allowed_rules=[{"IPProtocol": "udp", "ports": ["22"]}],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.firewalls = [firewall]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_ssh_access_from_the_internet_allowed.compute_ssh_access_from_the_internet_allowed.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_ssh_access_from_the_internet_allowed.compute_ssh_access_from_the_internet_allowed import (
|
||||
compute_ssh_access_from_the_internet_allowed,
|
||||
)
|
||||
|
||||
check = compute_ssh_access_from_the_internet_allowed()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
f"Firewall {firewall.name} does not expose port 22",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == firewall.id
|
||||
|
||||
def test_one_compliant_rule_with_valid_direction(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Firewall
|
||||
|
||||
firewall = Firewall(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
source_ranges=["0.0.0.0/0"],
|
||||
direction="EGRESS",
|
||||
allowed_rules=[{"IPProtocol": "tcp", "ports": ["22"]}],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.firewalls = [firewall]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_ssh_access_from_the_internet_allowed.compute_ssh_access_from_the_internet_allowed.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_ssh_access_from_the_internet_allowed.compute_ssh_access_from_the_internet_allowed import (
|
||||
compute_ssh_access_from_the_internet_allowed,
|
||||
)
|
||||
|
||||
check = compute_ssh_access_from_the_internet_allowed()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
f"Firewall {firewall.name} does not expose port 22",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == firewall.id
|
||||
|
||||
def test_one_non_compliant_rule_with_single_port(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Firewall
|
||||
|
||||
firewall = Firewall(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
source_ranges=["0.0.0.0/0"],
|
||||
direction="INGRESS",
|
||||
allowed_rules=[{"IPProtocol": "tcp", "ports": ["22"]}],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.firewalls = [firewall]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_ssh_access_from_the_internet_allowed.compute_ssh_access_from_the_internet_allowed.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_ssh_access_from_the_internet_allowed.compute_ssh_access_from_the_internet_allowed import (
|
||||
compute_ssh_access_from_the_internet_allowed,
|
||||
)
|
||||
|
||||
check = compute_ssh_access_from_the_internet_allowed()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
f"Firewall {firewall.name} does exposes port 22",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == firewall.id
|
||||
|
||||
def test_one_non_compliant_rule_with_port_range(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Firewall
|
||||
|
||||
firewall = Firewall(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
source_ranges=["0.0.0.0/0"],
|
||||
direction="INGRESS",
|
||||
allowed_rules=[{"IPProtocol": "tcp", "ports": ["20-443"]}],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.firewalls = [firewall]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_ssh_access_from_the_internet_allowed.compute_ssh_access_from_the_internet_allowed.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_ssh_access_from_the_internet_allowed.compute_ssh_access_from_the_internet_allowed import (
|
||||
compute_ssh_access_from_the_internet_allowed,
|
||||
)
|
||||
|
||||
check = compute_ssh_access_from_the_internet_allowed()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
f"Firewall {firewall.name} does exposes port 22",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == firewall.id
|
||||
|
||||
def test_one_non_compliant_with_all_ports_allowed(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Firewall
|
||||
|
||||
firewall = Firewall(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
source_ranges=["0.0.0.0/0"],
|
||||
direction="INGRESS",
|
||||
allowed_rules=[{"IPProtocol": "tcp"}],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.firewalls = [firewall]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_ssh_access_from_the_internet_allowed.compute_ssh_access_from_the_internet_allowed.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_ssh_access_from_the_internet_allowed.compute_ssh_access_from_the_internet_allowed import (
|
||||
compute_ssh_access_from_the_internet_allowed,
|
||||
)
|
||||
|
||||
check = compute_ssh_access_from_the_internet_allowed()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
f"Firewall {firewall.name} does exposes port 22",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == firewall.id
|
||||
|
||||
def test_one_non_compliant_with_all_protocols_allowed(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Firewall
|
||||
|
||||
firewall = Firewall(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
source_ranges=["0.0.0.0/0"],
|
||||
direction="INGRESS",
|
||||
allowed_rules=[{"IPProtocol": "all"}],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.firewalls = [firewall]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_ssh_access_from_the_internet_allowed.compute_ssh_access_from_the_internet_allowed.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_ssh_access_from_the_internet_allowed.compute_ssh_access_from_the_internet_allowed import (
|
||||
compute_ssh_access_from_the_internet_allowed,
|
||||
)
|
||||
|
||||
check = compute_ssh_access_from_the_internet_allowed()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
f"Firewall {firewall.name} does exposes port 22",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == firewall.id
|
||||
|
||||
def test_one_non_compliant_with_2_rules(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Firewall
|
||||
|
||||
firewall = Firewall(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
source_ranges=["0.0.0.0/0"],
|
||||
direction="INGRESS",
|
||||
allowed_rules=[
|
||||
{"IPProtocol": "udp", "ports": ["22"]},
|
||||
{"IPProtocol": "all"},
|
||||
],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.firewalls = [firewall]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_ssh_access_from_the_internet_allowed.compute_ssh_access_from_the_internet_allowed.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_ssh_access_from_the_internet_allowed.compute_ssh_access_from_the_internet_allowed import (
|
||||
compute_ssh_access_from_the_internet_allowed,
|
||||
)
|
||||
|
||||
check = compute_ssh_access_from_the_internet_allowed()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
f"Firewall {firewall.name} does exposes port 22",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == firewall.id
|
||||
|
||||
def test_one_compliant_with_3_rules(self):
|
||||
from prowler.providers.gcp.services.compute.compute_service import Firewall
|
||||
|
||||
firewall = Firewall(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
source_ranges=["0.0.0.0/0"],
|
||||
direction="INGRESS",
|
||||
allowed_rules=[
|
||||
{"IPProtocol": "udp", "ports": ["22"]},
|
||||
{"IPProtocol": "tcp", "ports": ["23"]},
|
||||
{"IPProtocol": "udp"},
|
||||
],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
compute_client = mock.MagicMock
|
||||
compute_client.project_ids = [GCP_PROJECT_ID]
|
||||
compute_client.firewalls = [firewall]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.compute.compute_ssh_access_from_the_internet_allowed.compute_ssh_access_from_the_internet_allowed.compute_client",
|
||||
new=compute_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.compute.compute_ssh_access_from_the_internet_allowed.compute_ssh_access_from_the_internet_allowed import (
|
||||
compute_ssh_access_from_the_internet_allowed,
|
||||
)
|
||||
|
||||
check = compute_ssh_access_from_the_internet_allowed()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
f"Firewall {firewall.name} does not expose port 22",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == firewall.id
|
||||
@@ -0,0 +1,88 @@
|
||||
from re import search
|
||||
from unittest import mock
|
||||
|
||||
GCP_PROJECT_ID = "123456789012"
|
||||
|
||||
|
||||
class Test_dataproc_encrypted_with_cmks_disabled:
|
||||
def test_dataproc_no_clsuters(self):
|
||||
dataproc_client = mock.MagicMock
|
||||
dataproc_client.clusters = []
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.dataproc.dataproc_encrypted_with_cmks_disabled.dataproc_encrypted_with_cmks_disabled.dataproc_client",
|
||||
new=dataproc_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.dataproc.dataproc_encrypted_with_cmks_disabled.dataproc_encrypted_with_cmks_disabled import (
|
||||
dataproc_encrypted_with_cmks_disabled,
|
||||
)
|
||||
|
||||
check = dataproc_encrypted_with_cmks_disabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_one_compliant_cluster(self):
|
||||
from prowler.providers.gcp.services.dataproc.dataproc_service import Cluster
|
||||
|
||||
cluster = Cluster(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
encryption_config={"gcePdKmsKeyName": "test"},
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
dataproc_client = mock.MagicMock
|
||||
dataproc_client.project_ids = [GCP_PROJECT_ID]
|
||||
dataproc_client.clusters = [cluster]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.dataproc.dataproc_encrypted_with_cmks_disabled.dataproc_encrypted_with_cmks_disabled.dataproc_client",
|
||||
new=dataproc_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.dataproc.dataproc_encrypted_with_cmks_disabled.dataproc_encrypted_with_cmks_disabled import (
|
||||
dataproc_encrypted_with_cmks_disabled,
|
||||
)
|
||||
|
||||
check = dataproc_encrypted_with_cmks_disabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
f"Dataproc cluster {cluster.name} is encrypted with customer managed encryption keys.",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == cluster.id
|
||||
|
||||
def test_cluster_without_encryption(self):
|
||||
from prowler.providers.gcp.services.dataproc.dataproc_service import Cluster
|
||||
|
||||
cluster = Cluster(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
encryption_config={},
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
dataproc_client = mock.MagicMock
|
||||
dataproc_client.project_ids = [GCP_PROJECT_ID]
|
||||
dataproc_client.clusters = [cluster]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.dataproc.dataproc_encrypted_with_cmks_disabled.dataproc_encrypted_with_cmks_disabled.dataproc_client",
|
||||
new=dataproc_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.dataproc.dataproc_encrypted_with_cmks_disabled.dataproc_encrypted_with_cmks_disabled import (
|
||||
dataproc_encrypted_with_cmks_disabled,
|
||||
)
|
||||
|
||||
check = dataproc_encrypted_with_cmks_disabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
f"Dataproc cluster {cluster.name} is not encrypted with customer managed encryption keys.",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == cluster.id
|
||||
@@ -0,0 +1,116 @@
|
||||
from re import search
|
||||
from unittest import mock
|
||||
|
||||
GCP_PROJECT_ID = "123456789012"
|
||||
|
||||
|
||||
class Test_dns_dnssec_disabled:
|
||||
def test_dns_no_managed_zones(self):
|
||||
dns_client = mock.MagicMock
|
||||
dns_client.managed_zones = []
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.dns.dns_dnssec_disabled.dns_dnssec_disabled.dns_client",
|
||||
new=dns_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.dns.dns_dnssec_disabled.dns_dnssec_disabled import (
|
||||
dns_dnssec_disabled,
|
||||
)
|
||||
|
||||
check = dns_dnssec_disabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_one_compliant_managed_zone(self):
|
||||
from prowler.providers.gcp.services.dns.dns_service import ManagedZone
|
||||
|
||||
managed_zone = ManagedZone(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
dnssec=True,
|
||||
key_specs=[
|
||||
{
|
||||
"keyType": "keySigning",
|
||||
"algorithm": "rsasha1",
|
||||
"keyLength": 2048,
|
||||
"kind": "dns#dnsKeySpec",
|
||||
},
|
||||
{
|
||||
"keyType": "zoneSigning",
|
||||
"algorithm": "rsasha1",
|
||||
"keyLength": 1024,
|
||||
"kind": "dns#dnsKeySpec",
|
||||
},
|
||||
],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
dns_client = mock.MagicMock
|
||||
dns_client.project_ids = [GCP_PROJECT_ID]
|
||||
dns_client.managed_zones = [managed_zone]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.dns.dns_dnssec_disabled.dns_dnssec_disabled.dns_client",
|
||||
new=dns_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.dns.dns_dnssec_disabled.dns_dnssec_disabled import (
|
||||
dns_dnssec_disabled,
|
||||
)
|
||||
|
||||
check = dns_dnssec_disabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
f"Cloud DNS {managed_zone.name} have DNSSEC enabled.",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == managed_zone.id
|
||||
|
||||
def test_managed_zone_with_dnssec_disabled(self):
|
||||
from prowler.providers.gcp.services.dns.dns_service import ManagedZone
|
||||
|
||||
managed_zone = ManagedZone(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
dnssec=False,
|
||||
key_specs=[
|
||||
{
|
||||
"keyType": "keySigning",
|
||||
"algorithm": "rsasha1",
|
||||
"keyLength": 2048,
|
||||
"kind": "dns#dnsKeySpec",
|
||||
},
|
||||
{
|
||||
"keyType": "zoneSigning",
|
||||
"algorithm": "rsasha1",
|
||||
"keyLength": 1024,
|
||||
"kind": "dns#dnsKeySpec",
|
||||
},
|
||||
],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
dns_client = mock.MagicMock
|
||||
dns_client.project_ids = [GCP_PROJECT_ID]
|
||||
dns_client.managed_zones = [managed_zone]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.dns.dns_dnssec_disabled.dns_dnssec_disabled.dns_client",
|
||||
new=dns_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.dns.dns_dnssec_disabled.dns_dnssec_disabled import (
|
||||
dns_dnssec_disabled,
|
||||
)
|
||||
|
||||
check = dns_dnssec_disabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
f"Cloud DNS {managed_zone.name} doens't have DNSSEC enabled.",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == managed_zone.id
|
||||
@@ -0,0 +1,116 @@
|
||||
from re import search
|
||||
from unittest import mock
|
||||
|
||||
GCP_PROJECT_ID = "123456789012"
|
||||
|
||||
|
||||
class Test_dns_rsasha1_in_use_to_key_sign_in_dnssec:
|
||||
def test_dns_no_managed_zones(self):
|
||||
dns_client = mock.MagicMock
|
||||
dns_client.managed_zones = []
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.dns.dns_rsasha1_in_use_to_key_sign_in_dnssec.dns_rsasha1_in_use_to_key_sign_in_dnssec.dns_client",
|
||||
new=dns_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.dns.dns_rsasha1_in_use_to_key_sign_in_dnssec.dns_rsasha1_in_use_to_key_sign_in_dnssec import (
|
||||
dns_rsasha1_in_use_to_key_sign_in_dnssec,
|
||||
)
|
||||
|
||||
check = dns_rsasha1_in_use_to_key_sign_in_dnssec()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_one_compliant_managed_zone(self):
|
||||
from prowler.providers.gcp.services.dns.dns_service import ManagedZone
|
||||
|
||||
managed_zone = ManagedZone(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
dnssec=True,
|
||||
key_specs=[
|
||||
{
|
||||
"keyType": "keySigning",
|
||||
"algorithm": "rsasha256",
|
||||
"keyLength": 2048,
|
||||
"kind": "dns#dnsKeySpec",
|
||||
},
|
||||
{
|
||||
"keyType": "zoneSigning",
|
||||
"algorithm": "rsasha1",
|
||||
"keyLength": 1024,
|
||||
"kind": "dns#dnsKeySpec",
|
||||
},
|
||||
],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
dns_client = mock.MagicMock
|
||||
dns_client.project_ids = [GCP_PROJECT_ID]
|
||||
dns_client.managed_zones = [managed_zone]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.dns.dns_rsasha1_in_use_to_key_sign_in_dnssec.dns_rsasha1_in_use_to_key_sign_in_dnssec.dns_client",
|
||||
new=dns_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.dns.dns_rsasha1_in_use_to_key_sign_in_dnssec.dns_rsasha1_in_use_to_key_sign_in_dnssec import (
|
||||
dns_rsasha1_in_use_to_key_sign_in_dnssec,
|
||||
)
|
||||
|
||||
check = dns_rsasha1_in_use_to_key_sign_in_dnssec()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
f"Cloud DNS {managed_zone.name} is not using RSASHA1 algorithm as key signing.",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == managed_zone.id
|
||||
|
||||
def test_managed_zone_with_rsasha1_key_sign(self):
|
||||
from prowler.providers.gcp.services.dns.dns_service import ManagedZone
|
||||
|
||||
managed_zone = ManagedZone(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
dnssec=False,
|
||||
key_specs=[
|
||||
{
|
||||
"keyType": "keySigning",
|
||||
"algorithm": "rsasha1",
|
||||
"keyLength": 2048,
|
||||
"kind": "dns#dnsKeySpec",
|
||||
},
|
||||
{
|
||||
"keyType": "zoneSigning",
|
||||
"algorithm": "rsasha256",
|
||||
"keyLength": 1024,
|
||||
"kind": "dns#dnsKeySpec",
|
||||
},
|
||||
],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
dns_client = mock.MagicMock
|
||||
dns_client.project_ids = [GCP_PROJECT_ID]
|
||||
dns_client.managed_zones = [managed_zone]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.dns.dns_rsasha1_in_use_to_key_sign_in_dnssec.dns_rsasha1_in_use_to_key_sign_in_dnssec.dns_client",
|
||||
new=dns_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.dns.dns_rsasha1_in_use_to_key_sign_in_dnssec.dns_rsasha1_in_use_to_key_sign_in_dnssec import (
|
||||
dns_rsasha1_in_use_to_key_sign_in_dnssec,
|
||||
)
|
||||
|
||||
check = dns_rsasha1_in_use_to_key_sign_in_dnssec()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
f"Cloud DNS {managed_zone.name} is using RSASHA1 algorithm as key signing.",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == managed_zone.id
|
||||
@@ -0,0 +1,116 @@
|
||||
from re import search
|
||||
from unittest import mock
|
||||
|
||||
GCP_PROJECT_ID = "123456789012"
|
||||
|
||||
|
||||
class Test_dns_rsasha1_in_use_to_zone_sign_in_dnssec:
|
||||
def test_dns_no_managed_zones(self):
|
||||
dns_client = mock.MagicMock
|
||||
dns_client.managed_zones = []
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.dns.dns_rsasha1_in_use_to_zone_sign_in_dnssec.dns_rsasha1_in_use_to_zone_sign_in_dnssec.dns_client",
|
||||
new=dns_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.dns.dns_rsasha1_in_use_to_zone_sign_in_dnssec.dns_rsasha1_in_use_to_zone_sign_in_dnssec import (
|
||||
dns_rsasha1_in_use_to_zone_sign_in_dnssec,
|
||||
)
|
||||
|
||||
check = dns_rsasha1_in_use_to_zone_sign_in_dnssec()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_one_compliant_managed_zone(self):
|
||||
from prowler.providers.gcp.services.dns.dns_service import ManagedZone
|
||||
|
||||
managed_zone = ManagedZone(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
dnssec=True,
|
||||
key_specs=[
|
||||
{
|
||||
"keyType": "keySigning",
|
||||
"algorithm": "rsasha1",
|
||||
"keyLength": 2048,
|
||||
"kind": "dns#dnsKeySpec",
|
||||
},
|
||||
{
|
||||
"keyType": "zoneSigning",
|
||||
"algorithm": "rsasha256",
|
||||
"keyLength": 1024,
|
||||
"kind": "dns#dnsKeySpec",
|
||||
},
|
||||
],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
dns_client = mock.MagicMock
|
||||
dns_client.project_ids = [GCP_PROJECT_ID]
|
||||
dns_client.managed_zones = [managed_zone]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.dns.dns_rsasha1_in_use_to_zone_sign_in_dnssec.dns_rsasha1_in_use_to_zone_sign_in_dnssec.dns_client",
|
||||
new=dns_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.dns.dns_rsasha1_in_use_to_zone_sign_in_dnssec.dns_rsasha1_in_use_to_zone_sign_in_dnssec import (
|
||||
dns_rsasha1_in_use_to_zone_sign_in_dnssec,
|
||||
)
|
||||
|
||||
check = dns_rsasha1_in_use_to_zone_sign_in_dnssec()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
f"Cloud DNS {managed_zone.name} is not using RSASHA1 algorithm as zone signing.",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == managed_zone.id
|
||||
|
||||
def test_managed_zone_with_dnssec_disabled(self):
|
||||
from prowler.providers.gcp.services.dns.dns_service import ManagedZone
|
||||
|
||||
managed_zone = ManagedZone(
|
||||
name="test",
|
||||
id="1234567890",
|
||||
dnssec=False,
|
||||
key_specs=[
|
||||
{
|
||||
"keyType": "keySigning",
|
||||
"algorithm": "rsasha256",
|
||||
"keyLength": 2048,
|
||||
"kind": "dns#dnsKeySpec",
|
||||
},
|
||||
{
|
||||
"keyType": "zoneSigning",
|
||||
"algorithm": "rsasha1",
|
||||
"keyLength": 1024,
|
||||
"kind": "dns#dnsKeySpec",
|
||||
},
|
||||
],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
dns_client = mock.MagicMock
|
||||
dns_client.project_ids = [GCP_PROJECT_ID]
|
||||
dns_client.managed_zones = [managed_zone]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.dns.dns_rsasha1_in_use_to_zone_sign_in_dnssec.dns_rsasha1_in_use_to_zone_sign_in_dnssec.dns_client",
|
||||
new=dns_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.dns.dns_rsasha1_in_use_to_zone_sign_in_dnssec.dns_rsasha1_in_use_to_zone_sign_in_dnssec import (
|
||||
dns_rsasha1_in_use_to_zone_sign_in_dnssec,
|
||||
)
|
||||
|
||||
check = dns_rsasha1_in_use_to_zone_sign_in_dnssec()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
f"Cloud DNS {managed_zone.name} is using RSASHA1 algorithm as zone signing.",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == managed_zone.id
|
||||
@@ -0,0 +1,141 @@
|
||||
from re import search
|
||||
from unittest import mock
|
||||
|
||||
GCP_PROJECT_ID = "123456789012"
|
||||
|
||||
|
||||
class Test_iam_no_service_roles_at_project_level:
|
||||
def test_iam_no_bindings(self):
|
||||
cloudresourcemanager_client = mock.MagicMock
|
||||
cloudresourcemanager_client.bindings = []
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.iam.iam_no_service_roles_at_project_level.iam_no_service_roles_at_project_level.cloudresourcemanager_client",
|
||||
new=cloudresourcemanager_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.iam.iam_no_service_roles_at_project_level.iam_no_service_roles_at_project_level import (
|
||||
iam_no_service_roles_at_project_level,
|
||||
)
|
||||
|
||||
check = iam_no_service_roles_at_project_level()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
"No IAM Users assigned to service roles at project level",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == GCP_PROJECT_ID
|
||||
|
||||
def test_three_compliant_binding(self):
|
||||
from prowler.providers.gcp.services.cloudresourcemanager.cloudresourcemanager_service import (
|
||||
Binding,
|
||||
)
|
||||
|
||||
binding1 = Binding(
|
||||
role="roles/cloudfunctions.serviceAgent",
|
||||
members=[["serviceAccount:685829395199@cloudbuild.gserviceaccount.com"]],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
binding2 = Binding(
|
||||
role="roles/compute.serviceAgent",
|
||||
members=[["serviceAccount:685829395199@cloudbuild.gserviceaccount.com"]],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
binding3 = Binding(
|
||||
role="roles/connectors.managedZoneViewer",
|
||||
members=[["serviceAccount:685829395199@cloudbuild.gserviceaccount.com"]],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
cloudresourcemanager_client = mock.MagicMock
|
||||
cloudresourcemanager_client.project_ids = [GCP_PROJECT_ID]
|
||||
cloudresourcemanager_client.bindings = [binding1, binding2, binding3]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.iam.iam_no_service_roles_at_project_level.iam_no_service_roles_at_project_level.cloudresourcemanager_client",
|
||||
new=cloudresourcemanager_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.iam.iam_no_service_roles_at_project_level.iam_no_service_roles_at_project_level import (
|
||||
iam_no_service_roles_at_project_level,
|
||||
)
|
||||
|
||||
check = iam_no_service_roles_at_project_level()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
for idx, r in enumerate(result):
|
||||
assert r.status == "PASS"
|
||||
assert search(
|
||||
"No IAM Users assigned to service roles at project level",
|
||||
r.status_extended,
|
||||
)
|
||||
assert r.resource_id == GCP_PROJECT_ID
|
||||
|
||||
def test_binding_with_service_account_user(self):
|
||||
from prowler.providers.gcp.services.cloudresourcemanager.cloudresourcemanager_service import (
|
||||
Binding,
|
||||
)
|
||||
|
||||
binding = Binding(
|
||||
role="roles/iam.serviceAccountUser",
|
||||
members=[["serviceAccount:685829395199@cloudbuild.gserviceaccount.com"]],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
cloudresourcemanager_client = mock.MagicMock
|
||||
cloudresourcemanager_client.project_ids = [GCP_PROJECT_ID]
|
||||
cloudresourcemanager_client.bindings = [binding]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.iam.iam_no_service_roles_at_project_level.iam_no_service_roles_at_project_level.cloudresourcemanager_client",
|
||||
new=cloudresourcemanager_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.iam.iam_no_service_roles_at_project_level.iam_no_service_roles_at_project_level import (
|
||||
iam_no_service_roles_at_project_level,
|
||||
)
|
||||
|
||||
check = iam_no_service_roles_at_project_level()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
f"IAM Users assigned to service role '{binding.role}' at project level",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == binding.role
|
||||
|
||||
def test_binding_with_service_account_token_creator(self):
|
||||
from prowler.providers.gcp.services.cloudresourcemanager.cloudresourcemanager_service import (
|
||||
Binding,
|
||||
)
|
||||
|
||||
binding = Binding(
|
||||
role="roles/iam.serviceAccountTokenCreator",
|
||||
members=[["serviceAccount:685829395199@cloudbuild.gserviceaccount.com"]],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
cloudresourcemanager_client = mock.MagicMock
|
||||
cloudresourcemanager_client.project_ids = [GCP_PROJECT_ID]
|
||||
cloudresourcemanager_client.bindings = [binding]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.gcp.services.iam.iam_no_service_roles_at_project_level.iam_no_service_roles_at_project_level.cloudresourcemanager_client",
|
||||
new=cloudresourcemanager_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.iam.iam_no_service_roles_at_project_level.iam_no_service_roles_at_project_level import (
|
||||
iam_no_service_roles_at_project_level,
|
||||
)
|
||||
|
||||
check = iam_no_service_roles_at_project_level()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
f"IAM Users assigned to service role '{binding.role}' at project level {GCP_PROJECT_ID}",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == binding.role
|
||||
Reference in New Issue
Block a user