feat(eventbridge): add EventBridge checks (#4020)

This commit is contained in:
Sergio Garcia
2024-05-21 08:51:10 +02:00
committed by GitHub
parent 41a4750b45
commit f4eaf2d909
19 changed files with 1009 additions and 0 deletions
@@ -0,0 +1,30 @@
{
"Provider": "aws",
"CheckID": "eventbridge_bus_cross_account_access",
"CheckTitle": "Ensure that AWS EventBridge event buses do not allow unknown cross-account access for delivery of events.",
"CheckType": [],
"ServiceName": "eventbridge",
"SubServiceName": "eventbus",
"ResourceIdTemplate": "arn:partition:events:region:account-id:event-bus/resource-id",
"Severity": "high",
"ResourceType": "AwsEventsEventbus",
"Description": "Ensure that AWS EventBridge event buses do not allow unknown cross-account access for delivery of events.",
"Risk": "If an AWS EventBridge event bus allows unknown cross-account access for delivery of events, it can lead to unauthorized access to the event bus and its events.",
"RelatedUrl": "https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/CWE_GettingStarted.html",
"Remediation": {
"Code": {
"CLI": "aws events remove-permission --event-bus-name <event_bus_name> --statement-id <statement_id>",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/CloudWatchEvents/event-bus-cross-account-access.html",
"Terraform": ""
},
"Recommendation": {
"Text": "To remediate this issue, remove the unknown cross-account access for delivery of events from the AWS EventBridge event bus.",
"Url": "https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/CloudWatchEvents-CrossAccountEventDelivery.html"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,29 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.eventbridge.eventbridge_client import (
eventbridge_client,
)
from prowler.providers.aws.services.iam.lib.policy import is_policy_cross_account
class eventbridge_bus_cross_account_access(Check):
def execute(self):
findings = []
for bus in eventbridge_client.buses.values():
report = Check_Report_AWS(self.metadata())
report.resource_id = bus.name
report.resource_arn = bus.arn
report.resource_tags = bus.tags
report.region = bus.region
report.status = "PASS"
report.status_extended = (
f"EventBridge event bus {bus.name} does not allow cross-account access."
)
if is_policy_cross_account(bus.policy, eventbridge_client.audited_account):
report.status = "FAIL"
report.status_extended = (
f"EventBridge event bus {bus.name} allows cross-account access."
)
findings.append(report)
return findings
@@ -0,0 +1,30 @@
{
"Provider": "aws",
"CheckID": "eventbridge_bus_exposed",
"CheckTitle": "Ensure that your AWS EventBridge event bus is not exposed to everyone",
"CheckType": [],
"ServiceName": "eventbridge",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "high",
"ResourceType": "AwsEventsEventbus",
"Description": "Ensure that your AWS EventBridge event bus is not exposed to everyone.",
"Risk": "If your AWS EventBridge event bus is exposed to everyone, unauthorized users can access your event bus and potentially view or modify your events.",
"RelatedUrl": "https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/CWE_GettingStarted.html",
"Remediation": {
"Code": {
"CLI": "aws events remove-permission --event-bus-name <event_bus_name> --statement-id <statement_id>",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/CloudWatchEvents/event-bus-exposed.html",
"Terraform": ""
},
"Recommendation": {
"Text": "To restrict access to your AWS EventBridge event bus, remove the permission that allows everyone to access it.",
"Url": "https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/CloudWatchEvents-CrossAccountEventDelivery.html"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,27 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.eventbridge.eventbridge_client import (
eventbridge_client,
)
from prowler.providers.aws.services.iam.lib.policy import is_policy_public
class eventbridge_bus_exposed(Check):
def execute(self):
findings = []
for bus in eventbridge_client.buses.values():
report = Check_Report_AWS(self.metadata())
report.status = "PASS"
report.status_extended = (
f"EventBridge event bus {bus.name} is not exposed to everyone."
)
report.resource_id = bus.name
report.resource_arn = bus.arn
report.resource_tags = bus.tags
report.region = bus.region
if is_policy_public(bus.policy):
report.status = "FAIL"
report.status_extended = (
f"EventBridge event bus {bus.name} is exposed to everyone."
)
findings.append(report)
return findings
@@ -0,0 +1,4 @@
from prowler.providers.aws.services.eventbridge.eventbridge_service import EventBridge
from prowler.providers.common.provider import Provider
eventbridge_client = EventBridge(Provider.get_global_provider())
@@ -0,0 +1,30 @@
{
"Provider": "aws",
"CheckID": "eventbridge_schema_registry_cross_account_access",
"CheckTitle": "Ensure that AWS EventBridge schema registries do not allow unknown cross-account access for delivery of events.",
"CheckType": [],
"ServiceName": "eventbridge",
"SubServiceName": "Schemas",
"ResourceIdTemplate": "arn:partition:events:region:account-id:event-bus/resource-id",
"Severity": "high",
"ResourceType": "AwsEventSchemasRegistry",
"Description": "Ensure that AWS EventBridge schema registries do not allow unknown cross-account access for delivery of events.",
"Risk": "The schema registry is a central location for storing and managing schemas. If the schema registry is not properly secured, it can lead to unauthorized access to the schema registry and the schemas stored within it.",
"RelatedUrl": "https://aws.amazon.com/about-aws/whats-new/2021/09/cross-account-discovery-amazon-eventbridge-schema/",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "To remediate this issue, ensure that the schema registry is not publicly accessible and that only authorized accounts have access to the schema registry.",
"Url": "https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-schema.html"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,23 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.lib.iam.iam import is_policy_cross_account
from prowler.providers.aws.services.eventbridge.schema_client import schema_client
class eventbridge_schema_registry_cross_account_access(Check):
def execute(self):
findings = []
for registry in schema_client.registries.values():
report = Check_Report_AWS(self.metadata())
report.resource_id = registry.name
report.resource_arn = registry.arn
report.resource_tags = registry.tags
report.region = registry.region
report.status = "PASS"
report.status_extended = f"EventBridge schema registry {registry.name} does not allow cross-account access."
if is_policy_cross_account(registry.policy, schema_client.audited_account):
report.status = "FAIL"
report.status_extended = f"EventBridge schema registry {registry.name} allows cross-account access."
findings.append(report)
return findings
@@ -0,0 +1,160 @@
import json
from typing import Optional
from botocore.exceptions import ClientError
from pydantic import BaseModel
from prowler.lib.logger import logger
from prowler.lib.scan_filters.scan_filters import is_resource_filtered
from prowler.providers.aws.lib.service.service import AWSService
################################ EventBridge
class EventBridge(AWSService):
def __init__(self, provider):
# Call AWSService's __init__
super().__init__("events", provider)
self.buses = {}
self.__threading_call__(self.__list_event_buses__)
self.__threading_call__(self.__describe_event_bus__)
self.__list_tags_for_resource__()
def __list_event_buses__(self, regional_client):
logger.info("EventBridge - Listing Event Buses...")
try:
for bus in regional_client.list_event_buses()["EventBuses"]:
bus_arn = bus["Arn"]
if not self.audit_resources or (
is_resource_filtered(bus_arn, self.audit_resources)
):
self.buses[bus_arn] = Bus(
name=bus.get("Name", ""),
arn=bus_arn,
region=regional_client.region,
)
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def __describe_event_bus__(self, regional_client):
logger.info("EventBridge - Describing Event Buses...")
try:
for bus in self.buses.values():
if bus.region == regional_client.region:
try:
response = regional_client.describe_event_bus(Name=bus.name)
bus.kms_key_id = response.get("KmsKeyIdentifier")
bus.policy = json.loads(response.get("Policy", "{}"))
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def __list_tags_for_resource__(self):
logger.info("EventBridge - Listing Tags...")
try:
for bus in self.buses.values():
try:
regional_client = self.regional_clients[bus.region]
bus.tags = regional_client.list_tags_for_resource(
ResourceARN=bus.arn
)["Tags"]
except ClientError as error:
if error.response["Error"]["Code"] == "ResourceNotFoundException":
logger.warning(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
else:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
class Bus(BaseModel):
name: str
arn: str
region: str
kms_key_id: Optional[str]
policy: Optional[str]
tags: Optional[list]
################################ Schema
class Schema(AWSService):
def __init__(self, provider):
# Call AWSService's __init__
super().__init__("schemas", provider)
self.registries = {}
self.__threading_call__(self.__list_registries__)
self.__threading_call__(self.__get_resource_policy__)
def __list_registries__(self, regional_client):
logger.info("EventBridge - Listing Schema Registries...")
try:
for registry in regional_client.list_registries()["Registries"]:
registry_arn = registry.get(
"RegistryArn",
f"arn:aws:schemas:{regional_client.region}:{self.audited_account}:registry/{registry.get('RegistryName', '')}",
)
if not self.audit_resources or (
is_resource_filtered(registry_arn, self.audit_resources)
):
self.registries[registry_arn] = Registry(
name=registry.get("RegistryName", ""),
arn=registry_arn,
region=regional_client.region,
tags=[registry["Tags"]],
)
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def __get_resource_policy__(self, regional_client):
logger.info("EventBridge - Describing Event Buses...")
try:
for registry in self.registries.values():
if registry.region == regional_client.region:
try:
response = regional_client.get_resource_policy(
RegistryName=registry.name
)
registry.policy = json.loads(response.get("Policy", "{}"))
except ClientError as error:
if error.response["Error"]["Code"] == "NotFoundException":
logger.warning(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
else:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
class Registry(BaseModel):
name: str
arn: str
region: str
policy: Optional[str]
tags: Optional[list]
@@ -0,0 +1,4 @@
from prowler.providers.aws.services.eventbridge.eventbridge_service import Schema
from prowler.providers.common.provider import Provider
schema_client = Schema(Provider.get_global_provider())
@@ -0,0 +1,70 @@
def is_policy_cross_account(policy: dict, audited_account: str) -> bool:
"""
is_policy_cross_account checks if the policy allows cross-account access.
Args:
policy (dict): The policy to check.
audited_account (str): The account to check if it has access.
Returns:
bool: True if the policy allows cross-account access, False otherwise.
"""
if policy and "Statement" in policy:
if isinstance(policy["Statement"], list):
for statement in policy["Statement"]:
if statement["Effect"] == "Allow" and "AWS" in statement["Principal"]:
if isinstance(statement["Principal"]["AWS"], list):
for aws_account in statement["Principal"]["AWS"]:
if audited_account not in aws_account or "*" == aws_account:
return True
else:
if (
audited_account not in statement["Principal"]["AWS"]
or "*" == statement["Principal"]["AWS"]
):
return True
else:
statement = policy["Statement"]
if statement["Effect"] == "Allow" and "AWS" in statement["Principal"]:
if isinstance(statement["Principal"]["AWS"], list):
for aws_account in statement["Principal"]["AWS"]:
if audited_account not in aws_account or "*" == aws_account:
return True
else:
if (
audited_account not in statement["Principal"]["AWS"]
or "*" == statement["Principal"]["AWS"]
):
return True
return False
def is_policy_public(policy: dict) -> bool:
"""
is_policy_public checks if the policy is publicly accessible.
If the "Principal" element value is set to { "AWS": "*" } and the policy statement is not using any Condition clauses to filter the access, the selected policy is publicly accessible.
Args:
policy (dict): The policy to check.
Returns:
bool: True if the policy is publicly accessible, False otherwise.
"""
if policy and "Statement" in policy:
for statement in policy["Statement"]:
if (
"Principal" in statement
and (
"*" == statement["Principal"]
or "arn:aws:iam::*:root" in statement["Principal"]
)
and "Condition" not in statement
):
return True
elif "Principal" in statement and "AWS" in statement["Principal"]:
if isinstance(statement["Principal"]["AWS"], str):
principals = [statement["Principal"]["AWS"]]
else:
principals = statement["Principal"]["AWS"]
for principal_arn in principals:
if (
principal_arn == "*" or principal_arn == "arn:aws:iam::*:root"
) and "Condition" not in statement:
return True
return False
@@ -0,0 +1,170 @@
from unittest import mock
from uuid import uuid4
from boto3 import client
from moto import mock_aws
from tests.providers.aws.utils import (
AWS_ACCOUNT_NUMBER,
AWS_REGION_EU_WEST_1,
set_mocked_aws_provider,
)
test_bus_name = str(uuid4())
test_bus_arn = f"arn:aws:eventbridge:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:event-bus/{test_bus_name}"
class Test_eventbridge_bus_cross_account_access:
@mock_aws
def test_default_bus(self):
from prowler.providers.aws.services.eventbridge.eventbridge_service import (
EventBridge,
)
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), mock.patch(
"prowler.providers.aws.services.eventbridge.eventbridge_bus_cross_account_access.eventbridge_bus_cross_account_access.eventbridge_client",
new=EventBridge(aws_provider),
):
from prowler.providers.aws.services.eventbridge.eventbridge_bus_cross_account_access.eventbridge_bus_cross_account_access import (
eventbridge_bus_cross_account_access,
)
check = eventbridge_bus_cross_account_access()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].status_extended == (
"EventBridge event bus default does not allow cross-account access."
)
assert result[0].resource_id == "default"
assert (
result[0].resource_arn
== f"arn:aws:events:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:event-bus/default"
)
assert result[0].resource_tags == []
assert result[0].region == AWS_REGION_EU_WEST_1
@mock_aws
def test_buses_self_account(self):
from prowler.providers.aws.services.eventbridge.eventbridge_service import (
EventBridge,
)
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
events_client = client("events", region_name=AWS_REGION_EU_WEST_1)
events_client.put_permission(
EventBusName="default",
Action="events:PutEvents",
Principal=AWS_ACCOUNT_NUMBER,
StatementId="test-statement",
)
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), mock.patch(
"prowler.providers.aws.services.eventbridge.eventbridge_bus_cross_account_access.eventbridge_bus_cross_account_access.eventbridge_client",
new=EventBridge(aws_provider),
):
from prowler.providers.aws.services.eventbridge.eventbridge_bus_cross_account_access.eventbridge_bus_cross_account_access import (
eventbridge_bus_cross_account_access,
)
check = eventbridge_bus_cross_account_access()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].status_extended == (
"EventBridge event bus default does not allow cross-account access."
)
assert result[0].resource_id == "default"
assert (
result[0].resource_arn
== f"arn:aws:events:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:event-bus/default"
)
assert result[0].resource_tags == []
assert result[0].region == AWS_REGION_EU_WEST_1
@mock_aws
def test_buses_other_account(self):
from prowler.providers.aws.services.eventbridge.eventbridge_service import (
EventBridge,
)
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
events_client = client("events", region_name=AWS_REGION_EU_WEST_1)
events_client.put_permission(
EventBusName="default",
Action="events:PutEvents",
Principal="111122223333",
StatementId="test-statement",
)
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), mock.patch(
"prowler.providers.aws.services.eventbridge.eventbridge_bus_cross_account_access.eventbridge_bus_cross_account_access.eventbridge_client",
new=EventBridge(aws_provider),
):
from prowler.providers.aws.services.eventbridge.eventbridge_bus_cross_account_access.eventbridge_bus_cross_account_access import (
eventbridge_bus_cross_account_access,
)
check = eventbridge_bus_cross_account_access()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].status_extended == (
"EventBridge event bus default allows cross-account access."
)
assert result[0].resource_id == "default"
assert (
result[0].resource_arn
== f"arn:aws:events:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:event-bus/default"
)
assert result[0].resource_tags == []
assert result[0].region == AWS_REGION_EU_WEST_1
@mock_aws
def test_buses_asterisk_principal(self):
from prowler.providers.aws.services.eventbridge.eventbridge_service import (
EventBridge,
)
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
events_client = client("events", region_name=AWS_REGION_EU_WEST_1)
events_client.put_permission(
EventBusName="default",
Action="events:PutEvents",
Principal="*",
StatementId="test-statement",
)
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), mock.patch(
"prowler.providers.aws.services.eventbridge.eventbridge_bus_cross_account_access.eventbridge_bus_cross_account_access.eventbridge_client",
new=EventBridge(aws_provider),
):
from prowler.providers.aws.services.eventbridge.eventbridge_bus_cross_account_access.eventbridge_bus_cross_account_access import (
eventbridge_bus_cross_account_access,
)
check = eventbridge_bus_cross_account_access()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].status_extended == (
"EventBridge event bus default allows cross-account access."
)
assert result[0].resource_id == "default"
assert (
result[0].resource_arn
== f"arn:aws:events:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:event-bus/default"
)
assert result[0].resource_tags == []
assert result[0].region == AWS_REGION_EU_WEST_1
@@ -0,0 +1,170 @@
from unittest import mock
from uuid import uuid4
from boto3 import client
from moto import mock_aws
from tests.providers.aws.utils import (
AWS_ACCOUNT_NUMBER,
AWS_REGION_EU_WEST_1,
set_mocked_aws_provider,
)
test_bus_name = str(uuid4())
test_bus_arn = f"arn:aws:eventbridge:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:event-bus/{test_bus_name}"
class Test_eventbridge_bus_exposed:
@mock_aws
def test_default_bus(self):
from prowler.providers.aws.services.eventbridge.eventbridge_service import (
EventBridge,
)
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), mock.patch(
"prowler.providers.aws.services.eventbridge.eventbridge_bus_exposed.eventbridge_bus_exposed.eventbridge_client",
new=EventBridge(aws_provider),
):
from prowler.providers.aws.services.eventbridge.eventbridge_bus_exposed.eventbridge_bus_exposed import (
eventbridge_bus_exposed,
)
check = eventbridge_bus_exposed()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].status_extended == (
"EventBridge event bus default is not exposed to everyone."
)
assert result[0].resource_id == "default"
assert (
result[0].resource_arn
== f"arn:aws:events:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:event-bus/default"
)
assert result[0].resource_tags == []
assert result[0].region == AWS_REGION_EU_WEST_1
@mock_aws
def test_buses_self_account(self):
from prowler.providers.aws.services.eventbridge.eventbridge_service import (
EventBridge,
)
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
events_client = client("events", region_name=AWS_REGION_EU_WEST_1)
events_client.put_permission(
EventBusName="default",
Action="events:PutEvents",
Principal=AWS_ACCOUNT_NUMBER,
StatementId="test-statement",
)
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), mock.patch(
"prowler.providers.aws.services.eventbridge.eventbridge_bus_exposed.eventbridge_bus_exposed.eventbridge_client",
new=EventBridge(aws_provider),
):
from prowler.providers.aws.services.eventbridge.eventbridge_bus_exposed.eventbridge_bus_exposed import (
eventbridge_bus_exposed,
)
check = eventbridge_bus_exposed()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].status_extended == (
"EventBridge event bus default is not exposed to everyone."
)
assert result[0].resource_id == "default"
assert (
result[0].resource_arn
== f"arn:aws:events:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:event-bus/default"
)
assert result[0].resource_tags == []
assert result[0].region == AWS_REGION_EU_WEST_1
@mock_aws
def test_buses_other_account(self):
from prowler.providers.aws.services.eventbridge.eventbridge_service import (
EventBridge,
)
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
events_client = client("events", region_name=AWS_REGION_EU_WEST_1)
events_client.put_permission(
EventBusName="default",
Action="events:PutEvents",
Principal="111122223333",
StatementId="test-statement",
)
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), mock.patch(
"prowler.providers.aws.services.eventbridge.eventbridge_bus_exposed.eventbridge_bus_exposed.eventbridge_client",
new=EventBridge(aws_provider),
):
from prowler.providers.aws.services.eventbridge.eventbridge_bus_exposed.eventbridge_bus_exposed import (
eventbridge_bus_exposed,
)
check = eventbridge_bus_exposed()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].status_extended == (
"EventBridge event bus default is not exposed to everyone."
)
assert result[0].resource_id == "default"
assert (
result[0].resource_arn
== f"arn:aws:events:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:event-bus/default"
)
assert result[0].resource_tags == []
assert result[0].region == AWS_REGION_EU_WEST_1
@mock_aws
def test_buses_asterisk_principal(self):
from prowler.providers.aws.services.eventbridge.eventbridge_service import (
EventBridge,
)
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
events_client = client("events", region_name=AWS_REGION_EU_WEST_1)
events_client.put_permission(
EventBusName="default",
Action="events:PutEvents",
Principal="*",
StatementId="test-statement",
)
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), mock.patch(
"prowler.providers.aws.services.eventbridge.eventbridge_bus_exposed.eventbridge_bus_exposed.eventbridge_client",
new=EventBridge(aws_provider),
):
from prowler.providers.aws.services.eventbridge.eventbridge_bus_exposed.eventbridge_bus_exposed import (
eventbridge_bus_exposed,
)
check = eventbridge_bus_exposed()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].status_extended == (
"EventBridge event bus default is exposed to everyone."
)
assert result[0].resource_id == "default"
assert (
result[0].resource_arn
== f"arn:aws:events:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:event-bus/default"
)
assert result[0].resource_tags == []
assert result[0].region == AWS_REGION_EU_WEST_1
@@ -0,0 +1,171 @@
from unittest.mock import patch
import botocore
from boto3 import client
from moto import mock_aws
from prowler.providers.aws.services.eventbridge.eventbridge_service import (
EventBridge,
Schema,
)
from tests.providers.aws.utils import (
AWS_ACCOUNT_NUMBER,
AWS_REGION_US_EAST_1,
set_mocked_aws_provider,
)
# Mocking Calls
make_api_call = botocore.client.BaseClient._make_api_call
def mock_make_api_call(self, operation_name, kwargs):
"""We have to mock every AWS API call using Boto3"""
if operation_name == "ListRegistries":
return {
"Registries": [
{
"RegistryArn": "arn:aws:schemas:us-east-1:123456789012:registry/test",
"RegistryName": "test",
"Tags": {"key1": "value1"},
},
]
}
if operation_name == "GetResourcePolicy":
return {
"Policy": '{"Version":"2012-10-17","Statement":[{"Sid":"AllowReadWrite","Effect":"Allow","Principal":{"AWS":"arn:aws:iam::123456789012:root"},"Action":"schemas:*","Resource":"arn:aws:schemas:eu-west-1:123456789012:registry/test"}]}',
"RevisionId": "1",
}
return make_api_call(self, operation_name, kwargs)
@patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call)
class Test_EventBridge_Service:
# Test EventBridge Service
@mock_aws
def test_service(self):
# EventBridge client for this test class
aws_provider = set_mocked_aws_provider()
eventbridge = EventBridge(aws_provider)
assert eventbridge.service == "events"
# Test EventBridge Client
@mock_aws
def test_client(self):
# EventBridge client for this test class
aws_provider = set_mocked_aws_provider()
eventbridge = EventBridge(aws_provider)
for client_ in eventbridge.regional_clients.values():
assert client_.__class__.__name__ == "EventBridge"
# Test EventBridge Session
@mock_aws
def test__get_session__(self):
# EventBridge client for this test class
aws_provider = set_mocked_aws_provider()
eventbridge = EventBridge(aws_provider)
assert eventbridge.session.__class__.__name__ == "Session"
# Test EventBridge Session
@mock_aws
def test_audited_account(self):
# EventBridge client for this test class
aws_provider = set_mocked_aws_provider()
eventbridge = EventBridge(aws_provider)
assert eventbridge.audited_account == AWS_ACCOUNT_NUMBER
# Test Schema Service
@mock_aws
def test_schema_service(self):
# Schema client for this test class
aws_provider = set_mocked_aws_provider()
schema = Schema(aws_provider)
assert schema.service == "schemas"
# Test Schema Client
@mock_aws
def test_schema_client(self):
# Schema client for this test class
aws_provider = set_mocked_aws_provider()
schema = Schema(aws_provider)
for client_ in schema.regional_clients.values():
assert client_.__class__.__name__ == "Schemas"
# Test Schema Session
@mock_aws
def test__schema_get_session__(self):
# Schema client for this test class
aws_provider = set_mocked_aws_provider()
schema = Schema(aws_provider)
assert schema.session.__class__.__name__ == "Session"
# Test Schema Session
@mock_aws
def test_schema_audited_account(self):
# Schema client for this test class
aws_provider = set_mocked_aws_provider()
schema = Schema(aws_provider)
assert schema.audited_account == AWS_ACCOUNT_NUMBER
# Test EventBridge Buses
@mock_aws
def test__list_event_buses__(self):
# EventBridge client for this test class
events_client = client("events", region_name=AWS_REGION_US_EAST_1)
events_client.create_event_bus(Name="test")
events_client.tag_resource(
ResourceARN=f"arn:aws:events:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:event-bus/test",
Tags=[{"Key": "key-1", "Value": "value-1"}],
)
events_client.put_permission(
EventBusName="test",
Action="events:PutEvents",
Principal="123456789012",
StatementId="test-statement",
)
aws_provider = set_mocked_aws_provider()
eventbridge = EventBridge(aws_provider)
assert len(eventbridge.buses) == 30 # 1 per region
for bus in eventbridge.buses.values():
if bus.name == "test":
assert (
bus.arn
== f"arn:aws:events:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:event-bus/test"
)
assert bus.name == "test"
assert bus.region == AWS_REGION_US_EAST_1
assert bus.policy == {
"Statement": [
{
"Action": "events:PutEvents",
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::123456789012:root"},
"Resource": "arn:aws:events:us-east-1:123456789012:event-bus/test",
"Sid": "test-statement",
}
],
"Version": "2012-10-17",
}
assert bus.tags == [{"Key": "key-1", "Value": "value-1"}]
# Test Schema Registries
def test__list_policies__(self):
aws_provider = set_mocked_aws_provider()
schema = Schema(aws_provider)
assert len(schema.registries) == 1
schema_arn = "arn:aws:schemas:us-east-1:123456789012:registry/test"
assert schema.registries[schema_arn].arn == schema_arn
assert schema.registries[schema_arn].name == "test"
assert schema.registries[schema_arn].tags == [{"key1": "value1"}]
assert schema.registries[schema_arn].policy == {
"Statement": [
{
"Action": "schemas:*",
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::123456789012:root"},
"Resource": "arn:aws:schemas:eu-west-1:123456789012:registry/test",
"Sid": "AllowReadWrite",
}
],
"Version": "2012-10-17",
}
@@ -0,0 +1,91 @@
from prowler.providers.aws.services.iam.lib.policy import (
is_policy_cross_account,
is_policy_public,
)
from tests.providers.aws.utils import AWS_ACCOUNT_NUMBER
class Test_Policy:
def test_is_policy_cross_account(self):
policy1 = {
"Statement": [
{
"Effect": "Allow",
"Principal": {"AWS": ["arn:aws:iam::123456789012:root", "*"]},
"Action": "s3:*",
"Resource": "arn:aws:s3:::example_bucket/*",
}
]
}
policy2 = {
"Statement": [
{
"Effect": "Allow",
"Principal": {"AWS": ["arn:aws:iam::123456789012:root"]},
"Action": "s3:*",
"Resource": "arn:aws:s3:::example_bucket/*",
}
]
}
policy3 = {
"Statement": [
{
"Effect": "Deny",
"Principal": {"AWS": ["arn:aws:iam::123456789012:root"]},
"Action": "s3:*",
"Resource": "arn:aws:s3:::example_bucket/*",
}
]
}
assert is_policy_cross_account(policy1, AWS_ACCOUNT_NUMBER)
assert not is_policy_cross_account(policy2, AWS_ACCOUNT_NUMBER)
assert not is_policy_cross_account(policy3, AWS_ACCOUNT_NUMBER)
def test_is_policy_public(self):
policy1 = {
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:*",
"Resource": "arn:aws:s3:::example_bucket/*",
}
]
}
policy2 = {
"Statement": [
{
"Effect": "Allow",
"Principal": {"AWS": "*"},
"Action": "s3:*",
"Resource": "arn:aws:s3:::example_bucket/*",
}
]
}
policy3 = {
"Statement": [
{
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::123456789012:root"},
"Action": "s3:*",
"Resource": "arn:aws:s3:::example_bucket/*",
}
]
}
policy4 = {
"Statement": [
{
"Effect": "Allow",
"Principal": {"AWS": "*"},
"Action": "s3:*",
"Resource": "arn:aws:s3:::example_bucket/*",
"Condition": {"IpAddress": {"aws:SourceIp": "192.0.2.0/24"}},
}
]
}
assert is_policy_public(policy1)
assert is_policy_public(policy2)
assert not is_policy_public(policy3)
assert not is_policy_public(policy4)