cartography 0.109.0rc1__py3-none-any.whl → 0.110.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of cartography might be problematic. Click here for more details.

Files changed (78) hide show
  1. cartography/_version.py +2 -2
  2. cartography/cli.py +14 -0
  3. cartography/config.py +4 -0
  4. cartography/data/indexes.cypher +0 -15
  5. cartography/data/jobs/analysis/aws_ec2_keypair_analysis.json +2 -2
  6. cartography/intel/aws/cloudtrail_management_events.py +21 -0
  7. cartography/intel/aws/cognito.py +201 -0
  8. cartography/intel/aws/ecs.py +7 -1
  9. cartography/intel/aws/eventbridge.py +91 -0
  10. cartography/intel/aws/glue.py +181 -0
  11. cartography/intel/aws/identitycenter.py +71 -23
  12. cartography/intel/aws/kms.py +173 -201
  13. cartography/intel/aws/lambda_function.py +206 -190
  14. cartography/intel/aws/rds.py +335 -445
  15. cartography/intel/aws/resources.py +6 -0
  16. cartography/intel/aws/route53.py +336 -332
  17. cartography/intel/aws/s3.py +104 -0
  18. cartography/intel/github/__init__.py +21 -25
  19. cartography/intel/github/repos.py +4 -36
  20. cartography/intel/kubernetes/__init__.py +4 -0
  21. cartography/intel/kubernetes/rbac.py +464 -0
  22. cartography/intel/kubernetes/util.py +17 -0
  23. cartography/intel/trivy/__init__.py +73 -13
  24. cartography/intel/trivy/scanner.py +115 -92
  25. cartography/models/aws/cognito/__init__.py +0 -0
  26. cartography/models/aws/cognito/identity_pool.py +70 -0
  27. cartography/models/aws/cognito/user_pool.py +47 -0
  28. cartography/models/aws/ec2/security_groups.py +1 -1
  29. cartography/models/aws/ecs/services.py +17 -0
  30. cartography/models/aws/ecs/tasks.py +1 -0
  31. cartography/models/aws/eventbridge/__init__.py +0 -0
  32. cartography/models/aws/eventbridge/rule.py +77 -0
  33. cartography/models/aws/glue/__init__.py +0 -0
  34. cartography/models/aws/glue/connection.py +51 -0
  35. cartography/models/aws/glue/job.py +69 -0
  36. cartography/models/aws/identitycenter/awspermissionset.py +44 -0
  37. cartography/models/aws/kms/__init__.py +0 -0
  38. cartography/models/aws/kms/aliases.py +86 -0
  39. cartography/models/aws/kms/grants.py +65 -0
  40. cartography/models/aws/kms/keys.py +88 -0
  41. cartography/models/aws/lambda_function/__init__.py +0 -0
  42. cartography/models/aws/lambda_function/alias.py +74 -0
  43. cartography/models/aws/lambda_function/event_source_mapping.py +88 -0
  44. cartography/models/aws/lambda_function/lambda_function.py +89 -0
  45. cartography/models/aws/lambda_function/layer.py +72 -0
  46. cartography/models/aws/rds/__init__.py +0 -0
  47. cartography/models/aws/rds/cluster.py +89 -0
  48. cartography/models/aws/rds/event_subscription.py +146 -0
  49. cartography/models/aws/rds/instance.py +154 -0
  50. cartography/models/aws/rds/snapshot.py +108 -0
  51. cartography/models/aws/rds/subnet_group.py +101 -0
  52. cartography/models/aws/route53/__init__.py +0 -0
  53. cartography/models/aws/route53/dnsrecord.py +235 -0
  54. cartography/models/aws/route53/nameserver.py +63 -0
  55. cartography/models/aws/route53/subzone.py +40 -0
  56. cartography/models/aws/route53/zone.py +47 -0
  57. cartography/models/github/dependencies.py +1 -2
  58. cartography/models/kubernetes/clusterrolebindings.py +98 -0
  59. cartography/models/kubernetes/clusterroles.py +52 -0
  60. cartography/models/kubernetes/rolebindings.py +119 -0
  61. cartography/models/kubernetes/roles.py +76 -0
  62. cartography/models/kubernetes/serviceaccounts.py +77 -0
  63. cartography/models/snipeit/asset.py +1 -0
  64. cartography/util.py +8 -1
  65. {cartography-0.109.0rc1.dist-info → cartography-0.110.0.dist-info}/METADATA +3 -3
  66. {cartography-0.109.0rc1.dist-info → cartography-0.110.0.dist-info}/RECORD +71 -41
  67. cartography/data/jobs/cleanup/aws_dns_cleanup.json +0 -65
  68. cartography/data/jobs/cleanup/aws_import_identity_center_cleanup.json +0 -16
  69. cartography/data/jobs/cleanup/aws_import_lambda_cleanup.json +0 -50
  70. cartography/data/jobs/cleanup/aws_import_rds_clusters_cleanup.json +0 -23
  71. cartography/data/jobs/cleanup/aws_import_rds_instances_cleanup.json +0 -47
  72. cartography/data/jobs/cleanup/aws_import_rds_snapshots_cleanup.json +0 -23
  73. cartography/data/jobs/cleanup/aws_kms_details.json +0 -10
  74. /cartography/data/jobs/{analysis → scoped_analysis}/aws_s3acl_analysis.json +0 -0
  75. {cartography-0.109.0rc1.dist-info → cartography-0.110.0.dist-info}/WHEEL +0 -0
  76. {cartography-0.109.0rc1.dist-info → cartography-0.110.0.dist-info}/entry_points.txt +0 -0
  77. {cartography-0.109.0rc1.dist-info → cartography-0.110.0.dist-info}/licenses/LICENSE +0 -0
  78. {cartography-0.109.0rc1.dist-info → cartography-0.110.0.dist-info}/top_level.txt +0 -0
