mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-03-28 19:10:11 +00:00
Compare commits
4 Commits
v5.22
...
fix/attack
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
622a399a2e | ||
|
|
f182471321 | ||
|
|
b5c0c6c96c | ||
|
|
79c1f8c2ec |
@@ -2,6 +2,12 @@
|
||||
|
||||
All notable changes to the **Prowler API** are documented in this file.
|
||||
|
||||
## [1.20.1] (UNRELEASED)
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
- Attack Paths: Fix `exposed_internet` never set on ELB/ELBv2 nodes due to cartography sync ordering and em-dash typo in analysis Cypher [(#10251)](https://github.com/prowler-cloud/prowler/pull/10251)
|
||||
|
||||
## [1.20.0] (Prowler v5.19.0)
|
||||
|
||||
### 🚀 Added
|
||||
|
||||
@@ -3,11 +3,14 @@
|
||||
|
||||
from typing import Any
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import aioboto3
|
||||
import boto3
|
||||
import neo4j
|
||||
|
||||
from cartography.config import Config as CartographyConfig
|
||||
from cartography.graph.job import GraphJob
|
||||
from cartography.intel import aws as cartography_aws
|
||||
from celery.utils.log import get_task_logger
|
||||
|
||||
@@ -17,6 +20,7 @@ from api.models import (
|
||||
)
|
||||
from prowler.providers.common.provider import Provider as ProwlerSDKProvider
|
||||
from tasks.jobs.attack_paths import db_utils, utils
|
||||
from tasks.jobs.attack_paths.cartography_overrides_aws_sync_order import AWS_SYNC_ORDER
|
||||
|
||||
logger = get_task_logger(__name__)
|
||||
|
||||
@@ -47,7 +51,11 @@ def start_aws_ingestion(
|
||||
|
||||
boto3_session = get_boto3_session(prowler_api_provider, prowler_sdk_provider)
|
||||
regions: list[str] = list(prowler_sdk_provider._enabled_regions)
|
||||
requested_syncs = list(cartography_aws.RESOURCE_FUNCTIONS.keys())
|
||||
# requested_syncs = list(cartography_aws.RESOURCE_FUNCTIONS.keys())
|
||||
# TODO: Uncomment the line above and remove the block below when Cartography fixes the RESOURCE_FUNCTIONS ordering
|
||||
requested_syncs = [
|
||||
s for s in AWS_SYNC_ORDER if s in cartography_aws.RESOURCE_FUNCTIONS
|
||||
]
|
||||
|
||||
sync_args = cartography_aws._build_aws_sync_kwargs(
|
||||
neo4j_session,
|
||||
@@ -144,6 +152,22 @@ def start_aws_ingestion(
|
||||
cartography_aws._perform_aws_analysis(
|
||||
requested_syncs, neo4j_session, common_job_parameters
|
||||
)
|
||||
|
||||
# TODO: Remove when Cartography fixes em dashes in `aws_ec2_asset_exposure.json`
|
||||
# Statements 5 and 6 use U+2014 (em dash) instead of U+002D (hyphen) in Cypher
|
||||
# arrows, so `exposed_internet` is never set on ELB/ELBv2 nodes
|
||||
logger.info(
|
||||
f"Running ELB asset exposure fix for AWS account {prowler_api_provider.uid}"
|
||||
)
|
||||
elb_fix_path = (
|
||||
Path(__file__).parent / "cartography_overrides_aws_ec2_asset_exposure.json"
|
||||
)
|
||||
GraphJob.run_from_json_file(
|
||||
elb_fix_path,
|
||||
neo4j_session,
|
||||
common_job_parameters,
|
||||
)
|
||||
|
||||
db_utils.update_attack_paths_scan_progress(attack_paths_scan, 94)
|
||||
|
||||
return failed_syncs
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"statements": [
|
||||
{
|
||||
"query": "MATCH (n:LoadBalancer) WHERE n.exposed_internet IS NOT NULL WITH n LIMIT $LIMIT_SIZE REMOVE n.exposed_internet, n.exposed_internet_type",
|
||||
"iterative": true,
|
||||
"iterationsize": 1000
|
||||
},
|
||||
{
|
||||
"query": "MATCH (n:LoadBalancerV2) WHERE n.exposed_internet IS NOT NULL WITH n LIMIT $LIMIT_SIZE REMOVE n.exposed_internet, n.exposed_internet_type",
|
||||
"iterative": true,
|
||||
"iterationsize": 1000
|
||||
},
|
||||
{
|
||||
"query": "MATCH (cidr:IpRange{range:'0.0.0.0/0'})-->(perm:IpPermissionInbound)-->(sg:EC2SecurityGroup)<-[:MEMBER_OF_EC2_SECURITY_GROUP]-(elbv2:LoadBalancerV2{scheme: 'internet-facing'})-->(listener:ELBV2Listener)\nWHERE perm.protocol = '-1' OR (listener.port>=perm.fromport AND listener.port<=perm.toport)\nSET elbv2.exposed_internet = true",
|
||||
"iterative": false
|
||||
},
|
||||
{
|
||||
"query": "MATCH (cidr:IpRange{range:'0.0.0.0/0'})-->(perm:IpPermissionInbound)-->(sg:EC2SecurityGroup)<-[:SOURCE_SECURITY_GROUP]-(elb:LoadBalancer{scheme: 'internet-facing'})-->(listener:ELBListener)\nWHERE perm.protocol = '-1' OR (listener.port>=perm.fromport AND listener.port<=perm.toport)\nSET elb.exposed_internet = true",
|
||||
"iterative": false
|
||||
},
|
||||
{
|
||||
"query": "MATCH (elb:LoadBalancer{exposed_internet: true})-[:EXPOSE]->(e:EC2Instance)\nWITH e\nWHERE (e.exposed_internet_type IS NULL) OR (NOT 'elb' IN e.exposed_internet_type)\nSET e.exposed_internet = true, e.exposed_internet_type = coalesce(e.exposed_internet_type, []) + 'elb'",
|
||||
"iterative": false
|
||||
},
|
||||
{
|
||||
"query": "MATCH (elbv2:LoadBalancerV2{exposed_internet: true})-[:EXPOSE]->(e:EC2Instance)\nWITH e\nWHERE (e.exposed_internet_type IS NULL) OR (NOT 'elbv2' IN e.exposed_internet_type)\nSET e.exposed_internet = true, e.exposed_internet_type = coalesce(e.exposed_internet_type, []) + 'elbv2'",
|
||||
"iterative": false
|
||||
}
|
||||
],
|
||||
"name": "ELB/ELBv2 asset internet exposure (cartography em-dash fix)"
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
# TODO: Remove this file when Cartography fixes the RESOURCE_FUNCTIONS ordering
|
||||
#
|
||||
# Explicit sync order for cartography AWS resource functions.
|
||||
# Based on cartography_aws.RESOURCE_FUNCTIONS (v0.129.0) with one change:
|
||||
# ec2:security_group moved before ec2:load_balancer, ec2:load_balancer_v2,
|
||||
# and ec2:network_interface. These resources use OPTIONAL MATCH to link to
|
||||
# EC2SecurityGroup nodes. On a fresh database the target nodes must exist
|
||||
# first, otherwise MEMBER_OF_EC2_SECURITY_GROUP edges are silently dropped
|
||||
# and exposed_internet is never set.
|
||||
|
||||
AWS_SYNC_ORDER: list[str] = [
|
||||
"iam",
|
||||
"iaminstanceprofiles",
|
||||
"s3",
|
||||
"kms",
|
||||
"dynamodb",
|
||||
"ec2:launch_templates",
|
||||
"ec2:autoscalinggroup",
|
||||
"ec2:instance",
|
||||
"ec2:images",
|
||||
"ec2:keypair",
|
||||
"ec2:security_group", # moved here (was after ec2:network_interface)
|
||||
"ec2:subnet",
|
||||
"ec2:load_balancer", # depends on ec2:security_group
|
||||
"ec2:load_balancer_v2", # depends on ec2:security_group
|
||||
"ec2:network_acls",
|
||||
"ec2:network_interface", # depends on ec2:security_group
|
||||
"ec2:tgw",
|
||||
"ec2:vpc",
|
||||
"ec2:vpc_endpoint",
|
||||
"ec2:route_table",
|
||||
"ec2:vpc_peering",
|
||||
"ec2:internet_gateway",
|
||||
"ec2:reserved_instances",
|
||||
"ec2:volumes",
|
||||
"ec2:snapshots",
|
||||
"ecr",
|
||||
"ecr:image_layers",
|
||||
"ecs",
|
||||
"eks",
|
||||
"elasticache",
|
||||
"elastic_ip_addresses",
|
||||
"emr",
|
||||
"lambda_function",
|
||||
"rds",
|
||||
"redshift",
|
||||
"route53",
|
||||
"elasticsearch",
|
||||
"permission_relationships",
|
||||
"resourcegroupstaggingapi",
|
||||
"apigateway",
|
||||
"apigatewayv2",
|
||||
"bedrock",
|
||||
"cloudfront",
|
||||
"secretsmanager",
|
||||
"securityhub",
|
||||
"s3accountpublicaccessblock",
|
||||
"sagemaker",
|
||||
"sns",
|
||||
"sqs",
|
||||
"ssm",
|
||||
"acm:certificate",
|
||||
"inspector",
|
||||
"config",
|
||||
"identitycenter",
|
||||
"cloudtrail",
|
||||
"cloudtrail_management_events",
|
||||
"cloudwatch",
|
||||
"efs",
|
||||
"guardduty",
|
||||
"codebuild",
|
||||
"cognito",
|
||||
"eventbridge",
|
||||
"glue",
|
||||
]
|
||||
@@ -3,9 +3,11 @@ from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock, call, patch
|
||||
|
||||
import pytest
|
||||
from cartography.intel import aws as cartography_aws
|
||||
from tasks.jobs.attack_paths import findings as findings_module
|
||||
from tasks.jobs.attack_paths import internet as internet_module
|
||||
from tasks.jobs.attack_paths import sync as sync_module
|
||||
from tasks.jobs.attack_paths.cartography_overrides_aws_sync_order import AWS_SYNC_ORDER
|
||||
from tasks.jobs.attack_paths.config import (
|
||||
get_deprecated_provider_resource_label,
|
||||
)
|
||||
@@ -1494,3 +1496,36 @@ class TestAttackPathsDbUtilsGraphDataReady:
|
||||
ap_scan_b.refresh_from_db()
|
||||
assert ap_scan_a.graph_data_ready is False
|
||||
assert ap_scan_b.graph_data_ready is True
|
||||
|
||||
|
||||
class TestAWSSyncOrder:
|
||||
"""Validate AWS_SYNC_ORDER covers all cartography resources and enforces SG ordering."""
|
||||
|
||||
def test_security_group_before_dependents(self):
|
||||
"""ec2:security_group must sync before resources that link to SGs via OPTIONAL MATCH."""
|
||||
sg_dependents = [
|
||||
"ec2:load_balancer",
|
||||
"ec2:load_balancer_v2",
|
||||
"ec2:network_interface",
|
||||
]
|
||||
sg_idx = AWS_SYNC_ORDER.index("ec2:security_group")
|
||||
for dep in sg_dependents:
|
||||
assert (
|
||||
AWS_SYNC_ORDER.index(dep) > sg_idx
|
||||
), f"{dep} must come after ec2:security_group in AWS_SYNC_ORDER"
|
||||
|
||||
def test_sync_order_covers_all_resource_functions(self):
|
||||
"""Every key in cartography RESOURCE_FUNCTIONS must appear in AWS_SYNC_ORDER."""
|
||||
missing = set(cartography_aws.RESOURCE_FUNCTIONS.keys()) - set(AWS_SYNC_ORDER)
|
||||
assert missing == set(), (
|
||||
f"AWS_SYNC_ORDER is missing cartography resources: {missing}. "
|
||||
"Add them to AWS_SYNC_ORDER in aws.py."
|
||||
)
|
||||
|
||||
def test_sync_order_has_no_stale_entries(self):
|
||||
"""Every key in AWS_SYNC_ORDER must exist in cartography RESOURCE_FUNCTIONS."""
|
||||
stale = set(AWS_SYNC_ORDER) - set(cartography_aws.RESOURCE_FUNCTIONS.keys())
|
||||
assert stale == set(), (
|
||||
f"AWS_SYNC_ORDER has entries not in cartography: {stale}. "
|
||||
"Remove them or update the cartography version."
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user