diff --git a/docs/tutorials/configuration_file.md b/docs/tutorials/configuration_file.md index c3a54f4e12..63fea6dd95 100644 --- a/docs/tutorials/configuration_file.md +++ b/docs/tutorials/configuration_file.md @@ -158,6 +158,7 @@ aws: ] # 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 c3ec35152b..a577f052fc 100644 --- a/prowler/config/config.yaml +++ b/prowler/config/config.yaml @@ -58,6 +58,7 @@ aws: ] # 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 7309a1573a..46e499453a 100644 --- a/tests/config/fixtures/config.yaml +++ b/tests/config/fixtures/config.yaml @@ -58,6 +58,7 @@ aws: ] # 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 e91da41e39..cbd3bf4fa0 100644 --- a/tests/config/fixtures/config_old.yaml +++ b/tests/config/fixtures/config_old.yaml @@ -19,6 +19,7 @@ ec2_allowed_instance_owners: ] # 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 9b1988f97f..9c218bb241 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 @@ -22,7 +22,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}" @@ -48,6 +48,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 @@ -57,6 +93,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"