merge branch 'master' into PRWLR-5093-design-the-fixer-class

This commit is contained in:
Daniel Barranquero committed 2025-06-23 10:20:06 +02:00
commit c5cf1c4bfb
540 files changed
+26179 -6626

No files matched your search

+4
View File
@@ -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,
+185 -1
View File
@@ -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
+3 -2
View File
@@ -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()
+2 -4
View File
@@ -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 = [
+3 -3
View File
@@ -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 = [
+21 -8
View File
@@ -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
+3 -3
View File
@@ -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
View File
@@ -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(
+44 -2
View File
@@ -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
@@ -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
@@ -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>"
)
@@ -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,
@@ -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"
@@ -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,
)
@@ -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
@@ -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
@@ -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"
@@ -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"
@@ -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,
@@ -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(
@@ -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
@@ -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
@@ -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"
@@ -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"
@@ -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"
@@ -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"
@@ -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"
@@ -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
@@ -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
+67
View File
@@ -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"
+132
View File
@@ -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,
)