cartography 0.101.1rc1__py3-none-any.whl → 0.102.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 (34) hide show
  1. cartography/_version.py +2 -2
  2. cartography/cli.py +61 -0
  3. cartography/config.py +16 -0
  4. cartography/data/indexes.cypher +0 -6
  5. cartography/data/jobs/cleanup/crowdstrike_import_cleanup.json +0 -5
  6. cartography/intel/aws/__init__.py +11 -1
  7. cartography/intel/aws/ec2/launch_templates.py +14 -5
  8. cartography/intel/aws/ec2/load_balancers.py +126 -148
  9. cartography/intel/aws/ec2/route_tables.py +287 -0
  10. cartography/intel/aws/resources.py +2 -0
  11. cartography/intel/aws/util/common.py +27 -0
  12. cartography/intel/crowdstrike/__init__.py +17 -5
  13. cartography/intel/crowdstrike/endpoints.py +12 -44
  14. cartography/intel/entra/__init__.py +43 -0
  15. cartography/intel/entra/users.py +205 -0
  16. cartography/intel/kandji/devices.py +27 -3
  17. cartography/models/aws/ec2/load_balancer_listeners.py +68 -0
  18. cartography/models/aws/ec2/load_balancers.py +102 -0
  19. cartography/models/aws/ec2/route_table_associations.py +87 -0
  20. cartography/models/aws/ec2/route_tables.py +121 -0
  21. cartography/models/aws/ec2/routes.py +77 -0
  22. cartography/models/crowdstrike/__init__.py +0 -0
  23. cartography/models/crowdstrike/hosts.py +49 -0
  24. cartography/models/entra/__init__.py +0 -0
  25. cartography/models/entra/tenant.py +33 -0
  26. cartography/models/entra/user.py +83 -0
  27. cartography/stats.py +1 -1
  28. cartography/sync.py +2 -0
  29. {cartography-0.101.1rc1.dist-info → cartography-0.102.0.dist-info}/METADATA +4 -1
  30. {cartography-0.101.1rc1.dist-info → cartography-0.102.0.dist-info}/RECORD +34 -21
  31. {cartography-0.101.1rc1.dist-info → cartography-0.102.0.dist-info}/WHEEL +1 -1
  32. {cartography-0.101.1rc1.dist-info → cartography-0.102.0.dist-info}/entry_points.txt +0 -0
  33. {cartography-0.101.1rc1.dist-info → cartography-0.102.0.dist-info}/licenses/LICENSE +0 -0
  34. {cartography-0.101.1rc1.dist-info → cartography-0.102.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.101.1rc1'
21
- __version_tuple__ = version_tuple = (0, 101, 1)
20
+ __version__ = version = '0.102.0'
21
+ __version_tuple__ = version_tuple = (0, 102, 0)
cartography/cli.py CHANGED
@@ -8,6 +8,7 @@ from typing import Optional
8
8
  import cartography.config
9
9
  import cartography.sync
10
10
  import cartography.util
11
+ from cartography.intel.aws.util.common import parse_and_validate_aws_regions
11
12
  from cartography.intel.aws.util.common import parse_and_validate_aws_requested_syncs
12
13
  from cartography.intel.semgrep.dependencies import parse_and_validate_semgrep_ecosystems
13
14
 
@@ -153,6 +154,23 @@ class CLI:
153
154
  'respects the AWS CLI/SDK environment variables and does not override them.'
154
155
  ),
155
156
  )
157
+ parser.add_argument(
158
+ '--aws-regions',
159
+ type=str,
160
+ default=None,
161
+ help=(
162
+ '[EXPERIMENTAL!] Comma-separated list of AWS regions to sync. Example: specify "us-east-1,us-east-2" '
163
+ 'to sync US East 1 and 2. Note that this syncs the same regions in ALL accounts and it is currently '
164
+ 'not possible to specify different regions per account. '
165
+ 'CAUTION: if you previously synced assets from regions that are _not_ included in your current list, '
166
+ 'those assets will be _deleted_ during this sync. '
167
+ 'This is because cartography\'s cleanup process uses "lastupdated" and "account id" to determine data '
168
+ 'freshness and not regions. So, if a previously synced region is missing in the current sync, '
169
+ 'Cartography assumes the associated assets are stale and removes them. '
170
+ 'Default behavior: If `--aws-regions` is not specified, cartography will _autodiscover_ the '
171
+ 'regions supported by each account being synced.'
172
+ ),
173
+ )
156
174
  parser.add_argument(
157
175
  '--aws-best-effort-mode',
158
176
  action='store_true',
@@ -211,6 +229,30 @@ class CLI:
211
229
  'The name of environment variable containing Azure Client Secret for Service Principal Authentication.'
212
230
  ),