cartography/_version.py CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.109.0rc1'
21
- __version_tuple__ = version_tuple = (0, 109, 0, 'rc1')
20
+ __version__ = version = '0.110.0'
21
+ __version_tuple__ = version_tuple = (0, 110, 0)
cartography/cli.py CHANGED
@@ -700,6 +700,15 @@ class CLI:
700
700
  "Required if you are using the Trivy module. Ignored otherwise."
701
701
  ),
702
702
  )
703
+ parser.add_argument(
704
+ "--trivy-results-dir",
705
+ type=str,
706
+ default=None,
707
+ help=(
708
+ "Path to a directory containing Trivy JSON results on disk. "
709
+ "Required if you are using the Trivy module with local results."
710
+ ),
711
+ )
703
712
  parser.add_argument(
704
713
  "--scaleway-org",
705
714
  type=str,
@@ -1089,6 +1098,9 @@ class CLI:
1089
1098
  if config.trivy_s3_prefix:
1090
1099
  logger.debug(f"Trivy S3 prefix: {config.trivy_s3_prefix}")
1091
1100
 
1101
+ if config.trivy_results_dir:
1102
+ logger.debug(f"Trivy results dir: {config.trivy_results_dir}")
1103
+
1092
1104
  # Scaleway config
1093
1105
  if config.scaleway_secret_key_env_var:
1094
1106
  logger.debug(
@@ -1118,6 +1130,8 @@ class CLI:
1118
1130
  config.sentinelone_api_token = os.environ.get(
1119
1131
  config.sentinelone_api_token_env_var
1120
1132
  )
1133
+ else:
1134
+ config.sentinelone_api_token = None
1121
1135
 
1122
1136
  # Run cartography
1123
1137
  try:
cartography/config.py CHANGED
@@ -152,6 +152,8 @@ class Config:
152
152
  :param trivy_s3_bucket: The S3 bucket name containing Trivy scan results. Optional.
153
153
  :type trivy_s3_prefix: str
154
154
  :param trivy_s3_prefix: The S3 prefix path containing Trivy scan results. Optional.
155
+ :type trivy_results_dir: str
156
+ :param trivy_results_dir: Local directory containing Trivy scan results. Optional.
155
157
  :type scaleway_access_key: str
156
158
  :param scaleway_access_key: Scaleway access key. Optional.
157
159
  :type scaleway_secret_key: str
@@ -243,6 +245,7 @@ class Config:
243
245
  airbyte_api_url=None,
244
246
  trivy_s3_bucket=None,
245
247
  trivy_s3_prefix=None,
248
+ trivy_results_dir=None,
246
249
  scaleway_access_key=None,
247
250
  scaleway_secret_key=None,
248
251
  scaleway_org=None,
@@ -327,6 +330,7 @@ class Config:
327
330
  self.airbyte_api_url = airbyte_api_url
328
331
  self.trivy_s3_bucket = trivy_s3_bucket
329
332
  self.trivy_s3_prefix = trivy_s3_prefix
333
+ self.trivy_results_dir = trivy_results_dir
330
334
  self.scaleway_access_key = scaleway_access_key
331
335
  self.scaleway_secret_key = scaleway_secret_key
332
336
  self.scaleway_org = scaleway_org
@@ -29,14 +29,6 @@ CREATE INDEX IF NOT EXISTS FOR (n:AWSIpv4CidrBlock) ON (n.id);
29
29
  CREATE INDEX IF NOT EXISTS FOR (n:AWSIpv4CidrBlock) ON (n.lastupdated);
30
30
  CREATE INDEX IF NOT EXISTS FOR (n:AWSIpv6CidrBlock) ON (n.id);
31
31
  CREATE INDEX IF NOT EXISTS FOR (n:AWSIpv6CidrBlock) ON (n.lastupdated);
32
- CREATE INDEX IF NOT EXISTS FOR (n:AWSLambda) ON (n.id);
33
- CREATE INDEX IF NOT EXISTS FOR (n:AWSLambda) ON (n.lastupdated);
34
- CREATE INDEX IF NOT EXISTS FOR (n:AWSLambdaEventSourceMapping) ON (n.id);
35
- CREATE INDEX IF NOT EXISTS FOR (n:AWSLambdaEventSourceMapping) ON (n.lastupdated);
36
- CREATE INDEX IF NOT EXISTS FOR (n:AWSLambdaFunctionAlias) ON (n.id);
37
- CREATE INDEX IF NOT EXISTS FOR (n:AWSLambdaFunctionAlias) ON (n.lastupdated);
38
- CREATE INDEX IF NOT EXISTS FOR (n:AWSLambdaLayer) ON (n.id);
39
- CREATE INDEX IF NOT EXISTS FOR (n:AWSLambdaLayer) ON (n.lastupdated);
40
32
  CREATE INDEX IF NOT EXISTS FOR (n:AWSPeeringConnection) ON (n.id);
41
33
  CREATE INDEX IF NOT EXISTS FOR (n:AWSPeeringConnection) ON (n.lastupdated);
42
34
  CREATE INDEX IF NOT EXISTS FOR (n:AWSPolicy) ON (n.id);
@@ -158,13 +150,6 @@ CREATE INDEX IF NOT EXISTS FOR (n:IpRange) ON (n.id);
158
150
  CREATE INDEX IF NOT EXISTS FOR (n:IpRange) ON (n.lastupdated);
159
151
  CREATE INDEX IF NOT EXISTS FOR (n:JamfComputerGroup) ON (n.id);
160
152
  CREATE INDEX IF NOT EXISTS FOR (n:JamfComputerGroup) ON (n.lastupdated);
161
- CREATE INDEX IF NOT EXISTS FOR (n:KMSKey) ON (n.id);
162
- CREATE INDEX IF NOT EXISTS FOR (n:KMSKey) ON (n.arn);
163
- CREATE INDEX IF NOT EXISTS FOR (n:KMSKey) ON (n.lastupdated);
164
- CREATE INDEX IF NOT EXISTS FOR (n:KMSAlias) ON (n.id);
165
- CREATE INDEX IF NOT EXISTS FOR (n:KMSAlias) ON (n.lastupdated);
166
- CREATE INDEX IF NOT EXISTS FOR (n:KMSGrant) ON (n.id);
167
- CREATE INDEX IF NOT EXISTS FOR (n:KMSGrant) ON (n.lastupdated);
168
153
  CREATE INDEX IF NOT EXISTS FOR (n:LaunchConfiguration) ON (n.id);
169
154
  CREATE INDEX IF NOT EXISTS FOR (n:LaunchConfiguration) ON (n.name);
170
155
  CREATE INDEX IF NOT EXISTS FOR (n:LaunchConfiguration) ON (n.lastupdated);
@@ -22,8 +22,8 @@
22
22
  "iterative": false
23
23
  },
24
24
  {
25
- "__comment__": "Attach EC2KeyPairs with matching fingerprints to eachother and set duplicate_keyfingerprint = True",
26
- "query": "MATCH (k1:EC2KeyPair), (k2:EC2KeyPair) WHERE k1.id <> k2.id AND k1.keyfingerprint = k2.keyfingerprint SET k1.duplicate_keyfingerprint = True, k2.duplicate_keyfingerprint = True MERGE (k1)-[r:MATCHING_FINGERPRINT]-(k2) ON CREATE SET r.firstseen = $UPDATE_TAG SET r.lastupdated = $UPDATE_TAG return COUNT(*) as TotalCompleted",
25
+ "__comment__": "Attach EC2KeyPairs with matching fingerprints to each other and set duplicate_keyfingerprint = True. Use id(k1) < id(k2) to avoid Cartesian product warning and ensure O(1) comparison.",
26
+ "query": "MATCH (k1:EC2KeyPair) MATCH (k2:EC2KeyPair) WHERE id(k1) < id(k2) AND k1.keyfingerprint = k2.keyfingerprint SET k1.duplicate_keyfingerprint = True, k2.duplicate_keyfingerprint = True MERGE (k1)-[r:MATCHING_FINGERPRINT]-(k2) ON CREATE SET r.firstseen = $UPDATE_TAG SET r.lastupdated = $UPDATE_TAG RETURN COUNT(*) as TotalCompleted",
27
27
  "iterative": false
28
28
  }
29
29
  ]
