feat(ec2): Implement AMI block public access check (#11794) (#11828)

Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
This commit is contained in:
GOUTHAM HARIGOVIND
2026-07-10 13:10:15 +02:00
committed by GitHub
co-authored by Daniel Barranquero
parent 106026614d
commit 3369e48260
6 changed files with 225 additions and 0 deletions
@@ -0,0 +1 @@
`ec2_ami_account_block_public_access` check for AWS provider, verifying AMI block public access is enabled at the account level in each Region so AMIs cannot be shared publicly
@@ -0,0 +1,40 @@
{
"Provider": "aws",
"CheckID": "ec2_ami_account_block_public_access",
"CheckTitle": "AMI block public access is enabled at the account level",
"CheckType": [
"Software and Configuration Checks/AWS Security Best Practices",
"Effects/Data Exposure"
],
"ServiceName": "ec2",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "Other",
"ResourceGroup": "compute",
"Description": "AMI block public access configuration is assessed to see whether public sharing of AMIs is blocked in the account and Region. When enabled (`block-new-sharing`), no AMI in the Region can be made public regardless of individual image permissions.",
"Risk": "Without blocking public access, AMIs could be accidentally or maliciously shared publicly, exposing baked-in secrets, source code, and infrastructure details to unauthorized actors.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/sharingamis-intro.html",
"https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/image-block-public-access.html"
],
"Remediation": {
"Code": {
"CLI": "aws ec2 enable-image-block-public-access --image-block-public-access-state block-new-sharing",
"NativeIaC": "",
"Other": "1. In the AWS console, select the target Region in the top-right.\n2. Go to EC2 > AMIs.\n3. In the AMIs page, choose Block public access for AMIs (or EC2 Dashboard > Account attributes > Data protection and security).\n4. Choose Manage and enable Block public access.\n5. Save changes.",
"Terraform": "```hcl\nresource \"aws_ec2_image_block_public_access\" \"<example_resource_name>\" {\n state = \"block-new-sharing\" # Blocks new public sharing of AMIs in the configured Region\n}\n```"
},
"Recommendation": {
"Text": "Enable AMI block public access (`block-new-sharing`) in every active Region. Apply guardrails (SCPs) to prevent it from being disabled, and review any AMIs that are currently shared publicly.",
"Url": "https://hub.prowler.com/check/ec2_ami_account_block_public_access"
}
},
"Categories": [
"internet-exposed"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,29 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.ec2.ec2_client import ec2_client
class ec2_ami_account_block_public_access(Check):
def execute(self):
findings = []
for state in ec2_client.ami_block_public_access_states:
report = Check_Report_AWS(
metadata=self.metadata(),
resource=state,
)
report.resource_id = ec2_client.audited_account
report.resource_arn = ec2_client.account_arn_template
if state.status == "block-new-sharing":
report.status = "PASS"
report.status_extended = (
f"AMI Block Public Access is enabled in {state.region}."
)
else:
report.status = "FAIL"
report.status_extended = (
f"AMI Block Public Access is disabled in {state.region}."
)
findings.append(report)
return findings
@@ -52,6 +52,8 @@ class EC2(AWSService):
self.__threading_call__(self._describe_ec2_addresses)
self.ebs_block_public_access_snapshots_states = []
self.__threading_call__(self._get_snapshot_block_public_access_state)
self.ami_block_public_access_states = []
self.__threading_call__(self._get_ami_block_public_access_state)
self.instance_metadata_defaults = []
self.__threading_call__(self._get_instance_metadata_defaults)
self.launch_templates = []
@@ -498,6 +500,21 @@ class EC2(AWSService):
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def _get_ami_block_public_access_state(self, regional_client):
try:
self.ami_block_public_access_states.append(
AmiBlockPublicAccess(
status=regional_client.get_image_block_public_access_state()[
"ImageBlockPublicAccessState"
],
region=regional_client.region,
)
)
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def _get_instance_metadata_defaults(self, regional_client):
try:
instances_in_region = self.attributes_for_regions.get(
@@ -798,6 +815,11 @@ class EbsSnapshotBlockPublicAccess(BaseModel):
region: str
class AmiBlockPublicAccess(BaseModel):
status: str
region: str
class InstanceMetadataDefaults(BaseModel):
http_tokens: Optional[str]
instances: bool
@@ -0,0 +1,133 @@
from unittest import mock
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_ec2_ami_account_block_public_access:
@mock_aws
def test_ec2_ami_block_public_access_state_unblocked(self):
from prowler.providers.aws.services.ec2.ec2_service import AmiBlockPublicAccess
ec2_client = mock.MagicMock()
ec2_client.ami_block_public_access_states = [
AmiBlockPublicAccess(status="unblocked", region=AWS_REGION_US_EAST_1)
]
ec2_client.audited_account = AWS_ACCOUNT_NUMBER
ec2_client.region = AWS_REGION_US_EAST_1
ec2_client.account_arn_template = (
f"arn:aws:ec2:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:account"
)
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_aws_provider(),
),
mock.patch(
"prowler.providers.aws.services.ec2.ec2_ami_account_block_public_access.ec2_ami_account_block_public_access.ec2_client",
new=ec2_client,
),
):
# Test Check
from prowler.providers.aws.services.ec2.ec2_ami_account_block_public_access.ec2_ami_account_block_public_access import (
ec2_ami_account_block_public_access,
)
check = ec2_ami_account_block_public_access()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"AMI Block Public Access is disabled in {AWS_REGION_US_EAST_1}."
)
assert (
result[0].resource_arn
== f"arn:aws:ec2:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:account"
)
assert result[0].resource_id == AWS_ACCOUNT_NUMBER
assert result[0].region == AWS_REGION_US_EAST_1
@mock_aws
def test_ec2_ami_block_public_access_state_block_new_sharing(self):
from prowler.providers.aws.services.ec2.ec2_service import AmiBlockPublicAccess
ec2_client = mock.MagicMock()
ec2_client.ami_block_public_access_states = [
AmiBlockPublicAccess(
status="block-new-sharing", region=AWS_REGION_US_EAST_1
)
]
ec2_client.audited_account = AWS_ACCOUNT_NUMBER
ec2_client.region = AWS_REGION_US_EAST_1
ec2_client.account_arn_template = (
f"arn:aws:ec2:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:account"
)
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_aws_provider(),
),
mock.patch(
"prowler.providers.aws.services.ec2.ec2_ami_account_block_public_access.ec2_ami_account_block_public_access.ec2_client",
new=ec2_client,
),
):
# Test Check
from prowler.providers.aws.services.ec2.ec2_ami_account_block_public_access.ec2_ami_account_block_public_access import (
ec2_ami_account_block_public_access,
)
check = ec2_ami_account_block_public_access()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"AMI Block Public Access is enabled in {AWS_REGION_US_EAST_1}."
)
assert (
result[0].resource_arn
== f"arn:aws:ec2:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:account"
)
assert result[0].resource_id == AWS_ACCOUNT_NUMBER
assert result[0].region == AWS_REGION_US_EAST_1
@mock_aws
def test_ec2_ami_block_public_access_no_resources(self):
ec2_client = mock.MagicMock()
ec2_client.ami_block_public_access_states = []
ec2_client.audited_account = AWS_ACCOUNT_NUMBER
ec2_client.region = AWS_REGION_US_EAST_1
ec2_client.account_arn_template = (
f"arn:aws:ec2:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:account"
)
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_aws_provider(),
),
mock.patch(
"prowler.providers.aws.services.ec2.ec2_ami_account_block_public_access.ec2_ami_account_block_public_access.ec2_client",
new=ec2_client,
),
):
# Test Check
from prowler.providers.aws.services.ec2.ec2_ami_account_block_public_access.ec2_ami_account_block_public_access import (
ec2_ami_account_block_public_access,
)
check = ec2_ami_account_block_public_access()
result = check.execute()
assert len(result) == 0