213
231
  )
232
+ parser.add_argument(
233
+ '--entra-tenant-id',
234
+ type=str,
235
+ default=None,
236
+ help=(
237
+ 'Entra Tenant Id for Service Principal Authentication.'
238
+ ),
239
+ )
240
+ parser.add_argument(
241
+ '--entra-client-id',
242
+ type=str,
243
+ default=None,
244
+ help=(
245
+ 'Entra Client Id for Service Principal Authentication.'
246
+ ),
247
+ )
248
+ parser.add_argument(
249
+ '--entra-client-secret-env-var',
250
+ type=str,
251
+ default=None,
252
+ help=(
253
+ 'The name of environment variable containing Entra Client Secret for Service Principal Authentication.'
254
+ ),
255
+ )
214
256
  parser.add_argument(
215
257
  '--aws-requested-syncs',
216
258
  type=str,
@@ -605,6 +647,11 @@ class CLI:
605
647
  # No need to store the returned value; we're using this for input validation.
606
648
  parse_and_validate_aws_requested_syncs(config.aws_requested_syncs)
607
649
 
650
+ # AWS regions
651
+ if config.aws_regions:
652
+ # No need to store the returned value; we're using this for input validation.
653
+ parse_and_validate_aws_regions(config.aws_regions)
654
+
608
655
  # Azure config
609
656
  if config.azure_sp_auth and config.azure_client_secret_env_var:
610
657
  logger.debug(
@@ -615,6 +662,16 @@ class CLI:
615
662
  else:
616
663
  config.azure_client_secret = None
617
664
 
665
+ # Entra config
666
+ if config.entra_tenant_id and config.entra_client_id and config.entra_client_secret_env_var:
667
+ logger.debug(
668
+ "Reading Client Secret for Entra Authentication from environment variable %s",
669
+ config.entra_client_secret_env_var,
670
+ )
671
+ config.entra_client_secret = os.environ.get(config.entra_client_secret_env_var)
672
+ else:
673
+ config.entra_client_secret = None
674
+
618
675
  # Okta config
619
676
  if config.okta_org_id and config.okta_api_key_env_var:
620
677
  logger.debug(f"Reading API key for Okta from environment variable {config.okta_api_key_env_var}")
@@ -798,5 +855,9 @@ def main(argv=None):
798
855
  logging.getLogger('botocore').setLevel(logging.WARNING)
799
856
  logging.getLogger('googleapiclient').setLevel(logging.WARNING)
800
857
  logging.getLogger('neo4j').setLevel(logging.WARNING)
858
+ logging.getLogger('azure.identity').setLevel(logging.WARNING)
859
+ logging.getLogger('httpx').setLevel(logging.WARNING)
860
+ logging.getLogger('azure.core.pipeline.policies.http_logging_policy').setLevel(logging.WARNING)
861
+
801
862
  argv = argv if argv is not None else sys.argv[1:]
802
863
  sys.exit(CLI(prog='cartography').main(argv))
cartography/config.py CHANGED
@@ -26,6 +26,8 @@ class Config:
26
26
  :type aws_sync_all_profiles: bool
27
27
  :param aws_sync_all_profiles: If True, AWS sync will run for all non-default profiles in the AWS_CONFIG_FILE. If
28
28
  False (default), AWS sync will run using the default credentials only. Optional.
29
+ :type aws_regions: str
30
+ :param aws_regions: Comma-separated list of AWS regions to sync. Optional.
29
31
  :type aws_best_effort_mode: bool
30
32
  :param aws_best_effort_mode: If True, AWS sync will not raise any exceptions, just log. If False (default),
31
33
  exceptions will be raised.
@@ -41,6 +43,12 @@ class Config:
41
43
  :param azure_client_id: Client Id for connecting in a Service Principal Authentication approach. Optional.
42
44
  :type azure_client_secret: str
43
45
  :param azure_client_secret: Client Secret for connecting in a Service Principal Authentication approach. Optional.
46
+ :type entra_tenant_id: str
47
+ :param entra_tenant_id: Tenant Id for connecting in a Service Principal Authentication approach. Optional.
48
+ :type entra_client_id: str
49
+ :param entra_client_id: Client Id for connecting in a Service Principal Authentication approach. Optional.
50
+ :type entra_client_secret: str
51
+ :param entra_client_secret: Client Secret for connecting in a Service Principal Authentication approach. Optional.
44
52
  :type aws_requested_syncs: str
45
53
  :param aws_requested_syncs: Comma-separated list of AWS resources to sync. Optional.
46
54
  :type analysis_job_directory: str
@@ -127,12 +135,16 @@ class Config:
127
135
  selected_modules=None,
128
136
  update_tag=None,
129
137
  aws_sync_all_profiles=False,
138
+ aws_regions=None,
130
139
  aws_best_effort_mode=False,
131
140
  azure_sync_all_subscriptions=False,
132
141
  azure_sp_auth=None,
133
142
  azure_tenant_id=None,
134
143
  azure_client_id=None,
135
144
  azure_client_secret=None,
145
+ entra_tenant_id=None,
146
+ entra_client_id=None,
147
+ entra_client_secret=None,
136
148
  aws_requested_syncs=None,
137
149
  analysis_job_directory=None,
138
150
  oci_sync_all_profiles=None,
@@ -185,12 +197,16 @@ class Config:
185
197
  self.selected_modules = selected_modules
186
198
  self.update_tag = update_tag
187
199
  self.aws_sync_all_profiles = aws_sync_all_profiles
200
+ self.aws_regions = aws_regions
188
201
  self.aws_best_effort_mode = aws_best_effort_mode
189
202
  self.azure_sync_all_subscriptions = azure_sync_all_subscriptions
190
203
  self.azure_sp_auth = azure_sp_auth
191
204
  self.azure_tenant_id = azure_tenant_id
192
205
  self.azure_client_id = azure_client_id
193
206
  self.azure_client_secret = azure_client_secret
207
+ self.entra_tenant_id = entra_tenant_id
208
+ self.entra_client_id = entra_client_id
209
+ self.entra_client_secret = entra_client_secret
194
210
  self.aws_requested_syncs = aws_requested_syncs
195
211
  self.analysis_job_directory = analysis_job_directory
196
212
  self.oci_sync_all_profiles = oci_sync_all_profiles
@@ -65,9 +65,6 @@ CREATE INDEX IF NOT EXISTS FOR (n:AccountAccessKey) ON (n.accesskeyid);
65
65
  CREATE INDEX IF NOT EXISTS FOR (n:AccountAccessKey) ON (n.lastupdated);
66
66
  CREATE INDEX IF NOT EXISTS FOR (n:AutoScalingGroup) ON (n.arn);
67
67
  CREATE INDEX IF NOT EXISTS FOR (n:AutoScalingGroup) ON (n.lastupdated);
68
- CREATE INDEX IF NOT EXISTS FOR (n:CrowdstrikeHost) ON (n.id);
69
- CREATE INDEX IF NOT EXISTS FOR (n:CrowdstrikeHost) ON (n.instance_id);
70
- CREATE INDEX IF NOT EXISTS FOR (n:CrowdstrikeHost) ON (n.lastupdated);
71
68
  CREATE INDEX IF NOT EXISTS FOR (n:CVE) ON (n.id);
72
69
  CREATE INDEX IF NOT EXISTS FOR (n:CVE) ON (n.lastupdated);
73
70
  CREATE INDEX IF NOT EXISTS FOR (n:Dependency) ON (n.id);
@@ -194,9 +191,6 @@ CREATE INDEX IF NOT EXISTS FOR (n:KMSGrant) ON (n.lastupdated);
194
191
  CREATE INDEX IF NOT EXISTS FOR (n:LaunchConfiguration) ON (n.id);
195
192
  CREATE INDEX IF NOT EXISTS FOR (n:LaunchConfiguration) ON (n.name);
196
193
  CREATE INDEX IF NOT EXISTS FOR (n:LaunchConfiguration) ON (n.lastupdated);
197
- CREATE INDEX IF NOT EXISTS FOR (n:LoadBalancer) ON (n.dnsname);
198
- CREATE INDEX IF NOT EXISTS FOR (n:LoadBalancer) ON (n.id);
199
- CREATE INDEX IF NOT EXISTS FOR (n:LoadBalancer) ON (n.lastupdated);
200
194
  CREATE INDEX IF NOT EXISTS FOR (n:LoadBalancerV2) ON (n.dnsname);
201
195
  CREATE INDEX IF NOT EXISTS FOR (n:LoadBalancerV2) ON (n.id);
202
196
  CREATE INDEX IF NOT EXISTS FOR (n:LoadBalancerV2) ON (n.lastupdated);
@@ -5,11 +5,6 @@
5
5
  "iterative": true,
6
6
  "iterationsize": 100
7
7
  },
8
- {
9
- "query": "MATCH (h:CrowdstrikeHost) WHERE h.lastupdated <> $UPDATE_TAG WITH h LIMIT $LIMIT_SIZE DETACH DELETE (h)",
10
- "iterative": true,
11
- "iterationsize": 100
12
- },
13
8
  {
14
9
  "query": "MATCH (:CrowdstrikeFinding)<-[hc:HAS_CVE]-(:SpotlightVulnerability) WHERE hc.lastupdated <> $UPDATE_TAG WITH hc LIMIT $LIMIT_SIZE DELETE (hc)",
15
10
  "iterative": true,
@@ -14,6 +14,7 @@ from . import ec2
14
14
  from . import organizations
15
15
  from .resources import RESOURCE_FUNCTIONS
16
16
  from cartography.config import Config
17
+ from cartography.intel.aws.util.common import parse_and_validate_aws_regions
17
18
  from cartography.intel.aws.util.common import parse_and_validate_aws_requested_syncs
18
19
  from cartography.stats import get_stats_client
19
20
  from cartography.util import merge_module_sync_metadata
@@ -48,9 +49,10 @@ def _sync_one_account(
48
49
  current_aws_account_id: str,
49
50
  update_tag: int,
50
51
  common_job_parameters: Dict[str, Any],
51
- regions: List[str] = [],
52
+ regions: list[str] | None = None,
52
53
  aws_requested_syncs: Iterable[str] = RESOURCE_FUNCTIONS.keys(),
53
54
  ) -> None:
55
+ # Autodiscover the regions supported by the account unless the user has specified the regions to sync.
54
56
  if not regions:
55
57
  regions = _autodiscover_account_regions(boto3_session, current_aws_account_id)
56
58
 
@@ -146,6 +148,7 @@ def _sync_multiple_accounts(
146
148
  common_job_parameters: Dict[str, Any],
147
149
  aws_best_effort_mode: bool,
148
150
  aws_requested_syncs: List[str] = [],
151
+ regions: list[str] | None = None,
149
152
  ) -> bool:
150
153
  logger.info("Syncing AWS accounts: %s", ', '.join(accounts.values()))
151
154
  organizations.sync(neo4j_session, accounts, sync_tag, common_job_parameters)
@@ -173,6 +176,7 @@ def _sync_multiple_accounts(
173
176
  account_id,
174
177
  sync_tag,
175
178
  common_job_parameters,
179
+ regions=regions,
176
180
  aws_requested_syncs=aws_requested_syncs, # Could be replaced later with per-account requested syncs
177
181
  )
178
182
  except Exception as e:
@@ -299,6 +303,11 @@ def start_aws_ingestion(neo4j_session: neo4j.Session, config: Config) -> None:
299
303
  if config.aws_requested_syncs:
300
304
  requested_syncs = parse_and_validate_aws_requested_syncs(config.aws_requested_syncs)
301
305
 
306
+ if config.aws_regions:
307
+ regions = parse_and_validate_aws_regions(config.aws_regions)
308
+ else:
309
+ regions = None
310
+
302
311
  sync_successful = _sync_multiple_accounts(
303
312
  neo4j_session,
304
313
  aws_accounts,
@@ -306,6 +315,7 @@ def start_aws_ingestion(neo4j_session: neo4j.Session, config: Config) -> None:
306
315
  common_job_parameters,
307
316
  config.aws_best_effort_mode,
308
317
  requested_syncs,
318
+ regions=regions,
309
319
  )
310
320
 
311
321
  if sync_successful:
@@ -69,10 +69,15 @@ def get_launch_template_versions_by_template(
69
69
  return template_versions
70
70
 
71
71
 
72
- def transform_launch_templates(templates: list[dict[str, Any]]) -> list[dict[str, Any]]:
72
+ def transform_launch_templates(templates: list[dict[str, Any]], versions: list[dict[str, Any]]) -> list[dict[str, Any]]:
73
+ valid_template_ids = {v['LaunchTemplateId'] for v in versions}
73
74
  result: list[dict[str, Any]] = []
74
75
  for template in templates:
76
+ if template['LaunchTemplateId'] not in valid_template_ids:
77
+ continue
78
+
75
79
  current = template.copy()
80
+ # Convert CreateTime to timestamp string
76
81
  current['CreateTime'] = str(int(current['CreateTime'].timestamp()))
77
82
  result.append(current)
78
83
  return result
@@ -165,9 +170,13 @@ def sync_ec2_launch_templates(
165
170
  logger.info(f"Syncing launch templates for region '{region}' in account '{current_aws_account_id}'.")
166
171
  templates = get_launch_templates(boto3_session, region)
167
172
  versions = get_launch_template_versions(boto3_session, region, templates)
168
- templates = transform_launch_templates(templates)
169
- load_launch_templates(neo4j_session, templates, region, current_aws_account_id, update_tag)
170
- versions = transform_launch_template_versions(versions)
171
- load_launch_template_versions(neo4j_session, versions, region, current_aws_account_id, update_tag)
173
+
174
+ # Transform and load the templates that have versions
175
+ transformed_templates = transform_launch_templates(templates, versions)
176
+ load_launch_templates(neo4j_session, transformed_templates, region, current_aws_account_id, update_tag)
177
+
178
+ # Transform and load the versions
179
+ transformed_versions = transform_launch_template_versions(versions)
180
+ load_launch_template_versions(neo4j_session, transformed_versions, region, current_aws_account_id, update_tag)
172
181
 
173
182
  cleanup(neo4j_session, common_job_parameters)
@@ -1,190 +1,168 @@
1
1
  import logging
2
- from typing import Dict
3
- from typing import List
4
2
 
5
3
  import boto3
6
4
  import neo4j
7
5
 
8
6
  from .util import get_botocore_config
7
+ from cartography.client.core.tx import load
8
+ from cartography.graph.job import GraphJob
9
+ from cartography.models.aws.ec2.load_balancer_listeners import ELBListenerSchema
10
+ from cartography.models.aws.ec2.load_balancers import LoadBalancerSchema
9
11
  from cartography.util import aws_handle_regions
10
- from cartography.util import run_cleanup_job
11
12
  from cartography.util import timeit
12
13
 
13
14
  logger = logging.getLogger(__name__)
14
15
 
15
16
 
17
+ def _get_listener_id(load_balancer_id: str, port: int, protocol: str) -> str:
18
+ """
19
+ Generate a unique ID for a load balancer listener.
20
+
21
+ Args:
22
+ load_balancer_id: The ID of the load balancer
23
+ port: The listener port
24
+ protocol: The listener protocol
25
+
26
+ Returns:
27
+ A unique ID string for the listener
28
+ """
29
+ return f"{load_balancer_id}{port}{protocol}"
30
+
31
+
32
+ def transform_load_balancer_listener_data(load_balancer_id: str, listener_data: list[dict]) -> list[dict]:
33
+ """
34
+ Transform load balancer listener data into a format suitable for cartography ingestion.
35
+
36
+ Args:
37
+ load_balancer_id: The ID of the load balancer
38
+ listener_data: List of listener data from AWS API
39
+
40
+ Returns:
41
+ List of transformed listener data
42
+ """
43
+ transformed = []
44
+ for listener in listener_data:
45
+ listener_info = listener['Listener']
46
+ transformed_listener = {
47
+ 'id': _get_listener_id(load_balancer_id, listener_info['LoadBalancerPort'], listener_info['Protocol']),
48
+ 'port': listener_info.get('LoadBalancerPort'),
49
+ 'protocol': listener_info.get('Protocol'),
50
+ 'instance_port': listener_info.get('InstancePort'),
51
+ 'instance_protocol': listener_info.get('InstanceProtocol'),
52
+ 'policy_names': listener.get('PolicyNames', []),
53
+ 'LoadBalancerId': load_balancer_id,
54
+ }
55
+ transformed.append(transformed_listener)
56
+ return transformed
57
+
58
+
59
+ def transform_load_balancer_data(load_balancers: list[dict]) -> tuple[list[dict], list[dict]]:
60
+ """
61
+ Transform load balancer data into a format suitable for cartography ingestion.
62
+
63
+ Args:
64
+ load_balancers: List of load balancer data from AWS API
65
+
66
+ Returns:
67
+ Tuple of (transformed load balancer data, transformed listener data)
68
+ """
69
+ transformed = []
70
+ listener_data = []
71
+
72
+ for lb in load_balancers:
73
+ load_balancer_id = lb['DNSName']
74
+ transformed_lb = {
75
+ 'id': load_balancer_id,
76
+ 'name': lb['LoadBalancerName'],
77
+ 'dnsname': lb['DNSName'],
78
+ 'canonicalhostedzonename': lb.get('CanonicalHostedZoneName'),
79
+ 'canonicalhostedzonenameid': lb.get('CanonicalHostedZoneNameID'),
80
+ 'scheme': lb.get('Scheme'),
81
+ 'createdtime': str(lb['CreatedTime']),
82
+ 'GROUP_NAME': lb.get('SourceSecurityGroup', {}).get('GroupName'),
83
+ 'GROUP_IDS': [str(group) for group in lb.get('SecurityGroups', [])],
84
+ 'INSTANCE_IDS': [instance['InstanceId'] for instance in lb.get('Instances', [])],
85
+ 'LISTENER_IDS': [
86
+ _get_listener_id(
87
+ load_balancer_id,
88
+ listener['Listener']['LoadBalancerPort'],
89
+ listener['Listener']['Protocol'],
90
+ ) for listener in lb.get('ListenerDescriptions', [])
91
+ ],
92
+ }
93
+ transformed.append(transformed_lb)
94
+
95
+ # Classic ELB listeners are not returned anywhere else in AWS, so we must parse them out
96
+ # of the describe_load_balancers response.
97
+ if lb.get('ListenerDescriptions'):
98
+ listener_data.extend(
99
+ transform_load_balancer_listener_data(
100
+ load_balancer_id,
101
+ lb.get('ListenerDescriptions', []),
102
+ ),
103
+ )
104
+
105
+ return transformed, listener_data
106
+
107
+
16
108
  @timeit
17
109
  @aws_handle_regions
18
- def get_loadbalancer_data(boto3_session: boto3.session.Session, region: str) -> List[Dict]:
110
+ def get_loadbalancer_data(boto3_session: boto3.session.Session, region: str) -> list[dict]:
19
111
  client = boto3_session.client('elb', region_name=region, config=get_botocore_config())
20
112
  paginator = client.get_paginator('describe_load_balancers')
21
- elbs: List[Dict] = []
113
+ elbs: list[dict] = []
22
114
  for page in paginator.paginate():
23
115
  elbs.extend(page['LoadBalancerDescriptions'])
24
116
  return elbs
25
117
 
26
118
 
27
119
  @timeit
28
- def load_load_balancer_listeners(
29
- neo4j_session: neo4j.Session, load_balancer_id: str, listener_data: List[Dict],
120
+ def load_load_balancers(
121
+ neo4j_session: neo4j.Session, data: list[dict], region: str, current_aws_account_id: str,
30
122
  update_tag: int,
31
123
  ) -> None:
32
- ingest_listener = """
33
- MATCH (elb:LoadBalancer{id: $LoadBalancerId})
34
- WITH elb
35
- UNWIND $Listeners as data
36
- MERGE (l:Endpoint:ELBListener{id: elb.id + toString(data.Listener.LoadBalancerPort) +
37
- toString(data.Listener.Protocol)})
38
- ON CREATE SET l.port = data.Listener.LoadBalancerPort, l.protocol = data.Listener.Protocol,
39
- l.firstseen = timestamp()
40
- SET l.instance_port = data.Listener.InstancePort, l.instance_protocol = data.Listener.InstanceProtocol,
41
- l.policy_names = data.PolicyNames,
42
- l.lastupdated = $update_tag
43
- WITH l, elb
44
- MERGE (elb)-[r:ELB_LISTENER]->(l)
45
- ON CREATE SET r.firstseen = timestamp()
46
- SET r.lastupdated = $update_tag
47
- """
48
-
49
- neo4j_session.run(
50
- ingest_listener,
51
- LoadBalancerId=load_balancer_id,
52
- Listeners=listener_data,
53
- update_tag=update_tag,
124
+ load(
125
+ neo4j_session,
126
+ LoadBalancerSchema(),
127
+ data,
128
+ Region=region,
129
+ AWS_ID=current_aws_account_id,
130
+ lastupdated=update_tag,
54
131
  )
55
132
 
56
133
 
57
134
  @timeit
58
- def load_load_balancer_subnets(
59
- neo4j_session: neo4j.Session, load_balancer_id: str, subnets_data: List[Dict],
60
- update_tag: int,
61
- ) -> None:
62
- ingest_load_balancer_subnet = """
63
- MATCH (elb:LoadBalancer{id: $ID}), (subnet:EC2Subnet{subnetid: $SUBNET_ID})
64
- MERGE (elb)-[r:SUBNET]->(subnet)
65
- ON CREATE SET r.firstseen = timestamp()
66
- SET r.lastupdated = $update_tag
67
- """
68
-
69
- for subnet_id in subnets_data:
70
- neo4j_session.run(
71
- ingest_load_balancer_subnet,
72
- ID=load_balancer_id,
73
- SUBNET_ID=subnet_id,
74
- update_tag=update_tag,
75
- )
76
-
77
-
78
- @timeit
79
- def load_load_balancers(
80
- neo4j_session: neo4j.Session, data: List[Dict], region: str, current_aws_account_id: str,
135
+ def load_load_balancer_listeners(
136
+ neo4j_session: neo4j.Session, data: list[dict], region: str, current_aws_account_id: str,
81
137
  update_tag: int,
82
138
  ) -> None:
83
- ingest_load_balancer = """
84
- MERGE (elb:LoadBalancer{id: $ID})
85
- ON CREATE SET elb.firstseen = timestamp(), elb.createdtime = $CREATED_TIME
86
- SET elb.lastupdated = $update_tag, elb.name = $NAME, elb.dnsname = $DNS_NAME,
87
- elb.canonicalhostedzonename = $HOSTED_ZONE_NAME, elb.canonicalhostedzonenameid = $HOSTED_ZONE_NAME_ID,
88
- elb.scheme = $SCHEME, elb.region = $Region
89
- WITH elb
90
- MATCH (aa:AWSAccount{id: $AWS_ACCOUNT_ID})
91
- MERGE (aa)-[r:RESOURCE]->(elb)
92
- ON CREATE SET r.firstseen = timestamp()
93
- SET r.lastupdated = $update_tag
94
- """
95
-
96
- ingest_load_balancersource_security_group = """
97
- MATCH (elb:LoadBalancer{id: $ID}),
98
- (group:EC2SecurityGroup{name: $GROUP_NAME})
99
- MERGE (elb)-[r:SOURCE_SECURITY_GROUP]->(group)
100
- ON CREATE SET r.firstseen = timestamp()
101
- SET r.lastupdated = $update_tag
102
- """
103
-
104
- ingest_load_balancer_security_group = """
105
- MATCH (elb:LoadBalancer{id: $ID}),
106
- (group:EC2SecurityGroup{groupid: $GROUP_ID})
107
- MERGE (elb)-[r:MEMBER_OF_EC2_SECURITY_GROUP]->(group)
108
- ON CREATE SET r.firstseen = timestamp()
109
- SET r.lastupdated = $update_tag
110
- """
111
-
112
- ingest_instances = """
113
- MATCH (elb:LoadBalancer{id: $ID}), (instance:EC2Instance{instanceid: $INSTANCE_ID})
114
- MERGE (elb)-[r:EXPOSE]->(instance)
115
- ON CREATE SET r.firstseen = timestamp()
116
- SET r.lastupdated = $update_tag
117
- WITH instance
118
- MATCH (aa:AWSAccount{id: $AWS_ACCOUNT_ID})
119
- MERGE (aa)-[r:RESOURCE]->(instance)
120
- ON CREATE SET r.firstseen = timestamp()
121
- SET r.lastupdated = $update_tag
122
- """
123
-
124
- for lb in data:
125
- load_balancer_id = lb["DNSName"]
126
-
127
- neo4j_session.run(
128
- ingest_load_balancer,
129
- ID=load_balancer_id,
130
- CREATED_TIME=str(lb["CreatedTime"]),
131
- NAME=lb["LoadBalancerName"],
132
- DNS_NAME=load_balancer_id,
133
- HOSTED_ZONE_NAME=lb.get("CanonicalHostedZoneName"),
134
- HOSTED_ZONE_NAME_ID=lb.get("CanonicalHostedZoneNameID"),
135
- SCHEME=lb.get("Scheme", ""),
136
- AWS_ACCOUNT_ID=current_aws_account_id,
137
- Region=region,
138
- update_tag=update_tag,
139
- )
140
-
141
- if lb["Subnets"]:
142
- load_load_balancer_subnets(neo4j_session, load_balancer_id, lb["Subnets"], update_tag)
143
-
144
- if lb["SecurityGroups"]:
145
- for group in lb["SecurityGroups"]:
146
- neo4j_session.run(
147
- ingest_load_balancer_security_group,
148
- ID=load_balancer_id,
149
- GROUP_ID=str(group),
150
- update_tag=update_tag,
151
- )
152
-
153
- if lb["SourceSecurityGroup"]:
154
- source_group = lb["SourceSecurityGroup"]
155
- neo4j_session.run(
156
- ingest_load_balancersource_security_group,
157
- ID=load_balancer_id,
158
- GROUP_NAME=source_group["GroupName"],
159
- update_tag=update_tag,
160
- )
161
-
162
- if lb["Instances"]:
163
- for instance in lb["Instances"]:
164
- neo4j_session.run(
165
- ingest_instances,
166
- ID=load_balancer_id,
167
- INSTANCE_ID=instance["InstanceId"],
168
- AWS_ACCOUNT_ID=current_aws_account_id,
169
- update_tag=update_tag,
170
- )
171
-
172
- if lb["ListenerDescriptions"]:
173
- load_load_balancer_listeners(neo4j_session, load_balancer_id, lb["ListenerDescriptions"], update_tag)
139
+ load(
140
+ neo4j_session,
141
+ ELBListenerSchema(),
142
+ data,
143
+ Region=region,
144
+ AWS_ID=current_aws_account_id,
145
+ lastupdated=update_tag,
146
+ )
174
147
 
175
148
 
176
149
  @timeit
177
- def cleanup_load_balancers(neo4j_session: neo4j.Session, common_job_parameters: Dict) -> None:
178
- run_cleanup_job('aws_ingest_load_balancers_cleanup.json', neo4j_session, common_job_parameters)
150
+ def cleanup_load_balancers(neo4j_session: neo4j.Session, common_job_parameters: dict) -> None:
151
+ GraphJob.from_node_schema(ELBListenerSchema(), common_job_parameters).run(neo4j_session)
152
+ GraphJob.from_node_schema(LoadBalancerSchema(), common_job_parameters).run(neo4j_session)
179
153
 
180
154
 
181
155
  @timeit
182
156
  def sync_load_balancers(
183
- neo4j_session: neo4j.Session, boto3_session: boto3.session.Session, regions: List[str], current_aws_account_id: str,
184
- update_tag: int, common_job_parameters: Dict,
157
+ neo4j_session: neo4j.Session, boto3_session: boto3.session.Session, regions: list[str], current_aws_account_id: str,
158
+ update_tag: int, common_job_parameters: dict,
185
159
  ) -> None:
186
160
  for region in regions:
187
161
  logger.info("Syncing EC2 load balancers for region '%s' in account '%s'.", region, current_aws_account_id)
188
162
  data = get_loadbalancer_data(boto3_session, region)
189
- load_load_balancers(neo4j_session, data, region, current_aws_account_id, update_tag)
163
+ transformed_data, listener_data = transform_load_balancer_data(data)
164
+
165
+ load_load_balancers(neo4j_session, transformed_data, region, current_aws_account_id, update_tag)
166
+ load_load_balancer_listeners(neo4j_session, listener_data, region, current_aws_account_id, update_tag)
167
+
190
168
  cleanup_load_balancers(neo4j_session, common_job_parameters)