mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 20:42:02 +00:00
feat(s3): Add new check s3_bucket_cross_account_access (#5082)
Co-authored-by: Sergio <sergio@prowler.com>
This commit is contained in:
committed by
GitHub
parent
744e7ff5ac
commit
8e087196c9
+32
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"Provider": "aws",
|
||||
"CheckID": "s3_bucket_cross_account_access",
|
||||
"CheckTitle": "Ensure that general-purpose bucket policies restrict access to other AWS accounts.",
|
||||
"CheckType": [
|
||||
"Effects/Data Exposure"
|
||||
],
|
||||
"ServiceName": "s3",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "arn:partition:s3:::bucket_name",
|
||||
"Severity": "high",
|
||||
"ResourceType": "AwsS3Bucket",
|
||||
"Description": "This check verifies that S3 bucket policies are configured in a way that limits access to the intended AWS accounts only, preventing unauthorized access by external or unintended accounts.",
|
||||
"Risk": "Allowing other AWS accounts to perform sensitive actions (e.g., modifying bucket policies, ACLs, or encryption settings) on your S3 buckets can lead to data exposure, unauthorized access, or misconfigurations, increasing the risk of insider threats or attacks.",
|
||||
"RelatedUrl": "https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-block-public-access.html",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "",
|
||||
"NativeIaC": "",
|
||||
"Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/s3-controls.html#s3-6",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Review and update your S3 bucket policies to remove permissions that grant external AWS accounts access to critical actions and implement least privilege principles to ensure sensitive operations are restricted to trusted accounts only",
|
||||
"Url": "https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-block-public-access.html"
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_AWS
|
||||
from prowler.providers.aws.services.iam.lib.policy import is_policy_public
|
||||
from prowler.providers.aws.services.s3.s3_client import s3_client
|
||||
|
||||
|
||||
class s3_bucket_cross_account_access(Check):
|
||||
def execute(self):
|
||||
findings = []
|
||||
for arn, bucket in s3_client.buckets.items():
|
||||
report = Check_Report_AWS(self.metadata())
|
||||
report.region = bucket.region
|
||||
report.resource_id = bucket.name
|
||||
report.resource_arn = arn
|
||||
report.resource_tags = bucket.tags
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"S3 Bucket {bucket.name} has a bucket policy but it does not allow cross account access."
|
||||
|
||||
if not bucket.policy:
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"S3 Bucket {bucket.name} does not have a bucket policy."
|
||||
)
|
||||
elif is_policy_public(
|
||||
bucket.policy, source_account=s3_client.audited_account
|
||||
):
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"S3 Bucket {bucket.name} has a bucket policy allowing cross account access."
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
import json
|
||||
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,
|
||||
set_mocked_aws_provider,
|
||||
)
|
||||
|
||||
|
||||
class Test_s3_bucket_cross_account_access:
|
||||
@mock_aws
|
||||
def test_no_buckets(self):
|
||||
from prowler.providers.aws.services.s3.s3_service import S3
|
||||
|
||||
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.s3.s3_bucket_cross_account_access.s3_bucket_cross_account_access.s3_client",
|
||||
new=S3(aws_provider),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.s3.s3_bucket_cross_account_access.s3_bucket_cross_account_access import (
|
||||
s3_bucket_cross_account_access,
|
||||
)
|
||||
|
||||
check = s3_bucket_cross_account_access()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 0
|
||||
|
||||
@mock_aws
|
||||
def test_bucket_no_policy(self):
|
||||
s3_client = client("s3", region_name=AWS_REGION_US_EAST_1)
|
||||
bucket_name_us = "bucket_test_us"
|
||||
s3_client.create_bucket(Bucket=bucket_name_us)
|
||||
|
||||
from prowler.providers.aws.services.s3.s3_service import S3
|
||||
|
||||
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.s3.s3_bucket_cross_account_access.s3_bucket_cross_account_access.s3_client",
|
||||
new=S3(aws_provider),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.s3.s3_bucket_cross_account_access.s3_bucket_cross_account_access import (
|
||||
s3_bucket_cross_account_access,
|
||||
)
|
||||
|
||||
check = s3_bucket_cross_account_access()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"S3 Bucket {bucket_name_us} does not have a bucket policy."
|
||||
)
|
||||
assert result[0].resource_id == bucket_name_us
|
||||
assert (
|
||||
result[0].resource_arn
|
||||
== f"arn:{aws_provider.identity.partition}:s3:::{bucket_name_us}"
|
||||
)
|
||||
assert result[0].region == AWS_REGION_US_EAST_1
|
||||
|
||||
@mock_aws
|
||||
def test_bucket_policy_allow_delete(self):
|
||||
s3_client = client("s3", region_name=AWS_REGION_US_EAST_1)
|
||||
delete_bucket_policy = "s3:DeleteBucketPolicy"
|
||||
bucket_name_us = "bucket_test_us"
|
||||
policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {"AWS": "*"},
|
||||
"Action": delete_bucket_policy,
|
||||
"Resource": "arn:aws:s3:::*",
|
||||
}
|
||||
],
|
||||
}
|
||||
s3_client.create_bucket(Bucket=bucket_name_us)
|
||||
s3_client.put_bucket_policy(
|
||||
Bucket=bucket_name_us,
|
||||
Policy=json.dumps(policy),
|
||||
)
|
||||
|
||||
from prowler.providers.aws.services.s3.s3_service import S3
|
||||
|
||||
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.s3.s3_bucket_cross_account_access.s3_bucket_cross_account_access.s3_client",
|
||||
new=S3(aws_provider),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.s3.s3_bucket_cross_account_access.s3_bucket_cross_account_access import (
|
||||
s3_bucket_cross_account_access,
|
||||
)
|
||||
|
||||
check = s3_bucket_cross_account_access()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"S3 Bucket {bucket_name_us} has a bucket policy allowing cross account access."
|
||||
)
|
||||
assert result[0].resource_id == bucket_name_us
|
||||
assert (
|
||||
result[0].resource_arn
|
||||
== f"arn:{aws_provider.identity.partition}:s3:::{bucket_name_us}"
|
||||
)
|
||||
assert result[0].region == AWS_REGION_US_EAST_1
|
||||
|
||||
@mock_aws
|
||||
def test_bucket_policy_allow_multiple_other_accounts(self):
|
||||
s3_client = client("s3", region_name=AWS_REGION_US_EAST_1)
|
||||
put_encryption_configuration = "s3:PutEncryptionConfiguration"
|
||||
put_bucket_policy = "s3:PutBucketPolicy"
|
||||
bucket_name_us = "bucket_test_us"
|
||||
policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {"AWS": "arn:aws:iam::*:root"},
|
||||
"Action": [
|
||||
put_encryption_configuration,
|
||||
put_bucket_policy,
|
||||
],
|
||||
"Resource": "arn:aws:s3:::*",
|
||||
}
|
||||
],
|
||||
}
|
||||
s3_client.create_bucket(Bucket=bucket_name_us)
|
||||
s3_client.put_bucket_policy(
|
||||
Bucket=bucket_name_us,
|
||||
Policy=json.dumps(policy),
|
||||
)
|
||||
|
||||
from prowler.providers.aws.services.s3.s3_service import S3
|
||||
|
||||
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.s3.s3_bucket_cross_account_access.s3_bucket_cross_account_access.s3_client",
|
||||
new=S3(aws_provider),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.s3.s3_bucket_cross_account_access.s3_bucket_cross_account_access import (
|
||||
s3_bucket_cross_account_access,
|
||||
)
|
||||
|
||||
check = s3_bucket_cross_account_access()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"S3 Bucket {bucket_name_us} has a bucket policy allowing cross account access."
|
||||
)
|
||||
assert result[0].resource_id == bucket_name_us
|
||||
assert (
|
||||
result[0].resource_arn
|
||||
== f"arn:{aws_provider.identity.partition}:s3:::{bucket_name_us}"
|
||||
)
|
||||
assert result[0].region == AWS_REGION_US_EAST_1
|
||||
|
||||
@mock_aws
|
||||
def test_bucket_policy_allow_same_account(self):
|
||||
s3_client = client("s3", region_name=AWS_REGION_US_EAST_1)
|
||||
delete_bucket_policy = "s3:DeleteBucketPolicy"
|
||||
bucket_name_us = "bucket_test_us"
|
||||
policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {"AWS": f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root"},
|
||||
"Action": delete_bucket_policy,
|
||||
"Resource": "arn:aws:s3:::*",
|
||||
}
|
||||
],
|
||||
}
|
||||
s3_client.create_bucket(Bucket=bucket_name_us)
|
||||
s3_client.put_bucket_policy(
|
||||
Bucket=bucket_name_us,
|
||||
Policy=json.dumps(policy),
|
||||
)
|
||||
|
||||
from prowler.providers.aws.services.s3.s3_service import S3
|
||||
|
||||
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.s3.s3_bucket_cross_account_access.s3_bucket_cross_account_access.s3_client",
|
||||
new=S3(aws_provider),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.s3.s3_bucket_cross_account_access.s3_bucket_cross_account_access import (
|
||||
s3_bucket_cross_account_access,
|
||||
)
|
||||
|
||||
check = s3_bucket_cross_account_access()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"S3 Bucket {bucket_name_us} has a bucket policy but it does not allow cross account access."
|
||||
)
|
||||
assert result[0].resource_id == bucket_name_us
|
||||
assert (
|
||||
result[0].resource_arn
|
||||
== f"arn:{aws_provider.identity.partition}:s3:::{bucket_name_us}"
|
||||
)
|
||||
assert result[0].region == AWS_REGION_US_EAST_1
|
||||
Reference in New Issue
Block a user