""" graph_builder.py ---------------- Builds a ConnectivityGraph by reading already-loaded AWS service clients from sys.modules. Only services that were actually scanned (i.e. whose client module is already imported) contribute nodes and edges. Unknown / unloaded services are silently skipped, so the output degrades gracefully when only a subset of checks has been run. """ import sys from typing import Tuple from prowler.lib.logger import logger from lib.models import ConnectivityGraph # Registry: (sys.modules key, attribute name inside that module, extractor module path) _SERVICE_REGISTRY: Tuple[Tuple[str, str, str], ...] = ( ( "prowler.providers.aws.services.awslambda.awslambda_client", "awslambda_client", "lib.extractors.lambda_extractor", ), ( "prowler.providers.aws.services.ec2.ec2_client", "ec2_client", "lib.extractors.ec2_extractor", ), ( "prowler.providers.aws.services.vpc.vpc_client", "vpc_client", "lib.extractors.vpc_extractor", ), ( "prowler.providers.aws.services.rds.rds_client", "rds_client", "lib.extractors.rds_extractor", ), ( "prowler.providers.aws.services.elbv2.elbv2_client", "elbv2_client", "lib.extractors.elbv2_extractor", ), ( "prowler.providers.aws.services.s3.s3_client", "s3_client", "lib.extractors.s3_extractor", ), ( "prowler.providers.aws.services.iam.iam_client", "iam_client", "lib.extractors.iam_extractor", ), ) def build_graph() -> ConnectivityGraph: """ Iterate over every registered service, check whether its client module is already loaded, and call the corresponding extractor. Returns a ConnectivityGraph with all discovered nodes and edges. Duplicate node IDs are silently deduplicated (first occurrence wins). """ graph = ConnectivityGraph() seen_node_ids: set = set() for client_module_key, client_attr, extractor_module_key in _SERVICE_REGISTRY: client_module = sys.modules.get(client_module_key) if client_module is None: continue service_client = getattr(client_module, client_attr, None) if service_client is None: continue extractor_module = sys.modules.get(extractor_module_key) if extractor_module is None: try: import importlib extractor_module = importlib.import_module(extractor_module_key) except ImportError as e: logger.debug( f"inventory graph_builder: cannot import extractor {extractor_module_key}: {e}" ) continue try: nodes, edges = extractor_module.extract(service_client) except Exception as e: logger.error( f"inventory graph_builder: extractor {extractor_module_key} failed: " f"{e.__class__.__name__}[{e.__traceback__.tb_lineno}]: {e}" ) continue for node in nodes: if node.id not in seen_node_ids: graph.add_node(node) seen_node_ids.add(node.id) for edge in edges: graph.add_edge(edge) return graph