From c29220ac3f56a2d7fb32b5690d05ebe36651f416 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 27 Jun 2025 07:29:08 +0000 Subject: [PATCH 1/5] new check added --- ..._certificates_transparency_logs_enabled.py | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 library/aws/tests/acl/test_acm_certificates_transparency_logs_enabled.py diff --git a/library/aws/tests/acl/test_acm_certificates_transparency_logs_enabled.py b/library/aws/tests/acl/test_acm_certificates_transparency_logs_enabled.py new file mode 100644 index 00000000..5e902981 --- /dev/null +++ b/library/aws/tests/acl/test_acm_certificates_transparency_logs_enabled.py @@ -0,0 +1,85 @@ +import pytest +from unittest.mock import MagicMock, patch +from botocore.exceptions import ClientError +from tevico.engine.entities.report.check_model import CheckStatus, CheckMetadata, Remediation, RemediationCode, RemediationRecommendation +from library.aws.checks.acm.acm_certificates_transparency_logs_enabled import acm_certificates_transparency_logs_enabled + +class TestAcmCertificatesTransparencyLogsEnabled: + """Test cases for ACM Certificates Transparency Logs Enabled check.""" + + def setup_method(self): + """Set up test method.""" + self.metadata = CheckMetadata( + Provider="AWS", + CheckID="acm_certificates_transparency_logs_enabled", + CheckTitle="Check ACM Certificates Transparency Logs Enabled", + CheckType=["Security"], + ServiceName="ACM", + SubServiceName="Certificate", + ResourceIdTemplate="arn:aws:acm:{region}:{account}:certificate/{certificate_id}", + Severity="medium", + ResourceType="AWS::ACM::Certificate", + Risk="Certificates without transparency logs may not be trusted.", + Description="Checks if ACM certificates have transparency logging enabled.", + Remediation=Remediation( + Code=RemediationCode(CLI="", NativeIaC="", Terraform=""), + Recommendation=RemediationRecommendation( + Text="Enable certificate transparency logging for ACM certificates.", + Url="https://docs.aws.amazon.com/acm/latest/userguide/acm-concepts.html#concept-transparency" + ) + ) + ) + self.check = acm_certificates_transparency_logs_enabled(metadata=self.metadata) + self.mock_session = MagicMock() + self.mock_acm = MagicMock() + self.mock_sts = MagicMock() + self.mock_sts.get_caller_identity.return_value = {"Account": "123456789012"} + self.mock_session.client.side_effect = lambda service: self.mock_acm if service == "acm" else self.mock_sts + + @patch("boto3.Session.client") + def test_no_certificates(self, mock_client): + """Test when there are no ACM certificates.""" + self.mock_acm.list_certificates.return_value = {"CertificateSummaryList": []} + report = self.check.execute(self.mock_session) + assert report.status == CheckStatus.NOT_APPLICABLE + assert report.resource_ids_status[0].summary is not None + assert "No ACM certificates found" in report.resource_ids_status[0].summary + + @patch("boto3.Session.client") + def test_transparency_logs_enabled(self, mock_client): + """Test when all certificates have transparency logs enabled.""" + self.mock_acm.list_certificates.return_value = { + "CertificateSummaryList": [{"CertificateArn": "arn:aws:acm:region:account:certificate/cert-1"}] + } + self.mock_acm.describe_certificate.return_value = { + "Certificate": {"Options": {"CertificateTransparencyLoggingPreference": "ENABLED"}} + } + report = self.check.execute(self.mock_session) + assert report.status == CheckStatus.PASSED + assert report.resource_ids_status[0].summary is not None + assert "has transparency logging enabled" in report.resource_ids_status[0].summary + + @patch("boto3.Session.client") + def test_transparency_logs_disabled(self, mock_client): + """Test when a certificate has transparency logs disabled.""" + self.mock_acm.list_certificates.return_value = { + "CertificateSummaryList": [{"CertificateArn": "arn:aws:acm:region:account:certificate/cert-2"}] + } + self.mock_acm.describe_certificate.return_value = { + "Certificate": {"Options": {"CertificateTransparencyLoggingPreference": "DISABLED"}} + } + report = self.check.execute(self.mock_session) + assert report.status == CheckStatus.FAILED + assert report.resource_ids_status[0].summary is not None + assert "has transparency logging disabled" in report.resource_ids_status[0].summary + + @patch("boto3.Session.client") + def test_client_error(self, mock_client): + """Test error handling when a ClientError occurs.""" + self.mock_acm.list_certificates.side_effect = ClientError({"Error": {"Code": "AccessDenied"}}, "ListCertificates") + report = self.check.execute(self.mock_session) + assert report.status == CheckStatus.UNKNOWN + assert report.resource_ids_status[0].summary is not None + # Accept any error message, just check it's present + assert report.resource_ids_status[0].summary + From 7f43650f40e634a91eba1a908654cd0e9542e133 Mon Sep 17 00:00:00 2001 From: prajwal-choudhari-comprinno Date: Thu, 3 Jul 2025 14:41:44 +0000 Subject: [PATCH 2/5] fix: correct test for missing arn on client error in ACM transparency check --- ..._certificates_transparency_logs_enabled.py | 48 +++++++++++-------- 1 file changed, 28 insertions(+), 20 deletions(-) rename library/aws/tests/{acl => acm}/test_acm_certificates_transparency_logs_enabled.py (73%) diff --git a/library/aws/tests/acl/test_acm_certificates_transparency_logs_enabled.py b/library/aws/tests/acm/test_acm_certificates_transparency_logs_enabled.py similarity index 73% rename from library/aws/tests/acl/test_acm_certificates_transparency_logs_enabled.py rename to library/aws/tests/acm/test_acm_certificates_transparency_logs_enabled.py index 5e902981..a5e5c7dd 100644 --- a/library/aws/tests/acl/test_acm_certificates_transparency_logs_enabled.py +++ b/library/aws/tests/acm/test_acm_certificates_transparency_logs_enabled.py @@ -1,14 +1,20 @@ import pytest -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock from botocore.exceptions import ClientError -from tevico.engine.entities.report.check_model import CheckStatus, CheckMetadata, Remediation, RemediationCode, RemediationRecommendation +from tevico.engine.entities.report.check_model import ( + CheckStatus, + CheckMetadata, + Remediation, + RemediationCode, + RemediationRecommendation, +) from library.aws.checks.acm.acm_certificates_transparency_logs_enabled import acm_certificates_transparency_logs_enabled class TestAcmCertificatesTransparencyLogsEnabled: """Test cases for ACM Certificates Transparency Logs Enabled check.""" def setup_method(self): - """Set up test method.""" + """Set up test method with mocks and check instance.""" self.metadata = CheckMetadata( Provider="AWS", CheckID="acm_certificates_transparency_logs_enabled", @@ -36,20 +42,21 @@ def setup_method(self): self.mock_sts.get_caller_identity.return_value = {"Account": "123456789012"} self.mock_session.client.side_effect = lambda service: self.mock_acm if service == "acm" else self.mock_sts - @patch("boto3.Session.client") - def test_no_certificates(self, mock_client): + + def test_no_certificates(self): """Test when there are no ACM certificates.""" self.mock_acm.list_certificates.return_value = {"CertificateSummaryList": []} report = self.check.execute(self.mock_session) assert report.status == CheckStatus.NOT_APPLICABLE assert report.resource_ids_status[0].summary is not None assert "No ACM certificates found" in report.resource_ids_status[0].summary - - @patch("boto3.Session.client") - def test_transparency_logs_enabled(self, mock_client): - """Test when all certificates have transparency logs enabled.""" + assert report.resource_ids_status[0].resource.name == "" + + def test_transparency_logs_enabled(self): + """Test when certificates have transparency logs enabled.""" + cert_arn = "arn:aws:acm:region:account:certificate/cert-1" self.mock_acm.list_certificates.return_value = { - "CertificateSummaryList": [{"CertificateArn": "arn:aws:acm:region:account:certificate/cert-1"}] + "CertificateSummaryList": [{"CertificateArn": cert_arn}] } self.mock_acm.describe_certificate.return_value = { "Certificate": {"Options": {"CertificateTransparencyLoggingPreference": "ENABLED"}} @@ -58,12 +65,13 @@ def test_transparency_logs_enabled(self, mock_client): assert report.status == CheckStatus.PASSED assert report.resource_ids_status[0].summary is not None assert "has transparency logging enabled" in report.resource_ids_status[0].summary + assert report.resource_ids_status[0].resource.arn == cert_arn - @patch("boto3.Session.client") - def test_transparency_logs_disabled(self, mock_client): + def test_transparency_logs_disabled(self): """Test when a certificate has transparency logs disabled.""" + cert_arn = "arn:aws:acm:region:account:certificate/cert-2" self.mock_acm.list_certificates.return_value = { - "CertificateSummaryList": [{"CertificateArn": "arn:aws:acm:region:account:certificate/cert-2"}] + "CertificateSummaryList": [{"CertificateArn": cert_arn}] } self.mock_acm.describe_certificate.return_value = { "Certificate": {"Options": {"CertificateTransparencyLoggingPreference": "DISABLED"}} @@ -72,14 +80,14 @@ def test_transparency_logs_disabled(self, mock_client): assert report.status == CheckStatus.FAILED assert report.resource_ids_status[0].summary is not None assert "has transparency logging disabled" in report.resource_ids_status[0].summary + assert report.resource_ids_status[0].resource.arn == cert_arn - @patch("boto3.Session.client") - def test_client_error(self, mock_client): - """Test error handling when a ClientError occurs.""" - self.mock_acm.list_certificates.side_effect = ClientError({"Error": {"Code": "AccessDenied"}}, "ListCertificates") + def test_client_error(self): + """Test error handling when list_certificates raises ClientError.""" + self.mock_acm.list_certificates.side_effect = ClientError( + {"Error": {"Code": "AccessDenied"}}, "ListCertificates" + ) report = self.check.execute(self.mock_session) assert report.status == CheckStatus.UNKNOWN assert report.resource_ids_status[0].summary is not None - # Accept any error message, just check it's present - assert report.resource_ids_status[0].summary - + assert report.resource_ids_status[0].resource.name == "" \ No newline at end of file From 66e9247e4c7328f206e1ca1caf79b465b2d5d5e7 Mon Sep 17 00:00:00 2001 From: prajwal-choudhari-comprinno Date: Thu, 3 Jul 2025 14:47:07 +0000 Subject: [PATCH 3/5] add describe_certificate error and NoSuchEntity tests, simplify setup, assert resource ARN --- ..._certificates_transparency_logs_enabled.py | 54 ++++++++++++++----- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/library/aws/tests/acm/test_acm_certificates_transparency_logs_enabled.py b/library/aws/tests/acm/test_acm_certificates_transparency_logs_enabled.py index a5e5c7dd..abab21d5 100644 --- a/library/aws/tests/acm/test_acm_certificates_transparency_logs_enabled.py +++ b/library/aws/tests/acm/test_acm_certificates_transparency_logs_enabled.py @@ -1,20 +1,15 @@ import pytest from unittest.mock import MagicMock from botocore.exceptions import ClientError -from tevico.engine.entities.report.check_model import ( - CheckStatus, - CheckMetadata, - Remediation, - RemediationCode, - RemediationRecommendation, -) +from tevico.engine.entities.report.check_model import CheckStatus, CheckMetadata, Remediation, RemediationCode, RemediationRecommendation from library.aws.checks.acm.acm_certificates_transparency_logs_enabled import acm_certificates_transparency_logs_enabled + class TestAcmCertificatesTransparencyLogsEnabled: """Test cases for ACM Certificates Transparency Logs Enabled check.""" def setup_method(self): - """Set up test method with mocks and check instance.""" + """Set up test method.""" self.metadata = CheckMetadata( Provider="AWS", CheckID="acm_certificates_transparency_logs_enabled", @@ -42,7 +37,6 @@ def setup_method(self): self.mock_sts.get_caller_identity.return_value = {"Account": "123456789012"} self.mock_session.client.side_effect = lambda service: self.mock_acm if service == "acm" else self.mock_sts - def test_no_certificates(self): """Test when there are no ACM certificates.""" self.mock_acm.list_certificates.return_value = {"CertificateSummaryList": []} @@ -50,10 +44,10 @@ def test_no_certificates(self): assert report.status == CheckStatus.NOT_APPLICABLE assert report.resource_ids_status[0].summary is not None assert "No ACM certificates found" in report.resource_ids_status[0].summary - assert report.resource_ids_status[0].resource.name == "" - + assert not hasattr(report.resource_ids_status[0].resource, "arn") + def test_transparency_logs_enabled(self): - """Test when certificates have transparency logs enabled.""" + """Test when all certificates have transparency logs enabled.""" cert_arn = "arn:aws:acm:region:account:certificate/cert-1" self.mock_acm.list_certificates.return_value = { "CertificateSummaryList": [{"CertificateArn": cert_arn}] @@ -90,4 +84,38 @@ def test_client_error(self): report = self.check.execute(self.mock_session) assert report.status == CheckStatus.UNKNOWN assert report.resource_ids_status[0].summary is not None - assert report.resource_ids_status[0].resource.name == "" \ No newline at end of file + assert not hasattr(report.resource_ids_status[0].resource, "arn") + + def test_describe_certificate_error(self): + """Test error handling when describe_certificate raises ClientError.""" + cert_arn = "arn:aws:acm:region:account:certificate/cert-3" + self.mock_acm.list_certificates.return_value = { + "CertificateSummaryList": [{"CertificateArn": cert_arn}] + } + self.mock_acm.describe_certificate.side_effect = ClientError( + {"Error": {"Code": "AccessDenied"}}, "DescribeCertificate" + ) + report = self.check.execute(self.mock_session) + assert report.status == CheckStatus.FAILED + assert report.resource_ids_status[0].summary is not None + assert "Error describing certificate" in report.resource_ids_status[0].summary + assert report.resource_ids_status[0].resource.arn == cert_arn + + def test_no_such_entity_exception(self): + """Test handling when describe_certificate raises NoSuchEntityException.""" + cert_arn = "arn:aws:acm:region:account:certificate/missing" + self.mock_acm.list_certificates.return_value = { + "CertificateSummaryList": [{"CertificateArn": cert_arn}] + } + + class NoSuchEntityException(Exception): + pass + + self.mock_acm.exceptions = MagicMock() + self.mock_acm.exceptions.ResourceNotFoundException = NoSuchEntityException + self.mock_acm.describe_certificate.side_effect = NoSuchEntityException("No such certificate") + report = self.check.execute(self.mock_session) + assert report.status == CheckStatus.FAILED + assert report.resource_ids_status[0].summary is not None + assert "Error describing certificate" in report.resource_ids_status[0].summary + assert report.resource_ids_status[0].resource.arn == cert_arn From 8f61ea431759380fd142fe65bc2ca79ce39cbabe Mon Sep 17 00:00:00 2001 From: prajwal-choudhari-comprinno Date: Wed, 16 Jul 2025 13:04:05 +0530 Subject: [PATCH 4/5] Fixed issure --- ..._certificates_transparency_logs_enabled.py | 73 ++++-- .../iam/test_iam_password_policy_lowercase.py | 227 ++++++++++-------- 2 files changed, 177 insertions(+), 123 deletions(-) diff --git a/library/aws/tests/acm/test_acm_certificates_transparency_logs_enabled.py b/library/aws/tests/acm/test_acm_certificates_transparency_logs_enabled.py index abab21d5..9e924ef2 100644 --- a/library/aws/tests/acm/test_acm_certificates_transparency_logs_enabled.py +++ b/library/aws/tests/acm/test_acm_certificates_transparency_logs_enabled.py @@ -1,8 +1,16 @@ import pytest from unittest.mock import MagicMock from botocore.exceptions import ClientError -from tevico.engine.entities.report.check_model import CheckStatus, CheckMetadata, Remediation, RemediationCode, RemediationRecommendation -from library.aws.checks.acm.acm_certificates_transparency_logs_enabled import acm_certificates_transparency_logs_enabled +from tevico.engine.entities.report.check_model import ( + CheckStatus, + CheckMetadata, + Remediation, + RemediationCode, + RemediationRecommendation, +) +from library.aws.checks.acm.acm_certificates_transparency_logs_enabled import ( + acm_certificates_transparency_logs_enabled, +) class TestAcmCertificatesTransparencyLogsEnabled: @@ -33,17 +41,15 @@ def setup_method(self): self.check = acm_certificates_transparency_logs_enabled(metadata=self.metadata) self.mock_session = MagicMock() self.mock_acm = MagicMock() - self.mock_sts = MagicMock() - self.mock_sts.get_caller_identity.return_value = {"Account": "123456789012"} - self.mock_session.client.side_effect = lambda service: self.mock_acm if service == "acm" else self.mock_sts + self.mock_session.client.return_value = self.mock_acm # Simplified (removed STS) def test_no_certificates(self): """Test when there are no ACM certificates.""" self.mock_acm.list_certificates.return_value = {"CertificateSummaryList": []} report = self.check.execute(self.mock_session) + assert report.status == CheckStatus.NOT_APPLICABLE - assert report.resource_ids_status[0].summary is not None - assert "No ACM certificates found" in report.resource_ids_status[0].summary + assert report.resource_ids_status[0].summary == "No ACM certificates found." assert not hasattr(report.resource_ids_status[0].resource, "arn") def test_transparency_logs_enabled(self): @@ -55,10 +61,11 @@ def test_transparency_logs_enabled(self): self.mock_acm.describe_certificate.return_value = { "Certificate": {"Options": {"CertificateTransparencyLoggingPreference": "ENABLED"}} } + report = self.check.execute(self.mock_session) + assert report.status == CheckStatus.PASSED - assert report.resource_ids_status[0].summary is not None - assert "has transparency logging enabled" in report.resource_ids_status[0].summary + assert report.resource_ids_status[0].summary == f"Certificate {cert_arn} has transparency logging enabled." assert report.resource_ids_status[0].resource.arn == cert_arn def test_transparency_logs_disabled(self): @@ -70,10 +77,11 @@ def test_transparency_logs_disabled(self): self.mock_acm.describe_certificate.return_value = { "Certificate": {"Options": {"CertificateTransparencyLoggingPreference": "DISABLED"}} } + report = self.check.execute(self.mock_session) + assert report.status == CheckStatus.FAILED - assert report.resource_ids_status[0].summary is not None - assert "has transparency logging disabled" in report.resource_ids_status[0].summary + assert report.resource_ids_status[0].summary == f"Certificate {cert_arn} has transparency logging disabled." assert report.resource_ids_status[0].resource.arn == cert_arn def test_client_error(self): @@ -81,10 +89,13 @@ def test_client_error(self): self.mock_acm.list_certificates.side_effect = ClientError( {"Error": {"Code": "AccessDenied"}}, "ListCertificates" ) + report = self.check.execute(self.mock_session) + assert report.status == CheckStatus.UNKNOWN - assert report.resource_ids_status[0].summary is not None - assert not hasattr(report.resource_ids_status[0].resource, "arn") + assert report.resource_ids_status[0].summary == "Error fetching ACM certificates." + assert report.resource_ids_status[0].exception is not None + assert "AccessDenied" in report.resource_ids_status[0].exception def test_describe_certificate_error(self): """Test error handling when describe_certificate raises ClientError.""" @@ -95,14 +106,16 @@ def test_describe_certificate_error(self): self.mock_acm.describe_certificate.side_effect = ClientError( {"Error": {"Code": "AccessDenied"}}, "DescribeCertificate" ) + report = self.check.execute(self.mock_session) + assert report.status == CheckStatus.FAILED - assert report.resource_ids_status[0].summary is not None - assert "Error describing certificate" in report.resource_ids_status[0].summary + assert report.resource_ids_status[0].summary == f"Error describing certificate {cert_arn}." + assert "AccessDenied" in report.resource_ids_status[0].exception assert report.resource_ids_status[0].resource.arn == cert_arn def test_no_such_entity_exception(self): - """Test handling when describe_certificate raises NoSuchEntityException.""" + """Test handling when describe_certificate raises a ResourceNotFoundException.""" cert_arn = "arn:aws:acm:region:account:certificate/missing" self.mock_acm.list_certificates.return_value = { "CertificateSummaryList": [{"CertificateArn": cert_arn}] @@ -114,8 +127,32 @@ class NoSuchEntityException(Exception): self.mock_acm.exceptions = MagicMock() self.mock_acm.exceptions.ResourceNotFoundException = NoSuchEntityException self.mock_acm.describe_certificate.side_effect = NoSuchEntityException("No such certificate") + report = self.check.execute(self.mock_session) + assert report.status == CheckStatus.FAILED - assert report.resource_ids_status[0].summary is not None - assert "Error describing certificate" in report.resource_ids_status[0].summary + assert report.resource_ids_status[0].summary == f"Error describing certificate {cert_arn}." + assert "No such certificate" in report.resource_ids_status[0].exception assert report.resource_ids_status[0].resource.arn == cert_arn + + def test_paginated_certificates_list(self): + """Test paginated ACM certificate list.""" + cert_arn_1 = "arn:aws:acm:region:account:certificate/cert-1" + cert_arn_2 = "arn:aws:acm:region:account:certificate/cert-2" + + self.mock_acm.list_certificates.side_effect = [ + {"CertificateSummaryList": [{"CertificateArn": cert_arn_1}], "NextToken": "token-1"}, + {"CertificateSummaryList": [{"CertificateArn": cert_arn_2}]} + ] + + self.mock_acm.describe_certificate.side_effect = lambda CertificateArn: { + "Certificate": {"Options": {"CertificateTransparencyLoggingPreference": "ENABLED"}} + } + + report = self.check.execute(self.mock_session) + + assert report.status == CheckStatus.PASSED + assert len(report.resource_ids_status) == 2 + assert all(r.status == CheckStatus.PASSED for r in report.resource_ids_status) + assert report.resource_ids_status[0].resource.arn == cert_arn_1 + assert report.resource_ids_status[1].resource.arn == cert_arn_2 diff --git a/library/aws/tests/iam/test_iam_password_policy_lowercase.py b/library/aws/tests/iam/test_iam_password_policy_lowercase.py index ba8b5bea..b28b89aa 100644 --- a/library/aws/tests/iam/test_iam_password_policy_lowercase.py +++ b/library/aws/tests/iam/test_iam_password_policy_lowercase.py @@ -1,141 +1,158 @@ -""" -Test for IAM password policy lowercase check. -""" - import pytest -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock from botocore.exceptions import ClientError - -from library.aws.checks.iam.iam_password_policy_lowercase import iam_password_policy_lowercase -from tevico.engine.entities.report.check_model import CheckStatus, CheckMetadata -from tevico.engine.entities.report.check_model import Remediation, RemediationCode, RemediationRecommendation +from tevico.engine.entities.report.check_model import ( + CheckStatus, + CheckMetadata, + Remediation, + RemediationCode, + RemediationRecommendation, +) +from library.aws.checks.acm.acm_certificates_transparency_logs_enabled import ( + acm_certificates_transparency_logs_enabled, +) -class TestIamPasswordPolicyLowercase: - """Test cases for IAM password policy lowercase check.""" +class TestAcmCertificatesTransparencyLogsEnabled: + """Test cases for ACM Certificates Transparency Logs Enabled check.""" def setup_method(self): """Set up test method.""" - # Create a mock metadata object for the check - metadata = CheckMetadata( - Provider="aws", - CheckID="iam_password_policy_lowercase", - CheckTitle="IAM Password Policy Requires Lowercase Characters", - CheckType=["security"], - ServiceName="iam", - SubServiceName="password-policy", - ResourceIdTemplate="arn:aws:iam::{account_id}:password-policy", + self.metadata = CheckMetadata( + Provider="AWS", + CheckID="acm_certificates_transparency_logs_enabled", + CheckTitle="Check ACM Certificates Transparency Logs Enabled", + CheckType=["Security"], + ServiceName="ACM", + SubServiceName="Certificate", + ResourceIdTemplate="arn:aws:acm:{region}:{account}:certificate/{certificate_id}", Severity="medium", - ResourceType="iam-password-policy", - Risk="Passwords without lowercase characters are easier to guess", - RelatedUrl="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_passwords_account-policy.html", + ResourceType="AWS::ACM::Certificate", + Risk="Certificates without transparency logs may not be trusted.", + Description="Checks if ACM certificates have transparency logging enabled.", Remediation=Remediation( - Code=RemediationCode( - CLI="aws iam update-account-password-policy --require-lowercase-characters", - Terraform="resource \"aws_iam_account_password_policy\" \"strict\" {\n require_lowercase_characters = true\n}", - NativeIaC=None, - Other=None - ), + Code=RemediationCode(CLI="", NativeIaC="", Terraform=""), Recommendation=RemediationRecommendation( - Text="Configure IAM password policy to require at least one lowercase letter", - Url="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_passwords_account-policy.html" + Text="Enable certificate transparency logging for ACM certificates.", + Url="https://docs.aws.amazon.com/acm/latest/userguide/acm-concepts.html#concept-transparency" ) - ), - Description="Checks if the IAM password policy requires at least one lowercase letter", - Categories=["security", "compliance"] + ) ) - - self.check = iam_password_policy_lowercase(metadata) + self.check = acm_certificates_transparency_logs_enabled(metadata=self.metadata) self.mock_session = MagicMock() - self.mock_client = MagicMock() - self.mock_session.client.return_value = self.mock_client - - # Set up the exceptions attribute on the mock client - self.mock_client.exceptions = MagicMock() - # Create a custom NoSuchEntityException for testing - class NoSuchEntityException(Exception): - pass - self.mock_client.exceptions.NoSuchEntityException = NoSuchEntityException - - def test_password_policy_requires_lowercase(self): - """Test when password policy requires lowercase characters.""" - # Mock the response for a policy that requires lowercase - self.mock_client.get_account_password_policy.return_value = { - 'PasswordPolicy': { - 'RequireLowercaseCharacters': True - } + self.mock_acm = MagicMock() + self.mock_session.client.return_value = self.mock_acm # Simplified (removed STS) + + def test_no_certificates(self): + """Test when there are no ACM certificates.""" + self.mock_acm.list_certificates.return_value = {"CertificateSummaryList": []} + report = self.check.execute(self.mock_session) + + assert report.status == CheckStatus.NOT_APPLICABLE + assert report.resource_ids_status[0].summary == "No ACM certificates found." + assert not hasattr(report.resource_ids_status[0].resource, "arn") + + def test_transparency_logs_enabled(self): + """Test when all certificates have transparency logs enabled.""" + cert_arn = "arn:aws:acm:region:account:certificate/cert-1" + self.mock_acm.list_certificates.return_value = { + "CertificateSummaryList": [{"CertificateArn": cert_arn}] + } + self.mock_acm.describe_certificate.return_value = { + "Certificate": {"Options": {"CertificateTransparencyLoggingPreference": "ENABLED"}} } - # Execute the check report = self.check.execute(self.mock_session) - # Verify the results assert report.status == CheckStatus.PASSED - assert len(report.resource_ids_status) == 1 - assert report.resource_ids_status[0].status == CheckStatus.PASSED - assert "enforces the use of at least one lowercase letter" in report.resource_ids_status[0].summary - - def test_password_policy_does_not_require_lowercase(self): - """Test when password policy does not require lowercase characters.""" - # Mock the response for a policy that doesn't require lowercase - self.mock_client.get_account_password_policy.return_value = { - 'PasswordPolicy': { - 'RequireLowercaseCharacters': False - } + assert report.resource_ids_status[0].summary == f"Certificate {cert_arn} has transparency logging enabled." + assert report.resource_ids_status[0].resource.arn == cert_arn + + def test_transparency_logs_disabled(self): + """Test when a certificate has transparency logs disabled.""" + cert_arn = "arn:aws:acm:region:account:certificate/cert-2" + self.mock_acm.list_certificates.return_value = { + "CertificateSummaryList": [{"CertificateArn": cert_arn}] + } + self.mock_acm.describe_certificate.return_value = { + "Certificate": {"Options": {"CertificateTransparencyLoggingPreference": "DISABLED"}} } - # Execute the check report = self.check.execute(self.mock_session) - # Verify the results assert report.status == CheckStatus.FAILED - assert len(report.resource_ids_status) == 1 - assert report.resource_ids_status[0].status == CheckStatus.FAILED - assert "does not enforce the use of at least one lowercase letter" in report.resource_ids_status[0].summary - - def test_password_policy_missing_lowercase_setting(self): - """Test when password policy exists but doesn't specify lowercase requirement.""" - # Mock the response for a policy that doesn't specify lowercase requirement - self.mock_client.get_account_password_policy.return_value = { - 'PasswordPolicy': { - # RequireLowercaseCharacters is missing - } + assert report.resource_ids_status[0].summary == f"Certificate {cert_arn} has transparency logging disabled." + assert report.resource_ids_status[0].resource.arn == cert_arn + + def test_client_error(self): + """Test error handling when list_certificates raises ClientError.""" + self.mock_acm.list_certificates.side_effect = ClientError( + {"Error": {"Code": "AccessDenied"}}, "ListCertificates" + ) + + report = self.check.execute(self.mock_session) + + assert report.status == CheckStatus.UNKNOWN + assert report.resource_ids_status[0].summary == "Error listing ACM certificates." + assert report.resource_ids_status[0].exception is not None + assert "AccessDenied" in report.resource_ids_status[0].exception + + def test_describe_certificate_error(self): + """Test error handling when describe_certificate raises ClientError.""" + cert_arn = "arn:aws:acm:region:account:certificate/cert-3" + self.mock_acm.list_certificates.return_value = { + "CertificateSummaryList": [{"CertificateArn": cert_arn}] } + self.mock_acm.describe_certificate.side_effect = ClientError( + {"Error": {"Code": "AccessDenied"}}, "DescribeCertificate" + ) - # Execute the check report = self.check.execute(self.mock_session) - # Verify the results assert report.status == CheckStatus.FAILED - assert len(report.resource_ids_status) == 1 - assert report.resource_ids_status[0].status == CheckStatus.FAILED - assert "does not enforce the use of at least one lowercase letter" in report.resource_ids_status[0].summary + assert report.resource_ids_status[0].summary == f"Error describing certificate {cert_arn}." + assert "AccessDenied" in report.resource_ids_status[0].exception + assert report.resource_ids_status[0].resource.arn == cert_arn + + def test_no_such_entity_exception(self): + """Test handling when describe_certificate raises a ResourceNotFoundException.""" + cert_arn = "arn:aws:acm:region:account:certificate/missing" + self.mock_acm.list_certificates.return_value = { + "CertificateSummaryList": [{"CertificateArn": cert_arn}] + } + + class NoSuchEntityException(Exception): + pass - def test_no_password_policy_exists(self): - """Test when no password policy exists.""" - # Set up the NoSuchEntityException - self.mock_client.get_account_password_policy.side_effect = self.mock_client.exceptions.NoSuchEntityException() + self.mock_acm.exceptions = MagicMock() + self.mock_acm.exceptions.ResourceNotFoundException = NoSuchEntityException + self.mock_acm.describe_certificate.side_effect = NoSuchEntityException("No such certificate") - # Execute the check report = self.check.execute(self.mock_session) - # Verify the results assert report.status == CheckStatus.FAILED - assert len(report.resource_ids_status) == 1 - assert report.resource_ids_status[0].status == CheckStatus.FAILED - assert "No custom IAM password policy is configured" in report.resource_ids_status[0].summary - - def test_client_error_handling(self): - """Test error handling when a ClientError occurs.""" - # Mock a ClientError with proper structure - error_response = {'Error': {'Code': 'SomeError', 'Message': 'An error occurred'}} - self.mock_client.get_account_password_policy.side_effect = ClientError(error_response, 'GetAccountPasswordPolicy') + assert report.resource_ids_status[0].summary == f"Error describing certificate {cert_arn}." + assert "No such certificate" in report.resource_ids_status[0].exception + assert report.resource_ids_status[0].resource.arn == cert_arn + + def test_paginated_certificates_list(self): + """Test paginated ACM certificate list.""" + cert_arn_1 = "arn:aws:acm:region:account:certificate/cert-1" + cert_arn_2 = "arn:aws:acm:region:account:certificate/cert-2" + + self.mock_acm.list_certificates.side_effect = [ + {"CertificateSummaryList": [{"CertificateArn": cert_arn_1}], "NextToken": "token-1"}, + {"CertificateSummaryList": [{"CertificateArn": cert_arn_2}]} + ] + + self.mock_acm.describe_certificate.side_effect = lambda CertificateArn: { + "Certificate": {"Options": {"CertificateTransparencyLoggingPreference": "ENABLED"}} + } - # Execute the check report = self.check.execute(self.mock_session) - # Verify the results - assert report.status == CheckStatus.UNKNOWN - assert len(report.resource_ids_status) == 1 - assert report.resource_ids_status[0].status == CheckStatus.UNKNOWN - assert "An error occurred while retrieving the IAM password policy" in report.resource_ids_status[0].summary + assert report.status == CheckStatus.PASSED + assert len(report.resource_ids_status) == 2 + assert all(r.status == CheckStatus.PASSED for r in report.resource_ids_status) + assert report.resource_ids_status[0].resource.arn == cert_arn_1 + assert report.resource_ids_status[1].resource.arn == cert_arn_2 \ No newline at end of file From 82046cce223e15e6407d1c26d46a1ab46b1e98da Mon Sep 17 00:00:00 2001 From: prajwal-choudhari-comprinno Date: Wed, 16 Jul 2025 13:07:03 +0530 Subject: [PATCH 5/5] Fixed issure --- .../iam/test_iam_password_policy_lowercase.py | 227 ++++++++---------- 1 file changed, 105 insertions(+), 122 deletions(-) diff --git a/library/aws/tests/iam/test_iam_password_policy_lowercase.py b/library/aws/tests/iam/test_iam_password_policy_lowercase.py index b28b89aa..ba8b5bea 100644 --- a/library/aws/tests/iam/test_iam_password_policy_lowercase.py +++ b/library/aws/tests/iam/test_iam_password_policy_lowercase.py @@ -1,158 +1,141 @@ +""" +Test for IAM password policy lowercase check. +""" + import pytest -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch from botocore.exceptions import ClientError -from tevico.engine.entities.report.check_model import ( - CheckStatus, - CheckMetadata, - Remediation, - RemediationCode, - RemediationRecommendation, -) -from library.aws.checks.acm.acm_certificates_transparency_logs_enabled import ( - acm_certificates_transparency_logs_enabled, -) + +from library.aws.checks.iam.iam_password_policy_lowercase import iam_password_policy_lowercase +from tevico.engine.entities.report.check_model import CheckStatus, CheckMetadata +from tevico.engine.entities.report.check_model import Remediation, RemediationCode, RemediationRecommendation -class TestAcmCertificatesTransparencyLogsEnabled: - """Test cases for ACM Certificates Transparency Logs Enabled check.""" +class TestIamPasswordPolicyLowercase: + """Test cases for IAM password policy lowercase check.""" def setup_method(self): """Set up test method.""" - self.metadata = CheckMetadata( - Provider="AWS", - CheckID="acm_certificates_transparency_logs_enabled", - CheckTitle="Check ACM Certificates Transparency Logs Enabled", - CheckType=["Security"], - ServiceName="ACM", - SubServiceName="Certificate", - ResourceIdTemplate="arn:aws:acm:{region}:{account}:certificate/{certificate_id}", + # Create a mock metadata object for the check + metadata = CheckMetadata( + Provider="aws", + CheckID="iam_password_policy_lowercase", + CheckTitle="IAM Password Policy Requires Lowercase Characters", + CheckType=["security"], + ServiceName="iam", + SubServiceName="password-policy", + ResourceIdTemplate="arn:aws:iam::{account_id}:password-policy", Severity="medium", - ResourceType="AWS::ACM::Certificate", - Risk="Certificates without transparency logs may not be trusted.", - Description="Checks if ACM certificates have transparency logging enabled.", + ResourceType="iam-password-policy", + Risk="Passwords without lowercase characters are easier to guess", + RelatedUrl="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_passwords_account-policy.html", Remediation=Remediation( - Code=RemediationCode(CLI="", NativeIaC="", Terraform=""), + Code=RemediationCode( + CLI="aws iam update-account-password-policy --require-lowercase-characters", + Terraform="resource \"aws_iam_account_password_policy\" \"strict\" {\n require_lowercase_characters = true\n}", + NativeIaC=None, + Other=None + ), Recommendation=RemediationRecommendation( - Text="Enable certificate transparency logging for ACM certificates.", - Url="https://docs.aws.amazon.com/acm/latest/userguide/acm-concepts.html#concept-transparency" + Text="Configure IAM password policy to require at least one lowercase letter", + Url="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_passwords_account-policy.html" ) - ) + ), + Description="Checks if the IAM password policy requires at least one lowercase letter", + Categories=["security", "compliance"] ) - self.check = acm_certificates_transparency_logs_enabled(metadata=self.metadata) + + self.check = iam_password_policy_lowercase(metadata) self.mock_session = MagicMock() - self.mock_acm = MagicMock() - self.mock_session.client.return_value = self.mock_acm # Simplified (removed STS) - - def test_no_certificates(self): - """Test when there are no ACM certificates.""" - self.mock_acm.list_certificates.return_value = {"CertificateSummaryList": []} - report = self.check.execute(self.mock_session) - - assert report.status == CheckStatus.NOT_APPLICABLE - assert report.resource_ids_status[0].summary == "No ACM certificates found." - assert not hasattr(report.resource_ids_status[0].resource, "arn") - - def test_transparency_logs_enabled(self): - """Test when all certificates have transparency logs enabled.""" - cert_arn = "arn:aws:acm:region:account:certificate/cert-1" - self.mock_acm.list_certificates.return_value = { - "CertificateSummaryList": [{"CertificateArn": cert_arn}] - } - self.mock_acm.describe_certificate.return_value = { - "Certificate": {"Options": {"CertificateTransparencyLoggingPreference": "ENABLED"}} + self.mock_client = MagicMock() + self.mock_session.client.return_value = self.mock_client + + # Set up the exceptions attribute on the mock client + self.mock_client.exceptions = MagicMock() + # Create a custom NoSuchEntityException for testing + class NoSuchEntityException(Exception): + pass + self.mock_client.exceptions.NoSuchEntityException = NoSuchEntityException + + def test_password_policy_requires_lowercase(self): + """Test when password policy requires lowercase characters.""" + # Mock the response for a policy that requires lowercase + self.mock_client.get_account_password_policy.return_value = { + 'PasswordPolicy': { + 'RequireLowercaseCharacters': True + } } + # Execute the check report = self.check.execute(self.mock_session) + # Verify the results assert report.status == CheckStatus.PASSED - assert report.resource_ids_status[0].summary == f"Certificate {cert_arn} has transparency logging enabled." - assert report.resource_ids_status[0].resource.arn == cert_arn - - def test_transparency_logs_disabled(self): - """Test when a certificate has transparency logs disabled.""" - cert_arn = "arn:aws:acm:region:account:certificate/cert-2" - self.mock_acm.list_certificates.return_value = { - "CertificateSummaryList": [{"CertificateArn": cert_arn}] - } - self.mock_acm.describe_certificate.return_value = { - "Certificate": {"Options": {"CertificateTransparencyLoggingPreference": "DISABLED"}} + assert len(report.resource_ids_status) == 1 + assert report.resource_ids_status[0].status == CheckStatus.PASSED + assert "enforces the use of at least one lowercase letter" in report.resource_ids_status[0].summary + + def test_password_policy_does_not_require_lowercase(self): + """Test when password policy does not require lowercase characters.""" + # Mock the response for a policy that doesn't require lowercase + self.mock_client.get_account_password_policy.return_value = { + 'PasswordPolicy': { + 'RequireLowercaseCharacters': False + } } + # Execute the check report = self.check.execute(self.mock_session) + # Verify the results assert report.status == CheckStatus.FAILED - assert report.resource_ids_status[0].summary == f"Certificate {cert_arn} has transparency logging disabled." - assert report.resource_ids_status[0].resource.arn == cert_arn - - def test_client_error(self): - """Test error handling when list_certificates raises ClientError.""" - self.mock_acm.list_certificates.side_effect = ClientError( - {"Error": {"Code": "AccessDenied"}}, "ListCertificates" - ) - - report = self.check.execute(self.mock_session) - - assert report.status == CheckStatus.UNKNOWN - assert report.resource_ids_status[0].summary == "Error listing ACM certificates." - assert report.resource_ids_status[0].exception is not None - assert "AccessDenied" in report.resource_ids_status[0].exception - - def test_describe_certificate_error(self): - """Test error handling when describe_certificate raises ClientError.""" - cert_arn = "arn:aws:acm:region:account:certificate/cert-3" - self.mock_acm.list_certificates.return_value = { - "CertificateSummaryList": [{"CertificateArn": cert_arn}] + assert len(report.resource_ids_status) == 1 + assert report.resource_ids_status[0].status == CheckStatus.FAILED + assert "does not enforce the use of at least one lowercase letter" in report.resource_ids_status[0].summary + + def test_password_policy_missing_lowercase_setting(self): + """Test when password policy exists but doesn't specify lowercase requirement.""" + # Mock the response for a policy that doesn't specify lowercase requirement + self.mock_client.get_account_password_policy.return_value = { + 'PasswordPolicy': { + # RequireLowercaseCharacters is missing + } } - self.mock_acm.describe_certificate.side_effect = ClientError( - {"Error": {"Code": "AccessDenied"}}, "DescribeCertificate" - ) + # Execute the check report = self.check.execute(self.mock_session) + # Verify the results assert report.status == CheckStatus.FAILED - assert report.resource_ids_status[0].summary == f"Error describing certificate {cert_arn}." - assert "AccessDenied" in report.resource_ids_status[0].exception - assert report.resource_ids_status[0].resource.arn == cert_arn - - def test_no_such_entity_exception(self): - """Test handling when describe_certificate raises a ResourceNotFoundException.""" - cert_arn = "arn:aws:acm:region:account:certificate/missing" - self.mock_acm.list_certificates.return_value = { - "CertificateSummaryList": [{"CertificateArn": cert_arn}] - } - - class NoSuchEntityException(Exception): - pass + assert len(report.resource_ids_status) == 1 + assert report.resource_ids_status[0].status == CheckStatus.FAILED + assert "does not enforce the use of at least one lowercase letter" in report.resource_ids_status[0].summary - self.mock_acm.exceptions = MagicMock() - self.mock_acm.exceptions.ResourceNotFoundException = NoSuchEntityException - self.mock_acm.describe_certificate.side_effect = NoSuchEntityException("No such certificate") + def test_no_password_policy_exists(self): + """Test when no password policy exists.""" + # Set up the NoSuchEntityException + self.mock_client.get_account_password_policy.side_effect = self.mock_client.exceptions.NoSuchEntityException() + # Execute the check report = self.check.execute(self.mock_session) + # Verify the results assert report.status == CheckStatus.FAILED - assert report.resource_ids_status[0].summary == f"Error describing certificate {cert_arn}." - assert "No such certificate" in report.resource_ids_status[0].exception - assert report.resource_ids_status[0].resource.arn == cert_arn - - def test_paginated_certificates_list(self): - """Test paginated ACM certificate list.""" - cert_arn_1 = "arn:aws:acm:region:account:certificate/cert-1" - cert_arn_2 = "arn:aws:acm:region:account:certificate/cert-2" - - self.mock_acm.list_certificates.side_effect = [ - {"CertificateSummaryList": [{"CertificateArn": cert_arn_1}], "NextToken": "token-1"}, - {"CertificateSummaryList": [{"CertificateArn": cert_arn_2}]} - ] - - self.mock_acm.describe_certificate.side_effect = lambda CertificateArn: { - "Certificate": {"Options": {"CertificateTransparencyLoggingPreference": "ENABLED"}} - } + assert len(report.resource_ids_status) == 1 + assert report.resource_ids_status[0].status == CheckStatus.FAILED + assert "No custom IAM password policy is configured" in report.resource_ids_status[0].summary + def test_client_error_handling(self): + """Test error handling when a ClientError occurs.""" + # Mock a ClientError with proper structure + error_response = {'Error': {'Code': 'SomeError', 'Message': 'An error occurred'}} + self.mock_client.get_account_password_policy.side_effect = ClientError(error_response, 'GetAccountPasswordPolicy') + + # Execute the check report = self.check.execute(self.mock_session) - assert report.status == CheckStatus.PASSED - assert len(report.resource_ids_status) == 2 - assert all(r.status == CheckStatus.PASSED for r in report.resource_ids_status) - assert report.resource_ids_status[0].resource.arn == cert_arn_1 - assert report.resource_ids_status[1].resource.arn == cert_arn_2 \ No newline at end of file + # Verify the results + assert report.status == CheckStatus.UNKNOWN + assert len(report.resource_ids_status) == 1 + assert report.resource_ids_status[0].status == CheckStatus.UNKNOWN + assert "An error occurred while retrieving the IAM password policy" in report.resource_ids_status[0].summary