@@ -223,6 +223,13 @@ def transform_assume_role_events_to_role_assumptions(
223
223
 
224
224
  cloudtrail_event = json.loads(event["CloudTrailEvent"])
225
225
 
226
+ # Skip events with null requestParameters since we can't extract roleArn
227
+ if not cloudtrail_event.get("requestParameters"):
228
+ logger.debug(
229
+ f"Skipping CloudTrail AssumeRole event due to missing requestParameters. Event: {event.get('EventId', 'unknown')}"
230
+ )
231
+ continue
232
+
226
233
  if cloudtrail_event.get("userIdentity", {}).get("arn"):
227
234
  source_principal = cloudtrail_event["userIdentity"]["arn"]
228
235
  destination_principal = cloudtrail_event["requestParameters"]["roleArn"]
@@ -298,6 +305,13 @@ def transform_saml_role_events_to_role_assumptions(
298
305
 
299
306
  cloudtrail_event = json.loads(event["CloudTrailEvent"])
300
307
 
308
+ # Skip events with null requestParameters since we can't extract roleArn
309
+ if not cloudtrail_event.get("requestParameters"):
310
+ logger.debug(
311
+ f"Skipping CloudTrail AssumeRoleWithSAML event due to missing requestParameters. Event: {event.get('EventId', 'unknown')}"
312
+ )
313
+ continue
314
+
301
315
  response_elements = cloudtrail_event.get("responseElements", {})
302
316
  assumed_role_user = response_elements.get("assumedRoleUser", {})
303
317
 
@@ -370,6 +384,13 @@ def transform_web_identity_role_events_to_role_assumptions(
370
384
 
371
385
  cloudtrail_event = json.loads(event["CloudTrailEvent"])
372
386
 
387
+ # Skip events with null requestParameters since we can't extract roleArn
388
+ if not cloudtrail_event.get("requestParameters"):
389
+ logger.debug(
390
+ f"Skipping CloudTrail AssumeRoleWithWebIdentity event due to missing requestParameters. Event: {event.get('EventId', 'unknown')}"
391
+ )
392
+ continue
393
+
373
394
  user_identity = cloudtrail_event.get("userIdentity", {})
374
395
 
375
396
  if user_identity.get("type") == "WebIdentityUser" and user_identity.get(
@@ -0,0 +1,201 @@
1
+ import logging
2
+ from typing import Any
3
+ from typing import Dict
4
+ from typing import List
5
+
6
+ import boto3
7
+ import neo4j
8
+
9
+ from cartography.client.core.tx import load
10
+ from cartography.graph.job import GraphJob
11
+ from cartography.intel.aws.ec2.util import get_botocore_config
12
+ from cartography.models.aws.cognito.identity_pool import CognitoIdentityPoolSchema
13
+ from cartography.models.aws.cognito.user_pool import CognitoUserPoolSchema
14
+ from cartography.util import aws_handle_regions
15
+ from cartography.util import timeit
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ @timeit
21
+ @aws_handle_regions
22
+ def get_identity_pools(
23
+ boto3_session: boto3.Session, region: str
24
+ ) -> List[Dict[str, Any]]:
25
+ client = boto3_session.client(
26
+ "cognito-identity", region_name=region, config=get_botocore_config()
27
+ )
28
+ paginator = client.get_paginator("list_identity_pools")
29
+
30
+ all_identity_pools = []
31
+
32
+ for page in paginator.paginate(MaxResults=50):
33
+ identity_pools = page.get("IdentityPools", [])
34
+ all_identity_pools.extend(identity_pools)
35
+ return all_identity_pools
36
+
37
+
38
+ @timeit
39
+ @aws_handle_regions
40
+ def get_identity_pool_roles(
41
+ boto3_session: boto3.Session, identity_pools: List[Dict[str, Any]], region: str
42
+ ) -> List[Dict[str, Any]]:
43
+ client = boto3_session.client(
44
+ "cognito-identity", region_name=region, config=get_botocore_config()
45
+ )
46
+ all_identity_pool_details = []
47
+ for identity_pool in identity_pools:
48
+ response = client.get_identity_pool_roles(
49
+ IdentityPoolId=identity_pool["IdentityPoolId"]
50
+ )
51
+ all_identity_pool_details.append(response)
52
+ return all_identity_pool_details
53
+
54
+
55
+ @timeit
56
+ @aws_handle_regions
57
+ def get_user_pools(boto3_session: boto3.Session, region: str) -> List[Dict[str, Any]]:
58
+ client = boto3_session.client(
59
+ "cognito-idp", region_name=region, config=get_botocore_config()
60
+ )
61
+ paginator = client.get_paginator("list_user_pools")
62
+ all_user_pools = []
63
+
64
+ for page in paginator.paginate(MaxResults=50):
65
+ user_pools = page.get("UserPools", [])
66
+ all_user_pools.extend(user_pools)
67
+ return all_user_pools
68
+
69
+
70
+ def transform_identity_pools(
71
+ identity_pools: List[Dict[str, Any]], region: str
72
+ ) -> List[Dict[str, Any]]:
73
+ transformed_identity_pools = []
74
+ for pool in identity_pools:
75
+ transformed_pool = {
76
+ "IdentityPoolId": pool["IdentityPoolId"],
77
+ "Region": region,
78
+ "Roles": list(pool.get("Roles", {}).values()),
79
+ }
80
+ transformed_identity_pools.append(transformed_pool)
81
+ return transformed_identity_pools
82
+
83
+
84
+ def transform_user_pools(
85
+ user_pools: List[Dict[str, Any]], region: str
86
+ ) -> List[Dict[str, Any]]:
87
+ transformed_user_pools = []
88
+ for pool in user_pools:
89
+ transformed_pool = {
90
+ "Id": pool["Id"],
91
+ "Region": region,
92
+ "Name": pool["Name"],
93
+ "Status": pool.get("Status"),
94
+ }
95
+ transformed_user_pools.append(transformed_pool)
96
+ return transformed_user_pools
97
+
98
+
99
+ @timeit
100
+ def load_identity_pools(
101
+ neo4j_session: neo4j.Session,
102
+ data: List[Dict[str, Any]],
103
+ region: str,
104
+ current_aws_account_id: str,
105
+ aws_update_tag: int,
106
+ ) -> None:
107
+ logger.info(
108
+ f"Loading Cognito Identity Pools {len(data)} for region '{region}' into graph.",
109
+ )
110
+ load(
111
+ neo4j_session,
112
+ CognitoIdentityPoolSchema(),
113
+ data,
114
+ lastupdated=aws_update_tag,
115
+ Region=region,
116
+ AWS_ID=current_aws_account_id,
117
+ )
118
+
119
+
120
+ @timeit
121
+ def load_user_pools(
122
+ neo4j_session: neo4j.Session,
123
+ data: List[Dict[str, Any]],
124
+ region: str,
125
+ current_aws_account_id: str,
126
+ aws_update_tag: int,
127
+ ) -> None:
128
+ logger.info(
129
+ f"Loading Cognito User Pools {len(data)} for region '{region}' into graph.",
130
+ )
131
+ load(
132
+ neo4j_session,
133
+ CognitoUserPoolSchema(),
134
+ data,
135
+ lastupdated=aws_update_tag,
136
+ Region=region,
137
+ AWS_ID=current_aws_account_id,
138
+ )
139
+
140
+
141
+ @timeit
142
+ def cleanup(
143
+ neo4j_session: neo4j.Session,
144
+ common_job_parameters: Dict[str, Any],
145
+ ) -> None:
146
+ logger.debug("Running Efs cleanup job.")
147
+ GraphJob.from_node_schema(CognitoIdentityPoolSchema(), common_job_parameters).run(
148
+ neo4j_session
149
+ )
150
+ GraphJob.from_node_schema(CognitoUserPoolSchema(), common_job_parameters).run(
151
+ neo4j_session
152
+ )
153
+
154
+
155
+ @timeit
156
+ def sync(
157
+ neo4j_session: neo4j.Session,
158
+ boto3_session: boto3.session.Session,
159
+ regions: List[str],
160
+ current_aws_account_id: str,
161
+ update_tag: int,
162
+ common_job_parameters: Dict[str, Any],
163
+ ) -> None:
164
+ for region in regions:
165
+ logger.info(
166
+ f"Syncing Cognito Identity Pools for region '{region}' in account '{current_aws_account_id}'.",
167
+ )
168
+
169
+ identity_pools = get_identity_pools(boto3_session, region)
170
+ if not identity_pools:
171
+ logger.info(
172
+ f"No Cognito Identity Pools found in region '{region}'. Skipping sync."
173
+ )
174
+ else:
175
+ identity_pool_roles = get_identity_pool_roles(
176
+ boto3_session, identity_pools, region
177
+ )
178
+ transformed_identity_pools = transform_identity_pools(
179
+ identity_pool_roles, region
180
+ )
181
+
182
+ load_identity_pools(
183
+ neo4j_session,
184
+ transformed_identity_pools,
185
+ region,
186
+ current_aws_account_id,
187
+ update_tag,
188
+ )
189
+
190
+ user_pools = get_user_pools(boto3_session, region)
191
+ transformed_user_pools = transform_user_pools(user_pools, region)
192
+
193
+ load_user_pools(
194
+ neo4j_session,
195
+ transformed_user_pools,
196
+ region,
197
+ current_aws_account_id,
198
+ update_tag,
199
+ )
200
+
201
+ cleanup(neo4j_session, common_job_parameters)
@@ -171,9 +171,15 @@ def _get_containers_from_tasks(tasks: list[dict[str, Any]]) -> list[dict[str, An
171
171
 
172
172
  def transform_ecs_tasks(tasks: list[dict[str, Any]]) -> list[dict[str, Any]]:
173
173
  """
174
- Extract network interface ID from task attachments.
174
+ Extract network interface ID from task attachments and service name from group.
175
175
  """
176
176
  for task in tasks:
177
+ # Extract serviceName from group field
178
+ group = task.get("group")
179
+ if group and group.startswith("service:"):
180
+ task["serviceName"] = group.split("service:", 1)[1]
181
+
182
+ # Extract network interface ID from task attachments
177
183
  for attachment in task.get("attachments", []):
178
184
  if attachment.get("type") == "ElasticNetworkInterface":
179
185
  details = attachment.get("details", [])
@@ -0,0 +1,91 @@
1
+ import logging
2
+ from typing import Any
3
+ from typing import Dict
4
+ from typing import List
5
+
6
+ import boto3
7
+ import neo4j
8
+
9
+ from cartography.client.core.tx import load
10
+ from cartography.graph.job import GraphJob
11
+ from cartography.intel.aws.ec2.util import get_botocore_config
12
+ from cartography.models.aws.eventbridge.rule import EventBridgeRuleSchema
13
+ from cartography.util import aws_handle_regions
14
+ from cartography.util import timeit
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ @timeit
20
+ @aws_handle_regions
21
+ def get_eventbridge_rules(
22
+ boto3_session: boto3.Session, region: str
23
+ ) -> List[Dict[str, Any]]:
24
+ client = boto3_session.client(
25
+ "events", region_name=region, config=get_botocore_config()
26
+ )
27
+ paginator = client.get_paginator("list_rules")
28
+ rules = []
29
+
30
+ for page in paginator.paginate():
31
+ rules.extend(page.get("Rules", []))
32
+
33
+ return rules
34
+
35
+
36
+ @timeit
37
+ def load_eventbridge_rules(
38
+ neo4j_session: neo4j.Session,
39
+ data: List[Dict[str, Any]],
40
+ region: str,
41
+ current_aws_account_id: str,
42
+ aws_update_tag: int,
43
+ ) -> None:
44
+ logger.info(
45
+ f"Loading EventBridge {len(data)} rules for region '{region}' into graph.",
46
+ )
47
+ load(
48
+ neo4j_session,
49
+ EventBridgeRuleSchema(),
50
+ data,
51
+ lastupdated=aws_update_tag,
52
+ Region=region,
53
+ AWS_ID=current_aws_account_id,
54
+ )
55
+
56
+
57
+ @timeit
58
+ def cleanup(
59
+ neo4j_session: neo4j.Session,
60
+ common_job_parameters: Dict[str, Any],
61
+ ) -> None:
62
+ logger.debug("Running EventBridge cleanup job.")
63
+ GraphJob.from_node_schema(EventBridgeRuleSchema(), common_job_parameters).run(
64
+ neo4j_session
65
+ )
66
+
67
+
68
+ @timeit
69
+ def sync(
70
+ neo4j_session: neo4j.Session,
71
+ boto3_session: boto3.session.Session,
72
+ regions: List[str],
73
+ current_aws_account_id: str,
74
+ update_tag: int,
75
+ common_job_parameters: Dict[str, Any],
76
+ ) -> None:
77
+ for region in regions:
78
+ logger.info(
79
+ f"Syncing EventBridge for region '{region}' in account '{current_aws_account_id}'.",
80
+ )
81
+
82
+ rules = get_eventbridge_rules(boto3_session, region)
83
+ load_eventbridge_rules(
84
+ neo4j_session,
85
+ rules,
86
+ region,
87
+ current_aws_account_id,
88
+ update_tag,
89
+ )
90
+
91
+ cleanup(neo4j_session, common_job_parameters)
@@ -0,0 +1,181 @@
1
+ import logging
2
+ from typing import Any
3
+ from typing import Dict
4
+ from typing import List
5
+
6
+ import boto3
7
+ import neo4j
8
+
9
+ from cartography.client.core.tx import load
10
+ from cartography.graph.job import GraphJob
11
+ from cartography.intel.aws.ec2.util import get_botocore_config
12
+ from cartography.models.aws.glue.connection import GlueConnectionSchema
13
+ from cartography.models.aws.glue.job import GlueJobSchema
14
+ from cartography.util import aws_handle_regions
15
+ from cartography.util import timeit
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ @timeit
21
+ @aws_handle_regions
22
+ def get_glue_connections(
23
+ boto3_session: boto3.Session, region: str
24
+ ) -> List[Dict[str, Any]]:
25
+ client = boto3_session.client(
26
+ "glue", region_name=region, config=get_botocore_config()
27
+ )
28
+ paginator = client.get_paginator("get_connections")
29
+ connections = []
30
+ for page in paginator.paginate():
31
+ connections.extend(page.get("ConnectionList", []))
32
+
33
+ return connections
34
+
35
+
36
+ @timeit
37
+ @aws_handle_regions
38
+ def get_glue_jobs(boto3_session: boto3.Session, region: str) -> List[Dict[str, Any]]:
39
+ client = boto3_session.client(
40
+ "glue", region_name=region, config=get_botocore_config()
41
+ )
42
+ paginator = client.get_paginator("get_jobs")
43
+ jobs = []
44
+ for page in paginator.paginate():
45
+ jobs.extend(page.get("Jobs", []))
46
+ return jobs
47
+
48
+
49
+ def transform_glue_job(jobs: List[Dict[str, Any]], region: str) -> List[Dict[str, Any]]:
50
+ """
51
+ Transform Glue job data for ingestion
52
+ """
53
+ transformed_jobs = []
54
+ for job in jobs:
55
+ transformed_job = {
56
+ "Name": job["Name"],
57
+ "ProfileName": job.get("ProfileName"),
58
+ "JobMode": job.get("JobMode"),
59
+ "Connections": job.get("Connections", {}).get("Connections"),
60
+ "Region": region,
61
+ "Description": job.get("Description"),
62
+ }
63
+ transformed_jobs.append(transformed_job)
64
+ return transformed_jobs
65
+
66
+
67
+ def transform_glue_connections(
68
+ connections: List[Dict[str, Any]], region: str
69
+ ) -> List[Dict[str, Any]]:
70
+ """
71
+ Transform Glue connection data for ingestion
72
+ """
73
+ transformed_connections = []
74
+ for connection in connections:
75
+ transformed_connection = {
76
+ "Name": connection["Name"],
77
+ "Description": connection.get("Description"),
78
+ "ConnectionType": connection.get("ConnectionType"),
79
+ "Status": connection.get("Status"),
80
+ "StatusReason": connection.get("StatusReason"),
81
+ "AuthenticationType": connection.get("AuthenticationConfiguration", {}).get(
82
+ "AuthenticationType"
83
+ ),
84
+ "SecretArn": connection.get("AuthenticationConfiguration", {}).get(
85
+ "SecretArn"
86
+ ),
87
+ "Region": region,
88
+ }
89
+ transformed_connections.append(transformed_connection)
90
+ return transformed_connections
91
+
92
+
93
+ @timeit
94
+ def load_glue_connections(
95
+ neo4j_session: neo4j.Session,
96
+ data: List[Dict[str, Any]],
97
+ region: str,
98
+ current_aws_account_id: str,
99
+ aws_update_tag: int,
100
+ ) -> None:
101
+ logger.info(
102
+ f"Loading Glue {len(data)} connections for region '{region}' into graph.",
103
+ )
104
+ load(
105
+ neo4j_session,
106
+ GlueConnectionSchema(),
107
+ data,
108
+ lastupdated=aws_update_tag,
109
+ Region=region,
110
+ AWS_ID=current_aws_account_id,
111
+ )
112
+
113
+
114
+ @timeit
115
+ def load_glue_jobs(
116
+ neo4j_session: neo4j.Session,
117
+ data: List[Dict[str, Any]],
118
+ region: str,
119
+ current_aws_account_id: str,
120
+ aws_update_tag: int,
121
+ ) -> None:
122
+ logger.info(
123
+ f"Loading Glue {len(data)} jobs for region '{region}' into graph.",
124
+ )
125
+ load(
126
+ neo4j_session,
127
+ GlueJobSchema(),
128
+ data,
129
+ lastupdated=aws_update_tag,
130
+ Region=region,
131
+ AWS_ID=current_aws_account_id,
132
+ )
133
+
134
+
135
+ @timeit
136
+ def cleanup(
137
+ neo4j_session: neo4j.Session,
138
+ common_job_parameters: Dict[str, Any],
139
+ ) -> None:
140
+ logger.debug("Running Glue cleanup job.")
141
+ GraphJob.from_node_schema(GlueConnectionSchema(), common_job_parameters).run(
142
+ neo4j_session
143
+ )
144
+ GraphJob.from_node_schema(GlueJobSchema(), common_job_parameters).run(neo4j_session)
145
+
146
+
147
+ @timeit
148
+ def sync(
149
+ neo4j_session: neo4j.Session,
150
+ boto3_session: boto3.session.Session,
151
+ regions: List[str],
152
+ current_aws_account_id: str,
153
+ update_tag: int,
154
+ common_job_parameters: Dict[str, Any],
155
+ ) -> None:
156
+ for region in regions:
157
+ logger.info(
158
+ f"Syncing Glue for region '{region}' in account '{current_aws_account_id}'.",
159
+ )
160
+
161
+ connections = get_glue_connections(boto3_session, region)
162
+ transformed_connections = transform_glue_connections(connections, region)
163
+ load_glue_connections(
164
+ neo4j_session,
165
+ transformed_connections,
166
+ region,
167
+ current_aws_account_id,
168
+ update_tag,
169
+ )
170
+
171
+ jobs = get_glue_jobs(boto3_session, region)
172
+ transformed_jobs = transform_glue_job(jobs, region)
173
+ load_glue_jobs(
174
+ neo4j_session,
175
+ transformed_jobs,
176
+ region,
177
+ current_aws_account_id,
178
+ update_tag,
179
+ )
180
+
181
+ cleanup(neo4j_session, common_job_parameters)