import json from typing import Any, Dict, List, Tuple from prowler.lib.logger import logger from lib.models import ResourceEdge, ResourceNode def _parse_trust_principals(assume_role_policy: Any) -> List[str]: """ Return a flat list of principal strings from an IAM assume-role policy document. The policy may be a dict already or a JSON string. """ if not assume_role_policy: return [] if isinstance(assume_role_policy, str): try: assume_role_policy = json.loads(assume_role_policy) except (json.JSONDecodeError, ValueError): return [] principals = [] for statement in assume_role_policy.get("Statement", []): principal = statement.get("Principal", {}) if isinstance(principal, str): principals.append(principal) elif isinstance(principal, dict): for v in principal.values(): if isinstance(v, list): principals.extend(v) else: principals.append(v) elif isinstance(principal, list): principals.extend(principal) return principals def extract(client) -> Tuple[List[ResourceNode], List[ResourceEdge]]: """ Extract IAM role nodes and their trust-relationship edges. Edges produced: - trusted-principal → role [iam] (who can assume this role) """ nodes: List[ResourceNode] = [] edges: List[ResourceEdge] = [] for role in client.roles: props: Dict[str, Any] = { "path": getattr(role, "path", None), "create_date": str(getattr(role, "create_date", "") or ""), } nodes.append( ResourceNode( id=role.arn, type="iam_role", name=role.name, service="iam", region="global", account_id=client.audited_account, properties={k: v for k, v in props.items() if v}, ) ) # Trust-relationship edges: principal → role (principal CAN assume role) try: for principal in _parse_trust_principals(role.assume_role_policy): if principal and principal != "*": edges.append( ResourceEdge( source_id=principal, target_id=role.arn, edge_type="iam", label="can-assume", ) ) except Exception as e: logger.debug( f"inventory iam_extractor: could not parse trust policy for {role.arn}: {e}" ) return nodes, edges