mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-19 18:41:50 +00:00
feat(eventbridge): add EventBridge checks (#4020)
This commit is contained in:
+30
@@ -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": ""
|
||||
}
|
||||
+29
@@ -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
|
||||
+30
@@ -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": ""
|
||||
}
|
||||
+27
@@ -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())
|
||||
+30
@@ -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": ""
|
||||
}
|
||||
+23
@@ -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
|
||||
+170
@@ -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
|
||||
+170
@@ -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)
|
||||
Reference in New Issue
Block a user