mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-30 07:14:20 +00:00
merge branch 'master' into PRWLR-5093-design-the-fixer-class
This commit is contained in:
commit
c5cf1c4bfb
540 files changed
+26179
-6626
No files matched your search
@@ -180,6 +180,9 @@ class TestCheckLoader:
|
||||
def test_load_checks_to_execute_with_compliance_frameworks(
|
||||
self,
|
||||
):
|
||||
bulk_checks_metatada = {
|
||||
S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata()
|
||||
}
|
||||
bulk_compliance_frameworks = {
|
||||
"soc2_aws": Compliance(
|
||||
Framework="SOC2",
|
||||
@@ -199,6 +202,7 @@ class TestCheckLoader:
|
||||
compliance_frameworks = ["soc2_aws"]
|
||||
|
||||
assert {S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME} == load_checks_to_execute(
|
||||
bulk_checks_metadata=bulk_checks_metatada,
|
||||
bulk_compliance_frameworks=bulk_compliance_frameworks,
|
||||
compliance_frameworks=compliance_frameworks,
|
||||
provider=self.provider,
|
||||
|
||||
@@ -94,6 +94,93 @@ custom_compliance_metadata = {
|
||||
)
|
||||
],
|
||||
),
|
||||
"framework1_gcp": Compliance(
|
||||
Framework="Framework1",
|
||||
Provider="gcp",
|
||||
Version="1.0",
|
||||
Description="Framework 2 Description",
|
||||
Requirements=[
|
||||
Compliance_Requirement(
|
||||
Id="1.1.1",
|
||||
Description="description",
|
||||
Attributes=[
|
||||
CIS_Requirement_Attribute(
|
||||
Section="1. Identity",
|
||||
Profile=CIS_Requirement_Attribute_Profile("Level 1"),
|
||||
AssessmentStatus=CIS_Requirement_Attribute_AssessmentStatus(
|
||||
"Manual"
|
||||
),
|
||||
Description="Description",
|
||||
RationaleStatement="Rationale",
|
||||
ImpactStatement="Impact",
|
||||
RemediationProcedure="Remediation",
|
||||
AuditProcedure="Audit",
|
||||
AdditionalInformation="Additional",
|
||||
References="References",
|
||||
)
|
||||
],
|
||||
Checks=[],
|
||||
)
|
||||
],
|
||||
),
|
||||
"framework1_k8s": Compliance(
|
||||
Framework="Framework1",
|
||||
Provider="Kubernetes",
|
||||
Version="1.0",
|
||||
Description="Framework 2 Description",
|
||||
Requirements=[
|
||||
Compliance_Requirement(
|
||||
Id="1.1.1",
|
||||
Description="description",
|
||||
Attributes=[
|
||||
CIS_Requirement_Attribute(
|
||||
Section="1. Identity",
|
||||
Profile=CIS_Requirement_Attribute_Profile("Level 1"),
|
||||
AssessmentStatus=CIS_Requirement_Attribute_AssessmentStatus(
|
||||
"Manual"
|
||||
),
|
||||
Description="Description",
|
||||
RationaleStatement="Rationale",
|
||||
ImpactStatement="Impact",
|
||||
RemediationProcedure="Remediation",
|
||||
AuditProcedure="Audit",
|
||||
AdditionalInformation="Additional",
|
||||
References="References",
|
||||
)
|
||||
],
|
||||
Checks=[],
|
||||
)
|
||||
],
|
||||
),
|
||||
"framework1_m365": Compliance(
|
||||
Framework="Framework1",
|
||||
Provider="m365",
|
||||
Version="1.0",
|
||||
Description="Framework 2 Description",
|
||||
Requirements=[
|
||||
Compliance_Requirement(
|
||||
Id="1.1.1",
|
||||
Description="description",
|
||||
Attributes=[
|
||||
CIS_Requirement_Attribute(
|
||||
Section="1. Identity",
|
||||
Profile=CIS_Requirement_Attribute_Profile("E3 Level 1"),
|
||||
AssessmentStatus=CIS_Requirement_Attribute_AssessmentStatus(
|
||||
"Manual"
|
||||
),
|
||||
Description="Description",
|
||||
RationaleStatement="Rationale",
|
||||
ImpactStatement="Impact",
|
||||
RemediationProcedure="Remediation",
|
||||
AuditProcedure="Audit",
|
||||
AdditionalInformation="Additional",
|
||||
References="References",
|
||||
)
|
||||
],
|
||||
Checks=[],
|
||||
)
|
||||
],
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -209,9 +296,12 @@ class TestCompliance:
|
||||
|
||||
list_compliance = Compliance.list(bulk_compliance_frameworks)
|
||||
|
||||
assert len(list_compliance) == 2
|
||||
assert len(list_compliance) == 5
|
||||
assert list_compliance[0] == "framework1_aws"
|
||||
assert list_compliance[1] == "framework1_azure"
|
||||
assert list_compliance[2] == "framework1_gcp"
|
||||
assert list_compliance[3] == "framework1_k8s"
|
||||
assert list_compliance[4] == "framework1_m365"
|
||||
|
||||
def test_list_with_provider_aws(self):
|
||||
bulk_compliance_frameworks = custom_compliance_metadata
|
||||
@@ -229,6 +319,30 @@ class TestCompliance:
|
||||
assert len(list_compliance) == 1
|
||||
assert list_compliance[0] == "framework1_azure"
|
||||
|
||||
def test_list_with_provider_gcp(self):
|
||||
bulk_compliance_frameworks = custom_compliance_metadata
|
||||
|
||||
list_compliance = Compliance.list(bulk_compliance_frameworks, provider="gcp")
|
||||
|
||||
assert len(list_compliance) == 1
|
||||
assert list_compliance[0] == "framework1_gcp"
|
||||
|
||||
def test_list_with_provider_k8s(self):
|
||||
bulk_compliance_frameworks = custom_compliance_metadata
|
||||
|
||||
list_compliance = Compliance.list(bulk_compliance_frameworks, provider="k8s")
|
||||
|
||||
assert len(list_compliance) == 1
|
||||
assert list_compliance[0] == "framework1_k8s"
|
||||
|
||||
def test_list_with_provider_m365(self):
|
||||
bulk_compliance_frameworks = custom_compliance_metadata
|
||||
|
||||
list_compliance = Compliance.list(bulk_compliance_frameworks, provider="m365")
|
||||
|
||||
assert len(list_compliance) == 1
|
||||
assert list_compliance[0] == "framework1_m365"
|
||||
|
||||
def test_get_compliance_frameworks(self):
|
||||
bulk_compliance_frameworks = custom_compliance_metadata
|
||||
|
||||
@@ -252,6 +366,76 @@ class TestCompliance:
|
||||
assert compliance_framework.Description == "Framework 2 Description"
|
||||
assert len(compliance_framework.Requirements) == 1
|
||||
|
||||
compliance_framework = Compliance.get(
|
||||
bulk_compliance_frameworks, compliance_framework_name="framework1_gcp"
|
||||
)
|
||||
|
||||
assert compliance_framework.Framework == "Framework1"
|
||||
assert compliance_framework.Provider == "gcp"
|
||||
assert compliance_framework.Version == "1.0"
|
||||
assert compliance_framework.Description == "Framework 2 Description"
|
||||
assert len(compliance_framework.Requirements) == 1
|
||||
|
||||
compliance_framework = Compliance.get(
|
||||
bulk_compliance_frameworks, compliance_framework_name="framework1_k8s"
|
||||
)
|
||||
|
||||
assert compliance_framework.Framework == "Framework1"
|
||||
assert compliance_framework.Provider == "Kubernetes"
|
||||
assert compliance_framework.Version == "1.0"
|
||||
assert compliance_framework.Description == "Framework 2 Description"
|
||||
assert len(compliance_framework.Requirements) == 1
|
||||
|
||||
compliance_framework = Compliance.get(
|
||||
bulk_compliance_frameworks, compliance_framework_name="framework1_m365"
|
||||
)
|
||||
|
||||
assert compliance_framework.Framework == "Framework1"
|
||||
assert compliance_framework.Provider == "m365"
|
||||
assert compliance_framework.Version == "1.0"
|
||||
assert compliance_framework.Description == "Framework 2 Description"
|
||||
assert len(compliance_framework.Requirements) == 1
|
||||
assert compliance_framework.Requirements[0].Id == "1.1.1"
|
||||
assert compliance_framework.Requirements[0].Description == "description"
|
||||
assert len(compliance_framework.Requirements[0].Attributes) == 1
|
||||
assert (
|
||||
compliance_framework.Requirements[0].Attributes[0].Section == "1. Identity"
|
||||
)
|
||||
assert (
|
||||
compliance_framework.Requirements[0].Attributes[0].Profile == "E3 Level 1"
|
||||
)
|
||||
assert (
|
||||
compliance_framework.Requirements[0].Attributes[0].AssessmentStatus
|
||||
== "Manual"
|
||||
)
|
||||
assert (
|
||||
compliance_framework.Requirements[0].Attributes[0].Description
|
||||
== "Description"
|
||||
)
|
||||
assert (
|
||||
compliance_framework.Requirements[0].Attributes[0].RationaleStatement
|
||||
== "Rationale"
|
||||
)
|
||||
assert (
|
||||
compliance_framework.Requirements[0].Attributes[0].ImpactStatement
|
||||
== "Impact"
|
||||
)
|
||||
assert (
|
||||
compliance_framework.Requirements[0].Attributes[0].RemediationProcedure
|
||||
== "Remediation"
|
||||
)
|
||||
assert (
|
||||
compliance_framework.Requirements[0].Attributes[0].AuditProcedure == "Audit"
|
||||
)
|
||||
assert (
|
||||
compliance_framework.Requirements[0].Attributes[0].AdditionalInformation
|
||||
== "Additional"
|
||||
)
|
||||
assert (
|
||||
compliance_framework.Requirements[0].Attributes[0].References
|
||||
== "References"
|
||||
)
|
||||
|
||||
def test_get_non_existent_framework(self):
|
||||
bulk_compliance_frameworks = custom_compliance_metadata
|
||||
|
||||
|
||||
@@ -304,13 +304,14 @@ class TestCheckMetada:
|
||||
# Assertions
|
||||
assert result == {"accessanalyzer_enabled"}
|
||||
|
||||
def test_list_by_compliance_empty(self):
|
||||
@mock.patch("prowler.lib.check.models.CheckMetadata.get_bulk")
|
||||
def test_list_by_compliance_empty(self, mock_get_bulk):
|
||||
mock_get_bulk.return_value = {}
|
||||
bulk_compliance_frameworks = custom_compliance_metadata
|
||||
result = CheckMetadata.list(
|
||||
bulk_compliance_frameworks=bulk_compliance_frameworks,
|
||||
compliance_framework="framework1_azure",
|
||||
)
|
||||
|
||||
# Assertions
|
||||
assert result == set()
|
||||
|
||||
|
||||
@@ -17,13 +17,11 @@ prowler_command = "prowler"
|
||||
|
||||
# capsys
|
||||
# https://docs.pytest.org/en/7.1.x/how-to/capture-stdout-stderr.html
|
||||
prowler_default_usage_error = (
|
||||
"usage: prowler [-h] [--version] {aws,azure,gcp,kubernetes,m365,nhn,dashboard} ..."
|
||||
)
|
||||
prowler_default_usage_error = "usage: prowler [-h] [--version] {aws,azure,gcp,kubernetes,m365,github,nhn,dashboard,iac} ..."
|
||||
|
||||
|
||||
def mock_get_available_providers():
|
||||
return ["aws", "azure", "gcp", "kubernetes", "m365", "nhn"]
|
||||
return ["aws", "azure", "gcp", "kubernetes", "m365", "github", "iac", "nhn"]
|
||||
|
||||
|
||||
@pytest.mark.arg_parser
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from datetime import datetime
|
||||
from io import StringIO
|
||||
from unittest import mock
|
||||
|
||||
from freezegun import freeze_time
|
||||
from mock import patch
|
||||
@@ -148,7 +149,11 @@ class TestAWSWellArchitected:
|
||||
assert output_data_manual.CheckId == "manual"
|
||||
assert output_data_manual.Muted is False
|
||||
|
||||
@freeze_time(datetime.now())
|
||||
@freeze_time("2025-01-01 00:00:00")
|
||||
@mock.patch(
|
||||
"prowler.lib.outputs.compliance.aws_well_architected.aws_well_architected.timestamp",
|
||||
"2025-01-01 00:00:00",
|
||||
)
|
||||
def test_batch_write_data_to_file(self):
|
||||
mock_file = StringIO()
|
||||
findings = [
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from datetime import datetime
|
||||
from io import StringIO
|
||||
from unittest import mock
|
||||
|
||||
from freezegun import freeze_time
|
||||
from mock import patch
|
||||
@@ -139,7 +140,10 @@ class TestAWSCIS:
|
||||
assert output_data_manual.CheckId == "manual"
|
||||
assert output_data_manual.Muted is False
|
||||
|
||||
@freeze_time(datetime.now())
|
||||
@freeze_time("2025-01-01 00:00:00")
|
||||
@mock.patch(
|
||||
"prowler.lib.outputs.compliance.cis.cis_aws.timestamp", "2025-01-01 00:00:00"
|
||||
)
|
||||
def test_batch_write_data_to_file(self):
|
||||
mock_file = StringIO()
|
||||
findings = [generate_finding_output(compliance={"CIS-1.4": "2.1.3"})]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from datetime import datetime
|
||||
from io import StringIO
|
||||
from unittest import mock
|
||||
|
||||
from freezegun import freeze_time
|
||||
from mock import patch
|
||||
@@ -158,7 +159,10 @@ class TestAzureCIS:
|
||||
assert output_data_manual.CheckId == "manual"
|
||||
assert output_data_manual.Muted is False
|
||||
|
||||
@freeze_time(datetime.now())
|
||||
@freeze_time("2025-01-01 00:00:00")
|
||||
@mock.patch(
|
||||
"prowler.lib.outputs.compliance.cis.cis_azure.timestamp", "2025-01-01 00:00:00"
|
||||
)
|
||||
def test_batch_write_data_to_file(self):
|
||||
mock_file = StringIO()
|
||||
findings = [
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from datetime import datetime
|
||||
from io import StringIO
|
||||
from unittest import mock
|
||||
|
||||
from freezegun import freeze_time
|
||||
from mock import patch
|
||||
@@ -150,7 +151,10 @@ class TestGCPCIS:
|
||||
assert output_data_manual.CheckId == "manual"
|
||||
assert output_data_manual.Muted is False
|
||||
|
||||
@freeze_time(datetime.now())
|
||||
@freeze_time("2025-01-01 00:00:00")
|
||||
@mock.patch(
|
||||
"prowler.lib.outputs.compliance.cis.cis_gcp.timestamp", "2025-01-01 00:00:00"
|
||||
)
|
||||
def test_batch_write_data_to_file(self):
|
||||
mock_file = StringIO()
|
||||
findings = [
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from datetime import datetime
|
||||
from io import StringIO
|
||||
from unittest import mock
|
||||
|
||||
from freezegun import freeze_time
|
||||
from mock import patch
|
||||
@@ -160,7 +161,11 @@ class TestKubernetesCIS:
|
||||
assert output_data_manual.CheckId == "manual"
|
||||
assert output_data_manual.Muted is False
|
||||
|
||||
@freeze_time(datetime.now())
|
||||
@freeze_time("2025-01-01 00:00:00")
|
||||
@mock.patch(
|
||||
"prowler.lib.outputs.compliance.cis.cis_kubernetes.timestamp",
|
||||
"2025-01-01 00:00:00",
|
||||
)
|
||||
def test_batch_write_data_to_file(self):
|
||||
mock_file = StringIO()
|
||||
findings = [
|
||||
@@ -181,5 +186,5 @@ class TestKubernetesCIS:
|
||||
|
||||
mock_file.seek(0)
|
||||
content = mock_file.read()
|
||||
expected_csv = f"PROVIDER;DESCRIPTION;CONTEXT;NAMESPACE;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_SECTION;REQUIREMENTS_ATTRIBUTES_SUBSECTION;REQUIREMENTS_ATTRIBUTES_PROFILE;REQUIREMENTS_ATTRIBUTES_ASSESSMENTSTATUS;REQUIREMENTS_ATTRIBUTES_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_RATIONALESTATEMENT;REQUIREMENTS_ATTRIBUTES_IMPACTSTATEMENT;REQUIREMENTS_ATTRIBUTES_REMEDIATIONPROCEDURE;REQUIREMENTS_ATTRIBUTES_AUDITPROCEDURE;REQUIREMENTS_ATTRIBUTES_ADDITIONALINFORMATION;REQUIREMENTS_ATTRIBUTES_REFERENCES;REQUIREMENTS_ATTRIBUTES_DEFAULTVALUE;STATUS;STATUSEXTENDED;RESOURCEID;RESOURCENAME;CHECKID;MUTED\r\nkubernetes;This CIS Kubernetes Benchmark provides prescriptive guidance for establishing a secure configuration posture for Kubernetes v1.27.;test-cluster;test-namespace;{datetime.now()};1.1.3;Ensure that the controller manager pod specification file permissions are set to 600 or more restrictive;1. Control Plane;1.1 Control Plane Node Configuration Files;Level 1 - Master Node;Automated;Ensure that the controller manager pod specification file has permissions of `600` or more restrictive.;The controller manager pod specification file controls various parameters that set the behavior of the Controller Manager on the master node. You should restrict its file permissions to maintain the integrity of the file. The file should be writable by only the administrators on the system.;;Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` chmod 600 /etc/kubernetes/manifests/kube-controller-manager.yaml ```;Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` stat -c %a /etc/kubernetes/manifests/kube-controller-manager.yaml ``` Verify that the permissions are `600` or more restrictive.;;https://kubernetes.io/docs/admin/kube-apiserver/;By default, the `kube-controller-manager.yaml` file has permissions of `640`.;PASS;;;;test-check-id;False\r\nkubernetes;This CIS Kubernetes Benchmark provides prescriptive guidance for establishing a secure configuration posture for Kubernetes v1.27.;;;{datetime.now()};1.1.4;Ensure that the controller manager pod specification file permissions are set to 600 or more restrictive;1.1 Control Plane Node Configuration Files;;Level 1 - Master Node;Automated;Ensure that the controller manager pod specification file has permissions of `600` or more restrictive.;The controller manager pod specification file controls various parameters that set the behavior of the Controller Manager on the master node. You should restrict its file permissions to maintain the integrity of the file. The file should be writable by only the administrators on the system.;;Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` chmod 600 /etc/kubernetes/manifests/kube-controller-manager.yaml ```;Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` stat -c %a /etc/kubernetes/manifests/kube-controller-manager.yaml ``` Verify that the permissions are `600` or more restrictive.;;https://kubernetes.io/docs/admin/kube-apiserver/;By default, the `kube-controller-manager.yaml` file has permissions of `640`.;MANUAL;Manual check;manual_check;Manual check;manual;False\r\n"
|
||||
expected_csv = f"PROVIDER;DESCRIPTION;CONTEXT;NAMESPACE;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_SECTION;REQUIREMENTS_ATTRIBUTES_SUBSECTION;REQUIREMENTS_ATTRIBUTES_PROFILE;REQUIREMENTS_ATTRIBUTES_ASSESSMENTSTATUS;REQUIREMENTS_ATTRIBUTES_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_RATIONALESTATEMENT;REQUIREMENTS_ATTRIBUTES_IMPACTSTATEMENT;REQUIREMENTS_ATTRIBUTES_REMEDIATIONPROCEDURE;REQUIREMENTS_ATTRIBUTES_AUDITPROCEDURE;REQUIREMENTS_ATTRIBUTES_ADDITIONALINFORMATION;REQUIREMENTS_ATTRIBUTES_REFERENCES;REQUIREMENTS_ATTRIBUTES_DEFAULTVALUE;STATUS;STATUSEXTENDED;RESOURCEID;RESOURCENAME;CHECKID;MUTED\r\nkubernetes;This CIS Kubernetes Benchmark provides prescriptive guidance for establishing a secure configuration posture for Kubernetes v1.27.;test-cluster;test-namespace;{datetime.now()};1.1.3;Ensure that the controller manager pod specification file permissions are set to 600 or more restrictive;1. Control Plane;1.1 Control Plane Node Configuration Files;Level 1;Automated;Ensure that the controller manager pod specification file has permissions of `600` or more restrictive.;The controller manager pod specification file controls various parameters that set the behavior of the Controller Manager on the master node. You should restrict its file permissions to maintain the integrity of the file. The file should be writable by only the administrators on the system.;;Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` chmod 600 /etc/kubernetes/manifests/kube-controller-manager.yaml ```;Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` stat -c %a /etc/kubernetes/manifests/kube-controller-manager.yaml ``` Verify that the permissions are `600` or more restrictive.;;https://kubernetes.io/docs/admin/kube-apiserver/;By default, the `kube-controller-manager.yaml` file has permissions of `640`.;PASS;;;;test-check-id;False\r\nkubernetes;This CIS Kubernetes Benchmark provides prescriptive guidance for establishing a secure configuration posture for Kubernetes v1.27.;;;{datetime.now()};1.1.4;Ensure that the controller manager pod specification file permissions are set to 600 or more restrictive;1.1 Control Plane Node Configuration Files;;Level 1;Automated;Ensure that the controller manager pod specification file has permissions of `600` or more restrictive.;The controller manager pod specification file controls various parameters that set the behavior of the Controller Manager on the master node. You should restrict its file permissions to maintain the integrity of the file. The file should be writable by only the administrators on the system.;;Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` chmod 600 /etc/kubernetes/manifests/kube-controller-manager.yaml ```;Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` stat -c %a /etc/kubernetes/manifests/kube-controller-manager.yaml ``` Verify that the permissions are `600` or more restrictive.;;https://kubernetes.io/docs/admin/kube-apiserver/;By default, the `kube-controller-manager.yaml` file has permissions of `640`.;MANUAL;Manual check;manual_check;Manual check;manual;False\r\n"
|
||||
assert content == expected_csv
|
||||
@@ -1,5 +1,6 @@
|
||||
from datetime import datetime
|
||||
from io import StringIO
|
||||
from unittest import mock
|
||||
|
||||
from freezegun import freeze_time
|
||||
from mock import patch
|
||||
@@ -155,7 +156,10 @@ class TestM365CIS:
|
||||
assert output_data_manual.CheckId == "manual"
|
||||
assert output_data_manual.Muted is False
|
||||
|
||||
@freeze_time(datetime.now())
|
||||
@freeze_time("2025-01-01 00:00:00")
|
||||
@mock.patch(
|
||||
"prowler.lib.outputs.compliance.cis.cis_m365.timestamp", "2025-01-01 00:00:00"
|
||||
)
|
||||
def test_batch_write_data_to_file(self):
|
||||
mock_file = StringIO()
|
||||
findings = [
|
||||
@@ -177,6 +181,6 @@ class TestM365CIS:
|
||||
mock_file.seek(0)
|
||||
content = mock_file.read()
|
||||
|
||||
expected_csv = f"PROVIDER;DESCRIPTION;TENANTID;LOCATION;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_SECTION;REQUIREMENTS_ATTRIBUTES_SUBSECTION;REQUIREMENTS_ATTRIBUTES_PROFILE;REQUIREMENTS_ATTRIBUTES_ASSESSMENTSTATUS;REQUIREMENTS_ATTRIBUTES_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_RATIONALESTATEMENT;REQUIREMENTS_ATTRIBUTES_IMPACTSTATEMENT;REQUIREMENTS_ATTRIBUTES_REMEDIATIONPROCEDURE;REQUIREMENTS_ATTRIBUTES_AUDITPROCEDURE;REQUIREMENTS_ATTRIBUTES_ADDITIONALINFORMATION;REQUIREMENTS_ATTRIBUTES_DEFAULTVALUE;REQUIREMENTS_ATTRIBUTES_REFERENCES;STATUS;STATUSEXTENDED;RESOURCEID;RESOURCENAME;CHECKID;MUTED\r\nm365;The CIS Microsoft 365 Foundations Benchmark provides prescriptive guidance for configuring security options for Microsoft 365 with an emphasis on foundational, testable, and architecture agnostic settings.;00000000-0000-0000-0000-000000000000;global;{datetime.now()};2.1.3;Ensure MFA Delete is enabled on S3 buckets;2.1. Simple Storage Service (S3);;Level 1;Automated;Once MFA Delete is enabled on your sensitive and classified S3 bucket it requires the user to have two forms of authentication.;Adding MFA delete to an S3 bucket, requires additional authentication when you change the version state of your bucket or you delete and object version adding another layer of security in the event your security credentials are compromised or unauthorized access is granted.;;Perform the steps below to enable MFA delete on an S3 bucket.Note:-You cannot enable MFA Delete using the AWS Management Console. You must use the AWS CLI or API.-You must use your 'root' account to enable MFA Delete on S3 buckets.**From Command line:**1. Run the s3api put-bucket-versioning command aws s3api put-bucket-versioning --profile my-root-profile --bucket Bucket_Name --versioning-configuration Status=Enabled,MFADelete=Enabled --mfa arn:aws:iam::aws_account_id:mfa/root-account-mfa-device passcode;Perform the steps below to confirm MFA delete is configured on an S3 Bucket**From Console:**1. Login to the S3 console at `https://console.aws.amazon.com/s3/`2. Click the `Check` box next to the Bucket name you want to confirm3. In the window under `Properties`4. Confirm that Versioning is `Enabled`5. Confirm that MFA Delete is `Enabled`**From Command Line:**1. Run the `get-bucket-versioning aws s3api get-bucket-versioning --bucket my-bucket Output example: <VersioningConfiguration xmlns=`http://s3.amazonaws.com/doc/2006-03-01/`> <Status>Enabled</Status> <MfaDelete>Enabled</MfaDelete></VersioningConfiguration>\ If the Console or the CLI output does not show Versioning and MFA Delete `enabled` refer to the remediation below.;;By default, MFA Delete is not enabled on S3 buckets.;https://docs.aws.amazon.com/AmazonS3/latest/dev/Versioning.html#MultiFactorAuthenticationDelete:https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMFADelete.html:https://aws.amazon.com/blogs/security/securing-access-to-aws-using-mfa-part-3/:https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_mfa_lost-or-broken.html;PASS;;;;test-check-id;False\r\nm365;The CIS Microsoft 365 Foundations Benchmark provides prescriptive guidance for configuring security options for Microsoft 365 with an emphasis on foundational, testable, and architecture agnostic settings.;00000000-0000-0000-0000-000000000000;global;{datetime.now()};2.1.4;Ensure that the controller manager pod specification file permissions are set to 600 or more restrictive;1.1 Control Plane Node Configuration Files;;Level 1 - Master Node;Automated;Ensure that the controller manager pod specification file has permissions of `600` or more restrictive.;The controller manager pod specification file controls various parameters that set the behavior of the Controller Manager on the master node. You should restrict its file permissions to maintain the integrity of the file. The file should be writable by only the administrators on the system.;;Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` chmod 600 /etc/kubernetes/manifests/kube-controller-manager.yaml ```;Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` stat -c %a /etc/kubernetes/manifests/kube-controller-manager.yaml ``` Verify that the permissions are `600` or more restrictive.;;By default, the `kube-controller-manager.yaml` file has permissions of `640`.;https://kubernetes.io/docs/admin/kube-apiserver/;MANUAL;Manual check;manual_check;Manual check;manual;False\r\n"
|
||||
expected_csv = f"PROVIDER;DESCRIPTION;TENANTID;LOCATION;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_SECTION;REQUIREMENTS_ATTRIBUTES_SUBSECTION;REQUIREMENTS_ATTRIBUTES_PROFILE;REQUIREMENTS_ATTRIBUTES_ASSESSMENTSTATUS;REQUIREMENTS_ATTRIBUTES_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_RATIONALESTATEMENT;REQUIREMENTS_ATTRIBUTES_IMPACTSTATEMENT;REQUIREMENTS_ATTRIBUTES_REMEDIATIONPROCEDURE;REQUIREMENTS_ATTRIBUTES_AUDITPROCEDURE;REQUIREMENTS_ATTRIBUTES_ADDITIONALINFORMATION;REQUIREMENTS_ATTRIBUTES_DEFAULTVALUE;REQUIREMENTS_ATTRIBUTES_REFERENCES;STATUS;STATUSEXTENDED;RESOURCEID;RESOURCENAME;CHECKID;MUTED\r\nm365;The CIS Microsoft 365 Foundations Benchmark provides prescriptive guidance for configuring security options for Microsoft 365 with an emphasis on foundational, testable, and architecture agnostic settings.;00000000-0000-0000-0000-000000000000;global;{datetime.now()};2.1.3;Ensure MFA Delete is enabled on S3 buckets;2.1. Simple Storage Service (S3);;Level 1;Automated;Once MFA Delete is enabled on your sensitive and classified S3 bucket it requires the user to have two forms of authentication.;Adding MFA delete to an S3 bucket, requires additional authentication when you change the version state of your bucket or you delete and object version adding another layer of security in the event your security credentials are compromised or unauthorized access is granted.;;Perform the steps below to enable MFA delete on an S3 bucket.Note:-You cannot enable MFA Delete using the AWS Management Console. You must use the AWS CLI or API.-You must use your 'root' account to enable MFA Delete on S3 buckets.**From Command line:**1. Run the s3api put-bucket-versioning command aws s3api put-bucket-versioning --profile my-root-profile --bucket Bucket_Name --versioning-configuration Status=Enabled,MFADelete=Enabled --mfa arn:aws:iam::aws_account_id:mfa/root-account-mfa-device passcode;Perform the steps below to confirm MFA delete is configured on an S3 Bucket**From Console:**1. Login to the S3 console at `https://console.aws.amazon.com/s3/`2. Click the `Check` box next to the Bucket name you want to confirm3. In the window under `Properties`4. Confirm that Versioning is `Enabled`5. Confirm that MFA Delete is `Enabled`**From Command Line:**1. Run the `get-bucket-versioning aws s3api get-bucket-versioning --bucket my-bucket Output example: <VersioningConfiguration xmlns=`http://s3.amazonaws.com/doc/2006-03-01/`> <Status>Enabled</Status> <MfaDelete>Enabled</MfaDelete></VersioningConfiguration>\ If the Console or the CLI output does not show Versioning and MFA Delete `enabled` refer to the remediation below.;;By default, MFA Delete is not enabled on S3 buckets.;https://docs.aws.amazon.com/AmazonS3/latest/dev/Versioning.html#MultiFactorAuthenticationDelete:https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMFADelete.html:https://aws.amazon.com/blogs/security/securing-access-to-aws-using-mfa-part-3/:https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_mfa_lost-or-broken.html;PASS;;;;test-check-id;False\r\nm365;The CIS Microsoft 365 Foundations Benchmark provides prescriptive guidance for configuring security options for Microsoft 365 with an emphasis on foundational, testable, and architecture agnostic settings.;00000000-0000-0000-0000-000000000000;global;{datetime.now()};2.1.4;Ensure that the controller manager pod specification file permissions are set to 600 or more restrictive;1.1 Control Plane Node Configuration Files;;Level 1;Automated;Ensure that the controller manager pod specification file has permissions of `600` or more restrictive.;The controller manager pod specification file controls various parameters that set the behavior of the Controller Manager on the master node. You should restrict its file permissions to maintain the integrity of the file. The file should be writable by only the administrators on the system.;;Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` chmod 600 /etc/kubernetes/manifests/kube-controller-manager.yaml ```;Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` stat -c %a /etc/kubernetes/manifests/kube-controller-manager.yaml ``` Verify that the permissions are `600` or more restrictive.;;By default, the `kube-controller-manager.yaml` file has permissions of `640`.;https://kubernetes.io/docs/admin/kube-apiserver/;MANUAL;Manual check;manual_check;Manual check;manual;False\r\n"
|
||||
|
||||
assert content == expected_csv
|
||||
@@ -1,5 +1,6 @@
|
||||
from datetime import datetime
|
||||
from io import StringIO
|
||||
from unittest import mock
|
||||
|
||||
from freezegun import freeze_time
|
||||
from mock import patch
|
||||
@@ -115,7 +116,10 @@ class TestAWSENS:
|
||||
assert output_data_manual.CheckId == "manual"
|
||||
assert output_data_manual.Muted is False
|
||||
|
||||
@freeze_time(datetime.now())
|
||||
@freeze_time("2025-01-01 00:00:00")
|
||||
@mock.patch(
|
||||
"prowler.lib.outputs.compliance.ens.ens_aws.timestamp", "2025-01-01 00:00:00"
|
||||
)
|
||||
def test_batch_write_data_to_file(self):
|
||||
mock_file = StringIO()
|
||||
findings = [
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from datetime import datetime
|
||||
from io import StringIO
|
||||
from unittest import mock
|
||||
|
||||
from freezegun import freeze_time
|
||||
from mock import patch
|
||||
@@ -118,7 +119,10 @@ class TestAzureENS:
|
||||
assert output_data_manual.CheckId == "manual"
|
||||
assert output_data_manual.Muted is False
|
||||
|
||||
@freeze_time(datetime.now())
|
||||
@freeze_time("2025-01-01 00:00:00")
|
||||
@mock.patch(
|
||||
"prowler.lib.outputs.compliance.ens.ens_azure.timestamp", "2025-01-01 00:00:00"
|
||||
)
|
||||
def test_batch_write_data_to_file(self):
|
||||
mock_file = StringIO()
|
||||
findings = [
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from datetime import datetime
|
||||
from io import StringIO
|
||||
from unittest import mock
|
||||
|
||||
from freezegun import freeze_time
|
||||
from mock import patch
|
||||
@@ -118,7 +119,10 @@ class TestGCPENS:
|
||||
assert output_data_manual.CheckId == "manual"
|
||||
assert output_data_manual.Muted is False
|
||||
|
||||
@freeze_time(datetime.now())
|
||||
@freeze_time("2025-01-01 00:00:00")
|
||||
@mock.patch(
|
||||
"prowler.lib.outputs.compliance.ens.ens_gcp.timestamp", "2025-01-01 00:00:00"
|
||||
)
|
||||
def test_batch_write_data_to_file(self):
|
||||
mock_file = StringIO()
|
||||
findings = [
|
||||
|
||||
@@ -179,7 +179,7 @@ CIS_1_8_KUBERNETES = Compliance(
|
||||
CIS_Requirement_Attribute(
|
||||
Section="1. Control Plane",
|
||||
SubSection="1.1 Control Plane Node Configuration Files",
|
||||
Profile="Level 1 - Master Node",
|
||||
Profile="Level 1",
|
||||
AssessmentStatus="Automated",
|
||||
Description="Ensure that the controller manager pod specification file has permissions of `600` or more restrictive.",
|
||||
RationaleStatement="The controller manager pod specification file controls various parameters that set the behavior of the Controller Manager on the master node. You should restrict its file permissions to maintain the integrity of the file. The file should be writable by only the administrators on the system.",
|
||||
@@ -199,7 +199,7 @@ CIS_1_8_KUBERNETES = Compliance(
|
||||
Attributes=[
|
||||
CIS_Requirement_Attribute(
|
||||
Section="1.1 Control Plane Node Configuration Files",
|
||||
Profile="Level 1 - Master Node",
|
||||
Profile="Level 1",
|
||||
AssessmentStatus="Automated",
|
||||
Description="Ensure that the controller manager pod specification file has permissions of `600` or more restrictive.",
|
||||
RationaleStatement="The controller manager pod specification file controls various parameters that set the behavior of the Controller Manager on the master node. You should restrict its file permissions to maintain the integrity of the file. The file should be writable by only the administrators on the system.",
|
||||
@@ -279,7 +279,7 @@ CIS_4_0_M365 = Compliance(
|
||||
Attributes=[
|
||||
CIS_Requirement_Attribute(
|
||||
Section="1.1 Control Plane Node Configuration Files",
|
||||
Profile="Level 1 - Master Node",
|
||||
Profile="Level 1",
|
||||
AssessmentStatus="Automated",
|
||||
Description="Ensure that the controller manager pod specification file has permissions of `600` or more restrictive.",
|
||||
RationaleStatement="The controller manager pod specification file controls various parameters that set the behavior of the Controller Manager on the master node. You should restrict its file permissions to maintain the integrity of the file. The file should be writable by only the administrators on the system.",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from datetime import datetime
|
||||
from io import StringIO
|
||||
from unittest import mock
|
||||
|
||||
from freezegun import freeze_time
|
||||
from mock import patch
|
||||
@@ -98,7 +99,11 @@ class TestAWSGenericCompliance:
|
||||
assert output_data_manual.CheckId == "manual"
|
||||
assert output_data_manual.Muted is False
|
||||
|
||||
@freeze_time(datetime.now())
|
||||
@freeze_time("2025-01-01 00:00:00")
|
||||
@mock.patch(
|
||||
"prowler.lib.outputs.compliance.generic.generic.timestamp",
|
||||
"2025-01-01 00:00:00",
|
||||
)
|
||||
def test_batch_write_data_to_file(self):
|
||||
mock_file = StringIO()
|
||||
findings = [
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from datetime import datetime
|
||||
from io import StringIO
|
||||
from unittest import mock
|
||||
|
||||
from freezegun import freeze_time
|
||||
from mock import patch
|
||||
@@ -73,7 +74,11 @@ class TestAWSISO27001:
|
||||
assert output_data_manual.CheckId == "manual"
|
||||
assert output_data_manual.Muted is False
|
||||
|
||||
@freeze_time(datetime.now())
|
||||
@freeze_time("2025-01-01 00:00:00")
|
||||
@mock.patch(
|
||||
"prowler.lib.outputs.compliance.iso27001.iso27001_aws.timestamp",
|
||||
"2025-01-01 00:00:00",
|
||||
)
|
||||
def test_batch_write_data_to_file(self):
|
||||
mock_file = StringIO()
|
||||
findings = [generate_finding_output(compliance={"ISO27001-2013": "A.10.1"})]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from datetime import datetime
|
||||
from io import StringIO
|
||||
from unittest import mock
|
||||
|
||||
from freezegun import freeze_time
|
||||
from mock import patch
|
||||
@@ -106,7 +107,11 @@ class TestAWSKISAISMSP:
|
||||
assert output_data_manual.CheckId == "manual"
|
||||
assert output_data_manual.Muted is False
|
||||
|
||||
@freeze_time(datetime.now())
|
||||
@freeze_time("2025-01-01 00:00:00")
|
||||
@mock.patch(
|
||||
"prowler.lib.outputs.compliance.kisa_ismsp.kisa_ismsp_aws.timestamp",
|
||||
"2025-01-01 00:00:00",
|
||||
)
|
||||
def test_batch_write_data_to_file(self):
|
||||
mock_file = StringIO()
|
||||
findings = [generate_finding_output(compliance={"KISA-ISMS-P-2023": ["2.5.3"]})]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from datetime import datetime
|
||||
from io import StringIO
|
||||
from unittest import mock
|
||||
|
||||
from freezegun import freeze_time
|
||||
from mock import patch
|
||||
@@ -112,7 +113,11 @@ class TestAWSMITREAttack:
|
||||
assert output_data_manual.CheckId == "manual"
|
||||
assert output_data_manual.Muted is False
|
||||
|
||||
@freeze_time(datetime.now())
|
||||
@freeze_time("2025-01-01 00:00:00")
|
||||
@mock.patch(
|
||||
"prowler.lib.outputs.compliance.mitre_attack.mitre_attack_aws.timestamp",
|
||||
"2025-01-01 00:00:00",
|
||||
)
|
||||
def test_batch_write_data_to_file(self):
|
||||
mock_file = StringIO()
|
||||
findings = [generate_finding_output(compliance={"MITRE-ATTACK": "T1190"})]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from datetime import datetime
|
||||
from io import StringIO
|
||||
from unittest import mock
|
||||
|
||||
from freezegun import freeze_time
|
||||
from mock import patch
|
||||
@@ -129,7 +130,11 @@ class TestAzureMITREAttack:
|
||||
assert output_data_manual.CheckId == "manual"
|
||||
assert output_data_manual.Muted is False
|
||||
|
||||
@freeze_time(datetime.now())
|
||||
@freeze_time("2025-01-01 00:00:00")
|
||||
@mock.patch(
|
||||
"prowler.lib.outputs.compliance.mitre_attack.mitre_attack_azure.timestamp",
|
||||
"2025-01-01 00:00:00",
|
||||
)
|
||||
def test_batch_write_data_to_file(self):
|
||||
mock_file = StringIO()
|
||||
findings = [
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from datetime import datetime
|
||||
from io import StringIO
|
||||
from unittest import mock
|
||||
|
||||
from freezegun import freeze_time
|
||||
from mock import patch
|
||||
@@ -120,7 +121,11 @@ class TestGCPMITREAttack:
|
||||
assert output_data_manual.CheckId == "manual"
|
||||
assert output_data_manual.Muted is False
|
||||
|
||||
@freeze_time(datetime.now())
|
||||
@freeze_time("2025-01-01 00:00:00")
|
||||
@mock.patch(
|
||||
"prowler.lib.outputs.compliance.mitre_attack.mitre_attack_gcp.timestamp",
|
||||
"2025-01-01 00:00:00",
|
||||
)
|
||||
def test_batch_write_data_to_file(self):
|
||||
mock_file = StringIO()
|
||||
findings = [
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from datetime import datetime
|
||||
from io import StringIO
|
||||
from unittest import mock
|
||||
|
||||
from freezegun import freeze_time
|
||||
from mock import patch
|
||||
@@ -123,7 +124,11 @@ class TestProwlerThreatScoreAWS:
|
||||
assert output_data_manual.CheckId == "manual"
|
||||
assert not output_data_manual.Muted
|
||||
|
||||
@freeze_time(datetime.now())
|
||||
@freeze_time("2025-01-01 00:00:00")
|
||||
@mock.patch(
|
||||
"prowler.lib.outputs.compliance.prowler_threatscore.prowler_threatscore_aws.timestamp",
|
||||
"2025-01-01 00:00:00",
|
||||
)
|
||||
def test_batch_write_data_to_file(self):
|
||||
mock_file = StringIO()
|
||||
findings = [
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from datetime import datetime
|
||||
from io import StringIO
|
||||
from unittest import mock
|
||||
|
||||
from freezegun import freeze_time
|
||||
from mock import patch
|
||||
@@ -133,7 +134,11 @@ class TestProwlerThreatScoreAzure:
|
||||
assert output_data_manual.ResourceName == "Manual check"
|
||||
assert output_data_manual.CheckId == "manual"
|
||||
|
||||
@freeze_time(datetime.now())
|
||||
@freeze_time("2025-01-01 00:00:00")
|
||||
@mock.patch(
|
||||
"prowler.lib.outputs.compliance.prowler_threatscore.prowler_threatscore_azure.timestamp",
|
||||
"2025-01-01 00:00:00",
|
||||
)
|
||||
def test_batch_write_data_to_file(self):
|
||||
mock_file = StringIO()
|
||||
findings = [
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from datetime import datetime
|
||||
from io import StringIO
|
||||
from unittest import mock
|
||||
|
||||
from freezegun import freeze_time
|
||||
from mock import patch
|
||||
@@ -129,7 +130,11 @@ class TestProwlerThreatScoreGCP:
|
||||
assert output_data_manual.CheckId == "manual"
|
||||
assert not output_data_manual.Muted
|
||||
|
||||
@freeze_time(datetime.now())
|
||||
@freeze_time("2025-01-01 00:00:00")
|
||||
@mock.patch(
|
||||
"prowler.lib.outputs.compliance.prowler_threatscore.prowler_threatscore_gcp.timestamp",
|
||||
"2025-01-01 00:00:00",
|
||||
)
|
||||
def test_batch_write_data_to_file(self):
|
||||
mock_file = StringIO()
|
||||
findings = [
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from datetime import datetime
|
||||
from io import StringIO
|
||||
from unittest import mock
|
||||
|
||||
from freezegun import freeze_time
|
||||
from mock import patch
|
||||
@@ -131,7 +132,11 @@ class TestProwlerThreatScoreM365:
|
||||
assert output_data_manual.CheckId == "manual"
|
||||
assert not output_data_manual.Muted
|
||||
|
||||
@freeze_time(datetime.now())
|
||||
@freeze_time("2025-01-01 00:00:00")
|
||||
@mock.patch(
|
||||
"prowler.lib.outputs.compliance.prowler_threatscore.prowler_threatscore_m365.timestamp",
|
||||
"2025-01-01 00:00:00",
|
||||
)
|
||||
def test_batch_write_data_to_file(self):
|
||||
mock_file = StringIO()
|
||||
findings = [
|
||||
|
||||
@@ -3,7 +3,7 @@ from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
from pydantic.v1 import ValidationError
|
||||
|
||||
from prowler.lib.check.models import (
|
||||
CheckMetadata,
|
||||
@@ -28,7 +28,7 @@ def mock_check_metadata(provider):
|
||||
SubServiceName="",
|
||||
ResourceIdTemplate="",
|
||||
Severity="high",
|
||||
ResourceType="",
|
||||
ResourceType="mock_resource_type",
|
||||
Description="",
|
||||
Risk="",
|
||||
RelatedUrl="",
|
||||
@@ -209,7 +209,7 @@ class TestFinding:
|
||||
assert finding_output.metadata.SubServiceName == ""
|
||||
assert finding_output.metadata.ResourceIdTemplate == ""
|
||||
assert finding_output.metadata.Severity == Severity.high
|
||||
assert finding_output.metadata.ResourceType == ""
|
||||
assert finding_output.metadata.ResourceType == "mock_resource_type"
|
||||
assert finding_output.metadata.Description == ""
|
||||
assert finding_output.metadata.Risk == ""
|
||||
assert finding_output.metadata.RelatedUrl == ""
|
||||
@@ -230,7 +230,7 @@ class TestFinding:
|
||||
assert finding_output.check_id == "mock_check_id"
|
||||
assert finding_output.severity == Severity.high.value
|
||||
assert finding_output.status == Status.PASS.value
|
||||
assert finding_output.resource_type == ""
|
||||
assert finding_output.resource_type == "mock_resource_type"
|
||||
assert finding_output.service_name == "mock_service_name"
|
||||
assert finding_output.raw == {}
|
||||
|
||||
@@ -310,7 +310,7 @@ class TestFinding:
|
||||
assert finding_output.metadata.SubServiceName == ""
|
||||
assert finding_output.metadata.ResourceIdTemplate == ""
|
||||
assert finding_output.metadata.Severity == Severity.high
|
||||
assert finding_output.metadata.ResourceType == ""
|
||||
assert finding_output.metadata.ResourceType == "mock_resource_type"
|
||||
assert finding_output.metadata.Description == ""
|
||||
assert finding_output.metadata.Risk == ""
|
||||
assert finding_output.metadata.RelatedUrl == ""
|
||||
@@ -405,7 +405,7 @@ class TestFinding:
|
||||
assert finding_output.metadata.SubServiceName == ""
|
||||
assert finding_output.metadata.ResourceIdTemplate == ""
|
||||
assert finding_output.metadata.Severity == Severity.high
|
||||
assert finding_output.metadata.ResourceType == ""
|
||||
assert finding_output.metadata.ResourceType == "mock_resource_type"
|
||||
assert finding_output.metadata.Description == ""
|
||||
assert finding_output.metadata.Risk == ""
|
||||
assert finding_output.metadata.RelatedUrl == ""
|
||||
@@ -490,7 +490,7 @@ class TestFinding:
|
||||
assert finding_output.metadata.SubServiceName == ""
|
||||
assert finding_output.metadata.ResourceIdTemplate == ""
|
||||
assert finding_output.metadata.Severity == Severity.high
|
||||
assert finding_output.metadata.ResourceType == ""
|
||||
assert finding_output.metadata.ResourceType == "mock_resource_type"
|
||||
assert finding_output.metadata.Description == ""
|
||||
assert finding_output.metadata.Risk == ""
|
||||
assert finding_output.metadata.RelatedUrl == ""
|
||||
@@ -573,6 +573,16 @@ class TestFinding:
|
||||
inserted_at = 1234567890
|
||||
provider = DummyProvider(uid="account123")
|
||||
provider.type = "aws"
|
||||
provider.organizations_metadata = SimpleNamespace(
|
||||
account_name="test-account",
|
||||
account_email="test@example.com",
|
||||
organization_arn="arn:aws:organizations::123456789012:organization/o-abcdef123456",
|
||||
organization_id="o-abcdef123456",
|
||||
account_tags={"Environment": "prod", "Project": "test"},
|
||||
)
|
||||
provider.identity = SimpleNamespace(
|
||||
account="123456789012", partition="aws", profile="default"
|
||||
)
|
||||
scan = DummyScan(provider=provider)
|
||||
|
||||
# Create a dummy resource with one tag
|
||||
@@ -655,7 +665,10 @@ class TestFinding:
|
||||
assert meta.Notes == "Some notes"
|
||||
|
||||
# Check other Finding fields
|
||||
assert finding_obj.uid == "prowler-aws-check-001--us-east-1-ResourceName1"
|
||||
assert (
|
||||
finding_obj.uid
|
||||
== "prowler-aws-check-001-123456789012-us-east-1-ResourceName1"
|
||||
)
|
||||
assert finding_obj.status == Status("FAIL")
|
||||
assert finding_obj.status_extended == "extended"
|
||||
# From the dummy resource
|
||||
|
||||
@@ -6,9 +6,9 @@ import requests
|
||||
from freezegun import freeze_time
|
||||
from mock import patch
|
||||
from py_ocsf_models.events.base_event import SeverityID, StatusID
|
||||
from py_ocsf_models.events.findings.detection_finding import DetectionFinding
|
||||
from py_ocsf_models.events.findings.detection_finding import (
|
||||
TypeID as DetectionFindingTypeID,
|
||||
DetectionFinding,
|
||||
DetectionFindingTypeID,
|
||||
)
|
||||
from py_ocsf_models.events.findings.finding import ActivityID, FindingInformation
|
||||
from py_ocsf_models.objects.account import Account, TypeID
|
||||
@@ -174,7 +174,7 @@ class TestOCSF:
|
||||
"vendor_name": "Prowler",
|
||||
"version": prowler_version,
|
||||
},
|
||||
"version": "1.4.0",
|
||||
"version": "1.5.0",
|
||||
"profiles": ["cloud", "datetime"],
|
||||
"tenant_uid": "test-organization-id",
|
||||
},
|
||||
|
||||
+57
-88
@@ -117,6 +117,7 @@ def mock_load_check_metadata():
|
||||
) as mock_load:
|
||||
mock_metadata = MagicMock()
|
||||
mock_metadata.CheckID = "accessanalyzer_enabled"
|
||||
mock_metadata.ResourceType = "AWS::IAM::AccessAnalyzer"
|
||||
mock_load.return_value = mock_metadata
|
||||
yield mock_load
|
||||
|
||||
@@ -130,8 +131,22 @@ def mock_load_checks_to_execute():
|
||||
yield mock_load
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_check_metadata_get_bulk():
|
||||
with mock.patch(
|
||||
"prowler.lib.check.models.CheckMetadata.get_bulk", autospec=True
|
||||
) as mock_get_bulk:
|
||||
mock_metadata = MagicMock()
|
||||
mock_metadata.CheckID = "accessanalyzer_enabled"
|
||||
mock_metadata.ResourceType = "AWS::IAM::AccessAnalyzer"
|
||||
mock_get_bulk.return_value = {"accessanalyzer_enabled": mock_metadata}
|
||||
yield mock_get_bulk
|
||||
|
||||
|
||||
class TestScan:
|
||||
def test_init(mock_provider):
|
||||
def test_init(
|
||||
mock_provider,
|
||||
):
|
||||
checks_to_execute = {
|
||||
"workspaces_vpc_2private_1public_subnets_nat",
|
||||
"workspaces_vpc_2private_1public_subnets_nat",
|
||||
@@ -194,102 +209,56 @@ class TestScan:
|
||||
"config_recorder_all_regions_enabled",
|
||||
}
|
||||
mock_provider.type = "aws"
|
||||
scan = Scan(mock_provider, checks=checks_to_execute)
|
||||
# Patch get_bulk to return all these checks
|
||||
with mock.patch(
|
||||
"prowler.lib.check.models.CheckMetadata.get_bulk"
|
||||
) as mock_get_bulk:
|
||||
mock_metadata = MagicMock()
|
||||
mock_metadata.ResourceType = "AWS::IAM::AccessAnalyzer"
|
||||
mock_metadata.Categories = []
|
||||
mock_get_bulk.return_value = {
|
||||
check: mock_metadata for check in checks_to_execute
|
||||
}
|
||||
scan = Scan(mock_provider, checks=checks_to_execute)
|
||||
|
||||
assert scan.provider == mock_provider
|
||||
# Check that the checks to execute are sorted and without duplicates
|
||||
assert scan.checks_to_execute == [
|
||||
"accessanalyzer_enabled",
|
||||
"accessanalyzer_enabled_without_findings",
|
||||
"account_maintain_current_contact_details",
|
||||
"account_maintain_different_contact_details_to_security_billing_and_operations",
|
||||
"account_security_contact_information_is_registered",
|
||||
"account_security_questions_are_registered_in_the_aws_account",
|
||||
"acm_certificates_expiration_check",
|
||||
"acm_certificates_transparency_logs_enabled",
|
||||
"apigateway_restapi_authorizers_enabled",
|
||||
"apigateway_restapi_client_certificate_enabled",
|
||||
"apigateway_restapi_logging_enabled",
|
||||
"apigateway_restapi_public",
|
||||
"awslambda_function_not_publicly_accessible",
|
||||
"awslambda_function_url_cors_policy",
|
||||
"awslambda_function_url_public",
|
||||
"awslambda_function_using_supported_runtimes",
|
||||
"backup_plans_exist",
|
||||
"backup_reportplans_exist",
|
||||
"backup_vaults_encrypted",
|
||||
"backup_vaults_exist",
|
||||
"cloudformation_stack_outputs_find_secrets",
|
||||
"cloudformation_stacks_termination_protection_enabled",
|
||||
"cloudwatch_cross_account_sharing_disabled",
|
||||
"cloudwatch_log_group_kms_encryption_enabled",
|
||||
"cloudwatch_log_group_no_secrets_in_logs",
|
||||
"cloudwatch_log_group_retention_policy_specific_days_enabled",
|
||||
"cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled",
|
||||
"cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled",
|
||||
"cloudwatch_log_metric_filter_authentication_failures",
|
||||
"cloudwatch_log_metric_filter_aws_organizations_changes",
|
||||
"cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk",
|
||||
"cloudwatch_log_metric_filter_for_s3_bucket_policy_changes",
|
||||
"cloudwatch_log_metric_filter_policy_changes",
|
||||
"cloudwatch_log_metric_filter_root_usage",
|
||||
"cloudwatch_log_metric_filter_security_group_changes",
|
||||
"cloudwatch_log_metric_filter_sign_in_without_mfa",
|
||||
"cloudwatch_log_metric_filter_unauthorized_api_calls",
|
||||
"codeartifact_packages_external_public_publishing_disabled",
|
||||
"codebuild_project_older_90_days",
|
||||
"codebuild_project_user_controlled_buildspec",
|
||||
"cognito_identity_pool_guest_access_disabled",
|
||||
"cognito_user_pool_advanced_security_enabled",
|
||||
"cognito_user_pool_blocks_compromised_credentials_sign_in_attempts",
|
||||
"cognito_user_pool_blocks_potential_malicious_sign_in_attempts",
|
||||
"cognito_user_pool_client_prevent_user_existence_errors",
|
||||
"cognito_user_pool_client_token_revocation_enabled",
|
||||
"cognito_user_pool_deletion_protection_enabled",
|
||||
"cognito_user_pool_mfa_enabled",
|
||||
"cognito_user_pool_password_policy_lowercase",
|
||||
"cognito_user_pool_password_policy_minimum_length_14",
|
||||
"cognito_user_pool_password_policy_number",
|
||||
"cognito_user_pool_password_policy_symbol",
|
||||
"cognito_user_pool_password_policy_uppercase",
|
||||
"cognito_user_pool_self_registration_disabled",
|
||||
"cognito_user_pool_temporary_password_expiration",
|
||||
"cognito_user_pool_waf_acl_attached",
|
||||
"config_recorder_all_regions_enabled",
|
||||
"workspaces_vpc_2private_1public_subnets_nat",
|
||||
]
|
||||
assert scan.service_checks_to_execute == get_service_checks_to_execute(
|
||||
checks_to_execute
|
||||
)
|
||||
assert scan.service_checks_completed == {}
|
||||
assert scan.progress == 0
|
||||
assert scan.duration == 0
|
||||
assert scan.get_completed_services() == set()
|
||||
assert scan.get_completed_checks() == set()
|
||||
assert scan.provider == mock_provider
|
||||
# Check that the checks to execute are sorted and without duplicates
|
||||
assert scan.checks_to_execute == sorted(list(checks_to_execute))
|
||||
assert scan.service_checks_to_execute == get_service_checks_to_execute(
|
||||
checks_to_execute
|
||||
)
|
||||
assert scan.service_checks_completed == {}
|
||||
assert scan.progress == 0
|
||||
assert scan.duration == 0
|
||||
assert scan.get_completed_services() == set()
|
||||
assert scan.get_completed_checks() == set()
|
||||
|
||||
def test_init_with_no_checks(
|
||||
mock_provider,
|
||||
mock_recover_checks_from_provider,
|
||||
mock_load_check_metadata,
|
||||
mock_load_checks_to_execute,
|
||||
):
|
||||
checks_to_execute = set()
|
||||
mock_provider.type = "aws"
|
||||
|
||||
scan = Scan(mock_provider, checks=checks_to_execute)
|
||||
mock_load_check_metadata.assert_called_once()
|
||||
mock_load_checks_to_execute.assert_called_once()
|
||||
mock_recover_checks_from_provider.assert_called_once_with("aws")
|
||||
|
||||
assert scan.provider == mock_provider
|
||||
assert scan.checks_to_execute == ["accessanalyzer_enabled"]
|
||||
assert scan.service_checks_to_execute == get_service_checks_to_execute(
|
||||
["accessanalyzer_enabled"]
|
||||
)
|
||||
assert scan.service_checks_completed == {}
|
||||
assert scan.progress == 0
|
||||
assert scan.get_completed_services() == set()
|
||||
assert scan.get_completed_checks() == set()
|
||||
# Patch get_bulk to return only accessanalyzer_enabled
|
||||
with mock.patch(
|
||||
"prowler.lib.check.models.CheckMetadata.get_bulk"
|
||||
) as mock_get_bulk:
|
||||
mock_metadata = MagicMock()
|
||||
mock_metadata.ResourceType = "AWS::IAM::AccessAnalyzer"
|
||||
mock_metadata.Categories = []
|
||||
mock_get_bulk.return_value = {"accessanalyzer_enabled": mock_metadata}
|
||||
scan = Scan(mock_provider, checks=checks_to_execute)
|
||||
# Remove assertion for mock_load_check_metadata
|
||||
assert scan.provider == mock_provider
|
||||
assert scan.checks_to_execute == ["accessanalyzer_enabled"]
|
||||
assert scan.service_checks_to_execute == get_service_checks_to_execute(
|
||||
["accessanalyzer_enabled"]
|
||||
)
|
||||
assert scan.service_checks_completed == {}
|
||||
assert scan.progress == 0
|
||||
assert scan.get_completed_services() == set()
|
||||
assert scan.get_completed_checks() == set()
|
||||
|
||||
@patch("importlib.import_module")
|
||||
def test_scan(
|
||||
|
||||
@@ -243,6 +243,27 @@ def mock_recover_checks_from_aws_provider_cognito_service(*_):
|
||||
return []
|
||||
|
||||
|
||||
def mock_recover_checks_from_aws_provider_eks_service(*_):
|
||||
return [
|
||||
(
|
||||
"eks_cluster_not_publicly_accessible",
|
||||
"/root_dir/fake_path/eks/eks_cluster_not_publicly_accessible",
|
||||
),
|
||||
(
|
||||
"eks_cluster_uses_a_supported_version",
|
||||
"/root_dir/fake_path/eks/eks_cluster_uses_a_supported_version",
|
||||
),
|
||||
(
|
||||
"eks_cluster_network_policy_enabled",
|
||||
"/root_dir/fake_path/eks/eks_cluster_network_policy_enabled",
|
||||
),
|
||||
(
|
||||
"eks_control_plane_logging_all_types_enabled",
|
||||
"/root_dir/fake_path/eks/eks_control_plane_logging_all_types_enabled",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
class TestAWSProvider:
|
||||
@mock_aws
|
||||
def test_aws_provider_default(self):
|
||||
@@ -1604,6 +1625,27 @@ aws:
|
||||
|
||||
assert recovered_checks == expected_checks
|
||||
|
||||
@mock_aws
|
||||
@patch(
|
||||
"prowler.lib.check.utils.recover_checks_from_provider",
|
||||
new=mock_recover_checks_from_aws_provider_eks_service,
|
||||
)
|
||||
def test_get_checks_from_input_arn_eks(self):
|
||||
expected_checks = [
|
||||
"eks_cluster_not_publicly_accessible",
|
||||
"eks_cluster_uses_a_supported_version",
|
||||
"eks_cluster_network_policy_enabled",
|
||||
"eks_control_plane_logging_all_types_enabled",
|
||||
]
|
||||
|
||||
aws_provider = AwsProvider()
|
||||
aws_provider._audit_resources = [
|
||||
f"arn:aws:eks:us-east-1:{AWS_ACCOUNT_NUMBER}:cluster/test-eks"
|
||||
]
|
||||
recovered_checks = aws_provider.get_checks_from_input_arn()
|
||||
|
||||
assert set(recovered_checks) == set(expected_checks)
|
||||
|
||||
@mock_aws
|
||||
@patch(
|
||||
"prowler.lib.check.utils.recover_checks_from_provider",
|
||||
@@ -1712,13 +1754,13 @@ aws:
|
||||
assert not recovered_regions
|
||||
|
||||
def test_get_regions_all_count(self):
|
||||
assert len(AwsProvider.get_regions(partition=None)) == 36
|
||||
assert len(AwsProvider.get_regions(partition=None)) == 37
|
||||
|
||||
def test_get_regions_cn_count(self):
|
||||
assert len(AwsProvider.get_regions("aws-cn")) == 2
|
||||
|
||||
def test_get_regions_aws_count(self):
|
||||
assert len(AwsProvider.get_regions(partition="aws")) == 32
|
||||
assert len(AwsProvider.get_regions(partition="aws")) == 33
|
||||
|
||||
def test_get_all_regions(self):
|
||||
with patch(
|
||||
|
||||
@@ -304,7 +304,7 @@ class TestAWSMutelist:
|
||||
|
||||
mutelist = AWSMutelist(mutelist_content=mutelist_fixture)
|
||||
|
||||
assert mutelist.validate_mutelist()
|
||||
assert len(mutelist.validate_mutelist(mutelist_fixture)) > 0
|
||||
assert mutelist.mutelist == mutelist_fixture
|
||||
|
||||
def test_validate_mutelist_not_valid_key(self):
|
||||
@@ -317,7 +317,7 @@ class TestAWSMutelist:
|
||||
|
||||
mutelist = AWSMutelist(mutelist_content=mutelist_fixture)
|
||||
|
||||
assert not mutelist.validate_mutelist()
|
||||
assert len(mutelist.validate_mutelist(mutelist_fixture)) == 0
|
||||
assert mutelist.mutelist == {}
|
||||
assert mutelist.mutelist_file_path is None
|
||||
|
||||
|
||||
+393
@@ -0,0 +1,393 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from boto3 import client
|
||||
from moto import mock_aws
|
||||
|
||||
from tests.providers.aws.utils import AWS_REGION_EU_WEST_1, set_mocked_aws_provider
|
||||
|
||||
AWS_ACCOUNT_NUMBER = "123456789012"
|
||||
|
||||
|
||||
class Test_codebuild_project_uses_allowed_github_organizations:
|
||||
@mock_aws
|
||||
def test_no_projects(self):
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
|
||||
from prowler.providers.aws.services.codebuild.codebuild_service import Codebuild
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.codebuild_client",
|
||||
new=Codebuild(aws_provider),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.codebuild_client.audit_config",
|
||||
{"codebuild_github_allowed_organizations": ["allowed-org"]},
|
||||
),
|
||||
):
|
||||
from prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations import (
|
||||
codebuild_project_uses_allowed_github_organizations,
|
||||
)
|
||||
|
||||
check = codebuild_project_uses_allowed_github_organizations()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 0
|
||||
|
||||
@mock_aws
|
||||
def test_project_github_allowed_organization(self):
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
codebuild_client = client("codebuild", region_name=AWS_REGION_EU_WEST_1)
|
||||
iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1)
|
||||
project_name = "test-project-github-allowed"
|
||||
role_name = "codebuild-test-role"
|
||||
role_arn = iam_client.create_role(
|
||||
RoleName=role_name,
|
||||
AssumeRolePolicyDocument="""{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {"Service": "codebuild.amazonaws.com"},
|
||||
"Action": "sts:AssumeRole"
|
||||
}
|
||||
]
|
||||
}""",
|
||||
)["Role"]["Arn"]
|
||||
project_arn = codebuild_client.create_project(
|
||||
name=project_name,
|
||||
source={
|
||||
"type": "GITHUB",
|
||||
"location": "https://github.com/allowed-org/repo",
|
||||
},
|
||||
artifacts={"type": "NO_ARTIFACTS"},
|
||||
environment={
|
||||
"type": "LINUX_CONTAINER",
|
||||
"image": "aws/codebuild/standard:4.0",
|
||||
"computeType": "BUILD_GENERAL1_SMALL",
|
||||
"environmentVariables": [],
|
||||
},
|
||||
serviceRole=role_arn,
|
||||
tags=[{"key": "Name", "value": "test"}],
|
||||
)["project"]["arn"]
|
||||
|
||||
from prowler.providers.aws.services.codebuild.codebuild_service import Codebuild
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.codebuild_client",
|
||||
new=Codebuild(aws_provider),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.codebuild_client.audit_config",
|
||||
{"codebuild_github_allowed_organizations": ["allowed-org"]},
|
||||
),
|
||||
):
|
||||
from prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations import (
|
||||
codebuild_project_uses_allowed_github_organizations,
|
||||
)
|
||||
|
||||
check = codebuild_project_uses_allowed_github_organizations()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert result[0].resource_id == project_name
|
||||
assert result[0].resource_arn == project_arn
|
||||
assert "which is in the allowed organizations" in result[0].status_extended
|
||||
assert result[0].region == AWS_REGION_EU_WEST_1
|
||||
|
||||
@mock_aws
|
||||
def test_project_github_not_allowed_organization(self):
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
codebuild_client = client("codebuild", region_name=AWS_REGION_EU_WEST_1)
|
||||
iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1)
|
||||
project_name = "test-project-github-not-allowed"
|
||||
role_name = "codebuild-test-role"
|
||||
role_arn = iam_client.create_role(
|
||||
RoleName=role_name,
|
||||
AssumeRolePolicyDocument="""{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {"Service": "codebuild.amazonaws.com"},
|
||||
"Action": "sts:AssumeRole"
|
||||
}
|
||||
]
|
||||
}""",
|
||||
)["Role"]["Arn"]
|
||||
project_arn = codebuild_client.create_project(
|
||||
name=project_name,
|
||||
source={
|
||||
"type": "GITHUB",
|
||||
"location": "https://github.com/not-allowed-org/repo",
|
||||
},
|
||||
artifacts={"type": "NO_ARTIFACTS"},
|
||||
environment={
|
||||
"type": "LINUX_CONTAINER",
|
||||
"image": "aws/codebuild/standard:4.0",
|
||||
"computeType": "BUILD_GENERAL1_SMALL",
|
||||
"environmentVariables": [],
|
||||
},
|
||||
serviceRole=role_arn,
|
||||
tags=[{"key": "Name", "value": "test"}],
|
||||
)["project"]["arn"]
|
||||
|
||||
from prowler.providers.aws.services.codebuild.codebuild_service import Codebuild
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.codebuild_client",
|
||||
new=Codebuild(aws_provider),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.codebuild_client.audit_config",
|
||||
{"codebuild_github_allowed_organizations": ["allowed-org"]},
|
||||
),
|
||||
):
|
||||
from prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations import (
|
||||
codebuild_project_uses_allowed_github_organizations,
|
||||
)
|
||||
|
||||
check = codebuild_project_uses_allowed_github_organizations()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].resource_id == project_name
|
||||
assert result[0].resource_arn == project_arn
|
||||
assert (
|
||||
"which is not in the allowed organizations" in result[0].status_extended
|
||||
)
|
||||
assert result[0].region == AWS_REGION_EU_WEST_1
|
||||
|
||||
@mock_aws
|
||||
def test_project_github_no_codebuild_trusted_principal(self):
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
|
||||
from prowler.providers.aws.services.codebuild.codebuild_service import Codebuild
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.codebuild_client",
|
||||
new=Codebuild(aws_provider),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.codebuild_client.audit_config",
|
||||
{"codebuild_github_allowed_organizations": ["allowed-org"]},
|
||||
),
|
||||
):
|
||||
from prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations import (
|
||||
codebuild_project_uses_allowed_github_organizations,
|
||||
)
|
||||
|
||||
check = codebuild_project_uses_allowed_github_organizations()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
@mock_aws
|
||||
def test_project_github_enterprise_allowed_organization(self):
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
codebuild_client = client("codebuild", region_name=AWS_REGION_EU_WEST_1)
|
||||
iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1)
|
||||
project_name = "test-project-github-enterprise-allowed"
|
||||
role_name = "codebuild-test-role"
|
||||
role_arn = iam_client.create_role(
|
||||
RoleName=role_name,
|
||||
AssumeRolePolicyDocument="""{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {"Service": "codebuild.amazonaws.com"},
|
||||
"Action": "sts:AssumeRole"
|
||||
}
|
||||
]
|
||||
}""",
|
||||
)["Role"]["Arn"]
|
||||
project_arn = codebuild_client.create_project(
|
||||
name=project_name,
|
||||
source={
|
||||
"type": "GITHUB_ENTERPRISE",
|
||||
"location": "https://github.enterprise.com/allowed-org/repo",
|
||||
},
|
||||
artifacts={"type": "NO_ARTIFACTS"},
|
||||
environment={
|
||||
"type": "LINUX_CONTAINER",
|
||||
"image": "aws/codebuild/standard:4.0",
|
||||
"computeType": "BUILD_GENERAL1_SMALL",
|
||||
"environmentVariables": [],
|
||||
},
|
||||
serviceRole=role_arn,
|
||||
tags=[{"key": "Name", "value": "test"}],
|
||||
)["project"]["arn"]
|
||||
|
||||
from prowler.providers.aws.services.codebuild.codebuild_service import Codebuild
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.codebuild_client",
|
||||
new=Codebuild(aws_provider),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.codebuild_client.audit_config",
|
||||
{"codebuild_github_allowed_organizations": ["allowed-org"]},
|
||||
),
|
||||
):
|
||||
from prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations import (
|
||||
codebuild_project_uses_allowed_github_organizations,
|
||||
)
|
||||
|
||||
check = codebuild_project_uses_allowed_github_organizations()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert result[0].resource_id == project_name
|
||||
assert result[0].resource_arn == project_arn
|
||||
assert "which is in the allowed organizations" in result[0].status_extended
|
||||
assert result[0].region == AWS_REGION_EU_WEST_1
|
||||
|
||||
@mock_aws
|
||||
def test_project_github_enterprise_not_allowed_organization(self):
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
codebuild_client = client("codebuild", region_name=AWS_REGION_EU_WEST_1)
|
||||
iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1)
|
||||
project_name = "test-project-github-enterprise-not-allowed"
|
||||
role_name = "codebuild-test-role"
|
||||
role_arn = iam_client.create_role(
|
||||
RoleName=role_name,
|
||||
AssumeRolePolicyDocument="""{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {"Service": "codebuild.amazonaws.com"},
|
||||
"Action": "sts:AssumeRole"
|
||||
}
|
||||
]
|
||||
}""",
|
||||
)["Role"]["Arn"]
|
||||
project_arn = codebuild_client.create_project(
|
||||
name=project_name,
|
||||
source={
|
||||
"type": "GITHUB_ENTERPRISE",
|
||||
"location": "https://github.enterprise.com/not-allowed-org/repo",
|
||||
},
|
||||
artifacts={"type": "NO_ARTIFACTS"},
|
||||
environment={
|
||||
"type": "LINUX_CONTAINER",
|
||||
"image": "aws/codebuild/standard:4.0",
|
||||
"computeType": "BUILD_GENERAL1_SMALL",
|
||||
"environmentVariables": [],
|
||||
},
|
||||
serviceRole=role_arn,
|
||||
tags=[{"key": "Name", "value": "test"}],
|
||||
)["project"]["arn"]
|
||||
|
||||
from prowler.providers.aws.services.codebuild.codebuild_service import Codebuild
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.codebuild_client",
|
||||
new=Codebuild(aws_provider),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.codebuild_client.audit_config",
|
||||
{"codebuild_github_allowed_organizations": ["allowed-org"]},
|
||||
),
|
||||
):
|
||||
from prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations import (
|
||||
codebuild_project_uses_allowed_github_organizations,
|
||||
)
|
||||
|
||||
check = codebuild_project_uses_allowed_github_organizations()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].resource_id == project_name
|
||||
assert result[0].resource_arn == project_arn
|
||||
assert (
|
||||
"which is not in the allowed organizations" in result[0].status_extended
|
||||
)
|
||||
assert result[0].region == AWS_REGION_EU_WEST_1
|
||||
|
||||
@mock_aws
|
||||
def test_project_github_enterprise_no_codebuild_trusted_principal(self):
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
|
||||
from prowler.providers.aws.services.codebuild.codebuild_service import Codebuild
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.codebuild_client",
|
||||
new=Codebuild(aws_provider),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.codebuild_client.audit_config",
|
||||
{"codebuild_github_allowed_organizations": ["allowed-org"]},
|
||||
),
|
||||
):
|
||||
from prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations import (
|
||||
codebuild_project_uses_allowed_github_organizations,
|
||||
)
|
||||
|
||||
check = codebuild_project_uses_allowed_github_organizations()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
+184
@@ -33,11 +33,13 @@ class Test_iam_no_root_access_key_test:
|
||||
service_client.credential_report[0][
|
||||
"arn"
|
||||
] = "arn:aws:iam::123456789012:user/<root_account>"
|
||||
service_client.credential_report[0]["password_enabled"] = "true"
|
||||
service_client.credential_report[0]["access_key_1_active"] = "false"
|
||||
service_client.credential_report[0]["access_key_2_active"] = "false"
|
||||
check = iam_no_root_access_key()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
@@ -75,11 +77,13 @@ class Test_iam_no_root_access_key_test:
|
||||
service_client.credential_report[0][
|
||||
"arn"
|
||||
] = "arn:aws:iam::123456789012:user/<root_account>"
|
||||
service_client.credential_report[0]["password_enabled"] = "true"
|
||||
service_client.credential_report[0]["access_key_1_active"] = "true"
|
||||
service_client.credential_report[0]["access_key_2_active"] = "false"
|
||||
check = iam_no_root_access_key()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
@@ -117,11 +121,13 @@ class Test_iam_no_root_access_key_test:
|
||||
service_client.credential_report[0][
|
||||
"arn"
|
||||
] = "arn:aws:iam::123456789012:user/<root_account>"
|
||||
service_client.credential_report[0]["password_enabled"] = "false"
|
||||
service_client.credential_report[0]["access_key_1_active"] = "false"
|
||||
service_client.credential_report[0]["access_key_2_active"] = "true"
|
||||
check = iam_no_root_access_key()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
@@ -159,11 +165,13 @@ class Test_iam_no_root_access_key_test:
|
||||
service_client.credential_report[0][
|
||||
"arn"
|
||||
] = "arn:aws:iam::123456789012:user/<root_account>"
|
||||
service_client.credential_report[0]["password_enabled"] = "false"
|
||||
service_client.credential_report[0]["access_key_1_active"] = "true"
|
||||
service_client.credential_report[0]["access_key_2_active"] = "true"
|
||||
check = iam_no_root_access_key()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
@@ -174,3 +182,179 @@ class Test_iam_no_root_access_key_test:
|
||||
result[0].resource_arn
|
||||
== "arn:aws:iam::123456789012:user/<root_account>"
|
||||
)
|
||||
|
||||
@mock_aws
|
||||
def test_root_no_credentials(self):
|
||||
iam_client = client("iam")
|
||||
user = "test"
|
||||
iam_client.create_user(UserName=user)["User"]["Arn"]
|
||||
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
):
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_no_root_access_key.iam_no_root_access_key.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
) as service_client:
|
||||
from prowler.providers.aws.services.iam.iam_no_root_access_key.iam_no_root_access_key import (
|
||||
iam_no_root_access_key,
|
||||
)
|
||||
|
||||
service_client.credential_report[0]["user"] = "<root_account>"
|
||||
service_client.credential_report[0][
|
||||
"arn"
|
||||
] = "arn:aws:iam::123456789012:user/<root_account>"
|
||||
service_client.credential_report[0]["password_enabled"] = "false"
|
||||
service_client.credential_report[0]["access_key_1_active"] = "false"
|
||||
service_client.credential_report[0]["access_key_2_active"] = "false"
|
||||
check = iam_no_root_access_key()
|
||||
result = check.execute()
|
||||
|
||||
# Should return no findings since root has no credentials
|
||||
assert len(result) == 0
|
||||
|
||||
@mock_aws
|
||||
def test_root_no_access_keys_with_organizational_management_enabled(self):
|
||||
iam_client = client("iam")
|
||||
user = "test"
|
||||
iam_client.create_user(UserName=user)["User"]["Arn"]
|
||||
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
):
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_no_root_access_key.iam_no_root_access_key.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
) as service_client:
|
||||
from prowler.providers.aws.services.iam.iam_no_root_access_key.iam_no_root_access_key import (
|
||||
iam_no_root_access_key,
|
||||
)
|
||||
|
||||
# Set up organizational root management
|
||||
service_client.organization_features = ["RootCredentialsManagement"]
|
||||
|
||||
service_client.credential_report[0]["user"] = "<root_account>"
|
||||
service_client.credential_report[0][
|
||||
"arn"
|
||||
] = "arn:aws:iam::123456789012:user/<root_account>"
|
||||
service_client.credential_report[0]["password_enabled"] = "true"
|
||||
service_client.credential_report[0]["access_key_1_active"] = "false"
|
||||
service_client.credential_report[0]["access_key_2_active"] = "false"
|
||||
check = iam_no_root_access_key()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Root account has password configured but no access keys. Consider removing individual root credentials since organizational root management is active."
|
||||
)
|
||||
assert result[0].resource_id == "<root_account>"
|
||||
assert (
|
||||
result[0].resource_arn
|
||||
== "arn:aws:iam::123456789012:user/<root_account>"
|
||||
)
|
||||
|
||||
@mock_aws
|
||||
def test_root_access_keys_with_organizational_management_enabled(self):
|
||||
iam_client = client("iam")
|
||||
user = "test"
|
||||
iam_client.create_user(UserName=user)["User"]["Arn"]
|
||||
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
):
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_no_root_access_key.iam_no_root_access_key.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
) as service_client:
|
||||
from prowler.providers.aws.services.iam.iam_no_root_access_key.iam_no_root_access_key import (
|
||||
iam_no_root_access_key,
|
||||
)
|
||||
|
||||
# Set up organizational root management
|
||||
service_client.organization_features = ["RootCredentialsManagement"]
|
||||
|
||||
service_client.credential_report[0]["user"] = "<root_account>"
|
||||
service_client.credential_report[0][
|
||||
"arn"
|
||||
] = "arn:aws:iam::123456789012:user/<root_account>"
|
||||
service_client.credential_report[0]["password_enabled"] = "true"
|
||||
service_client.credential_report[0]["access_key_1_active"] = "true"
|
||||
service_client.credential_report[0]["access_key_2_active"] = "false"
|
||||
check = iam_no_root_access_key()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Root account has one active access key despite organizational root management being enabled."
|
||||
)
|
||||
assert result[0].resource_id == "<root_account>"
|
||||
assert (
|
||||
result[0].resource_arn
|
||||
== "arn:aws:iam::123456789012:user/<root_account>"
|
||||
)
|
||||
|
||||
@mock_aws
|
||||
def test_root_both_access_keys_with_organizational_management_enabled(self):
|
||||
iam_client = client("iam")
|
||||
user = "test"
|
||||
iam_client.create_user(UserName=user)["User"]["Arn"]
|
||||
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
):
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_no_root_access_key.iam_no_root_access_key.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
) as service_client:
|
||||
from prowler.providers.aws.services.iam.iam_no_root_access_key.iam_no_root_access_key import (
|
||||
iam_no_root_access_key,
|
||||
)
|
||||
|
||||
# Set up organizational root management
|
||||
service_client.organization_features = ["RootCredentialsManagement"]
|
||||
|
||||
service_client.credential_report[0]["user"] = "<root_account>"
|
||||
service_client.credential_report[0][
|
||||
"arn"
|
||||
] = "arn:aws:iam::123456789012:user/<root_account>"
|
||||
service_client.credential_report[0]["password_enabled"] = "false"
|
||||
service_client.credential_report[0]["access_key_1_active"] = "true"
|
||||
service_client.credential_report[0]["access_key_2_active"] = "true"
|
||||
check = iam_no_root_access_key()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Root account has two active access keys despite organizational root management being enabled."
|
||||
)
|
||||
assert result[0].resource_id == "<root_account>"
|
||||
assert (
|
||||
result[0].resource_arn
|
||||
== "arn:aws:iam::123456789012:user/<root_account>"
|
||||
)
|
||||
+109
@@ -334,6 +334,59 @@ class Test_iam_policy_allows_privilege_escalation:
|
||||
)
|
||||
assert search("iam:PassRole", result[0].status_extended)
|
||||
|
||||
@mock_aws
|
||||
def test_iam_policy_allows_privilege_escalation_iam_PassRole_using_wildcard(
|
||||
self,
|
||||
):
|
||||
iam_client = client("iam", region_name=AWS_REGION_US_EAST_1)
|
||||
policy_name = "policy1"
|
||||
policy_document = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": "iam:Pass*",
|
||||
"Resource": f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:role/ecs",
|
||||
}
|
||||
],
|
||||
}
|
||||
policy_arn = iam_client.create_policy(
|
||||
PolicyName=policy_name, PolicyDocument=dumps(policy_document)
|
||||
)["Policy"]["Arn"]
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_policy_allows_privilege_escalation.iam_policy_allows_privilege_escalation.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.iam.iam_policy_allows_privilege_escalation.iam_policy_allows_privilege_escalation import (
|
||||
iam_policy_allows_privilege_escalation,
|
||||
)
|
||||
|
||||
check = iam_policy_allows_privilege_escalation()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].resource_id == policy_name
|
||||
assert result[0].resource_arn == policy_arn
|
||||
assert result[0].region == AWS_REGION_US_EAST_1
|
||||
assert result[0].resource_tags == []
|
||||
|
||||
assert search(
|
||||
f"Custom Policy {policy_arn} allows privilege escalation using the following actions: ",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert search("iam:PassRole", result[0].status_extended)
|
||||
|
||||
@mock_aws
|
||||
def test_iam_policy_allows_privilege_escalation_two_combinations(
|
||||
self,
|
||||
@@ -918,6 +971,62 @@ class Test_iam_policy_allows_privilege_escalation:
|
||||
)
|
||||
assert search("iam:Put*", finding.status_extended)
|
||||
|
||||
@mock_aws
|
||||
def test_iam_policy_allows_privilege_escalation_iam_put_using_wildcard(
|
||||
self,
|
||||
):
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
iam_client = client("iam", region_name=AWS_REGION_US_EAST_1)
|
||||
policy_name_1 = "privileged_policy_1"
|
||||
policy_document_1 = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "Statement01",
|
||||
"Effect": "Allow",
|
||||
"Action": ["iam:Put*y"],
|
||||
"Resource": "*",
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
policy_arn_1 = iam_client.create_policy(
|
||||
PolicyName=policy_name_1, PolicyDocument=dumps(policy_document_1)
|
||||
)["Policy"]["Arn"]
|
||||
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_policy_allows_privilege_escalation.iam_policy_allows_privilege_escalation.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.iam.iam_policy_allows_privilege_escalation.iam_policy_allows_privilege_escalation import (
|
||||
iam_policy_allows_privilege_escalation,
|
||||
)
|
||||
|
||||
check = iam_policy_allows_privilege_escalation()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
for finding in result:
|
||||
if finding.resource_id == policy_name_1:
|
||||
assert finding.status == "FAIL"
|
||||
assert finding.resource_id == policy_name_1
|
||||
assert finding.resource_arn == policy_arn_1
|
||||
assert finding.region == AWS_REGION_US_EAST_1
|
||||
assert finding.resource_tags == []
|
||||
assert search(
|
||||
f"Custom Policy {policy_arn_1} allows privilege escalation using the following actions:",
|
||||
finding.status_extended,
|
||||
)
|
||||
assert search("iam:Put*", finding.status_extended)
|
||||
|
||||
@mock_aws
|
||||
def test_iam_policy_allows_privilege_escalation_iam_wildcard(
|
||||
self,
|
||||
|
||||
+165
@@ -48,6 +48,88 @@ class Test_iam_policy_no_full_access_to_kms:
|
||||
assert result[0].resource_arn == arn
|
||||
assert result[0].region == "us-east-1"
|
||||
|
||||
|
||||
class Test_iam_policy_no_full_access_to_kms_with_double_start:
|
||||
@mock_aws
|
||||
def test_policy_full_access_to_kms(self):
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
iam_client = client("iam")
|
||||
policy_name = "policy_kms_full"
|
||||
policy_document_full_access = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{"Effect": "Allow", "Action": "kms:**", "Resource": "*"},
|
||||
],
|
||||
}
|
||||
arn = iam_client.create_policy(
|
||||
PolicyName=policy_name, PolicyDocument=dumps(policy_document_full_access)
|
||||
)["Policy"]["Arn"]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
):
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_policy_no_full_access_to_kms.iam_policy_no_full_access_to_kms.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.iam.iam_policy_no_full_access_to_kms.iam_policy_no_full_access_to_kms import (
|
||||
iam_policy_no_full_access_to_kms,
|
||||
)
|
||||
|
||||
check = iam_policy_no_full_access_to_kms()
|
||||
result = check.execute()
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Custom Policy {policy_name} allows 'kms:*' privileges."
|
||||
)
|
||||
assert result[0].resource_id == "policy_kms_full"
|
||||
assert result[0].resource_arn == arn
|
||||
assert result[0].region == "us-east-1"
|
||||
|
||||
|
||||
class Test_iam_policy_no_full_access_to_kms_with_unicode:
|
||||
@mock_aws
|
||||
def test_policy_full_access_to_kms(self):
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
iam_client = client("iam")
|
||||
policy_name = "policy_kms_full"
|
||||
policy_document_full_access = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{"Effect": "\u0041llow", "Action": "km\u0073:*", "Resource": "*"},
|
||||
],
|
||||
}
|
||||
arn = iam_client.create_policy(
|
||||
PolicyName=policy_name, PolicyDocument=dumps(policy_document_full_access)
|
||||
)["Policy"]["Arn"]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
):
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_policy_no_full_access_to_kms.iam_policy_no_full_access_to_kms.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.iam.iam_policy_no_full_access_to_kms.iam_policy_no_full_access_to_kms import (
|
||||
iam_policy_no_full_access_to_kms,
|
||||
)
|
||||
|
||||
check = iam_policy_no_full_access_to_kms()
|
||||
result = check.execute()
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Custom Policy {policy_name} allows 'kms:*' privileges."
|
||||
)
|
||||
assert result[0].resource_id == "policy_kms_full"
|
||||
assert result[0].resource_arn == arn
|
||||
assert result[0].region == "us-east-1"
|
||||
|
||||
@mock_aws
|
||||
def test_policy_no_full_access_to_kms(self):
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
@@ -165,6 +247,49 @@ class Test_iam_policy_no_full_access_to_kms:
|
||||
assert result[0].resource_arn == arn
|
||||
assert result[0].region == "us-east-1"
|
||||
|
||||
@mock_aws
|
||||
def test_policy_full_access_to_limited_kms_key(self):
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
iam_client = client("iam")
|
||||
policy_name = "policy_dev_kms"
|
||||
policy_document_no_full_access = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": ["kms:*"],
|
||||
"Resource": f"arn:aws:kms:{AWS_REGION_US_EAST_1}:123456789012:alias/dev-key",
|
||||
},
|
||||
],
|
||||
}
|
||||
arn = iam_client.create_policy(
|
||||
PolicyName=policy_name, PolicyDocument=dumps(policy_document_no_full_access)
|
||||
)["Policy"]["Arn"]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
):
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_policy_no_full_access_to_kms.iam_policy_no_full_access_to_kms.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.iam.iam_policy_no_full_access_to_kms.iam_policy_no_full_access_to_kms import (
|
||||
iam_policy_no_full_access_to_kms,
|
||||
)
|
||||
|
||||
check = iam_policy_no_full_access_to_kms()
|
||||
result = check.execute()
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Custom Policy {policy_name} does not allow 'kms:*' privileges."
|
||||
)
|
||||
assert result[0].resource_id == "policy_dev_kms"
|
||||
assert result[0].resource_arn == arn
|
||||
assert result[0].region == "us-east-1"
|
||||
|
||||
@mock_aws
|
||||
def test_policy_no_full_access_to_kms_through_no_actions(self):
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
@@ -203,3 +328,43 @@ class Test_iam_policy_no_full_access_to_kms:
|
||||
assert result[0].resource_id == "policy_no_kms_full"
|
||||
assert result[0].resource_arn == arn
|
||||
assert result[0].region == "us-east-1"
|
||||
|
||||
@mock_aws
|
||||
def test_policy_full_access_and_full_deny_to_kms(self):
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
iam_client = client("iam")
|
||||
policy_name = "policy_no_kms_full"
|
||||
policy_document_full_access = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{"Effect": "Allow", "Action": "kms:*", "Resource": "*"},
|
||||
{"Effect": "Deny", "Action": "kms:*", "Resource": "*"},
|
||||
],
|
||||
}
|
||||
arn = iam_client.create_policy(
|
||||
PolicyName=policy_name, PolicyDocument=dumps(policy_document_full_access)
|
||||
)["Policy"]["Arn"]
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
):
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_policy_no_full_access_to_kms.iam_policy_no_full_access_to_kms.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.iam.iam_policy_no_full_access_to_kms.iam_policy_no_full_access_to_kms import (
|
||||
iam_policy_no_full_access_to_kms,
|
||||
)
|
||||
|
||||
check = iam_policy_no_full_access_to_kms()
|
||||
result = check.execute()
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Custom Policy {policy_name} does not allow 'kms:*' privileges."
|
||||
)
|
||||
assert result[0].resource_id == "policy_no_kms_full"
|
||||
assert result[0].resource_arn == arn
|
||||
assert result[0].region == "us-east-1"
|
||||
+267
-36
@@ -1,5 +1,9 @@
|
||||
from re import search
|
||||
from unittest import mock
|
||||
|
||||
from boto3 import client
|
||||
from moto import mock_aws
|
||||
|
||||
from tests.providers.aws.utils import (
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
AWS_REGION_US_EAST_1,
|
||||
@@ -15,23 +19,14 @@ class Test_iam_root_hardware_mfa_enabled_test:
|
||||
set_mocked_aws_provider,
|
||||
)
|
||||
|
||||
@mock_aws
|
||||
def test_root_virtual_mfa_enabled(self):
|
||||
iam_client = mock.MagicMock
|
||||
iam_client.account_summary = {
|
||||
"SummaryMap": {"AccountMFAEnabled": 1},
|
||||
}
|
||||
iam_client.virtual_mfa_devices = [
|
||||
{
|
||||
"SerialNumber": f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:mfa/mfa",
|
||||
"User": {"Arn": f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root"},
|
||||
}
|
||||
]
|
||||
iam_client.audited_partition = "aws"
|
||||
iam_client.region = AWS_REGION_US_EAST_1
|
||||
iam_client.mfa_arn_template = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:mfa"
|
||||
iam_client.organization_features = []
|
||||
iam_client = client("iam")
|
||||
user = "test-user"
|
||||
iam_client.create_user(UserName=user)["User"]["Arn"]
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
@@ -40,13 +35,32 @@ class Test_iam_root_hardware_mfa_enabled_test:
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_root_hardware_mfa_enabled.iam_root_hardware_mfa_enabled.iam_client",
|
||||
new=iam_client,
|
||||
),
|
||||
new=IAM(aws_provider),
|
||||
) as service_client,
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_root_hardware_mfa_enabled.iam_root_hardware_mfa_enabled import (
|
||||
iam_root_hardware_mfa_enabled,
|
||||
)
|
||||
|
||||
# Set up virtual MFA device for root
|
||||
service_client.virtual_mfa_devices = [
|
||||
{
|
||||
"SerialNumber": f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:mfa/mfa",
|
||||
"User": {"Arn": f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root"},
|
||||
}
|
||||
]
|
||||
service_client.account_summary = {
|
||||
"SummaryMap": {"AccountMFAEnabled": 1},
|
||||
}
|
||||
|
||||
service_client.credential_report[0]["user"] = "<root_account>"
|
||||
service_client.credential_report[0]["password_enabled"] = "true"
|
||||
service_client.credential_report[0]["access_key_1_active"] = "false"
|
||||
service_client.credential_report[0]["access_key_2_active"] = "false"
|
||||
service_client.credential_report[0][
|
||||
"arn"
|
||||
] = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root"
|
||||
|
||||
check = iam_root_hardware_mfa_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
@@ -58,18 +72,14 @@ class Test_iam_root_hardware_mfa_enabled_test:
|
||||
assert result[0].resource_id == "<root_account>"
|
||||
assert result[0].resource_arn == f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:mfa"
|
||||
|
||||
@mock_aws
|
||||
def test_root_hardware_mfa_enabled(self):
|
||||
iam_client = mock.MagicMock
|
||||
iam_client.account_summary = {
|
||||
"SummaryMap": {"AccountMFAEnabled": 1},
|
||||
}
|
||||
iam_client.virtual_mfa_devices = []
|
||||
iam_client.audited_partition = "aws"
|
||||
iam_client.region = AWS_REGION_US_EAST_1
|
||||
iam_client.mfa_arn_template = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:mfa"
|
||||
iam_client.organization_features = []
|
||||
iam_client = client("iam")
|
||||
user = "test-user"
|
||||
iam_client.create_user(UserName=user)["User"]["Arn"]
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
@@ -78,13 +88,27 @@ class Test_iam_root_hardware_mfa_enabled_test:
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_root_hardware_mfa_enabled.iam_root_hardware_mfa_enabled.iam_client",
|
||||
new=iam_client,
|
||||
),
|
||||
new=IAM(aws_provider),
|
||||
) as service_client,
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_root_hardware_mfa_enabled.iam_root_hardware_mfa_enabled import (
|
||||
iam_root_hardware_mfa_enabled,
|
||||
)
|
||||
|
||||
# No virtual MFA devices (indicating hardware MFA)
|
||||
service_client.virtual_mfa_devices = []
|
||||
service_client.account_summary = {
|
||||
"SummaryMap": {"AccountMFAEnabled": 1},
|
||||
}
|
||||
|
||||
service_client.credential_report[0]["user"] = "<root_account>"
|
||||
service_client.credential_report[0]["password_enabled"] = "true"
|
||||
service_client.credential_report[0]["access_key_1_active"] = "false"
|
||||
service_client.credential_report[0]["access_key_2_active"] = "false"
|
||||
service_client.credential_report[0][
|
||||
"arn"
|
||||
] = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root"
|
||||
|
||||
check = iam_root_hardware_mfa_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
@@ -96,16 +120,14 @@ class Test_iam_root_hardware_mfa_enabled_test:
|
||||
assert result[0].resource_id == "<root_account>"
|
||||
assert result[0].resource_arn == f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:mfa"
|
||||
|
||||
@mock_aws
|
||||
def test_root_hardware_mfa_enabled_none_summary(self):
|
||||
iam_client = mock.MagicMock
|
||||
iam_client.account_summary = None
|
||||
iam_client.virtual_mfa_devices = []
|
||||
iam_client.audited_partition = "aws"
|
||||
iam_client.region = AWS_REGION_US_EAST_1
|
||||
iam_client.mfa_arn_template = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:mfa"
|
||||
iam_client.organization_features = []
|
||||
iam_client = client("iam")
|
||||
user = "test-user"
|
||||
iam_client.create_user(UserName=user)["User"]["Arn"]
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
@@ -114,13 +136,222 @@ class Test_iam_root_hardware_mfa_enabled_test:
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_root_hardware_mfa_enabled.iam_root_hardware_mfa_enabled.iam_client",
|
||||
new=iam_client,
|
||||
),
|
||||
new=IAM(aws_provider),
|
||||
) as service_client,
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_root_hardware_mfa_enabled.iam_root_hardware_mfa_enabled import (
|
||||
iam_root_hardware_mfa_enabled,
|
||||
)
|
||||
|
||||
# No account summary
|
||||
service_client.account_summary = None
|
||||
service_client.virtual_mfa_devices = []
|
||||
|
||||
service_client.credential_report[0]["user"] = "<root_account>"
|
||||
service_client.credential_report[0]["password_enabled"] = "true"
|
||||
service_client.credential_report[0]["access_key_1_active"] = "false"
|
||||
service_client.credential_report[0]["access_key_2_active"] = "false"
|
||||
service_client.credential_report[0][
|
||||
"arn"
|
||||
] = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root"
|
||||
|
||||
check = iam_root_hardware_mfa_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
@mock_aws
|
||||
def test_root_no_credentials(self):
|
||||
iam_client = client("iam")
|
||||
user = "test-user"
|
||||
iam_client.create_user(UserName=user)["User"]["Arn"]
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_root_hardware_mfa_enabled.iam_root_hardware_mfa_enabled.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
) as service_client,
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_root_hardware_mfa_enabled.iam_root_hardware_mfa_enabled import (
|
||||
iam_root_hardware_mfa_enabled,
|
||||
)
|
||||
|
||||
service_client.account_summary = {
|
||||
"SummaryMap": {"AccountMFAEnabled": 1},
|
||||
}
|
||||
service_client.virtual_mfa_devices = []
|
||||
|
||||
service_client.credential_report[0]["user"] = "<root_account>"
|
||||
service_client.credential_report[0]["password_enabled"] = "false"
|
||||
service_client.credential_report[0]["access_key_1_active"] = "false"
|
||||
service_client.credential_report[0]["access_key_2_active"] = "false"
|
||||
service_client.credential_report[0][
|
||||
"arn"
|
||||
] = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root"
|
||||
|
||||
check = iam_root_hardware_mfa_enabled()
|
||||
result = check.execute()
|
||||
# Should return no findings since root has no credentials
|
||||
assert len(result) == 0
|
||||
|
||||
@mock_aws
|
||||
def test_root_hardware_mfa_with_organizational_management_enabled(self):
|
||||
iam_client = client("iam")
|
||||
user = "test-user"
|
||||
iam_client.create_user(UserName=user)["User"]["Arn"]
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_root_hardware_mfa_enabled.iam_root_hardware_mfa_enabled.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
) as service_client,
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_root_hardware_mfa_enabled.iam_root_hardware_mfa_enabled import (
|
||||
iam_root_hardware_mfa_enabled,
|
||||
)
|
||||
|
||||
# Set up organizational root management
|
||||
service_client.organization_features = ["RootCredentialsManagement"]
|
||||
service_client.account_summary = {
|
||||
"SummaryMap": {"AccountMFAEnabled": 1},
|
||||
}
|
||||
service_client.virtual_mfa_devices = []
|
||||
|
||||
service_client.credential_report[0]["user"] = "<root_account>"
|
||||
service_client.credential_report[0]["password_enabled"] = "true"
|
||||
service_client.credential_report[0]["access_key_1_active"] = "true"
|
||||
service_client.credential_report[0]["access_key_2_active"] = "false"
|
||||
service_client.credential_report[0][
|
||||
"arn"
|
||||
] = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root"
|
||||
|
||||
check = iam_root_hardware_mfa_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
"Root account has credentials with hardware MFA enabled. "
|
||||
"Consider removing individual root credentials since organizational "
|
||||
"root management is active.",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == "<root_account>"
|
||||
assert result[0].resource_arn == f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:mfa"
|
||||
|
||||
@mock_aws
|
||||
def test_root_virtual_mfa_with_organizational_management_enabled(self):
|
||||
iam_client = client("iam")
|
||||
user = "test-user"
|
||||
iam_client.create_user(UserName=user)["User"]["Arn"]
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_root_hardware_mfa_enabled.iam_root_hardware_mfa_enabled.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
) as service_client,
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_root_hardware_mfa_enabled.iam_root_hardware_mfa_enabled import (
|
||||
iam_root_hardware_mfa_enabled,
|
||||
)
|
||||
|
||||
# Set up organizational root management
|
||||
service_client.organization_features = ["RootCredentialsManagement"]
|
||||
service_client.account_summary = {
|
||||
"SummaryMap": {"AccountMFAEnabled": 1},
|
||||
}
|
||||
service_client.virtual_mfa_devices = [
|
||||
{
|
||||
"SerialNumber": f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:mfa/mfa",
|
||||
"User": {"Arn": f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root"},
|
||||
}
|
||||
]
|
||||
|
||||
service_client.credential_report[0]["user"] = "<root_account>"
|
||||
service_client.credential_report[0]["password_enabled"] = "true"
|
||||
service_client.credential_report[0]["access_key_1_active"] = "false"
|
||||
service_client.credential_report[0]["access_key_2_active"] = "true"
|
||||
service_client.credential_report[0][
|
||||
"arn"
|
||||
] = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root"
|
||||
|
||||
check = iam_root_hardware_mfa_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
"Root account has credentials with virtual MFA instead of hardware MFA "
|
||||
"despite organizational root management being enabled.",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == "<root_account>"
|
||||
assert result[0].resource_arn == f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:mfa"
|
||||
|
||||
@mock_aws
|
||||
def test_root_no_mfa_with_organizational_management_enabled(self):
|
||||
iam_client = client("iam")
|
||||
user = "test-user"
|
||||
iam_client.create_user(UserName=user)["User"]["Arn"]
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_root_hardware_mfa_enabled.iam_root_hardware_mfa_enabled.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
) as service_client,
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_root_hardware_mfa_enabled.iam_root_hardware_mfa_enabled import (
|
||||
iam_root_hardware_mfa_enabled,
|
||||
)
|
||||
|
||||
# Set up organizational root management
|
||||
service_client.organization_features = ["RootCredentialsManagement"]
|
||||
service_client.account_summary = {
|
||||
"SummaryMap": {"AccountMFAEnabled": 0},
|
||||
}
|
||||
service_client.virtual_mfa_devices = []
|
||||
|
||||
service_client.credential_report[0]["user"] = "<root_account>"
|
||||
service_client.credential_report[0]["password_enabled"] = "true"
|
||||
service_client.credential_report[0]["access_key_1_active"] = "false"
|
||||
service_client.credential_report[0]["access_key_2_active"] = "false"
|
||||
service_client.credential_report[0][
|
||||
"arn"
|
||||
] = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root"
|
||||
|
||||
check = iam_root_hardware_mfa_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
"Root account has credentials without MFA "
|
||||
"despite organizational root management being enabled.",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == "<root_account>"
|
||||
assert result[0].resource_arn == f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:mfa"
|
||||
@@ -42,12 +42,16 @@ class Test_iam_root_mfa_enabled_test:
|
||||
|
||||
service_client.credential_report[0]["user"] = "<root_account>"
|
||||
service_client.credential_report[0]["mfa_active"] = "false"
|
||||
service_client.credential_report[0]["password_enabled"] = "true"
|
||||
service_client.credential_report[0]["access_key_1_active"] = "false"
|
||||
service_client.credential_report[0]["access_key_2_active"] = "false"
|
||||
service_client.credential_report[0][
|
||||
"arn"
|
||||
] = "arn:aws:iam::123456789012:<root_account>:root"
|
||||
|
||||
check = iam_root_mfa_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
"MFA is not enabled for root account.", result[0].status_extended
|
||||
@@ -80,13 +84,149 @@ class Test_iam_root_mfa_enabled_test:
|
||||
|
||||
service_client.credential_report[0]["user"] = "<root_account>"
|
||||
service_client.credential_report[0]["mfa_active"] = "true"
|
||||
service_client.credential_report[0]["password_enabled"] = "true"
|
||||
service_client.credential_report[0]["access_key_1_active"] = "false"
|
||||
service_client.credential_report[0]["access_key_2_active"] = "false"
|
||||
service_client.credential_report[0][
|
||||
"arn"
|
||||
] = "arn:aws:iam::123456789012:<root_account>:root"
|
||||
|
||||
check = iam_root_mfa_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search("MFA is enabled for root account.", result[0].status_extended)
|
||||
assert result[0].resource_id == "<root_account>"
|
||||
assert result[0].resource_arn == service_client.credential_report[0]["arn"]
|
||||
|
||||
@mock_aws
|
||||
def test_root_no_credentials(self):
|
||||
iam_client = client("iam")
|
||||
user = "test-user"
|
||||
iam_client.create_user(UserName=user)["User"]["Arn"]
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_root_mfa_enabled.iam_root_mfa_enabled.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
) as service_client,
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_root_mfa_enabled.iam_root_mfa_enabled import (
|
||||
iam_root_mfa_enabled,
|
||||
)
|
||||
|
||||
service_client.credential_report[0]["user"] = "<root_account>"
|
||||
service_client.credential_report[0]["mfa_active"] = "false"
|
||||
service_client.credential_report[0]["password_enabled"] = "false"
|
||||
service_client.credential_report[0]["access_key_1_active"] = "false"
|
||||
service_client.credential_report[0]["access_key_2_active"] = "false"
|
||||
service_client.credential_report[0][
|
||||
"arn"
|
||||
] = "arn:aws:iam::123456789012:<root_account>:root"
|
||||
|
||||
check = iam_root_mfa_enabled()
|
||||
result = check.execute()
|
||||
# Should return no findings since root has no credentials
|
||||
assert len(result) == 0
|
||||
|
||||
@mock_aws
|
||||
def test_root_mfa_with_organizational_management_enabled(self):
|
||||
iam_client = client("iam")
|
||||
user = "test-user"
|
||||
iam_client.create_user(UserName=user)["User"]["Arn"]
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_root_mfa_enabled.iam_root_mfa_enabled.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
) as service_client,
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_root_mfa_enabled.iam_root_mfa_enabled import (
|
||||
iam_root_mfa_enabled,
|
||||
)
|
||||
|
||||
# Set up organizational root management
|
||||
service_client.organization_features = ["RootCredentialsManagement"]
|
||||
|
||||
service_client.credential_report[0]["user"] = "<root_account>"
|
||||
service_client.credential_report[0]["mfa_active"] = "true"
|
||||
service_client.credential_report[0]["password_enabled"] = "true"
|
||||
service_client.credential_report[0]["access_key_1_active"] = "true"
|
||||
service_client.credential_report[0]["access_key_2_active"] = "false"
|
||||
service_client.credential_report[0][
|
||||
"arn"
|
||||
] = "arn:aws:iam::123456789012:<root_account>:root"
|
||||
|
||||
check = iam_root_mfa_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
"Root account has credentials with MFA enabled. "
|
||||
"Consider removing individual root credentials since organizational "
|
||||
"root management is active.",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == "<root_account>"
|
||||
assert result[0].resource_arn == service_client.credential_report[0]["arn"]
|
||||
|
||||
@mock_aws
|
||||
def test_root_mfa_disabled_with_organizational_management_enabled(self):
|
||||
iam_client = client("iam")
|
||||
user = "test-user"
|
||||
iam_client.create_user(UserName=user)["User"]["Arn"]
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_root_mfa_enabled.iam_root_mfa_enabled.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
) as service_client,
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_root_mfa_enabled.iam_root_mfa_enabled import (
|
||||
iam_root_mfa_enabled,
|
||||
)
|
||||
|
||||
# Set up organizational root management
|
||||
service_client.organization_features = ["RootCredentialsManagement"]
|
||||
|
||||
service_client.credential_report[0]["user"] = "<root_account>"
|
||||
service_client.credential_report[0]["mfa_active"] = "false"
|
||||
service_client.credential_report[0]["password_enabled"] = "true"
|
||||
service_client.credential_report[0]["access_key_1_active"] = "false"
|
||||
service_client.credential_report[0]["access_key_2_active"] = "true"
|
||||
service_client.credential_report[0][
|
||||
"arn"
|
||||
] = "arn:aws:iam::123456789012:<root_account>:root"
|
||||
|
||||
check = iam_root_mfa_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
"Root account has credentials without MFA "
|
||||
"despite organizational root management being enabled.",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == "<root_account>"
|
||||
assert result[0].resource_arn == service_client.credential_report[0]["arn"]
|
||||
@@ -1,13 +1,18 @@
|
||||
import pytest
|
||||
|
||||
from prowler.providers.aws.services.iam.lib.policy import (
|
||||
_get_patterns_from_standard_value,
|
||||
check_admin_access,
|
||||
check_full_service_access,
|
||||
get_effective_actions,
|
||||
has_codebuild_trusted_principal,
|
||||
is_codebuild_using_allowed_github_org,
|
||||
is_condition_block_restrictive,
|
||||
is_condition_block_restrictive_organization,
|
||||
is_condition_block_restrictive_sns_endpoint,
|
||||
is_condition_restricting_from_private_ip,
|
||||
is_policy_public,
|
||||
)
|
||||
import pytest
|
||||
|
||||
TRUSTED_AWS_ACCOUNT_NUMBER = "123456789012"
|
||||
NON_TRUSTED_AWS_ACCOUNT_NUMBER = "111222333444"
|
||||
@@ -19,6 +24,115 @@ ALL_ORGS = "*"
|
||||
|
||||
|
||||
class Test_Policy:
|
||||
def test_get_patterns_from_standard_value_string(self):
|
||||
"""Test _get_patterns_from_standard_value with a string input"""
|
||||
result = _get_patterns_from_standard_value("s3:GetObject")
|
||||
assert result == {"s3:GetObject"}
|
||||
|
||||
result = _get_patterns_from_standard_value("")
|
||||
assert result == {""}
|
||||
|
||||
def test_get_patterns_from_standard_value_list(self):
|
||||
"""Test _get_patterns_from_standard_value with a list input"""
|
||||
result = _get_patterns_from_standard_value(["s3:GetObject", "s3:PutObject"])
|
||||
assert result == {"s3:GetObject", "s3:PutObject"}
|
||||
|
||||
result = _get_patterns_from_standard_value([])
|
||||
assert result == set()
|
||||
|
||||
result = _get_patterns_from_standard_value(["s3:GetObject", 123, None])
|
||||
assert result == {"s3:GetObject"}
|
||||
|
||||
def test_get_patterns_from_standard_value_invalid_input(self):
|
||||
"""Test _get_patterns_from_standard_value with invalid inputs"""
|
||||
result = _get_patterns_from_standard_value(None)
|
||||
assert result == set()
|
||||
|
||||
result = _get_patterns_from_standard_value(123)
|
||||
assert result == set()
|
||||
|
||||
def test_get_effective_actions_empty_policy(self):
|
||||
"""Test get_effective_actions with an empty policy"""
|
||||
result = get_effective_actions({})
|
||||
assert result == set()
|
||||
|
||||
result = get_effective_actions({"Version": "2012-10-17"})
|
||||
assert result == set()
|
||||
|
||||
def test_get_effective_actions_simple_allow(self):
|
||||
"""Test get_effective_actions with a simple Allow statement"""
|
||||
policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": {"Effect": "Allow", "Action": "s3:GetObject"},
|
||||
}
|
||||
result = get_effective_actions(policy)
|
||||
assert result == {"s3:GetObject"}
|
||||
|
||||
def test_get_effective_actions_simple_deny(self):
|
||||
"""Test get_effective_actions with a simple Deny statement"""
|
||||
policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": {"Effect": "Deny", "Action": "s3:GetObject"},
|
||||
}
|
||||
result = get_effective_actions(policy)
|
||||
assert result == set()
|
||||
|
||||
def test_get_effective_actions_allow_and_deny(self):
|
||||
"""Test get_effective_actions with both Allow and Deny statements"""
|
||||
policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{"Effect": "Allow", "Action": ["s3:GetObject", "s3:PutObject"]},
|
||||
{"Effect": "Deny", "Action": "s3:GetObject"},
|
||||
],
|
||||
}
|
||||
result = get_effective_actions(policy)
|
||||
assert result == {"s3:PutObject"}
|
||||
|
||||
def test_get_effective_actions_with_not_action(self):
|
||||
"""Test get_effective_actions with NotAction statements"""
|
||||
policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": {"Effect": "Allow", "NotAction": "s3:GetObject"},
|
||||
}
|
||||
result = get_effective_actions(policy)
|
||||
assert "s3:GetObject" not in result
|
||||
assert "s3:PutObject" in result
|
||||
|
||||
def test_get_effective_actions_with_wildcards(self):
|
||||
"""Test get_effective_actions with wildcard actions"""
|
||||
policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": {"Effect": "Allow", "Action": "s3:*"},
|
||||
}
|
||||
result = get_effective_actions(policy)
|
||||
assert "s3:GetObject" in result
|
||||
assert "s3:PutObject" in result
|
||||
assert "s3:ListBucket" in result
|
||||
|
||||
def test_get_effective_actions_with_invalid_effect(self):
|
||||
"""Test get_effective_actions with invalid Effect value"""
|
||||
policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": {"Effect": "Invalid", "Action": "s3:GetObject"},
|
||||
}
|
||||
result = get_effective_actions(policy)
|
||||
assert result == set()
|
||||
|
||||
def test_get_effective_actions_with_multiple_statements(self):
|
||||
"""Test get_effective_actions with multiple statements"""
|
||||
policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{"Effect": "Allow", "Action": ["s3:GetObject", "s3:PutObject"]},
|
||||
{"Effect": "Allow", "Action": "s3:ListBucket"},
|
||||
{"Effect": "Deny", "Action": "s3:PutObject"},
|
||||
],
|
||||
}
|
||||
result = get_effective_actions(policy)
|
||||
assert result == {"s3:GetObject", "s3:ListBucket"}
|
||||
assert "s3:PutObject" not in result
|
||||
|
||||
# Test lowercase context key name --> aws
|
||||
def test_condition_parser_string_equals_aws_SourceAccount_list(self):
|
||||
condition_statement = {
|
||||
@@ -1618,6 +1732,38 @@ class Test_Policy:
|
||||
"s3", policy_allow_wildcard_action_and_resource
|
||||
)
|
||||
|
||||
def test_policy_allows_full_service_access_with_wildcard_action_and_resource_using_unicode(
|
||||
self,
|
||||
):
|
||||
policy_allow_wildcard_action_and_resource = {
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "\u0041llow",
|
||||
"Action": "\u00733:*",
|
||||
"Resource": "*",
|
||||
}
|
||||
]
|
||||
}
|
||||
assert check_full_service_access(
|
||||
"s3", policy_allow_wildcard_action_and_resource
|
||||
)
|
||||
|
||||
def test_policy_allows_full_service_access_with_wildcard_action_and_resource_using_double_start(
|
||||
self,
|
||||
):
|
||||
policy_allow_wildcard_action_and_resource = {
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": "s3:**",
|
||||
"Resource": "*",
|
||||
}
|
||||
]
|
||||
}
|
||||
assert check_full_service_access(
|
||||
"s3", policy_allow_wildcard_action_and_resource
|
||||
)
|
||||
|
||||
def test_policy_does_not_allow_full_service_access_with_specific_get_action(self):
|
||||
policy_allow_specific_get_action = {
|
||||
"Statement": [
|
||||
@@ -1672,6 +1818,22 @@ class Test_Policy:
|
||||
"s3", policy_allow_not_action_excluding_other_service
|
||||
)
|
||||
|
||||
def test_policy_allows_full_service_access_with_invalid_service_as_not_action(
|
||||
self,
|
||||
):
|
||||
policy_allow_not_action_excluding_other_service = {
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"NotAction": "prowler:check",
|
||||
"Resource": "*",
|
||||
}
|
||||
]
|
||||
}
|
||||
assert check_full_service_access(
|
||||
"s3", policy_allow_not_action_excluding_other_service
|
||||
)
|
||||
|
||||
def test_policy_does_not_allow_full_service_access_with_not_action_including_service(
|
||||
self,
|
||||
):
|
||||
@@ -2108,3 +2270,184 @@ class Test_Policy:
|
||||
],
|
||||
}
|
||||
assert check_admin_access(policy)
|
||||
|
||||
|
||||
def test_is_codebuild_using_allowed_github_org_allows():
|
||||
trust_policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {"Service": "codebuild.amazonaws.com"},
|
||||
"Action": "sts:AssumeRole",
|
||||
}
|
||||
],
|
||||
}
|
||||
github_repo_url = "https://github.com/allowed-org/repo"
|
||||
allowed_organizations = ["allowed-org"]
|
||||
is_allowed, org_name = is_codebuild_using_allowed_github_org(
|
||||
trust_policy, github_repo_url, allowed_organizations
|
||||
)
|
||||
assert is_allowed is True
|
||||
assert org_name == "allowed-org"
|
||||
|
||||
|
||||
def test_is_codebuild_using_allowed_github_org_denies():
|
||||
trust_policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {"Service": "codebuild.amazonaws.com"},
|
||||
"Action": "sts:AssumeRole",
|
||||
}
|
||||
],
|
||||
}
|
||||
github_repo_url = "https://github.com/not-allowed-org/repo"
|
||||
allowed_organizations = ["allowed-org"]
|
||||
is_allowed, org_name = is_codebuild_using_allowed_github_org(
|
||||
trust_policy, github_repo_url, allowed_organizations
|
||||
)
|
||||
assert is_allowed is False
|
||||
assert org_name == "not-allowed-org"
|
||||
|
||||
|
||||
def test_is_codebuild_using_allowed_github_org_no_codebuild_principal():
|
||||
trust_policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {"Service": "lambda.amazonaws.com"},
|
||||
"Action": "sts:AssumeRole",
|
||||
}
|
||||
],
|
||||
}
|
||||
github_repo_url = "https://github.com/allowed-org/repo"
|
||||
allowed_organizations = ["allowed-org"]
|
||||
is_allowed, org_name = is_codebuild_using_allowed_github_org(
|
||||
trust_policy, github_repo_url, allowed_organizations
|
||||
)
|
||||
assert is_allowed is False
|
||||
assert org_name is None
|
||||
|
||||
|
||||
def test_is_codebuild_using_allowed_github_org_invalid_url():
|
||||
trust_policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {"Service": "codebuild.amazonaws.com"},
|
||||
"Action": "sts:AssumeRole",
|
||||
}
|
||||
],
|
||||
}
|
||||
github_repo_url = "https://github.com//test" # Malformed, no org
|
||||
allowed_organizations = ["allowed-org"]
|
||||
is_allowed, org_name = is_codebuild_using_allowed_github_org(
|
||||
trust_policy, github_repo_url, allowed_organizations
|
||||
)
|
||||
assert is_allowed is False
|
||||
assert org_name is None
|
||||
|
||||
|
||||
def test_has_codebuild_trusted_principal_true():
|
||||
trust_policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {"Service": "codebuild.amazonaws.com"},
|
||||
"Action": "sts:AssumeRole",
|
||||
}
|
||||
],
|
||||
}
|
||||
assert has_codebuild_trusted_principal(trust_policy) is True
|
||||
|
||||
|
||||
def test_has_codebuild_trusted_principal_false():
|
||||
trust_policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {"Service": "lambda.amazonaws.com"},
|
||||
"Action": "sts:AssumeRole",
|
||||
}
|
||||
],
|
||||
}
|
||||
assert has_codebuild_trusted_principal(trust_policy) is False
|
||||
|
||||
|
||||
def test_has_codebuild_trusted_principal_empty():
|
||||
trust_policy = {}
|
||||
assert has_codebuild_trusted_principal(trust_policy) is False
|
||||
|
||||
|
||||
def test_is_codebuild_using_allowed_github_org_principal_string():
|
||||
trust_policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": "codebuild.amazonaws.com",
|
||||
"Action": "sts:AssumeRole",
|
||||
}
|
||||
],
|
||||
}
|
||||
github_repo_url = "https://github.com/allowed-org/repo"
|
||||
allowed_organizations = ["allowed-org"]
|
||||
is_allowed, org_name = is_codebuild_using_allowed_github_org(
|
||||
trust_policy, github_repo_url, allowed_organizations
|
||||
)
|
||||
assert is_allowed is True
|
||||
assert org_name == "allowed-org"
|
||||
|
||||
|
||||
def test_is_codebuild_using_allowed_github_org_principal_list():
|
||||
trust_policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": ["codebuild.amazonaws.com", "lambda.amazonaws.com"],
|
||||
"Action": "sts:AssumeRole",
|
||||
}
|
||||
],
|
||||
}
|
||||
github_repo_url = "https://github.com/allowed-org/repo"
|
||||
allowed_organizations = ["allowed-org"]
|
||||
is_allowed, org_name = is_codebuild_using_allowed_github_org(
|
||||
trust_policy, github_repo_url, allowed_organizations
|
||||
)
|
||||
assert is_allowed is True
|
||||
assert org_name == "allowed-org"
|
||||
|
||||
|
||||
def test_has_codebuild_trusted_principal_string():
|
||||
trust_policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": "codebuild.amazonaws.com",
|
||||
"Action": "sts:AssumeRole",
|
||||
}
|
||||
],
|
||||
}
|
||||
assert has_codebuild_trusted_principal(trust_policy) is True
|
||||
|
||||
|
||||
def test_has_codebuild_trusted_principal_list():
|
||||
trust_policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": ["codebuild.amazonaws.com", "lambda.amazonaws.com"],
|
||||
"Action": "sts:AssumeRole",
|
||||
}
|
||||
],
|
||||
}
|
||||
assert has_codebuild_trusted_principal(trust_policy) is True
|
||||
@@ -1,88 +1,38 @@
|
||||
from prowler.providers.aws.services.iam.lib.privilege_escalation import (
|
||||
check_privilege_escalation,
|
||||
find_privilege_escalation_combinations,
|
||||
privilege_escalation_policies_combination,
|
||||
)
|
||||
|
||||
|
||||
# Helper function to parse the output string into a set for easier comparison
|
||||
def parse_result_string(result_str: str) -> set:
|
||||
"""Helper to parse the output string back into a set."""
|
||||
if not result_str:
|
||||
return set()
|
||||
# Removes the single quotes around each action and splits by comma+space
|
||||
return set(part.strip("'") for part in result_str.split(", "))
|
||||
|
||||
|
||||
class Test_PrivilegeEscalation:
|
||||
def test_find_privilege_escalation_combinations_no_priv_escalation(self):
|
||||
allowed_actions = set()
|
||||
denied_actions = set()
|
||||
allowed_not_actions = set()
|
||||
denied_not_actions = set()
|
||||
|
||||
allowed_actions.add("s3:GetObject")
|
||||
denied_actions.add("s3:PutObject")
|
||||
denied_not_actions.add("s3:DeleteObject")
|
||||
|
||||
assert (
|
||||
find_privilege_escalation_combinations(
|
||||
allowed_actions, denied_actions, allowed_not_actions, denied_not_actions
|
||||
)
|
||||
== set()
|
||||
)
|
||||
|
||||
def test_find_privilege_escalation_combinations_priv_escalation_iam_all_and_ec2_RunInstances(
|
||||
self,
|
||||
):
|
||||
allowed_actions = set()
|
||||
denied_actions = set()
|
||||
allowed_not_actions = set()
|
||||
denied_not_actions = set()
|
||||
|
||||
allowed_actions.add("iam:*")
|
||||
denied_actions.add("ec2:RunInstances")
|
||||
|
||||
assert find_privilege_escalation_combinations(
|
||||
allowed_actions, denied_actions, allowed_not_actions, denied_not_actions
|
||||
) == {
|
||||
"iam:Put*",
|
||||
"iam:AddUserToGroup",
|
||||
"iam:AttachRolePolicy",
|
||||
"iam:PassRole",
|
||||
"iam:CreateLoginProfile",
|
||||
"iam:CreateAccessKey",
|
||||
"iam:AttachGroupPolicy",
|
||||
"iam:SetDefaultPolicyVersion",
|
||||
"iam:PutRolePolicy",
|
||||
"iam:UpdateAssumeRolePolicy",
|
||||
"iam:*",
|
||||
"iam:PutGroupPolicy",
|
||||
"iam:PutUserPolicy",
|
||||
"iam:CreatePolicyVersion",
|
||||
"iam:AttachUserPolicy",
|
||||
"iam:UpdateLoginProfile",
|
||||
}
|
||||
|
||||
def test_find_privilege_escalation_combinations_priv_escalation_iam_PassRole(self):
|
||||
allowed_actions = set()
|
||||
allowed_not_actions = set()
|
||||
denied_actions = set()
|
||||
denied_not_actions = set()
|
||||
|
||||
allowed_actions.add("iam:PassRole")
|
||||
|
||||
assert find_privilege_escalation_combinations(
|
||||
allowed_actions, denied_actions, allowed_not_actions, denied_not_actions
|
||||
) == {"iam:PassRole"}
|
||||
|
||||
def test_check_privilege_escalation_no_priv_escalation(self):
|
||||
policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": ["s3:GetObject"],
|
||||
"Resource": ["arn:aws:s3:::example_bucket/*"],
|
||||
}
|
||||
]
|
||||
],
|
||||
}
|
||||
|
||||
assert check_privilege_escalation(policy) == ""
|
||||
expected_result = ""
|
||||
assert check_privilege_escalation(policy) == expected_result
|
||||
|
||||
def test_check_privilege_escalation_priv_escalation_iam_all_and_ec2_RunInstances(
|
||||
self,
|
||||
):
|
||||
policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
@@ -91,14 +41,14 @@ class Test_PrivilegeEscalation:
|
||||
},
|
||||
{
|
||||
"Effect": "Deny",
|
||||
"Action": ["ec2:RunInstances"],
|
||||
"Action": ["ec2:RunInstances"], # This denies one part of a combo
|
||||
"Resource": ["*"],
|
||||
},
|
||||
]
|
||||
],
|
||||
}
|
||||
|
||||
# Should match all IAM combos, but NOT PassRole+EC2
|
||||
result = check_privilege_escalation(policy)
|
||||
|
||||
assert "ec2:RunInstances" not in result
|
||||
assert "iam:Put*" in result
|
||||
assert "iam:AddUserToGroup" in result
|
||||
assert "iam:AttachRolePolicy" in result
|
||||
@@ -118,17 +68,32 @@ class Test_PrivilegeEscalation:
|
||||
|
||||
def test_check_privilege_escalation_priv_escalation_iam_PassRole(self):
|
||||
policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": ["iam:PassRole"],
|
||||
"Resource": ["*"],
|
||||
}
|
||||
]
|
||||
],
|
||||
}
|
||||
|
||||
result = check_privilege_escalation(policy)
|
||||
assert "iam:PassRole" in result
|
||||
|
||||
def test_check_privilege_escalation_priv_escalation_iam_PassRole_using_wildcard(
|
||||
self,
|
||||
):
|
||||
policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": ["iam:*Role"], # Should expand to include PassRole
|
||||
"Resource": ["*"],
|
||||
}
|
||||
],
|
||||
}
|
||||
result = check_privilege_escalation(policy)
|
||||
assert "iam:PassRole" in result
|
||||
|
||||
def test_check_privilege_escalation_priv_escalation_not_action(
|
||||
@@ -140,14 +105,16 @@ class Test_PrivilegeEscalation:
|
||||
{
|
||||
"Sid": "Statement1",
|
||||
"Effect": "Allow",
|
||||
"NotAction": "iam:Put*",
|
||||
"NotAction": "iam:Put*", # Allows everything EXCEPT iam:Put* actions
|
||||
"Resource": "*",
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
# Should match all combos EXCEPT those requiring iam:Put*
|
||||
result = check_privilege_escalation(policy)
|
||||
assert "iam:*" not in result
|
||||
assert "iam:Put*" not in result
|
||||
assert "'iam:PutGroupPolicy'" not in result
|
||||
assert "iam:AddUserToGroup" in result
|
||||
assert "iam:AttachRolePolicy" in result
|
||||
assert "iam:PassRole" in result
|
||||
@@ -155,11 +122,7 @@ class Test_PrivilegeEscalation:
|
||||
assert "iam:CreateAccessKey" in result
|
||||
assert "iam:AttachGroupPolicy" in result
|
||||
assert "iam:SetDefaultPolicyVersion" in result
|
||||
assert "iam:PutRolePolicy" in result
|
||||
assert "iam:UpdateAssumeRolePolicy" in result
|
||||
assert "iam:*" in result
|
||||
assert "iam:PutGroupPolicy" in result
|
||||
assert "iam:PutUserPolicy" in result
|
||||
assert "iam:CreatePolicyVersion" in result
|
||||
assert "iam:AttachUserPolicy" in result
|
||||
assert "iam:UpdateLoginProfile" in result
|
||||
@@ -173,27 +136,35 @@ class Test_PrivilegeEscalation:
|
||||
{
|
||||
"Sid": "Statement1",
|
||||
"Effect": "Allow",
|
||||
"NotAction": "prowler:action",
|
||||
"NotAction": "prowler:action", # Invalid action -> Allows ALL
|
||||
"Resource": "*",
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
# Since it allows ALL, expect all original patterns from ALL combos
|
||||
result = check_privilege_escalation(policy)
|
||||
assert "proler:action" not in result
|
||||
assert "iam:Put*" in result
|
||||
assert "iam:AddUserToGroup" in result
|
||||
assert "iam:AttachRolePolicy" in result
|
||||
assert "iam:PassRole" in result
|
||||
assert "iam:CreateLoginProfile" in result
|
||||
assert "iam:CreateAccessKey" in result
|
||||
assert "iam:AttachGroupPolicy" in result
|
||||
assert "iam:SetDefaultPolicyVersion" in result
|
||||
assert "iam:PutRolePolicy" in result
|
||||
assert "iam:UpdateAssumeRolePolicy" in result
|
||||
assert "iam:*" in result
|
||||
assert "iam:PutGroupPolicy" in result
|
||||
assert "iam:PutUserPolicy" in result
|
||||
assert "iam:CreatePolicyVersion" in result
|
||||
assert "iam:AttachUserPolicy" in result
|
||||
assert "iam:UpdateLoginProfile" in result
|
||||
for combo_patterns in privilege_escalation_policies_combination.values():
|
||||
for pattern in combo_patterns:
|
||||
assert (
|
||||
f"'{pattern}'" in result
|
||||
), f"Expected pattern '{pattern}' not found in result: {result}"
|
||||
|
||||
def test_check_privilege_escalation_administrator_policy(self):
|
||||
policy_document_admin = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "Statement01",
|
||||
"Effect": "Allow",
|
||||
"Action": ["*"], # Admin policy
|
||||
"Resource": "*",
|
||||
}
|
||||
],
|
||||
}
|
||||
# Admin policy should match ALL combos, so expect all original patterns
|
||||
result = check_privilege_escalation(policy_document_admin)
|
||||
for combo_patterns in privilege_escalation_policies_combination.values():
|
||||
for pattern in combo_patterns:
|
||||
assert (
|
||||
f"'{pattern}'" in result
|
||||
), f"Expected pattern '{pattern}' not found in result: {result}"
|
||||
@@ -36,7 +36,7 @@ class TestAzureMutelist:
|
||||
|
||||
mutelist = AzureMutelist(mutelist_content=mutelist_fixture)
|
||||
|
||||
assert not mutelist.validate_mutelist()
|
||||
assert len(mutelist.validate_mutelist(mutelist_fixture)) == 0
|
||||
assert mutelist.mutelist == {}
|
||||
assert mutelist.mutelist_file_path is None
|
||||
|
||||
@@ -63,7 +63,7 @@ class TestAzureMutelist:
|
||||
finding.location = "West Europe"
|
||||
finding.status = "FAIL"
|
||||
finding.resource_name = "test_resource"
|
||||
finding.resource_tags = []
|
||||
finding.resource_tags = {}
|
||||
finding.subscription = "subscription_1"
|
||||
|
||||
assert mutelist.is_finding_muted(finding)
|
||||
@@ -91,7 +91,7 @@ class TestAzureMutelist:
|
||||
account_uid="subscription_1",
|
||||
region="subscription_1",
|
||||
resource_uid="test_resource",
|
||||
resource_tags=[],
|
||||
resource_tags={},
|
||||
muted=False,
|
||||
)
|
||||
|
||||
|
||||
+2
-78
@@ -56,7 +56,6 @@ class Test_app_function_application_insights_enabled:
|
||||
|
||||
def test_app_function_no_app_insights(self):
|
||||
app_client = mock.MagicMock
|
||||
app_insights = mock.MagicMock
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
@@ -67,18 +66,11 @@ class Test_app_function_application_insights_enabled:
|
||||
"prowler.providers.azure.services.app.app_function_application_insights_enabled.app_function_application_insights_enabled.app_client",
|
||||
new=app_client,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.app.app_function_application_insights_enabled.app_function_application_insights_enabled.appinsights_client",
|
||||
new=app_insights,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.app.app_function_application_insights_enabled.app_function_application_insights_enabled import (
|
||||
app_function_application_insights_enabled,
|
||||
)
|
||||
from prowler.providers.azure.services.app.app_service import FunctionApp
|
||||
from prowler.providers.azure.services.appinsights.appinsights_service import (
|
||||
Component,
|
||||
)
|
||||
|
||||
function_id = str(uuid4())
|
||||
|
||||
@@ -100,17 +92,6 @@ class Test_app_function_application_insights_enabled:
|
||||
}
|
||||
}
|
||||
|
||||
app_insights.components = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
"app_id-1": Component(
|
||||
resource_id="component_id",
|
||||
resource_name="component_name",
|
||||
location="West Europe",
|
||||
instrumentation_key="1234",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
check = app_function_application_insights_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
@@ -126,7 +107,6 @@ class Test_app_function_application_insights_enabled:
|
||||
|
||||
def test_app_function_using_app_insights(self):
|
||||
app_client = mock.MagicMock
|
||||
app_insights = mock.MagicMock
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
@@ -137,18 +117,11 @@ class Test_app_function_application_insights_enabled:
|
||||
"prowler.providers.azure.services.app.app_function_application_insights_enabled.app_function_application_insights_enabled.app_client",
|
||||
new=app_client,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.app.app_function_application_insights_enabled.app_function_application_insights_enabled.appinsights_client",
|
||||
new=app_insights,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.app.app_function_application_insights_enabled.app_function_application_insights_enabled import (
|
||||
app_function_application_insights_enabled,
|
||||
)
|
||||
from prowler.providers.azure.services.app.app_service import FunctionApp
|
||||
from prowler.providers.azure.services.appinsights.appinsights_service import (
|
||||
Component,
|
||||
)
|
||||
|
||||
function_id = str(uuid4())
|
||||
|
||||
@@ -170,17 +143,6 @@ class Test_app_function_application_insights_enabled:
|
||||
}
|
||||
}
|
||||
|
||||
app_insights.components = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
"app_id-1": Component(
|
||||
resource_id="component_id",
|
||||
resource_name="component_name",
|
||||
location="West Europe",
|
||||
instrumentation_key="1234",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
check = app_function_application_insights_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
@@ -196,7 +158,6 @@ class Test_app_function_application_insights_enabled:
|
||||
|
||||
def test_app_function_using_app_insights_different_key(self):
|
||||
app_client = mock.MagicMock
|
||||
app_insights = mock.MagicMock
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
@@ -207,18 +168,11 @@ class Test_app_function_application_insights_enabled:
|
||||
"prowler.providers.azure.services.app.app_function_application_insights_enabled.app_function_application_insights_enabled.app_client",
|
||||
new=app_client,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.app.app_function_application_insights_enabled.app_function_application_insights_enabled.appinsights_client",
|
||||
new=app_insights,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.app.app_function_application_insights_enabled.app_function_application_insights_enabled import (
|
||||
app_function_application_insights_enabled,
|
||||
)
|
||||
from prowler.providers.azure.services.app.app_service import FunctionApp
|
||||
from prowler.providers.azure.services.appinsights.appinsights_service import (
|
||||
Component,
|
||||
)
|
||||
|
||||
function_id = str(uuid4())
|
||||
|
||||
@@ -240,24 +194,13 @@ class Test_app_function_application_insights_enabled:
|
||||
}
|
||||
}
|
||||
|
||||
app_insights.components = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
"app_id-1": Component(
|
||||
resource_id="component_id",
|
||||
resource_name="component_name",
|
||||
location="West Europe",
|
||||
instrumentation_key="5678",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
check = app_function_application_insights_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Function function1 is not using Application Insights."
|
||||
== "Function function1 is using Application Insights."
|
||||
)
|
||||
assert result[0].resource_id == function_id
|
||||
assert result[0].resource_name == "function1"
|
||||
@@ -266,7 +209,6 @@ class Test_app_function_application_insights_enabled:
|
||||
|
||||
def test_app_function_with_app_insights_no_key(self):
|
||||
app_client = mock.MagicMock
|
||||
app_insights = mock.MagicMock
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
@@ -277,18 +219,11 @@ class Test_app_function_application_insights_enabled:
|
||||
"prowler.providers.azure.services.app.app_function_application_insights_enabled.app_function_application_insights_enabled.app_client",
|
||||
new=app_client,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.app.app_function_application_insights_enabled.app_function_application_insights_enabled.appinsights_client",
|
||||
new=app_insights,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.app.app_function_application_insights_enabled.app_function_application_insights_enabled import (
|
||||
app_function_application_insights_enabled,
|
||||
)
|
||||
from prowler.providers.azure.services.app.app_service import FunctionApp
|
||||
from prowler.providers.azure.services.appinsights.appinsights_service import (
|
||||
Component,
|
||||
)
|
||||
|
||||
function_id = str(uuid4())
|
||||
|
||||
@@ -310,17 +245,6 @@ class Test_app_function_application_insights_enabled:
|
||||
}
|
||||
}
|
||||
|
||||
app_insights.components = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
"app_id-1": Component(
|
||||
resource_id="component_id",
|
||||
resource_name="component_name",
|
||||
location="West Europe",
|
||||
instrumentation_key="Not Found",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
check = app_function_application_insights_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
|
||||
+31
-19
@@ -146,24 +146,29 @@ class Test_app_function_identity_without_admin_privileges:
|
||||
|
||||
iam_client.role_assignments = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
"1": RoleAssignment(
|
||||
role_id="1",
|
||||
"role-assignment-id-1": RoleAssignment(
|
||||
id="role-assignment-id-1",
|
||||
name="role-assignment-name-1",
|
||||
scope="/subscriptions/{}/resourceGroups/rg/providers/Microsoft.Web/sites/function1".format(
|
||||
AZURE_SUBSCRIPTION_ID
|
||||
),
|
||||
agent_id="123",
|
||||
agent_type="User",
|
||||
role_id="role-id-1",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
iam_client.roles = {
|
||||
AZURE_SUBSCRIPTION_ID: [
|
||||
Role(
|
||||
id="1",
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
"role-id-1": Role(
|
||||
id="role-id-1",
|
||||
name="role1",
|
||||
type="User",
|
||||
type="BuiltInRole",
|
||||
assignable_scopes=[],
|
||||
permissions=[],
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
check = app_function_identity_without_admin_privileges()
|
||||
@@ -207,9 +212,9 @@ class Test_app_function_identity_without_admin_privileges:
|
||||
)
|
||||
|
||||
function_id = str(uuid4())
|
||||
|
||||
function_scope = f"/subscriptions/{AZURE_SUBSCRIPTION_ID}/resourceGroups/rg/providers/Microsoft.Web/sites/function1"
|
||||
app_client.functions = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
"subscription-name-1": {
|
||||
function_id: FunctionApp(
|
||||
id=function_id,
|
||||
name="function1",
|
||||
@@ -226,26 +231,33 @@ class Test_app_function_identity_without_admin_privileges:
|
||||
}
|
||||
}
|
||||
|
||||
iam_client.subscriptions = {
|
||||
"subscription-name-1": AZURE_SUBSCRIPTION_ID,
|
||||
}
|
||||
|
||||
iam_client.role_assignments = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
"1": RoleAssignment(
|
||||
role_id=USER_ACCESS_ADMINISTRATOR_ROLE_ID,
|
||||
"subscription-name-1": {
|
||||
"role-assignment-id-2": RoleAssignment(
|
||||
id="role-assignment-id-2",
|
||||
name="role-assignment-name-2",
|
||||
scope=function_scope,
|
||||
agent_id="123",
|
||||
agent_type="User",
|
||||
role_id=USER_ACCESS_ADMINISTRATOR_ROLE_ID,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
iam_client.roles = {
|
||||
AZURE_SUBSCRIPTION_ID: [
|
||||
Role(
|
||||
id=USER_ACCESS_ADMINISTRATOR_ROLE_ID,
|
||||
"subscription-name-1": {
|
||||
f"/subscriptions/{AZURE_SUBSCRIPTION_ID}/providers/Microsoft.Authorization/roleDefinitions/{USER_ACCESS_ADMINISTRATOR_ROLE_ID}": Role(
|
||||
id=f"/subscriptions/{AZURE_SUBSCRIPTION_ID}/providers/Microsoft.Authorization/roleDefinitions/{USER_ACCESS_ADMINISTRATOR_ROLE_ID}",
|
||||
name="User Access Administrator",
|
||||
type="User",
|
||||
type="BuiltInRole",
|
||||
assignable_scopes=[],
|
||||
permissions=[],
|
||||
)
|
||||
]
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
check = app_function_identity_without_admin_privileges()
|
||||
@@ -258,5 +270,5 @@ class Test_app_function_identity_without_admin_privileges:
|
||||
)
|
||||
assert result[0].resource_id == function_id
|
||||
assert result[0].resource_name == "function1"
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].subscription == "subscription-name-1"
|
||||
assert result[0].location == "West Europe"
|
||||
@@ -0,0 +1,92 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from prowler.providers.azure.services.databricks.databricks_service import (
|
||||
Databricks,
|
||||
DatabricksWorkspace,
|
||||
ManagedDiskEncryption,
|
||||
)
|
||||
from tests.providers.azure.azure_fixtures import (
|
||||
AZURE_SUBSCRIPTION_ID,
|
||||
set_mocked_azure_provider,
|
||||
)
|
||||
|
||||
|
||||
def mock_databricks_get_workspaces(_):
|
||||
return {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
"test-workspace-id": DatabricksWorkspace(
|
||||
id="test-workspace-id",
|
||||
name="test-workspace",
|
||||
location="eastus",
|
||||
custom_managed_vnet_id="test-vnet-id",
|
||||
managed_disk_encryption=ManagedDiskEncryption(
|
||||
key_name="test-key",
|
||||
key_version="test-version",
|
||||
key_vault_uri="test-vault-uri",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@patch(
|
||||
"prowler.providers.azure.services.databricks.databricks_service.Databricks._get_workspaces",
|
||||
new=mock_databricks_get_workspaces,
|
||||
)
|
||||
class Test_Databricks_Service:
|
||||
def test_get_client(self):
|
||||
databricks = Databricks(set_mocked_azure_provider())
|
||||
assert (
|
||||
databricks.clients[AZURE_SUBSCRIPTION_ID].__class__.__name__
|
||||
== "AzureDatabricksManagementClient"
|
||||
)
|
||||
|
||||
def test_get_workspaces(self):
|
||||
databricks = Databricks(set_mocked_azure_provider())
|
||||
assert (
|
||||
databricks.workspaces[AZURE_SUBSCRIPTION_ID][
|
||||
"test-workspace-id"
|
||||
].__class__.__name__
|
||||
== "DatabricksWorkspace"
|
||||
)
|
||||
workspace = databricks.workspaces[AZURE_SUBSCRIPTION_ID]["test-workspace-id"]
|
||||
assert workspace.id == "test-workspace-id"
|
||||
assert workspace.name == "test-workspace"
|
||||
assert workspace.location == "eastus"
|
||||
assert workspace.custom_managed_vnet_id == "test-vnet-id"
|
||||
assert (
|
||||
workspace.managed_disk_encryption.__class__.__name__
|
||||
== "ManagedDiskEncryption"
|
||||
)
|
||||
assert workspace.managed_disk_encryption.key_name == "test-key"
|
||||
assert workspace.managed_disk_encryption.key_version == "test-version"
|
||||
assert workspace.managed_disk_encryption.key_vault_uri == "test-vault-uri"
|
||||
|
||||
|
||||
def mock_databricks_get_workspaces_no_encryption(_):
|
||||
return {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
"test-workspace-id": DatabricksWorkspace(
|
||||
id="test-workspace-id",
|
||||
name="test-workspace",
|
||||
location="eastus",
|
||||
custom_managed_vnet_id="test-vnet-id",
|
||||
managed_disk_encryption=None,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@patch(
|
||||
"prowler.providers.azure.services.databricks.databricks_service.Databricks._get_workspaces",
|
||||
new=mock_databricks_get_workspaces_no_encryption,
|
||||
)
|
||||
class Test_Databricks_Service_No_Encryption:
|
||||
def test_get_workspaces_no_encryption(self):
|
||||
databricks = Databricks(set_mocked_azure_provider())
|
||||
workspace = databricks.workspaces[AZURE_SUBSCRIPTION_ID]["test-workspace-id"]
|
||||
assert workspace.id == "test-workspace-id"
|
||||
assert workspace.name == "test-workspace"
|
||||
assert workspace.location == "eastus"
|
||||
assert workspace.custom_managed_vnet_id == "test-vnet-id"
|
||||
assert workspace.managed_disk_encryption is None
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
from unittest import mock
|
||||
from uuid import uuid4
|
||||
|
||||
from prowler.providers.azure.services.databricks.databricks_service import (
|
||||
DatabricksWorkspace,
|
||||
ManagedDiskEncryption,
|
||||
)
|
||||
from tests.providers.azure.azure_fixtures import (
|
||||
AZURE_SUBSCRIPTION_ID,
|
||||
set_mocked_azure_provider,
|
||||
)
|
||||
|
||||
|
||||
class Test_databricks_workspace_cmk_encryption_enabled:
|
||||
def test_no_databricks_workspaces(self):
|
||||
databricks_client = mock.MagicMock
|
||||
databricks_client.workspaces = {}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.databricks.databricks_workspace_cmk_encryption_enabled.databricks_workspace_cmk_encryption_enabled.databricks_client",
|
||||
new=databricks_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.databricks.databricks_workspace_cmk_encryption_enabled.databricks_workspace_cmk_encryption_enabled import (
|
||||
databricks_workspace_cmk_encryption_enabled,
|
||||
)
|
||||
|
||||
check = databricks_workspace_cmk_encryption_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_databricks_workspace_cmk_encryption_disabled(self):
|
||||
workspace_id = str(uuid4())
|
||||
workspace_name = "test-workspace"
|
||||
|
||||
databricks_client = mock.MagicMock
|
||||
databricks_client.workspaces = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
workspace_id: DatabricksWorkspace(
|
||||
id=workspace_id,
|
||||
name=workspace_name,
|
||||
location="eastus",
|
||||
custom_managed_vnet_id=None,
|
||||
managed_disk_encryption=None,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.databricks.databricks_workspace_cmk_encryption_enabled.databricks_workspace_cmk_encryption_enabled.databricks_client",
|
||||
new=databricks_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.databricks.databricks_workspace_cmk_encryption_enabled.databricks_workspace_cmk_encryption_enabled import (
|
||||
databricks_workspace_cmk_encryption_enabled,
|
||||
)
|
||||
|
||||
check = databricks_workspace_cmk_encryption_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Databricks workspace {workspace_name} in subscription {AZURE_SUBSCRIPTION_ID} does not have customer-managed key (CMK) encryption enabled."
|
||||
)
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == workspace_name
|
||||
assert result[0].resource_id == workspace_id
|
||||
assert result[0].location == "eastus"
|
||||
|
||||
def test_databricks_workspace_cmk_encryption_enabled(self):
|
||||
workspace_id = str(uuid4())
|
||||
workspace_name = "test-workspace"
|
||||
key_name = "test-key"
|
||||
key_version = "test-version"
|
||||
key_vault_uri = "test-vault-uri"
|
||||
|
||||
databricks_client = mock.MagicMock
|
||||
databricks_client.workspaces = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
workspace_id: DatabricksWorkspace(
|
||||
id=workspace_id,
|
||||
name=workspace_name,
|
||||
location="eastus",
|
||||
custom_managed_vnet_id=None,
|
||||
managed_disk_encryption=ManagedDiskEncryption(
|
||||
key_name=key_name,
|
||||
key_version=key_version,
|
||||
key_vault_uri=key_vault_uri,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.databricks.databricks_workspace_cmk_encryption_enabled.databricks_workspace_cmk_encryption_enabled.databricks_client",
|
||||
new=databricks_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.databricks.databricks_workspace_cmk_encryption_enabled.databricks_workspace_cmk_encryption_enabled import (
|
||||
databricks_workspace_cmk_encryption_enabled,
|
||||
)
|
||||
|
||||
check = databricks_workspace_cmk_encryption_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Databricks workspace {workspace_name} in subscription {AZURE_SUBSCRIPTION_ID} has customer-managed key (CMK) encryption enabled with key {key_vault_uri}/{key_name}/{key_version}."
|
||||
)
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == workspace_name
|
||||
assert result[0].resource_id == workspace_id
|
||||
assert result[0].location == "eastus"
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
from unittest import mock
|
||||
from uuid import uuid4
|
||||
|
||||
from prowler.providers.azure.services.databricks.databricks_service import (
|
||||
DatabricksWorkspace,
|
||||
)
|
||||
from tests.providers.azure.azure_fixtures import (
|
||||
AZURE_SUBSCRIPTION_ID,
|
||||
set_mocked_azure_provider,
|
||||
)
|
||||
|
||||
|
||||
class Test_databricks_workspace_vnet_injection_enabled:
|
||||
def test_databricks_no_workspaces(self):
|
||||
databricks_client = mock.MagicMock
|
||||
databricks_client.workspaces = {}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.databricks.databricks_workspace_vnet_injection_enabled.databricks_workspace_vnet_injection_enabled.databricks_client",
|
||||
new=databricks_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.databricks.databricks_workspace_vnet_injection_enabled.databricks_workspace_vnet_injection_enabled import (
|
||||
databricks_workspace_vnet_injection_enabled,
|
||||
)
|
||||
|
||||
check = databricks_workspace_vnet_injection_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_databricks_workspace_vnet_injection_disabled(self):
|
||||
workspace_id = str(uuid4())
|
||||
workspace_name = "test-workspace"
|
||||
databricks_client = mock.MagicMock
|
||||
databricks_client.workspaces = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
workspace_id: DatabricksWorkspace(
|
||||
id=workspace_id,
|
||||
name=workspace_name,
|
||||
location="eastus",
|
||||
custom_managed_vnet_id=None,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.databricks.databricks_workspace_vnet_injection_enabled.databricks_workspace_vnet_injection_enabled.databricks_client",
|
||||
new=databricks_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.databricks.databricks_workspace_vnet_injection_enabled.databricks_workspace_vnet_injection_enabled import (
|
||||
databricks_workspace_vnet_injection_enabled,
|
||||
)
|
||||
|
||||
check = databricks_workspace_vnet_injection_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Databricks workspace {workspace_name} in subscription {AZURE_SUBSCRIPTION_ID} is not deployed in a customer-managed VNet (VNet Injection is not enabled)."
|
||||
)
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == workspace_name
|
||||
assert result[0].resource_id == workspace_id
|
||||
assert result[0].location == "eastus"
|
||||
|
||||
def test_databricks_workspace_vnet_injection_enabled(self):
|
||||
workspace_id = str(uuid4())
|
||||
workspace_name = "test-workspace"
|
||||
vnet_id = "test-vnet-id"
|
||||
databricks_client = mock.MagicMock
|
||||
databricks_client.workspaces = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
workspace_id: DatabricksWorkspace(
|
||||
id=workspace_id,
|
||||
name=workspace_name,
|
||||
location="eastus",
|
||||
custom_managed_vnet_id=vnet_id,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.databricks.databricks_workspace_vnet_injection_enabled.databricks_workspace_vnet_injection_enabled.databricks_client",
|
||||
new=databricks_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.databricks.databricks_workspace_vnet_injection_enabled.databricks_workspace_vnet_injection_enabled import (
|
||||
databricks_workspace_vnet_injection_enabled,
|
||||
)
|
||||
|
||||
check = databricks_workspace_vnet_injection_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Databricks workspace {workspace_name} in subscription {AZURE_SUBSCRIPTION_ID} is deployed in a customer-managed VNet ({vnet_id})."
|
||||
)
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == workspace_name
|
||||
assert result[0].resource_id == workspace_id
|
||||
assert result[0].location == "eastus"
|
||||
+12
@@ -75,6 +75,9 @@ class Test_iam_assignment_priviledge_access_vm_has_mfa:
|
||||
iam_client.role_assignments = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
role_assigment_id: RoleAssignment(
|
||||
id=role_assigment_id,
|
||||
name="test",
|
||||
scope=AZURE_SUBSCRIPTION_ID,
|
||||
role_id=VIRTUAL_MACHINE_ADMINISTRATOR_LOGIN_ROLE_ID,
|
||||
agent_type="User",
|
||||
agent_id=user_id,
|
||||
@@ -149,6 +152,9 @@ class Test_iam_assignment_priviledge_access_vm_has_mfa:
|
||||
iam_client.role_assignments = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
role_assigment_id: RoleAssignment(
|
||||
id=role_assigment_id,
|
||||
name="test",
|
||||
scope=AZURE_SUBSCRIPTION_ID,
|
||||
role_id=VIRTUAL_MACHINE_ADMINISTRATOR_LOGIN_ROLE_ID,
|
||||
agent_type="User",
|
||||
agent_id=user_id,
|
||||
@@ -216,6 +222,9 @@ class Test_iam_assignment_priviledge_access_vm_has_mfa:
|
||||
iam_client.role_assignments = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
role_assigment_id: RoleAssignment(
|
||||
id=role_assigment_id,
|
||||
name="test",
|
||||
scope=AZURE_SUBSCRIPTION_ID,
|
||||
role_id=VIRTUAL_MACHINE_ADMINISTRATOR_LOGIN_ROLE_ID,
|
||||
agent_type="User",
|
||||
agent_id=user_id,
|
||||
@@ -269,6 +278,9 @@ class Test_iam_assignment_priviledge_access_vm_has_mfa:
|
||||
iam_client.role_assignments = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
role_assigment_id: RoleAssignment(
|
||||
id=role_assigment_id,
|
||||
name="test",
|
||||
scope=AZURE_SUBSCRIPTION_ID,
|
||||
role_id=str(uuid4()),
|
||||
agent_type="User",
|
||||
agent_id=user_id,
|
||||
|
||||
+24
-19
@@ -1,5 +1,4 @@
|
||||
from unittest import mock
|
||||
from uuid import uuid4
|
||||
|
||||
from azure.mgmt.authorization.v2022_04_01.models import Permission
|
||||
|
||||
@@ -39,9 +38,9 @@ class Test_iam_custom_role_has_permissions_to_administer_resource_locks:
|
||||
defender_client = mock.MagicMock
|
||||
role_name = "test-role"
|
||||
defender_client.custom_roles = {
|
||||
AZURE_SUBSCRIPTION_ID: [
|
||||
Role(
|
||||
id=str(uuid4()),
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
"test-role-id": Role(
|
||||
id="test-role-id",
|
||||
name=role_name,
|
||||
type="CustomRole",
|
||||
assignable_scopes=["/.*", "/test"],
|
||||
@@ -54,7 +53,7 @@ class Test_iam_custom_role_has_permissions_to_administer_resource_locks:
|
||||
)
|
||||
],
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
with (
|
||||
@@ -82,7 +81,9 @@ class Test_iam_custom_role_has_permissions_to_administer_resource_locks:
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert (
|
||||
result[0].resource_id
|
||||
== defender_client.custom_roles[AZURE_SUBSCRIPTION_ID][0].id
|
||||
== defender_client.custom_roles[AZURE_SUBSCRIPTION_ID][
|
||||
"test-role-id"
|
||||
].id
|
||||
)
|
||||
assert result[0].resource_name == role_name
|
||||
|
||||
@@ -92,15 +93,15 @@ class Test_iam_custom_role_has_permissions_to_administer_resource_locks:
|
||||
defender_client = mock.MagicMock
|
||||
role_name = "test-role"
|
||||
defender_client.custom_roles = {
|
||||
AZURE_SUBSCRIPTION_ID: [
|
||||
Role(
|
||||
id=str(uuid4()),
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
"test-role-id": Role(
|
||||
id="test-role-id",
|
||||
name=role_name,
|
||||
type="CustomRole",
|
||||
assignable_scopes=["/*"],
|
||||
permissions=[Permission(actions=["*"])],
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
with (
|
||||
@@ -128,7 +129,9 @@ class Test_iam_custom_role_has_permissions_to_administer_resource_locks:
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert (
|
||||
result[0].resource_id
|
||||
== defender_client.custom_roles[AZURE_SUBSCRIPTION_ID][0].id
|
||||
== defender_client.custom_roles[AZURE_SUBSCRIPTION_ID][
|
||||
"test-role-id"
|
||||
].id
|
||||
)
|
||||
assert result[0].resource_name == role_name
|
||||
|
||||
@@ -139,9 +142,9 @@ class Test_iam_custom_role_has_permissions_to_administer_resource_locks:
|
||||
role_name = "test-role"
|
||||
role_name2 = "test-role2"
|
||||
defender_client.custom_roles = {
|
||||
AZURE_SUBSCRIPTION_ID: [
|
||||
Role(
|
||||
id=str(uuid4()),
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
"test-role-id": Role(
|
||||
id="test-role-id",
|
||||
name=role_name,
|
||||
type="CustomRole",
|
||||
assignable_scopes=["/.*", "/test"],
|
||||
@@ -154,8 +157,8 @@ class Test_iam_custom_role_has_permissions_to_administer_resource_locks:
|
||||
)
|
||||
],
|
||||
),
|
||||
Role(
|
||||
id=str(uuid4()),
|
||||
"test-role-id2": Role(
|
||||
id="test-role-id2",
|
||||
name=role_name2,
|
||||
type="CustomRole",
|
||||
assignable_scopes=["/.*", "/test"],
|
||||
@@ -168,7 +171,7 @@ class Test_iam_custom_role_has_permissions_to_administer_resource_locks:
|
||||
)
|
||||
],
|
||||
),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
with (
|
||||
@@ -196,12 +199,14 @@ class Test_iam_custom_role_has_permissions_to_administer_resource_locks:
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert (
|
||||
result[0].resource_id
|
||||
== defender_client.custom_roles[AZURE_SUBSCRIPTION_ID][0].id
|
||||
== defender_client.custom_roles[AZURE_SUBSCRIPTION_ID][
|
||||
"test-role-id"
|
||||
].id
|
||||
)
|
||||
|
||||
def test_iam_custom_roles_empty_list_but_with_key(self):
|
||||
defender_client = mock.MagicMock
|
||||
defender_client.custom_roles = {AZURE_SUBSCRIPTION_ID: []}
|
||||
defender_client.custom_roles = {AZURE_SUBSCRIPTION_ID: {}}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
from unittest import mock
|
||||
from uuid import uuid4
|
||||
|
||||
from prowler.providers.azure.services.iam.iam_service import Role, RoleAssignment
|
||||
from tests.providers.azure.azure_fixtures import (
|
||||
AZURE_SUBSCRIPTION_ID,
|
||||
set_mocked_azure_provider,
|
||||
)
|
||||
|
||||
|
||||
class Test_iam_role_user_access_admin_restricted:
|
||||
def test_iam_no_role_assignments(self):
|
||||
iam_client = mock.MagicMock
|
||||
iam_client.role_assignments = {}
|
||||
iam_client.roles = {}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.iam.iam_role_user_access_admin_restricted.iam_role_user_access_admin_restricted.iam_client",
|
||||
new=iam_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.iam.iam_role_user_access_admin_restricted.iam_role_user_access_admin_restricted import (
|
||||
iam_role_user_access_admin_restricted,
|
||||
)
|
||||
|
||||
check = iam_role_user_access_admin_restricted()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_iam_user_access_administrator_role_assigned(self):
|
||||
iam_client = mock.MagicMock
|
||||
role_id = str(uuid4())
|
||||
role_assignment_id = str(uuid4())
|
||||
agent_id = str(uuid4())
|
||||
role_name = "User Access Administrator"
|
||||
|
||||
iam_client.subscriptions = {
|
||||
"subscription-name-1": AZURE_SUBSCRIPTION_ID,
|
||||
}
|
||||
|
||||
iam_client.role_assignments = {
|
||||
"subscription-name-1": {
|
||||
role_assignment_id: RoleAssignment(
|
||||
id=role_assignment_id,
|
||||
name="test-assignment",
|
||||
scope=f"/subscriptions/{AZURE_SUBSCRIPTION_ID}",
|
||||
agent_id=agent_id,
|
||||
agent_type="User",
|
||||
role_id=role_id,
|
||||
)
|
||||
}
|
||||
}
|
||||
iam_client.roles = {
|
||||
"subscription-name-1": {
|
||||
f"/subscriptions/{AZURE_SUBSCRIPTION_ID}/providers/Microsoft.Authorization/roleDefinitions/{role_id}": Role(
|
||||
id=role_id,
|
||||
name=role_name,
|
||||
type="BuiltInRole",
|
||||
assignable_scopes=[f"/subscriptions/{AZURE_SUBSCRIPTION_ID}"],
|
||||
permissions=[],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.iam.iam_role_user_access_admin_restricted.iam_role_user_access_admin_restricted.iam_client",
|
||||
new=iam_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.iam.iam_role_user_access_admin_restricted.iam_role_user_access_admin_restricted import (
|
||||
iam_role_user_access_admin_restricted,
|
||||
)
|
||||
|
||||
check = iam_role_user_access_admin_restricted()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Role assignment test-assignment in subscription subscription-name-1 grants User Access Administrator role to User {agent_id}."
|
||||
)
|
||||
assert result[0].subscription == "subscription-name-1"
|
||||
assert result[0].resource_id == role_assignment_id
|
||||
|
||||
def test_iam_non_user_access_administrator_role_assigned(self):
|
||||
iam_client = mock.MagicMock
|
||||
role_id = str(uuid4())
|
||||
role_assignment_id = str(uuid4())
|
||||
agent_id = str(uuid4())
|
||||
role_name = "Reader"
|
||||
|
||||
iam_client.subscriptions = {
|
||||
"subscription-name-1": AZURE_SUBSCRIPTION_ID,
|
||||
}
|
||||
|
||||
iam_client.role_assignments = {
|
||||
"subscription-name-1": {
|
||||
role_assignment_id: RoleAssignment(
|
||||
id=role_assignment_id,
|
||||
name="test-assignment",
|
||||
scope=f"/subscriptions/{AZURE_SUBSCRIPTION_ID}",
|
||||
agent_id=agent_id,
|
||||
agent_type="User",
|
||||
role_id=role_id,
|
||||
)
|
||||
}
|
||||
}
|
||||
iam_client.roles = {
|
||||
"subscription-name-1": {
|
||||
f"/subscriptions/{AZURE_SUBSCRIPTION_ID}/providers/Microsoft.Authorization/roleDefinitions/{role_id}": Role(
|
||||
id=role_id,
|
||||
name=role_name,
|
||||
type="BuiltInRole",
|
||||
assignable_scopes=[f"/subscriptions/{AZURE_SUBSCRIPTION_ID}"],
|
||||
permissions=[],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.iam.iam_role_user_access_admin_restricted.iam_role_user_access_admin_restricted.iam_client",
|
||||
new=iam_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.iam.iam_role_user_access_admin_restricted.iam_role_user_access_admin_restricted import (
|
||||
iam_role_user_access_admin_restricted,
|
||||
)
|
||||
|
||||
check = iam_role_user_access_admin_restricted()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Role assignment test-assignment in subscription subscription-name-1 does not grant User Access Administrator role."
|
||||
)
|
||||
assert result[0].subscription == "subscription-name-1"
|
||||
assert result[0].resource_id == role_assignment_id
|
||||
+14
-11
@@ -1,5 +1,4 @@
|
||||
from unittest import mock
|
||||
from uuid import uuid4
|
||||
|
||||
from azure.mgmt.authorization.v2022_04_01.models import Permission
|
||||
|
||||
@@ -37,15 +36,15 @@ class Test_iam_subscription_roles_owner_custom_not_created:
|
||||
defender_client = mock.MagicMock
|
||||
role_name = "test-role"
|
||||
defender_client.custom_roles = {
|
||||
AZURE_SUBSCRIPTION_ID: [
|
||||
Role(
|
||||
id=str(uuid4()),
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
"test-role-id": Role(
|
||||
id="test-role-id",
|
||||
name=role_name,
|
||||
type="CustomRole",
|
||||
assignable_scopes=["/*"],
|
||||
permissions=[Permission(actions="*")],
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
with (
|
||||
@@ -73,7 +72,9 @@ class Test_iam_subscription_roles_owner_custom_not_created:
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert (
|
||||
result[0].resource_id
|
||||
== defender_client.custom_roles[AZURE_SUBSCRIPTION_ID][0].id
|
||||
== defender_client.custom_roles[AZURE_SUBSCRIPTION_ID][
|
||||
"test-role-id"
|
||||
].id
|
||||
)
|
||||
assert result[0].resource_name == role_name
|
||||
|
||||
@@ -81,15 +82,15 @@ class Test_iam_subscription_roles_owner_custom_not_created:
|
||||
defender_client = mock.MagicMock
|
||||
role_name = "test-role"
|
||||
defender_client.custom_roles = {
|
||||
AZURE_SUBSCRIPTION_ID: [
|
||||
Role(
|
||||
id=str(uuid4()),
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
"test-role-id": Role(
|
||||
id="test-role-id",
|
||||
name=role_name,
|
||||
type="type-role",
|
||||
assignable_scopes=[""],
|
||||
permissions=[Permission()],
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
with (
|
||||
@@ -117,6 +118,8 @@ class Test_iam_subscription_roles_owner_custom_not_created:
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert (
|
||||
result[0].resource_id
|
||||
== defender_client.custom_roles[AZURE_SUBSCRIPTION_ID][0].id
|
||||
== defender_client.custom_roles[AZURE_SUBSCRIPTION_ID][
|
||||
"test-role-id"
|
||||
].id
|
||||
)
|
||||
assert result[0].resource_name == role_name
|
||||
+52
@@ -188,6 +188,58 @@ class Test_network_flow_log_more_than_90_days:
|
||||
assert result[0].resource_id == network_watcher_id
|
||||
assert result[0].location == "location"
|
||||
|
||||
def test_network_network_watchers_flow_logs_retention_days_0(self):
|
||||
network_client = mock.MagicMock
|
||||
network_watcher_name = "Network Watcher Name"
|
||||
network_watcher_id = str(uuid4())
|
||||
|
||||
network_client.network_watchers = {
|
||||
AZURE_SUBSCRIPTION_ID: [
|
||||
NetworkWatcher(
|
||||
id=network_watcher_id,
|
||||
name=network_watcher_name,
|
||||
location="location",
|
||||
flow_logs=[
|
||||
FlowLog(
|
||||
enabled=True,
|
||||
retention_policy=RetentionPolicyParameters(days=0),
|
||||
)
|
||||
],
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.network.network_service.Network",
|
||||
new=network_client,
|
||||
) as service_client,
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.network.network_client.network_client",
|
||||
new=service_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.network.network_flow_log_more_than_90_days.network_flow_log_more_than_90_days import (
|
||||
network_flow_log_more_than_90_days,
|
||||
)
|
||||
|
||||
check = network_flow_log_more_than_90_days()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Network Watcher {network_watcher_name} from subscription {AZURE_SUBSCRIPTION_ID} has flow logs enabled for more than 90 days"
|
||||
)
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == network_watcher_name
|
||||
assert result[0].resource_id == network_watcher_id
|
||||
assert result[0].location == "location"
|
||||
|
||||
def test_network_network_watchers_flow_logs_well_configured(self):
|
||||
network_client = mock.MagicMock
|
||||
network_watcher_name = "Network Watcher Name"
|
||||
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
from unittest import mock
|
||||
from uuid import uuid4
|
||||
|
||||
from prowler.providers.azure.services.storage.storage_service import Account
|
||||
from tests.providers.azure.azure_fixtures import (
|
||||
AZURE_SUBSCRIPTION_ID,
|
||||
set_mocked_azure_provider,
|
||||
)
|
||||
|
||||
|
||||
class Test_storage_account_key_access_disabled:
|
||||
def test_no_storage_accounts(self):
|
||||
storage_client = mock.MagicMock
|
||||
storage_client.storage_accounts = {}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.storage.storage_account_key_access_disabled.storage_account_key_access_disabled.storage_client",
|
||||
new=storage_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.storage.storage_account_key_access_disabled.storage_account_key_access_disabled import (
|
||||
storage_account_key_access_disabled,
|
||||
)
|
||||
|
||||
check = storage_account_key_access_disabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_storage_account_shared_key_access_enabled(self):
|
||||
storage_account_id = str(uuid4())
|
||||
storage_account_name = "Test Storage Account"
|
||||
storage_client = mock.MagicMock
|
||||
storage_client.storage_accounts = {
|
||||
AZURE_SUBSCRIPTION_ID: [
|
||||
Account(
|
||||
id=storage_account_id,
|
||||
name=storage_account_name,
|
||||
resouce_group_name=None,
|
||||
enable_https_traffic_only=False,
|
||||
infrastructure_encryption=False,
|
||||
allow_blob_public_access=None,
|
||||
network_rule_set=None,
|
||||
encryption_type=None,
|
||||
minimum_tls_version=None,
|
||||
key_expiration_period_in_days=None,
|
||||
location="westeurope",
|
||||
private_endpoint_connections=None,
|
||||
allow_shared_key_access=True,
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.storage.storage_account_key_access_disabled.storage_account_key_access_disabled.storage_client",
|
||||
new=storage_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.storage.storage_account_key_access_disabled.storage_account_key_access_disabled import (
|
||||
storage_account_key_access_disabled,
|
||||
)
|
||||
|
||||
check = storage_account_key_access_disabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Storage account {storage_account_name} from subscription {AZURE_SUBSCRIPTION_ID} has shared key access enabled."
|
||||
)
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == storage_account_name
|
||||
assert result[0].resource_id == storage_account_id
|
||||
assert result[0].location == "westeurope"
|
||||
|
||||
def test_storage_account_shared_key_access_disabled(self):
|
||||
storage_account_id = str(uuid4())
|
||||
storage_account_name = "Test Storage Account"
|
||||
storage_client = mock.MagicMock
|
||||
storage_client.storage_accounts = {
|
||||
AZURE_SUBSCRIPTION_ID: [
|
||||
Account(
|
||||
id=storage_account_id,
|
||||
name=storage_account_name,
|
||||
resouce_group_name=None,
|
||||
enable_https_traffic_only=False,
|
||||
infrastructure_encryption=False,
|
||||
allow_blob_public_access=None,
|
||||
network_rule_set=None,
|
||||
encryption_type=None,
|
||||
minimum_tls_version=None,
|
||||
key_expiration_period_in_days=None,
|
||||
location="westeurope",
|
||||
private_endpoint_connections=None,
|
||||
allow_shared_key_access=False,
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.storage.storage_account_key_access_disabled.storage_account_key_access_disabled.storage_client",
|
||||
new=storage_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.storage.storage_account_key_access_disabled.storage_account_key_access_disabled import (
|
||||
storage_account_key_access_disabled,
|
||||
)
|
||||
|
||||
check = storage_account_key_access_disabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Storage account {storage_account_name} from subscription {AZURE_SUBSCRIPTION_ID} has shared key access disabled."
|
||||
)
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == storage_account_name
|
||||
assert result[0].resource_id == storage_account_id
|
||||
assert result[0].location == "westeurope"
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
from unittest import mock
|
||||
from uuid import uuid4
|
||||
|
||||
from tests.providers.azure.azure_fixtures import (
|
||||
AZURE_SUBSCRIPTION_ID,
|
||||
set_mocked_azure_provider,
|
||||
)
|
||||
|
||||
|
||||
class Test_storage_blob_versioning_is_enabled:
|
||||
def test_storage_no_storage_accounts(self):
|
||||
storage_client = mock.MagicMock
|
||||
storage_client.storage_accounts = {}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.storage.storage_blob_versioning_is_enabled.storage_blob_versioning_is_enabled.storage_client",
|
||||
new=storage_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.storage.storage_blob_versioning_is_enabled.storage_blob_versioning_is_enabled import (
|
||||
storage_blob_versioning_is_enabled,
|
||||
)
|
||||
|
||||
check = storage_blob_versioning_is_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_storage_no_blob_properties(self):
|
||||
storage_account_id = str(uuid4())
|
||||
storage_account_name = "Test Storage Account"
|
||||
storage_client = mock.MagicMock
|
||||
storage_account_blob_properties = None
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.storage.storage_blob_versioning_is_enabled.storage_blob_versioning_is_enabled.storage_client",
|
||||
new=storage_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.storage.storage_service import Account
|
||||
|
||||
storage_client.storage_accounts = {
|
||||
AZURE_SUBSCRIPTION_ID: [
|
||||
Account(
|
||||
id=storage_account_id,
|
||||
name=storage_account_name,
|
||||
resouce_group_name=None,
|
||||
enable_https_traffic_only=False,
|
||||
infrastructure_encryption=False,
|
||||
allow_blob_public_access=None,
|
||||
network_rule_set=None,
|
||||
encryption_type="None",
|
||||
minimum_tls_version=None,
|
||||
key_expiration_period_in_days=None,
|
||||
location="westeurope",
|
||||
private_endpoint_connections=None,
|
||||
blob_properties=storage_account_blob_properties,
|
||||
)
|
||||
]
|
||||
}
|
||||
from prowler.providers.azure.services.storage.storage_blob_versioning_is_enabled.storage_blob_versioning_is_enabled import (
|
||||
storage_blob_versioning_is_enabled,
|
||||
)
|
||||
|
||||
check = storage_blob_versioning_is_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_storage_blob_versioning_is_enabled(self):
|
||||
storage_account_id = str(uuid4())
|
||||
storage_account_name = "Test Storage Account"
|
||||
storage_client = mock.MagicMock
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.storage.storage_blob_versioning_is_enabled.storage_blob_versioning_is_enabled.storage_client",
|
||||
new=storage_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.storage.storage_service import (
|
||||
Account,
|
||||
BlobProperties,
|
||||
DeleteRetentionPolicy,
|
||||
)
|
||||
|
||||
storage_account_blob_properties = BlobProperties(
|
||||
id=None,
|
||||
name=None,
|
||||
type=None,
|
||||
default_service_version=None,
|
||||
container_delete_retention_policy=DeleteRetentionPolicy(
|
||||
enabled=False, days=0
|
||||
),
|
||||
versioning_enabled=True,
|
||||
)
|
||||
storage_client.storage_accounts = {
|
||||
AZURE_SUBSCRIPTION_ID: [
|
||||
Account(
|
||||
id=storage_account_id,
|
||||
name=storage_account_name,
|
||||
resouce_group_name=None,
|
||||
enable_https_traffic_only=False,
|
||||
infrastructure_encryption=False,
|
||||
allow_blob_public_access=None,
|
||||
network_rule_set=None,
|
||||
encryption_type="None",
|
||||
minimum_tls_version=None,
|
||||
key_expiration_period_in_days=None,
|
||||
location="westeurope",
|
||||
private_endpoint_connections=None,
|
||||
blob_properties=storage_account_blob_properties,
|
||||
)
|
||||
]
|
||||
}
|
||||
from prowler.providers.azure.services.storage.storage_blob_versioning_is_enabled.storage_blob_versioning_is_enabled import (
|
||||
storage_blob_versioning_is_enabled,
|
||||
)
|
||||
|
||||
check = storage_blob_versioning_is_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Storage account {storage_account_name} from subscription {AZURE_SUBSCRIPTION_ID} has blob versioning enabled."
|
||||
)
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == storage_account_name
|
||||
assert result[0].resource_id == storage_account_id
|
||||
assert result[0].location == "westeurope"
|
||||
|
||||
def test_storage_blob_versioning_is_disabled(self):
|
||||
storage_account_id = str(uuid4())
|
||||
storage_account_name = "Test Storage Account"
|
||||
storage_client = mock.MagicMock
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.storage.storage_blob_versioning_is_enabled.storage_blob_versioning_is_enabled.storage_client",
|
||||
new=storage_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.storage.storage_service import (
|
||||
Account,
|
||||
BlobProperties,
|
||||
DeleteRetentionPolicy,
|
||||
)
|
||||
|
||||
storage_account_blob_properties = BlobProperties(
|
||||
id=None,
|
||||
name=None,
|
||||
type=None,
|
||||
default_service_version=None,
|
||||
container_delete_retention_policy=DeleteRetentionPolicy(
|
||||
enabled=False, days=0
|
||||
),
|
||||
versioning_enabled=False,
|
||||
)
|
||||
storage_client.storage_accounts = {
|
||||
AZURE_SUBSCRIPTION_ID: [
|
||||
Account(
|
||||
id=storage_account_id,
|
||||
name=storage_account_name,
|
||||
resouce_group_name=None,
|
||||
enable_https_traffic_only=False,
|
||||
infrastructure_encryption=False,
|
||||
allow_blob_public_access=None,
|
||||
network_rule_set=None,
|
||||
encryption_type="None",
|
||||
minimum_tls_version=None,
|
||||
key_expiration_period_in_days=None,
|
||||
location="westeurope",
|
||||
private_endpoint_connections=None,
|
||||
blob_properties=storage_account_blob_properties,
|
||||
)
|
||||
]
|
||||
}
|
||||
from prowler.providers.azure.services.storage.storage_blob_versioning_is_enabled.storage_blob_versioning_is_enabled import (
|
||||
storage_blob_versioning_is_enabled,
|
||||
)
|
||||
|
||||
check = storage_blob_versioning_is_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Storage account {storage_account_name} from subscription {AZURE_SUBSCRIPTION_ID} does not have blob versioning enabled."
|
||||
)
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == storage_account_name
|
||||
assert result[0].resource_id == storage_account_id
|
||||
assert result[0].location == "westeurope"
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
from unittest import mock
|
||||
from uuid import uuid4
|
||||
|
||||
from prowler.providers.azure.services.storage.storage_service import Account
|
||||
from tests.providers.azure.azure_fixtures import (
|
||||
AZURE_SUBSCRIPTION_ID,
|
||||
set_mocked_azure_provider,
|
||||
)
|
||||
|
||||
|
||||
class Test_storage_cross_tenant_replication_disabled:
|
||||
def test_no_storage_accounts(self):
|
||||
storage_client = mock.MagicMock
|
||||
storage_client.storage_accounts = {}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.storage.storage_cross_tenant_replication_disabled.storage_cross_tenant_replication_disabled.storage_client",
|
||||
new=storage_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.storage.storage_cross_tenant_replication_disabled.storage_cross_tenant_replication_disabled import (
|
||||
storage_cross_tenant_replication_disabled,
|
||||
)
|
||||
|
||||
check = storage_cross_tenant_replication_disabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_storage_account_cross_tenant_replication_enabled(self):
|
||||
storage_account_id = str(uuid4())
|
||||
storage_account_name = "Test Storage Account"
|
||||
storage_client = mock.MagicMock
|
||||
storage_client.storage_accounts = {
|
||||
AZURE_SUBSCRIPTION_ID: [
|
||||
Account(
|
||||
id=storage_account_id,
|
||||
name=storage_account_name,
|
||||
resouce_group_name=None,
|
||||
enable_https_traffic_only=False,
|
||||
infrastructure_encryption=False,
|
||||
allow_blob_public_access=None,
|
||||
network_rule_set=None,
|
||||
encryption_type=None,
|
||||
minimum_tls_version=None,
|
||||
key_expiration_period_in_days=None,
|
||||
location="westeurope",
|
||||
private_endpoint_connections=None,
|
||||
allow_cross_tenant_replication=True,
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.storage.storage_cross_tenant_replication_disabled.storage_cross_tenant_replication_disabled.storage_client",
|
||||
new=storage_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.storage.storage_cross_tenant_replication_disabled.storage_cross_tenant_replication_disabled import (
|
||||
storage_cross_tenant_replication_disabled,
|
||||
)
|
||||
|
||||
check = storage_cross_tenant_replication_disabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Storage account {storage_account_name} from subscription {AZURE_SUBSCRIPTION_ID} has cross-tenant replication enabled."
|
||||
)
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == storage_account_name
|
||||
assert result[0].resource_id == storage_account_id
|
||||
assert result[0].location == "westeurope"
|
||||
|
||||
def test_storage_account_cross_tenant_replication_disabled(self):
|
||||
storage_account_id = str(uuid4())
|
||||
storage_account_name = "Test Storage Account"
|
||||
storage_client = mock.MagicMock
|
||||
storage_client.storage_accounts = {
|
||||
AZURE_SUBSCRIPTION_ID: [
|
||||
Account(
|
||||
id=storage_account_id,
|
||||
name=storage_account_name,
|
||||
resouce_group_name=None,
|
||||
enable_https_traffic_only=False,
|
||||
infrastructure_encryption=False,
|
||||
allow_blob_public_access=None,
|
||||
network_rule_set=None,
|
||||
encryption_type=None,
|
||||
minimum_tls_version=None,
|
||||
key_expiration_period_in_days=None,
|
||||
location="westeurope",
|
||||
private_endpoint_connections=None,
|
||||
allow_cross_tenant_replication=False,
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.storage.storage_cross_tenant_replication_disabled.storage_cross_tenant_replication_disabled.storage_client",
|
||||
new=storage_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.storage.storage_cross_tenant_replication_disabled.storage_cross_tenant_replication_disabled import (
|
||||
storage_cross_tenant_replication_disabled,
|
||||
)
|
||||
|
||||
check = storage_cross_tenant_replication_disabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Storage account {storage_account_name} from subscription {AZURE_SUBSCRIPTION_ID} has cross-tenant replication disabled."
|
||||
)
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == storage_account_name
|
||||
assert result[0].resource_id == storage_account_id
|
||||
assert result[0].location == "westeurope"
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
from unittest import mock
|
||||
from uuid import uuid4
|
||||
|
||||
from prowler.providers.azure.services.storage.storage_service import Account
|
||||
from tests.providers.azure.azure_fixtures import (
|
||||
AZURE_SUBSCRIPTION_ID,
|
||||
set_mocked_azure_provider,
|
||||
)
|
||||
|
||||
|
||||
class Test_storage_default_to_entra_authorization_enabled:
|
||||
def test_no_storage_accounts(self):
|
||||
storage_client = mock.MagicMock()
|
||||
storage_client.storage_accounts = {}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.storage.storage_default_to_entra_authorization_enabled.storage_default_to_entra_authorization_enabled.storage_client",
|
||||
new=storage_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.storage.storage_default_to_entra_authorization_enabled.storage_default_to_entra_authorization_enabled import (
|
||||
storage_default_to_entra_authorization_enabled,
|
||||
)
|
||||
|
||||
check = storage_default_to_entra_authorization_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_storage_default_to_entra_authorization_enabled(self):
|
||||
storage_account_id = str(uuid4())
|
||||
storage_account_name = "Test Storage Account Entra Auth Enabled"
|
||||
storage_client = mock.MagicMock()
|
||||
storage_client.storage_accounts = {
|
||||
AZURE_SUBSCRIPTION_ID: [
|
||||
Account(
|
||||
id=storage_account_id,
|
||||
name=storage_account_name,
|
||||
resouce_group_name=None,
|
||||
enable_https_traffic_only=False,
|
||||
infrastructure_encryption=False,
|
||||
allow_blob_public_access=False,
|
||||
network_rule_set=None,
|
||||
encryption_type=None,
|
||||
minimum_tls_version=None,
|
||||
key_expiration_period_in_days=None,
|
||||
location="westeurope",
|
||||
private_endpoint_connections=None,
|
||||
default_to_entra_authorization=True,
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.storage.storage_default_to_entra_authorization_enabled.storage_default_to_entra_authorization_enabled.storage_client",
|
||||
new=storage_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.storage.storage_default_to_entra_authorization_enabled.storage_default_to_entra_authorization_enabled import (
|
||||
storage_default_to_entra_authorization_enabled,
|
||||
)
|
||||
|
||||
check = storage_default_to_entra_authorization_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Default to Microsoft Entra authorization is enabled for storage account {storage_account_name}."
|
||||
)
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == storage_account_name
|
||||
assert result[0].resource_id == storage_account_id
|
||||
assert result[0].location == "westeurope"
|
||||
|
||||
def test_storage_account_default_to_entra_authorization_disabled(self):
|
||||
storage_account_id = str(uuid4())
|
||||
storage_account_name = "Test Storage Account Entra Auth Disabled"
|
||||
storage_client = mock.MagicMock()
|
||||
storage_client.storage_accounts = {
|
||||
AZURE_SUBSCRIPTION_ID: [
|
||||
Account(
|
||||
id=storage_account_id,
|
||||
name=storage_account_name,
|
||||
resouce_group_name=None,
|
||||
enable_https_traffic_only=False,
|
||||
infrastructure_encryption=False,
|
||||
allow_blob_public_access=False,
|
||||
network_rule_set=None,
|
||||
encryption_type=None,
|
||||
minimum_tls_version=None,
|
||||
key_expiration_period_in_days=None,
|
||||
location="westeurope",
|
||||
private_endpoint_connections=None,
|
||||
default_to_entra_authorization=False,
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.storage.storage_default_to_entra_authorization_enabled.storage_default_to_entra_authorization_enabled.storage_client",
|
||||
new=storage_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.storage.storage_default_to_entra_authorization_enabled.storage_default_to_entra_authorization_enabled import (
|
||||
storage_default_to_entra_authorization_enabled,
|
||||
)
|
||||
|
||||
check = storage_default_to_entra_authorization_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Default to Microsoft Entra authorization is not enabled for storage account {storage_account_name}."
|
||||
)
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == storage_account_name
|
||||
assert result[0].resource_id == storage_account_id
|
||||
assert result[0].location == "westeurope"
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
from unittest import mock
|
||||
from uuid import uuid4
|
||||
|
||||
from prowler.providers.azure.services.storage.storage_service import Account, FileShare
|
||||
from tests.providers.azure.azure_fixtures import (
|
||||
AZURE_SUBSCRIPTION_ID,
|
||||
set_mocked_azure_provider,
|
||||
)
|
||||
|
||||
|
||||
class Test_storage_ensure_file_shares_soft_delete_is_enabled:
|
||||
def test_no_storage_accounts(self):
|
||||
storage_client = mock.MagicMock
|
||||
storage_client.storage_accounts = {}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.storage.storage_ensure_file_shares_soft_delete_is_enabled.storage_ensure_file_shares_soft_delete_is_enabled.storage_client",
|
||||
new=storage_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.storage.storage_ensure_file_shares_soft_delete_is_enabled.storage_ensure_file_shares_soft_delete_is_enabled import (
|
||||
storage_ensure_file_shares_soft_delete_is_enabled,
|
||||
)
|
||||
|
||||
check = storage_ensure_file_shares_soft_delete_is_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_no_file_shares(self):
|
||||
storage_account_id = str(uuid4())
|
||||
storage_account_name = "Test Storage Account"
|
||||
storage_client = mock.MagicMock
|
||||
storage_client.storage_accounts = {
|
||||
AZURE_SUBSCRIPTION_ID: [
|
||||
Account(
|
||||
id=storage_account_id,
|
||||
name=storage_account_name,
|
||||
resouce_group_name=None,
|
||||
enable_https_traffic_only=False,
|
||||
infrastructure_encryption=False,
|
||||
allow_blob_public_access=None,
|
||||
network_rule_set=None,
|
||||
encryption_type="None",
|
||||
minimum_tls_version=None,
|
||||
key_expiration_period_in_days=None,
|
||||
location="westeurope",
|
||||
private_endpoint_connections=None,
|
||||
file_shares=[],
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.storage.storage_ensure_file_shares_soft_delete_is_enabled.storage_ensure_file_shares_soft_delete_is_enabled.storage_client",
|
||||
new=storage_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.storage.storage_ensure_file_shares_soft_delete_is_enabled.storage_ensure_file_shares_soft_delete_is_enabled import (
|
||||
storage_ensure_file_shares_soft_delete_is_enabled,
|
||||
)
|
||||
|
||||
check = storage_ensure_file_shares_soft_delete_is_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_file_share_soft_delete_disabled(self):
|
||||
storage_account_id = str(uuid4())
|
||||
storage_account_name = "Test Storage Account"
|
||||
file_share = FileShare(
|
||||
id="fs1",
|
||||
name="share1",
|
||||
soft_delete_enabled=False,
|
||||
retention_days=0,
|
||||
)
|
||||
storage_client = mock.MagicMock
|
||||
storage_client.storage_accounts = {
|
||||
AZURE_SUBSCRIPTION_ID: [
|
||||
Account(
|
||||
id=storage_account_id,
|
||||
name=storage_account_name,
|
||||
resouce_group_name=None,
|
||||
enable_https_traffic_only=False,
|
||||
infrastructure_encryption=False,
|
||||
allow_blob_public_access=None,
|
||||
network_rule_set=None,
|
||||
encryption_type="None",
|
||||
minimum_tls_version=None,
|
||||
key_expiration_period_in_days=None,
|
||||
location="westeurope",
|
||||
private_endpoint_connections=None,
|
||||
file_shares=[file_share],
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.storage.storage_ensure_file_shares_soft_delete_is_enabled.storage_ensure_file_shares_soft_delete_is_enabled.storage_client",
|
||||
new=storage_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.storage.storage_ensure_file_shares_soft_delete_is_enabled.storage_ensure_file_shares_soft_delete_is_enabled import (
|
||||
storage_ensure_file_shares_soft_delete_is_enabled,
|
||||
)
|
||||
|
||||
check = storage_ensure_file_shares_soft_delete_is_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"File share {file_share.name} in storage account {storage_account_name} from subscription {AZURE_SUBSCRIPTION_ID} does not have soft delete enabled or has an invalid retention period."
|
||||
)
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_id == file_share.name
|
||||
assert result[0].location == "westeurope"
|
||||
assert result[0].resource_name == storage_account_name
|
||||
|
||||
def test_file_share_soft_delete_enabled(self):
|
||||
storage_account_id = str(uuid4())
|
||||
storage_account_name = "Test Storage Account"
|
||||
file_share = FileShare(
|
||||
id="fs2",
|
||||
name="share2",
|
||||
soft_delete_enabled=True,
|
||||
retention_days=7,
|
||||
)
|
||||
storage_client = mock.MagicMock
|
||||
storage_client.storage_accounts = {
|
||||
AZURE_SUBSCRIPTION_ID: [
|
||||
Account(
|
||||
id=storage_account_id,
|
||||
name=storage_account_name,
|
||||
resouce_group_name=None,
|
||||
enable_https_traffic_only=False,
|
||||
infrastructure_encryption=False,
|
||||
allow_blob_public_access=None,
|
||||
network_rule_set=None,
|
||||
encryption_type="None",
|
||||
minimum_tls_version=None,
|
||||
key_expiration_period_in_days=None,
|
||||
location="westeurope",
|
||||
private_endpoint_connections=None,
|
||||
file_shares=[file_share],
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.storage.storage_ensure_file_shares_soft_delete_is_enabled.storage_ensure_file_shares_soft_delete_is_enabled.storage_client",
|
||||
new=storage_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.storage.storage_ensure_file_shares_soft_delete_is_enabled.storage_ensure_file_shares_soft_delete_is_enabled import (
|
||||
storage_ensure_file_shares_soft_delete_is_enabled,
|
||||
)
|
||||
|
||||
check = storage_ensure_file_shares_soft_delete_is_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"File share {file_share.name} in storage account {storage_account_name} from subscription {AZURE_SUBSCRIPTION_ID} has soft delete enabled with a retention period of {file_share.retention_days} days."
|
||||
)
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_id == file_share.name
|
||||
assert result[0].location == "westeurope"
|
||||
assert result[0].resource_name == storage_account_name
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
from unittest import mock
|
||||
from uuid import uuid4
|
||||
|
||||
from prowler.providers.azure.services.storage.storage_service import (
|
||||
Account,
|
||||
ReplicationSettings,
|
||||
)
|
||||
from tests.providers.azure.azure_fixtures import (
|
||||
AZURE_SUBSCRIPTION_ID,
|
||||
set_mocked_azure_provider,
|
||||
)
|
||||
|
||||
|
||||
class Test_storage_geo_redundant_enabled:
|
||||
def test_no_storage_accounts(self):
|
||||
storage_client = mock.MagicMock()
|
||||
storage_client.storage_accounts = {}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.storage.storage_geo_redundant_enabled.storage_geo_redundant_enabled.storage_client",
|
||||
new=storage_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.storage.storage_geo_redundant_enabled.storage_geo_redundant_enabled import (
|
||||
storage_geo_redundant_enabled,
|
||||
)
|
||||
|
||||
check = storage_geo_redundant_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_storage_geo_redundant_enabled(self):
|
||||
storage_account_id = str(uuid4())
|
||||
storage_account_name = "Test Storage Account GRS"
|
||||
storage_client = mock.MagicMock()
|
||||
storage_client.storage_accounts = {
|
||||
AZURE_SUBSCRIPTION_ID: [
|
||||
Account(
|
||||
id=storage_account_id,
|
||||
name=storage_account_name,
|
||||
resouce_group_name=None,
|
||||
enable_https_traffic_only=False,
|
||||
infrastructure_encryption=False,
|
||||
allow_blob_public_access=False,
|
||||
network_rule_set=None,
|
||||
encryption_type=None,
|
||||
minimum_tls_version=None,
|
||||
key_expiration_period_in_days=None,
|
||||
location="westeurope",
|
||||
private_endpoint_connections=None,
|
||||
replication_settings=ReplicationSettings.STANDARD_GRS,
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.storage.storage_geo_redundant_enabled.storage_geo_redundant_enabled.storage_client",
|
||||
new=storage_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.storage.storage_geo_redundant_enabled.storage_geo_redundant_enabled import (
|
||||
storage_geo_redundant_enabled,
|
||||
)
|
||||
|
||||
check = storage_geo_redundant_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Storage account {storage_account_name} from subscription {AZURE_SUBSCRIPTION_ID} has Geo-redundant storage (GRS) enabled."
|
||||
)
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == storage_account_name
|
||||
assert result[0].resource_id == storage_account_id
|
||||
assert result[0].location == "westeurope"
|
||||
|
||||
def test_storage_account_geo_redundant_disabled(self):
|
||||
storage_account_id = str(uuid4())
|
||||
storage_account_name = "Test Storage Account LRS"
|
||||
storage_client = mock.MagicMock()
|
||||
storage_client.storage_accounts = {
|
||||
AZURE_SUBSCRIPTION_ID: [
|
||||
Account(
|
||||
id=storage_account_id,
|
||||
name=storage_account_name,
|
||||
resouce_group_name=None,
|
||||
enable_https_traffic_only=False,
|
||||
infrastructure_encryption=False,
|
||||
allow_blob_public_access=False,
|
||||
network_rule_set=None,
|
||||
encryption_type=None,
|
||||
minimum_tls_version=None,
|
||||
key_expiration_period_in_days=None,
|
||||
location="westeurope",
|
||||
private_endpoint_connections=None,
|
||||
replication_settings=ReplicationSettings.STANDARD_LRS,
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.storage.storage_geo_redundant_enabled.storage_geo_redundant_enabled.storage_client",
|
||||
new=storage_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.storage.storage_geo_redundant_enabled.storage_geo_redundant_enabled import (
|
||||
storage_geo_redundant_enabled,
|
||||
)
|
||||
|
||||
check = storage_geo_redundant_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Storage account {storage_account_name} from subscription {AZURE_SUBSCRIPTION_ID} does not have Geo-redundant storage (GRS) enabled."
|
||||
)
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == storage_account_name
|
||||
assert result[0].resource_id == storage_account_id
|
||||
assert result[0].location == "westeurope"
|
||||
@@ -3,6 +3,8 @@ from unittest.mock import patch
|
||||
from prowler.providers.azure.services.storage.storage_service import (
|
||||
Account,
|
||||
BlobProperties,
|
||||
FileShare,
|
||||
ReplicationSettings,
|
||||
Storage,
|
||||
)
|
||||
from tests.providers.azure.azure_fixtures import (
|
||||
@@ -19,6 +21,10 @@ def mock_storage_get_storage_accounts(_):
|
||||
default_service_version=None,
|
||||
container_delete_retention_policy=None,
|
||||
)
|
||||
file_shares = [
|
||||
FileShare(id="fs1", name="share1", soft_delete_enabled=True, retention_days=7),
|
||||
FileShare(id="fs2", name="share2", soft_delete_enabled=False, retention_days=0),
|
||||
]
|
||||
return {
|
||||
AZURE_SUBSCRIPTION_ID: [
|
||||
Account(
|
||||
@@ -35,6 +41,11 @@ def mock_storage_get_storage_accounts(_):
|
||||
private_endpoint_connections=None,
|
||||
location="westeurope",
|
||||
blob_properties=blob_properties,
|
||||
default_to_entra_authorization=True,
|
||||
replication_settings=ReplicationSettings.STANDARD_LRS,
|
||||
allow_cross_tenant_replication=True,
|
||||
allow_shared_key_access=True,
|
||||
file_shares=file_shares,
|
||||
)
|
||||
]
|
||||
}
|
||||
@@ -110,6 +121,23 @@ class Test_Storage_Service:
|
||||
default_service_version=None,
|
||||
container_delete_retention_policy=None,
|
||||
)
|
||||
assert storage.storage_accounts[AZURE_SUBSCRIPTION_ID][
|
||||
0
|
||||
].default_to_entra_authorization
|
||||
assert (
|
||||
storage.storage_accounts[AZURE_SUBSCRIPTION_ID][0].replication_settings
|
||||
== ReplicationSettings.STANDARD_LRS
|
||||
)
|
||||
assert (
|
||||
storage.storage_accounts[AZURE_SUBSCRIPTION_ID][
|
||||
0
|
||||
].allow_cross_tenant_replication
|
||||
is True
|
||||
)
|
||||
assert (
|
||||
storage.storage_accounts[AZURE_SUBSCRIPTION_ID][0].allow_shared_key_access
|
||||
is True
|
||||
)
|
||||
|
||||
def test_get_blob_properties(self):
|
||||
storage = Storage(set_mocked_azure_provider())
|
||||
@@ -143,3 +171,15 @@ class Test_Storage_Service:
|
||||
].blob_properties.container_delete_retention_policy
|
||||
is None
|
||||
)
|
||||
|
||||
def test_get_file_shares_properties(self):
|
||||
storage = Storage(set_mocked_azure_provider())
|
||||
account = storage.storage_accounts[AZURE_SUBSCRIPTION_ID][0]
|
||||
assert hasattr(account, "file_shares")
|
||||
assert len(account.file_shares) == 2
|
||||
assert account.file_shares[0].name == "share1"
|
||||
assert account.file_shares[0].soft_delete_enabled is True
|
||||
assert account.file_shares[0].retention_days == 7
|
||||
assert account.file_shares[1].name == "share2"
|
||||
assert account.file_shares[1].soft_delete_enabled is False
|
||||
assert account.file_shares[1].retention_days == 0
|
||||
@@ -34,7 +34,7 @@ class TestGCPMutelist:
|
||||
|
||||
mutelist = GCPMutelist(mutelist_content=mutelist_fixture)
|
||||
|
||||
assert not mutelist.validate_mutelist()
|
||||
assert len(mutelist.validate_mutelist(mutelist_fixture)) == 0
|
||||
assert mutelist.mutelist == {}
|
||||
assert mutelist.mutelist_file_path is None
|
||||
|
||||
@@ -89,7 +89,7 @@ class TestGCPMutelist:
|
||||
account_uid="project_1",
|
||||
region="test-region",
|
||||
resource_uid="test_resource",
|
||||
resource_tags=[],
|
||||
resource_tags={},
|
||||
muted=False,
|
||||
)
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ class TestGithubMutelist:
|
||||
|
||||
mutelist = GithubMutelist(mutelist_content=mutelist_fixture)
|
||||
|
||||
assert not mutelist.validate_mutelist()
|
||||
assert len(mutelist.validate_mutelist(mutelist_fixture)) == 0
|
||||
assert mutelist.mutelist == {}
|
||||
assert mutelist.mutelist_file_path is None
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
# IAC Provider Constants
|
||||
DEFAULT_SCAN_PATH = "."
|
||||
|
||||
# Sample Checkov Output
|
||||
SAMPLE_CHECKOV_OUTPUT = [
|
||||
{
|
||||
"check_type": "terraform",
|
||||
"results": {
|
||||
"failed_checks": [
|
||||
{
|
||||
"check_id": "CKV_AWS_1",
|
||||
"check_name": "Ensure S3 bucket has encryption enabled",
|
||||
"guideline": "https://docs.bridgecrew.io/docs/s3_1-s3-bucket-has-encryption-enabled",
|
||||
"severity": "low",
|
||||
},
|
||||
{
|
||||
"check_id": "CKV_AWS_2",
|
||||
"check_name": "Ensure S3 bucket has public access blocked",
|
||||
"guideline": "https://docs.bridgecrew.io/docs/s3_2-s3-bucket-has-public-access-blocked",
|
||||
"severity": "low",
|
||||
},
|
||||
],
|
||||
"passed_checks": [
|
||||
{
|
||||
"check_id": "CKV_AWS_3",
|
||||
"check_name": "Ensure S3 bucket has versioning enabled",
|
||||
"guideline": "https://docs.bridgecrew.io/docs/s3_3-s3-bucket-has-versioning-enabled",
|
||||
"severity": "low",
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
# Sample Finding Data
|
||||
SAMPLE_FINDING = SAMPLE_CHECKOV_OUTPUT[0]
|
||||
|
||||
SAMPLE_FAILED_CHECK = {
|
||||
"check_id": "CKV_AWS_1",
|
||||
"check_name": "Ensure S3 bucket has encryption enabled",
|
||||
"guideline": "https://docs.bridgecrew.io/docs/s3_1-s3-bucket-has-encryption-enabled",
|
||||
"severity": "low",
|
||||
}
|
||||
|
||||
SAMPLE_PASSED_CHECK = {
|
||||
"check_id": "CKV_AWS_3",
|
||||
"check_name": "Ensure S3 bucket has versioning enabled",
|
||||
"guideline": "https://docs.bridgecrew.io/docs/s3_3-s3-bucket-has-versioning-enabled",
|
||||
"severity": "low",
|
||||
}
|
||||
|
||||
|
||||
def get_sample_checkov_json_output():
|
||||
"""Return sample Checkov JSON output as string"""
|
||||
import json
|
||||
|
||||
return json.dumps(SAMPLE_CHECKOV_OUTPUT)
|
||||
|
||||
|
||||
def get_empty_checkov_output():
|
||||
"""Return empty Checkov output as string"""
|
||||
return "[]"
|
||||
|
||||
|
||||
def get_invalid_checkov_output():
|
||||
"""Return invalid JSON output as string"""
|
||||
return "invalid json output"
|
||||
@@ -0,0 +1,132 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from prowler.lib.check.models import CheckReportIAC
|
||||
from prowler.providers.iac.iac_provider import IacProvider
|
||||
from tests.providers.iac.iac_fixtures import (
|
||||
DEFAULT_SCAN_PATH,
|
||||
SAMPLE_FAILED_CHECK,
|
||||
SAMPLE_FINDING,
|
||||
SAMPLE_PASSED_CHECK,
|
||||
get_empty_checkov_output,
|
||||
get_invalid_checkov_output,
|
||||
get_sample_checkov_json_output,
|
||||
)
|
||||
|
||||
|
||||
class TestIacProvider:
|
||||
def test_iac_provider(self):
|
||||
"""Test IAC provider with default parameters"""
|
||||
|
||||
provider = IacProvider()
|
||||
|
||||
assert provider._type == "iac"
|
||||
assert provider.type == "iac"
|
||||
assert provider.scan_path == DEFAULT_SCAN_PATH
|
||||
assert provider._audit_config == {}
|
||||
assert provider._mutelist is None
|
||||
|
||||
def test_iac_provider_custom_scan_path(self):
|
||||
"""Test IAC provider with custom scan path"""
|
||||
custom_path = "/custom/path"
|
||||
|
||||
provider = IacProvider(scan_path=custom_path)
|
||||
|
||||
assert provider._type == "iac"
|
||||
assert provider.scan_path == custom_path
|
||||
|
||||
def test_iac_provider_process_check_failed(self):
|
||||
"""Test processing a failed check"""
|
||||
provider = IacProvider()
|
||||
|
||||
report = provider._process_check(SAMPLE_FINDING, SAMPLE_FAILED_CHECK, "FAIL")
|
||||
|
||||
assert isinstance(report, CheckReportIAC)
|
||||
assert report.status == "FAIL"
|
||||
|
||||
assert report.check_metadata.Provider == "iac"
|
||||
assert report.check_metadata.CheckID == SAMPLE_FAILED_CHECK["check_id"]
|
||||
assert report.check_metadata.CheckTitle == SAMPLE_FAILED_CHECK["check_name"]
|
||||
assert report.check_metadata.Severity == "low"
|
||||
assert report.check_metadata.RelatedUrl == SAMPLE_FAILED_CHECK["guideline"]
|
||||
|
||||
def test_iac_provider_process_check_passed(self):
|
||||
"""Test processing a passed check"""
|
||||
provider = IacProvider()
|
||||
|
||||
report = provider._process_check(SAMPLE_FINDING, SAMPLE_PASSED_CHECK, "PASS")
|
||||
|
||||
assert isinstance(report, CheckReportIAC)
|
||||
assert report.status == "PASS"
|
||||
|
||||
assert report.check_metadata.Provider == "iac"
|
||||
assert report.check_metadata.CheckID == SAMPLE_PASSED_CHECK["check_id"]
|
||||
assert report.check_metadata.CheckTitle == SAMPLE_PASSED_CHECK["check_name"]
|
||||
assert report.check_metadata.Severity == "low"
|
||||
|
||||
@patch("subprocess.run")
|
||||
def test_iac_provider_run_scan_success(self, mock_subprocess):
|
||||
"""Test successful IAC scan with Checkov"""
|
||||
provider = IacProvider()
|
||||
|
||||
mock_subprocess.return_value = MagicMock(
|
||||
stdout=get_sample_checkov_json_output(), stderr=""
|
||||
)
|
||||
|
||||
reports = provider.run_scan("/test/directory")
|
||||
|
||||
# Should have 2 failed checks + 1 passed check = 3 total reports
|
||||
assert len(reports) == 3
|
||||
|
||||
# Check that we have both failed and passed reports
|
||||
failed_reports = [r for r in reports if r.status == "FAIL"]
|
||||
passed_reports = [r for r in reports if r.status == "PASS"]
|
||||
|
||||
assert len(failed_reports) == 2
|
||||
assert len(passed_reports) == 1
|
||||
|
||||
# Verify subprocess was called correctly
|
||||
mock_subprocess.assert_called_once_with(
|
||||
["checkov", "-d", "/test/directory", "-o", "json"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
@patch("subprocess.run")
|
||||
def test_iac_provider_run_scan_empty_output(self, mock_subprocess):
|
||||
"""Test IAC scan with empty Checkov output"""
|
||||
provider = IacProvider()
|
||||
|
||||
mock_subprocess.return_value = MagicMock(
|
||||
stdout=get_empty_checkov_output(), stderr=""
|
||||
)
|
||||
|
||||
reports = provider.run_scan("/test/directory")
|
||||
|
||||
assert len(reports) == 0
|
||||
|
||||
@patch("subprocess.run")
|
||||
def test_iac_provider_run_scan_invalid_json(self, mock_subprocess):
|
||||
"""Test IAC scan with invalid JSON output"""
|
||||
provider = IacProvider()
|
||||
|
||||
mock_subprocess.return_value = MagicMock(
|
||||
stdout=get_invalid_checkov_output(), stderr=""
|
||||
)
|
||||
|
||||
with pytest.raises(SystemExit) as excinfo:
|
||||
provider.run_scan("/test/directory")
|
||||
|
||||
assert excinfo.value.code == 1
|
||||
|
||||
@patch("subprocess.run")
|
||||
def test_iac_provider_run_scan_null_output(self, mock_subprocess):
|
||||
"""Test IAC scan with null Checkov output"""
|
||||
provider = IacProvider()
|
||||
|
||||
mock_subprocess.return_value = MagicMock(stdout="null", stderr="")
|
||||
|
||||
reports = provider.run_scan("/test/directory")
|
||||
|
||||
assert len(reports) == 0
|
||||
@@ -1,5 +1,5 @@
|
||||
from argparse import Namespace
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest.mock import patch
|
||||
|
||||
from kubernetes.config.config_exception import ConfigException
|
||||
|
||||
@@ -354,58 +354,322 @@ class TestKubernetesProvider:
|
||||
assert isinstance(session, KubernetesSession)
|
||||
assert session.context["context"]["cluster"] == "cli-cluster-name"
|
||||
|
||||
def test_kubernetes_provider_proxy_from_env(self, monkeypatch):
|
||||
@patch(
|
||||
"prowler.providers.kubernetes.kubernetes_provider.client.CoreV1Api.list_namespace"
|
||||
)
|
||||
@patch("kubernetes.config.list_kube_config_contexts")
|
||||
@patch("kubernetes.config.load_kube_config_from_dict")
|
||||
def test_kubernetes_provider_proxy_from_env(
|
||||
self,
|
||||
mock_load_kube_config_from_dict,
|
||||
mock_list_kube_config_contexts,
|
||||
mock_list_namespace,
|
||||
monkeypatch,
|
||||
):
|
||||
|
||||
monkeypatch.setenv("HTTPS_PROXY", "http://my.internal.proxy:8888")
|
||||
|
||||
captured = {}
|
||||
mock_load_kube_config_from_dict.return_value = None
|
||||
mock_list_kube_config_contexts.return_value = (
|
||||
[
|
||||
{
|
||||
"name": "example-context",
|
||||
"context": {
|
||||
"cluster": "example-cluster",
|
||||
"user": "example-user",
|
||||
},
|
||||
}
|
||||
],
|
||||
None,
|
||||
)
|
||||
mock_list_namespace.return_value.items = [
|
||||
client.V1Namespace(metadata=client.V1ObjectMeta(name="namespace-1")),
|
||||
]
|
||||
|
||||
def fake_api_client(configuration):
|
||||
captured["proxy"] = getattr(configuration, "proxy", None)
|
||||
return MagicMock()
|
||||
kubeconfig_content = '{"apiVersion": "v1", "clusters": [{"cluster": {"server": "https://kubernetes.example.com"}, "name": "example-cluster"}], "contexts": [{"context": {"cluster": "example-cluster", "user": "example-user"}, "name": "example-context"}], "current-context": "example-context", "kind": "Config", "preferences": {}, "users": [{"name": "example-user", "user": {"token": "EXAMPLE_TOKEN"}}]}'
|
||||
|
||||
with (
|
||||
patch(
|
||||
"kubernetes.config.load_kube_config",
|
||||
side_effect=ConfigException("No kubeconfig"),
|
||||
),
|
||||
patch("kubernetes.config.load_incluster_config", return_value=None),
|
||||
patch(
|
||||
"prowler.providers.kubernetes.kubernetes_provider.ApiClient",
|
||||
side_effect=fake_api_client,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.kubernetes.kubernetes_provider.KubernetesProvider.get_all_namespaces",
|
||||
return_value=["default"],
|
||||
),
|
||||
):
|
||||
KubernetesProvider.setup_session()
|
||||
session = KubernetesProvider.setup_session(
|
||||
kubeconfig_content=kubeconfig_content,
|
||||
context="example-context",
|
||||
)
|
||||
|
||||
assert captured["proxy"] == "http://my.internal.proxy:8888"
|
||||
assert isinstance(session, KubernetesSession)
|
||||
assert isinstance(session.api_client, client.ApiClient)
|
||||
assert isinstance(session.api_client.configuration, client.Configuration)
|
||||
assert session.api_client.configuration.verify_ssl
|
||||
assert session.api_client.configuration.proxy == "http://my.internal.proxy:8888"
|
||||
|
||||
def test_kubernetes_provider_disable_tls_verification(self, monkeypatch):
|
||||
@patch(
|
||||
"prowler.providers.kubernetes.kubernetes_provider.client.CoreV1Api.list_namespace"
|
||||
)
|
||||
@patch("kubernetes.config.list_kube_config_contexts")
|
||||
@patch("kubernetes.config.load_kube_config_from_dict")
|
||||
def test_kubernetes_provider_disable_tls_verification(
|
||||
self,
|
||||
mock_load_kube_config_from_dict,
|
||||
mock_list_kube_config_contexts,
|
||||
mock_list_namespace,
|
||||
monkeypatch,
|
||||
):
|
||||
monkeypatch.setenv("K8S_SKIP_TLS_VERIFY", "true")
|
||||
|
||||
captured = {}
|
||||
mock_load_kube_config_from_dict.return_value = None
|
||||
mock_list_kube_config_contexts.return_value = (
|
||||
[
|
||||
{
|
||||
"name": "example-context",
|
||||
"context": {
|
||||
"cluster": "example-cluster",
|
||||
"user": "example-user",
|
||||
},
|
||||
}
|
||||
],
|
||||
None,
|
||||
)
|
||||
mock_list_namespace.return_value.items = [
|
||||
client.V1Namespace(metadata=client.V1ObjectMeta(name="namespace-1")),
|
||||
]
|
||||
|
||||
def fake_api_client(configuration):
|
||||
captured["verify_ssl"] = getattr(configuration, "verify_ssl", True)
|
||||
return MagicMock()
|
||||
kubeconfig_content = '{"apiVersion": "v1", "clusters": [{"cluster": {"server": "https://kubernetes.example.com"}, "name": "example-cluster"}], "contexts": [{"context": {"cluster": "example-cluster", "user": "example-user"}, "name": "example-context"}], "current-context": "example-context", "kind": "Config", "preferences": {}, "users": [{"name": "example-user", "user": {"token": "EXAMPLE_TOKEN"}}]}'
|
||||
|
||||
with (
|
||||
patch(
|
||||
"kubernetes.config.load_kube_config",
|
||||
side_effect=ConfigException("No kubeconfig"),
|
||||
),
|
||||
patch("kubernetes.config.load_incluster_config", return_value=None),
|
||||
patch(
|
||||
"prowler.providers.kubernetes.kubernetes_provider.ApiClient",
|
||||
side_effect=fake_api_client,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.kubernetes.kubernetes_provider.KubernetesProvider.get_all_namespaces",
|
||||
return_value=["default"],
|
||||
),
|
||||
session = KubernetesProvider.setup_session(
|
||||
kubeconfig_content=kubeconfig_content,
|
||||
context="example-context",
|
||||
)
|
||||
|
||||
assert isinstance(session, KubernetesSession)
|
||||
assert isinstance(session.api_client, client.ApiClient)
|
||||
assert isinstance(session.api_client.configuration, client.Configuration)
|
||||
assert session.api_client.configuration.verify_ssl is False
|
||||
assert session.api_client.configuration.proxy is None
|
||||
|
||||
@patch(
|
||||
"prowler.providers.kubernetes.kubernetes_provider.client.CoreV1Api.list_namespace"
|
||||
)
|
||||
@patch("kubernetes.config.list_kube_config_contexts")
|
||||
@patch("kubernetes.config.load_kube_config_from_dict")
|
||||
def test_kubernetes_provider_kubeconfig_content(
|
||||
self,
|
||||
mock_load_kube_config_from_dict,
|
||||
mock_list_kube_config_contexts,
|
||||
mock_list_namespace,
|
||||
):
|
||||
mock_load_kube_config_from_dict.return_value = None
|
||||
mock_list_kube_config_contexts.return_value = (
|
||||
[
|
||||
{
|
||||
"name": "example-context",
|
||||
"context": {
|
||||
"cluster": "example-cluster",
|
||||
"user": "example-user",
|
||||
},
|
||||
}
|
||||
],
|
||||
None,
|
||||
)
|
||||
mock_list_namespace.return_value.items = [
|
||||
client.V1Namespace(metadata=client.V1ObjectMeta(name="namespace-1")),
|
||||
]
|
||||
|
||||
kubeconfig_content = '{"apiVersion": "v1", "clusters": [{"cluster": {"server": "https://kubernetes.example.com"}, "name": "example-cluster"}], "contexts": [{"context": {"cluster": "example-cluster", "user": "example-user"}, "name": "example-context"}], "current-context": "example-context", "kind": "Config", "preferences": {}, "users": [{"name": "example-user", "user": {"token": "EXAMPLE_TOKEN"}}]}'
|
||||
|
||||
session = KubernetesProvider.setup_session(
|
||||
kubeconfig_content=kubeconfig_content,
|
||||
context="example-context",
|
||||
)
|
||||
|
||||
assert isinstance(session, KubernetesSession)
|
||||
assert isinstance(session.api_client, client.ApiClient)
|
||||
|
||||
assert session.context == {
|
||||
"name": "example-context",
|
||||
"context": {
|
||||
"cluster": "example-cluster",
|
||||
"user": "example-user",
|
||||
},
|
||||
}
|
||||
|
||||
@patch(
|
||||
"prowler.providers.kubernetes.kubernetes_provider.client.CoreV1Api.list_namespace"
|
||||
)
|
||||
@patch("kubernetes.config.list_kube_config_contexts")
|
||||
@patch("kubernetes.config.load_kube_config_from_dict")
|
||||
def test_kubernetes_provider_kubeconfig_content_proxy_settings(
|
||||
self,
|
||||
mock_load_kube_config_from_dict,
|
||||
mock_list_kube_config_contexts,
|
||||
mock_list_namespace,
|
||||
monkeypatch,
|
||||
):
|
||||
monkeypatch.setenv("HTTPS_PROXY", "http://my.internal.proxy:8888")
|
||||
monkeypatch.setenv("K8S_SKIP_TLS_VERIFY", "true")
|
||||
|
||||
mock_load_kube_config_from_dict.return_value = None
|
||||
mock_list_kube_config_contexts.return_value = (
|
||||
[
|
||||
{
|
||||
"name": "example-context",
|
||||
"context": {
|
||||
"cluster": "example-cluster",
|
||||
"user": "example-user",
|
||||
},
|
||||
}
|
||||
],
|
||||
None,
|
||||
)
|
||||
mock_list_namespace.return_value.items = [
|
||||
client.V1Namespace(metadata=client.V1ObjectMeta(name="namespace-1")),
|
||||
]
|
||||
|
||||
kubeconfig_content = '{"apiVersion": "v1", "clusters": [{"cluster": {"server": "https://kubernetes.example.com"}, "name": "example-cluster"}], "contexts": [{"context": {"cluster": "example-cluster", "user": "example-user"}, "name": "example-context"}], "current-context": "example-context", "kind": "Config", "preferences": {}, "users": [{"name": "example-user", "user": {"token": "EXAMPLE_TOKEN"}}]}'
|
||||
|
||||
session = KubernetesProvider.setup_session(
|
||||
kubeconfig_content=kubeconfig_content,
|
||||
context="example-context",
|
||||
)
|
||||
|
||||
assert isinstance(session, KubernetesSession)
|
||||
assert isinstance(session.api_client, client.ApiClient)
|
||||
|
||||
assert session.context == {
|
||||
"name": "example-context",
|
||||
"context": {
|
||||
"cluster": "example-cluster",
|
||||
"user": "example-user",
|
||||
},
|
||||
}
|
||||
|
||||
assert session.api_client.configuration.proxy == "http://my.internal.proxy:8888"
|
||||
assert session.api_client.configuration.verify_ssl is False
|
||||
|
||||
def test_set_proxy_settings_no_proxy_no_tls_skip(self):
|
||||
"""Test set_proxy_settings with no environment variables set."""
|
||||
with patch.dict("os.environ", {}, clear=True):
|
||||
config = KubernetesProvider.set_proxy_settings()
|
||||
|
||||
# Verify it's a Configuration instance from kubernetes.client
|
||||
from kubernetes.client import Configuration
|
||||
|
||||
assert isinstance(config, Configuration)
|
||||
|
||||
assert hasattr(config, "proxy")
|
||||
assert config.proxy is None
|
||||
assert hasattr(config, "verify_ssl")
|
||||
assert config.verify_ssl is True
|
||||
|
||||
def test_set_proxy_settings_with_https_proxy_uppercase(self):
|
||||
"""Test set_proxy_settings with HTTPS_PROXY environment variable."""
|
||||
proxy_url = "http://proxy.example.com:8080"
|
||||
with patch.dict("os.environ", {"HTTPS_PROXY": proxy_url}, clear=True):
|
||||
config = KubernetesProvider.set_proxy_settings()
|
||||
|
||||
# Verify it's a Configuration instance from kubernetes.client
|
||||
from kubernetes.client import Configuration
|
||||
|
||||
assert isinstance(config, Configuration)
|
||||
|
||||
assert config.proxy == proxy_url
|
||||
assert config.verify_ssl is True
|
||||
|
||||
def test_set_proxy_settings_with_https_proxy_lowercase(self):
|
||||
"""Test set_proxy_settings with https_proxy environment variable."""
|
||||
proxy_url = "http://proxy.example.com:3128"
|
||||
with patch.dict("os.environ", {"https_proxy": proxy_url}, clear=True):
|
||||
config = KubernetesProvider.set_proxy_settings()
|
||||
|
||||
# Verify it's a Configuration instance from kubernetes.client
|
||||
from kubernetes.client import Configuration
|
||||
|
||||
assert isinstance(config, Configuration)
|
||||
|
||||
assert config.proxy == proxy_url
|
||||
assert config.verify_ssl is True
|
||||
|
||||
def test_set_proxy_settings_uppercase_proxy_takes_precedence(self):
|
||||
"""Test that HTTPS_PROXY takes precedence over https_proxy."""
|
||||
uppercase_proxy = "http://uppercase.proxy.com:8080"
|
||||
lowercase_proxy = "http://lowercase.proxy.com:3128"
|
||||
with patch.dict(
|
||||
"os.environ",
|
||||
{"HTTPS_PROXY": uppercase_proxy, "https_proxy": lowercase_proxy},
|
||||
clear=True,
|
||||
):
|
||||
KubernetesProvider.setup_session()
|
||||
config = KubernetesProvider.set_proxy_settings()
|
||||
|
||||
assert captured["verify_ssl"] is False
|
||||
# Verify it's a Configuration instance from kubernetes.client
|
||||
from kubernetes.client import Configuration
|
||||
|
||||
assert isinstance(config, Configuration)
|
||||
|
||||
assert config.proxy == uppercase_proxy
|
||||
assert config.verify_ssl is True
|
||||
|
||||
def test_set_proxy_settings_with_tls_skip_true(self):
|
||||
"""Test set_proxy_settings with K8S_SKIP_TLS_VERIFY set to true."""
|
||||
with patch.dict("os.environ", {"K8S_SKIP_TLS_VERIFY": "true"}, clear=True):
|
||||
config = KubernetesProvider.set_proxy_settings()
|
||||
|
||||
# Verify it's a Configuration instance from kubernetes.client
|
||||
from kubernetes.client import Configuration
|
||||
|
||||
assert isinstance(config, Configuration)
|
||||
|
||||
assert config.proxy is None
|
||||
assert config.verify_ssl is False
|
||||
|
||||
def test_set_proxy_settings_with_tls_skip_true_uppercase(self):
|
||||
"""Test set_proxy_settings with K8S_SKIP_TLS_VERIFY set to TRUE."""
|
||||
with patch.dict("os.environ", {"K8S_SKIP_TLS_VERIFY": "TRUE"}, clear=True):
|
||||
config = KubernetesProvider.set_proxy_settings()
|
||||
|
||||
# Verify it's a Configuration instance from kubernetes.client
|
||||
from kubernetes.client import Configuration
|
||||
|
||||
assert isinstance(config, Configuration)
|
||||
|
||||
assert config.proxy is None
|
||||
assert config.verify_ssl is False
|
||||
|
||||
def test_set_proxy_settings_with_tls_skip_false(self):
|
||||
"""Test set_proxy_settings with K8S_SKIP_TLS_VERIFY set to false."""
|
||||
with patch.dict("os.environ", {"K8S_SKIP_TLS_VERIFY": "false"}, clear=True):
|
||||
config = KubernetesProvider.set_proxy_settings()
|
||||
|
||||
# Verify it's a Configuration instance from kubernetes.client
|
||||
from kubernetes.client import Configuration
|
||||
|
||||
assert isinstance(config, Configuration)
|
||||
|
||||
assert config.proxy is None
|
||||
assert config.verify_ssl is True
|
||||
|
||||
def test_set_proxy_settings_with_tls_skip_invalid_value(self):
|
||||
"""Test set_proxy_settings with K8S_SKIP_TLS_VERIFY set to invalid value."""
|
||||
with patch.dict("os.environ", {"K8S_SKIP_TLS_VERIFY": "invalid"}, clear=True):
|
||||
config = KubernetesProvider.set_proxy_settings()
|
||||
|
||||
# Verify it's a Configuration instance from kubernetes.client
|
||||
from kubernetes.client import Configuration
|
||||
|
||||
assert isinstance(config, Configuration)
|
||||
|
||||
assert config.proxy is None
|
||||
assert config.verify_ssl is True
|
||||
|
||||
def test_set_proxy_settings_with_both_proxy_and_tls_skip(self):
|
||||
"""Test set_proxy_settings with both proxy and TLS skip settings."""
|
||||
proxy_url = "http://secure.proxy.com:8080"
|
||||
with patch.dict(
|
||||
"os.environ",
|
||||
{"HTTPS_PROXY": proxy_url, "K8S_SKIP_TLS_VERIFY": "true"},
|
||||
clear=True,
|
||||
):
|
||||
config = KubernetesProvider.set_proxy_settings()
|
||||
|
||||
# Verify it's a Configuration instance from kubernetes.client
|
||||
from kubernetes.client import Configuration
|
||||
|
||||
assert isinstance(config, Configuration)
|
||||
|
||||
assert config.proxy == proxy_url
|
||||
assert config.verify_ssl is False
|
||||
@@ -36,7 +36,7 @@ class TestKubernetesMutelist:
|
||||
|
||||
mutelist = KubernetesMutelist(mutelist_content=mutelist_fixture)
|
||||
|
||||
assert not mutelist.validate_mutelist()
|
||||
assert len(mutelist.validate_mutelist(mutelist_fixture)) == 0
|
||||
assert mutelist.mutelist == {}
|
||||
assert mutelist.mutelist_file_path is None
|
||||
|
||||
@@ -153,7 +153,7 @@ class TestKubernetesMutelist:
|
||||
account_uid="cluster_1",
|
||||
region="test-region",
|
||||
resource_uid="test_resource",
|
||||
resource_tags=[],
|
||||
resource_tags={},
|
||||
muted=False,
|
||||
)
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ class TestM365Mutelist:
|
||||
|
||||
mutelist = M365Mutelist(mutelist_content=mutelist_fixture)
|
||||
|
||||
assert not mutelist.validate_mutelist()
|
||||
assert len(mutelist.validate_mutelist(mutelist_fixture)) == 0
|
||||
assert mutelist.mutelist == {}
|
||||
assert mutelist.mutelist_file_path is None
|
||||
|
||||
|
||||
@@ -257,7 +257,70 @@ class Testm365PowerShell:
|
||||
session.process.stdin.write = MagicMock()
|
||||
session.read_output = MagicMock(return_value="decrypted_password")
|
||||
|
||||
assert session.test_credentials(credentials) is False
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
session.test_credentials(credentials)
|
||||
assert (
|
||||
"Unexpected error: Acquiring token in behalf of user did not return a result."
|
||||
in str(exc_info.value)
|
||||
)
|
||||
|
||||
mock_msal.assert_called_once_with(
|
||||
client_id="test_client_id",
|
||||
client_credential="test_client_secret",
|
||||
authority="https://login.microsoftonline.com/test_tenant_id",
|
||||
)
|
||||
mock_msal_instance.acquire_token_by_username_password.assert_called_once_with(
|
||||
username="test@contoso.onmicrosoft.com",
|
||||
password="test_password",
|
||||
scopes=["https://graph.microsoft.com/.default"],
|
||||
)
|
||||
|
||||
session.close()
|
||||
|
||||
@patch("subprocess.Popen")
|
||||
@patch("msal.ConfidentialClientApplication")
|
||||
def test_test_credentials_auth_failure_no_access_token(self, mock_msal, mock_popen):
|
||||
mock_process = MagicMock()
|
||||
mock_popen.return_value = mock_process
|
||||
mock_msal_instance = MagicMock()
|
||||
mock_msal.return_value = mock_msal_instance
|
||||
mock_msal_instance.acquire_token_by_username_password.return_value = {
|
||||
"error_description": "invalid_grant: authentication failed"
|
||||
}
|
||||
|
||||
credentials = M365Credentials(
|
||||
user="test@contoso.onmicrosoft.com",
|
||||
passwd="test_password",
|
||||
encrypted_passwd="test_encrypted_password",
|
||||
client_id="test_client_id",
|
||||
client_secret="test_client_secret",
|
||||
tenant_id="test_tenant_id",
|
||||
)
|
||||
identity = M365IdentityInfo(
|
||||
identity_id="test_id",
|
||||
identity_type="User",
|
||||
tenant_id="test_tenant",
|
||||
tenant_domain="contoso.onmicrosoft.com",
|
||||
tenant_domains=["contoso.onmicrosoft.com"],
|
||||
location="test_location",
|
||||
)
|
||||
session = M365PowerShell(credentials, identity)
|
||||
|
||||
# Mock the execute method to return the decrypted password
|
||||
def mock_execute(command, *args, **kwargs):
|
||||
if "Write-Output" in command:
|
||||
return "decrypted_password"
|
||||
return None
|
||||
|
||||
session.execute = MagicMock(side_effect=mock_execute)
|
||||
session.process.stdin.write = MagicMock()
|
||||
session.read_output = MagicMock(return_value="decrypted_password")
|
||||
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
session.test_credentials(credentials)
|
||||
assert "MsGraph Error invalid_grant: authentication failed" in str(
|
||||
exc_info.value
|
||||
)
|
||||
|
||||
mock_msal.assert_called_once_with(
|
||||
client_id="test_client_id",
|
||||
|
||||
@@ -34,7 +34,7 @@ class TestNHNMutelist:
|
||||
|
||||
mutelist = NHNMutelist(mutelist_content=mutelist_fixture)
|
||||
|
||||
assert not mutelist.validate_mutelist()
|
||||
assert len(mutelist.validate_mutelist(mutelist_fixture)) == 0
|
||||
assert mutelist.mutelist == {}
|
||||
assert mutelist.mutelist_file_path is None
|
||||
|
||||
@@ -62,7 +62,7 @@ class TestNHNMutelist:
|
||||
finding.status = "FAIL"
|
||||
finding.resource_name = "test_resource"
|
||||
finding.location = "test_region"
|
||||
finding.resource_tags = []
|
||||
finding.resource_tags = {}
|
||||
|
||||
assert mutelist.is_finding_muted(finding)
|
||||
|
||||
@@ -89,7 +89,7 @@ class TestNHNMutelist:
|
||||
account_uid="resource_1",
|
||||
region="test_region",
|
||||
resource_uid="test_resource",
|
||||
resource_tags=[],
|
||||
resource_tags={},
|
||||
muted=False,
|
||||
)
|
||||
|
||||
|
||||
Reference in new issue
Block a user