Repository: lyft/cartography

Issue:
IAM statement not being transformed properly
*Title*: IAM Statement is not being transformed properly

*Description*:
In  iam.py: _transform_policy_statements method if the original statement is not a list, it is transposed as a list:
    if not isinstance(statements, list):
        statements = [statements]

However the original dictionary value is not updated, in transform_policy_data:
statements = _transform_policy_statements(
                statements, policy_id,
            )

it should be:
policy_statement_map[policy_key] =  _transform_policy_statements(
                statements, policy_id,
            )


*To Reproduce*:
> inline policy statement value happens to be only this (note this is not a list but just a dict object) prior to transformation
{'Effect': 'Allow', 'Action': 'secretsmanager:GetSecretValue', 'Resource': 'arn:aws:secretsmanager:XXXXX:XXXXXXXX:secret:*'}

*Logs*:
 File "/var/cartography/cartography/intel/aws/iam.py", line 854, in load_policy_statements
statement_data_id=statement_data['id'],
 TypeError: string indices must be integers




Discussion:

Patch:
diff --git a/cartography/intel/aws/iam.py b/cartography/intel/aws/iam.py
index 6e013388..ff5f3574 100644
--- a/cartography/intel/aws/iam.py
+++ b/cartography/intel/aws/iam.py
@@ -540,11 +540,12 @@ def transform_policy_data(policy_map: Dict, policy_type: str) -> None:
     for principal_arn, policy_statement_map in policy_map.items():
         logger.debug(f"Transforming IAM {policy_type} policies for principal {principal_arn}")
         for policy_key, statements in policy_statement_map.items():
-            policy_id = transform_policy_id(principal_arn, policy_type, policy_key) \
-                if policy_type == PolicyType.inline.value else policy_key
-            statements = _transform_policy_statements(
-                statements, policy_id,
-            )
+            policy_id = transform_policy_id(
+                principal_arn,
+                policy_type,
+                policy_key,
+            ) if policy_type == PolicyType.inline.value else policy_key
+            policy_statement_map[policy_key] = _transform_policy_statements(statements, policy_id)
 
 
 def transform_policy_id(principal_arn: str, policy_type: str, name: str) -> str:


Test patch:
diff --git a/tests/unit/cartography/intel/aws/iam/test_iam.py b/tests/unit/cartography/intel/aws/iam/test_iam.py
index 5cd01ad0..7486a5f5 100644
--- a/tests/unit/cartography/intel/aws/iam/test_iam.py
+++ b/tests/unit/cartography/intel/aws/iam/test_iam.py
@@ -1,4 +1,6 @@
 from cartography.intel.aws import iam
+from cartography.intel.aws.iam import PolicyType
+from cartography.intel.aws.iam import transform_policy_data
 
 SINGLE_STATEMENT = {
     "Resource": "*",
@@ -94,3 +96,23 @@ def test__get_role_tags_no_tags(mocker):
     result = iam.get_role_tags(mock_session)
 
     assert result == []
+
+
+def test_transform_policy_data_correctly_creates_lists_of_statements():
+    # "pol-name" is a policy containing a single statement
+    # See https://github.com/lyft/cartography/issues/1102
+    pol_statement_map = {
+        'some-arn': {
+            'pol-name': {
+                'Effect': 'Allow',
+                'Action': 'secretsmanager:GetSecretValue',
+                'Resource': 'arn:aws:secretsmanager:XXXXX:XXXXXXXX',
+            },
+        },
+    }
+
+    # Act: call transform on the object
+    transform_policy_data(pol_statement_map, PolicyType.inline.value)
+
+    # Assert that we correctly converted the statement to a list
+    assert type(pol_statement_map['some-arn']['pol-name']) == list