diff --git a/docs/tutorials/configuration_file.md b/docs/tutorials/configuration_file.md index 53e433d2c7..ef3dc54274 100644 --- a/docs/tutorials/configuration_file.md +++ b/docs/tutorials/configuration_file.md @@ -81,6 +81,7 @@ aws: max_ec2_instance_age_in_days: 180 # AWS VPC Configuration (vpc_endpoint_connections_trust_boundaries, vpc_endpoint_services_allowed_principals_trust_boundaries) + # AWS SSM Configuration (aws.ssm_documents_set_as_public) # Single account environment: No action required. The AWS account number will be automatically added by the checks. # Multi account environment: Any additional trusted account number should be added as a space separated list, e.g. # trusted_account_ids : ["123456789012", "098765432109", "678901234567"] diff --git a/prowler/config/config.yaml b/prowler/config/config.yaml index 05161daf96..7496ab88c4 100644 --- a/prowler/config/config.yaml +++ b/prowler/config/config.yaml @@ -31,6 +31,7 @@ aws: max_ec2_instance_age_in_days: 180 # AWS VPC Configuration (vpc_endpoint_connections_trust_boundaries, vpc_endpoint_services_allowed_principals_trust_boundaries) + # AWS SSM Configuration (aws.ssm_documents_set_as_public) # Single account environment: No action required. The AWS account number will be automatically added by the checks. # Multi account environment: Any additional trusted account number should be added as a space separated list, e.g. # trusted_account_ids : ["123456789012", "098765432109", "678901234567"] diff --git a/prowler/providers/aws/services/ssm/ssm_documents_set_as_public/ssm_documents_set_as_public.py b/prowler/providers/aws/services/ssm/ssm_documents_set_as_public/ssm_documents_set_as_public.py index f13d493705..3cbc1082af 100644 --- a/prowler/providers/aws/services/ssm/ssm_documents_set_as_public/ssm_documents_set_as_public.py +++ b/prowler/providers/aws/services/ssm/ssm_documents_set_as_public/ssm_documents_set_as_public.py @@ -11,12 +11,25 @@ class ssm_documents_set_as_public(Check): report.resource_arn = document.arn report.resource_id = document.name report.resource_tags = document.tags - if document.account_owners: - report.status = "FAIL" - report.status_extended = f"SSM Document {document.name} is public." - else: + trusted_account_ids = ssm_client.audit_config.get("trusted_account_ids", []) + if ssm_client.audited_account not in trusted_account_ids: + trusted_account_ids.append(ssm_client.audited_account) + if not document.account_owners or document.account_owners == [ + ssm_client.audited_account + ]: report.status = "PASS" report.status_extended = f"SSM Document {document.name} is not public." + elif document.account_owners == ["all"]: + report.status = "FAIL" + report.status_extended = f"SSM Document {document.name} is public." + elif all(owner in trusted_account_ids for owner in document.account_owners): + report.status = "PASS" + report.status_extended = f"SSM Document {document.name} is shared to trusted AWS accounts: {', '.join(document.account_owners)}." + elif not all( + owner in trusted_account_ids for owner in document.account_owners + ): + report.status = "FAIL" + report.status_extended = f"SSM Document {document.name} is shared to non-trusted AWS accounts: {', '.join(document.account_owners)}." findings.append(report) diff --git a/tests/config/fixtures/config.yaml b/tests/config/fixtures/config.yaml index 4f155057a6..1cbda6342e 100644 --- a/tests/config/fixtures/config.yaml +++ b/tests/config/fixtures/config.yaml @@ -12,6 +12,7 @@ aws: max_ec2_instance_age_in_days: 180 # AWS VPC Configuration (vpc_endpoint_connections_trust_boundaries, vpc_endpoint_services_allowed_principals_trust_boundaries) + # AWS SSM Configuration (aws.ssm_documents_set_as_public) # Single account environment: No action required. The AWS account number will be automatically added by the checks. # Multi account environment: Any additional trusted account number should be added as a space separated list, e.g. # trusted_account_ids : ["123456789012", "098765432109", "678901234567"] diff --git a/tests/config/fixtures/config_old.yaml b/tests/config/fixtures/config_old.yaml index 348aef2dcd..35514768f4 100644 --- a/tests/config/fixtures/config_old.yaml +++ b/tests/config/fixtures/config_old.yaml @@ -7,6 +7,7 @@ max_security_group_rules: 50 max_ec2_instance_age_in_days: 180 # AWS VPC Configuration (vpc_endpoint_connections_trust_boundaries, vpc_endpoint_services_allowed_principals_trust_boundaries) +# AWS SSM Configuration (aws.ssm_documents_set_as_public) # Single account environment: No action required. The AWS account number will be automatically added by the checks. # Multi account environment: Any additional trusted account number should be added as a space separated list, e.g. # trusted_account_ids : ["123456789012", "098765432109", "678901234567"] diff --git a/tests/providers/aws/services/ssm/ssm_documents_set_as_public/ssm_documents_set_as_public_test.py b/tests/providers/aws/services/ssm/ssm_documents_set_as_public/ssm_documents_set_as_public_test.py index a932c9e474..d945ac407f 100644 --- a/tests/providers/aws/services/ssm/ssm_documents_set_as_public/ssm_documents_set_as_public_test.py +++ b/tests/providers/aws/services/ssm/ssm_documents_set_as_public/ssm_documents_set_as_public_test.py @@ -25,7 +25,7 @@ class Test_ssm_documents_set_as_public: assert len(result) == 0 - def test_document_public(self): + def test_document_public_account_owners(self): ssm_client = mock.MagicMock document_name = "test-document" document_arn = f"arn:aws:ssm:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:document/{document_name}" @@ -51,6 +51,42 @@ class Test_ssm_documents_set_as_public: check = ssm_documents_set_as_public() result = check.execute() + assert len(result) == 1 + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == document_name + assert result[0].resource_arn == document_arn + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"SSM Document {document_name} is shared to non-trusted AWS accounts: 111111111111, 111111222222." + ) + + def test_document_public_all_account_owners(self): + ssm_client = mock.MagicMock + document_name = "test-document" + document_arn = f"arn:aws:ssm:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:document/{document_name}" + ssm_client.audited_account = AWS_ACCOUNT_NUMBER + ssm_client.documents = { + document_name: Document( + arn=document_arn, + name=document_name, + region=AWS_REGION_US_EAST_1, + content="", + account_owners=["all"], + ) + } + with mock.patch( + "prowler.providers.aws.services.ssm.ssm_service.SSM", + new=ssm_client, + ): + # Test Check + from prowler.providers.aws.services.ssm.ssm_documents_set_as_public.ssm_documents_set_as_public import ( + ssm_documents_set_as_public, + ) + + check = ssm_documents_set_as_public() + result = check.execute() + assert len(result) == 1 assert result[0].region == AWS_REGION_US_EAST_1 assert result[0].resource_id == document_name @@ -60,6 +96,81 @@ class Test_ssm_documents_set_as_public: result[0].status_extended == f"SSM Document {document_name} is public." ) + def test_document_public_to_other_trusted_AWS_accounts(self): + ssm_client = mock.MagicMock + document_name = "test-document" + document_arn = f"arn:aws:ssm:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:document/{document_name}" + ssm_client.audited_account = AWS_ACCOUNT_NUMBER + ssm_client.documents = { + document_name: Document( + arn=document_arn, + name=document_name, + region=AWS_REGION_US_EAST_1, + content="", + account_owners=["111111111333", "111111222444"], + ) + } + ssm_client.audit_config = { + "trusted_account_ids": ["111111111333", "111111222444"] + } + with mock.patch( + "prowler.providers.aws.services.ssm.ssm_service.SSM", + new=ssm_client, + ): + # Test Check + from prowler.providers.aws.services.ssm.ssm_documents_set_as_public.ssm_documents_set_as_public import ( + ssm_documents_set_as_public, + ) + + check = ssm_documents_set_as_public() + result = check.execute() + + assert len(result) == 1 + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == document_name + assert result[0].resource_arn == document_arn + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"SSM Document {document_name} is shared to trusted AWS accounts: 111111111333, 111111222444." + ) + + def test_document_public_to_self_account(self): + ssm_client = mock.MagicMock + document_name = "test-document" + document_arn = f"arn:aws:ssm:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:document/{document_name}" + ssm_client.audited_account = AWS_ACCOUNT_NUMBER + ssm_client.documents = { + document_name: Document( + arn=document_arn, + name=document_name, + region=AWS_REGION_US_EAST_1, + content="", + account_owners=[AWS_ACCOUNT_NUMBER], + ) + } + with mock.patch( + "prowler.providers.aws.services.ssm.ssm_service.SSM", + new=ssm_client, + ): + # Test Check + from prowler.providers.aws.services.ssm.ssm_documents_set_as_public.ssm_documents_set_as_public import ( + ssm_documents_set_as_public, + ) + + check = ssm_documents_set_as_public() + result = check.execute() + + assert len(result) == 1 + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == document_name + assert result[0].resource_arn == document_arn + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"SSM Document {document_name} is not public." + ) + def test_document_not_public(self): ssm_client = mock.MagicMock document_name = "test-document"