diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible.py b/prowler/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible.py index 468c54c8..0c9ce4fd 100644 --- a/prowler/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible.py +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible.py @@ -10,6 +10,7 @@ class cloudtrail_logs_s3_bucket_is_not_publicly_accessible(Check): findings = [] for trail in cloudtrail_client.trails: if trail.name: + trail_bucket_is_in_account = False trail_bucket = trail.s3_bucket report = Check_Report_AWS(self.metadata()) report.region = trail.region @@ -23,19 +24,23 @@ class cloudtrail_logs_s3_bucket_is_not_publicly_accessible(Check): for bucket in s3_client.buckets: # Here we need to ensure that acl_grantee is filled since if we don't have permissions to query the api for a concrete region # (for example due to a SCP) we are going to try access an attribute from a None type - if trail_bucket == bucket.name and bucket.acl_grantees: - for grant in bucket.acl_grantees: - if ( - grant.URI - == "http://acs.amazonaws.com/groups/global/AllUsers" - ): - report.status = "FAIL" - if trail.is_multiregion: - report.status_extended = f"S3 Bucket {trail_bucket} from multiregion trail {trail.name} is publicly accessible" - else: - report.status_extended = f"S3 Bucket {trail_bucket} from single region trail {trail.name} is publicly accessible" - break - + if trail_bucket == bucket.name: + trail_bucket_is_in_account = True + if bucket.acl_grantees: + for grant in bucket.acl_grantees: + if ( + grant.URI + == "http://acs.amazonaws.com/groups/global/AllUsers" + ): + report.status = "FAIL" + if trail.is_multiregion: + report.status_extended = f"S3 Bucket {trail_bucket} from multiregion trail {trail.name} is publicly accessible" + else: + report.status_extended = f"S3 Bucket {trail_bucket} from single region trail {trail.name} is publicly accessible" + break + # check if trail bucket is a cross account bucket + if not trail_bucket_is_in_account: + report.status_extended = f"Trail {trail.name} bucket ({trail_bucket}) is a cross-account bucket in another account out of Prowler's permissions scope, please check it manually" findings.append(report) return findings diff --git a/tests/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible_test.py b/tests/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible_test.py index 2d8103f1..2ae7c0d4 100644 --- a/tests/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible_test.py +++ b/tests/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible_test.py @@ -148,6 +148,7 @@ class Test_cloudtrail_logs_s3_bucket_is_not_publicly_accessible: from prowler.providers.aws.services.cloudtrail.cloudtrail_service import ( Cloudtrail, ) + from prowler.providers.aws.services.s3.s3_service import S3 current_audit_info.audited_partition = "aws" @@ -155,19 +156,71 @@ class Test_cloudtrail_logs_s3_bucket_is_not_publicly_accessible: "prowler.providers.aws.services.cloudtrail.cloudtrail_logs_s3_bucket_is_not_publicly_accessible.cloudtrail_logs_s3_bucket_is_not_publicly_accessible.cloudtrail_client", new=Cloudtrail(current_audit_info), ): - # Test Check - from prowler.providers.aws.services.cloudtrail.cloudtrail_logs_s3_bucket_is_not_publicly_accessible.cloudtrail_logs_s3_bucket_is_not_publicly_accessible import ( - cloudtrail_logs_s3_bucket_is_not_publicly_accessible, - ) + with mock.patch( + "prowler.providers.aws.services.cloudtrail.cloudtrail_logs_s3_bucket_is_not_publicly_accessible.cloudtrail_logs_s3_bucket_is_not_publicly_accessible.s3_client", + new=S3(current_audit_info), + ): + # Test Check + from prowler.providers.aws.services.cloudtrail.cloudtrail_logs_s3_bucket_is_not_publicly_accessible.cloudtrail_logs_s3_bucket_is_not_publicly_accessible import ( + cloudtrail_logs_s3_bucket_is_not_publicly_accessible, + ) - check = cloudtrail_logs_s3_bucket_is_not_publicly_accessible() - result = check.execute() + check = cloudtrail_logs_s3_bucket_is_not_publicly_accessible() + result = check.execute() - assert len(result) == 1 - assert result[0].status == "PASS" - assert result[0].resource_id == trail_name_us - assert result[0].resource_arn == trail_us["TrailARN"] - assert search( - result[0].status_extended, - f"S3 Bucket {bucket_name_us} from single region trail {trail_name_us} is not publicly accessible", - ) + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].resource_id == trail_name_us + assert result[0].resource_arn == trail_us["TrailARN"] + assert search( + result[0].status_extended, + f"S3 Bucket {bucket_name_us} from single region trail {trail_name_us} is not publicly accessible", + ) + + @mock_cloudtrail + @mock_s3 + def test_trail_bucket_cross_account(self): + cloudtrail_client = client("cloudtrail", region_name="us-east-1") + s3_client = client("s3", region_name="us-east-1") + trail_name_us = "trail_test_us" + bucket_name_us = "bucket_test_us" + s3_client.create_bucket(Bucket=bucket_name_us) + trail_us = cloudtrail_client.create_trail( + Name=trail_name_us, S3BucketName=bucket_name_us, IsMultiRegionTrail=False + ) + + from prowler.providers.aws.lib.audit_info.audit_info import current_audit_info + from prowler.providers.aws.services.cloudtrail.cloudtrail_service import ( + Cloudtrail, + ) + from prowler.providers.aws.services.s3.s3_service import S3 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "prowler.providers.aws.services.cloudtrail.cloudtrail_logs_s3_bucket_is_not_publicly_accessible.cloudtrail_logs_s3_bucket_is_not_publicly_accessible.cloudtrail_client", + new=Cloudtrail(current_audit_info), + ): + with mock.patch( + "prowler.providers.aws.services.cloudtrail.cloudtrail_logs_s3_bucket_is_not_publicly_accessible.cloudtrail_logs_s3_bucket_is_not_publicly_accessible.s3_client", + new=S3(current_audit_info), + ) as s3_client: + # Test Check + from prowler.providers.aws.services.cloudtrail.cloudtrail_logs_s3_bucket_is_not_publicly_accessible.cloudtrail_logs_s3_bucket_is_not_publicly_accessible import ( + cloudtrail_logs_s3_bucket_is_not_publicly_accessible, + ) + + # Empty s3 buckets to simulate the bucket is in another account + s3_client.buckets = [] + + check = cloudtrail_logs_s3_bucket_is_not_publicly_accessible() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].resource_id == trail_name_us + assert result[0].resource_arn == trail_us["TrailARN"] + assert search( + "is a cross-account bucket in another account out of Prowler's permissions scope", + result[0].status_extended, + )