cartography 0.117.0__py3-none-any.whl → 0.119.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 (107) hide show
  1. cartography/_version.py +2 -2
  2. cartography/cli.py +31 -0
  3. cartography/client/core/tx.py +19 -3
  4. cartography/config.py +14 -0
  5. cartography/data/indexes.cypher +0 -6
  6. cartography/graph/job.py +13 -7
  7. cartography/graph/statement.py +4 -0
  8. cartography/intel/aws/__init__.py +22 -9
  9. cartography/intel/aws/apigateway.py +18 -5
  10. cartography/intel/aws/ec2/elastic_ip_addresses.py +3 -1
  11. cartography/intel/aws/ec2/internet_gateways.py +4 -2
  12. cartography/intel/aws/ec2/load_balancer_v2s.py +11 -5
  13. cartography/intel/aws/ec2/network_interfaces.py +4 -0
  14. cartography/intel/aws/ec2/reserved_instances.py +3 -1
  15. cartography/intel/aws/ec2/tgw.py +11 -5
  16. cartography/intel/aws/ec2/volumes.py +1 -1
  17. cartography/intel/aws/ecr.py +209 -26
  18. cartography/intel/aws/ecr_image_layers.py +143 -42
  19. cartography/intel/aws/elasticsearch.py +13 -4
  20. cartography/intel/aws/identitycenter.py +93 -54
  21. cartography/intel/aws/inspector.py +90 -46
  22. cartography/intel/aws/permission_relationships.py +3 -3
  23. cartography/intel/aws/resourcegroupstaggingapi.py +1 -1
  24. cartography/intel/aws/s3.py +26 -13
  25. cartography/intel/aws/ssm.py +3 -5
  26. cartography/intel/azure/compute.py +9 -4
  27. cartography/intel/azure/cosmosdb.py +31 -15
  28. cartography/intel/azure/sql.py +25 -12
  29. cartography/intel/azure/storage.py +19 -9
  30. cartography/intel/azure/subscription.py +3 -1
  31. cartography/intel/crowdstrike/spotlight.py +5 -2
  32. cartography/intel/entra/app_role_assignments.py +9 -2
  33. cartography/intel/gcp/__init__.py +26 -9
  34. cartography/intel/gcp/clients.py +8 -4
  35. cartography/intel/gcp/compute.py +42 -21
  36. cartography/intel/gcp/crm/folders.py +9 -3
  37. cartography/intel/gcp/crm/orgs.py +8 -3
  38. cartography/intel/gcp/crm/projects.py +14 -3
  39. cartography/intel/github/repos.py +23 -5
  40. cartography/intel/gsuite/__init__.py +12 -8
  41. cartography/intel/gsuite/groups.py +291 -0
  42. cartography/intel/gsuite/users.py +142 -0
  43. cartography/intel/jamf/computers.py +7 -1
  44. cartography/intel/oci/iam.py +23 -9
  45. cartography/intel/oci/organizations.py +3 -1
  46. cartography/intel/oci/utils.py +28 -5
  47. cartography/intel/okta/awssaml.py +9 -8
  48. cartography/intel/okta/users.py +1 -1
  49. cartography/intel/ontology/__init__.py +44 -0
  50. cartography/intel/ontology/devices.py +54 -0
  51. cartography/intel/ontology/users.py +54 -0
  52. cartography/intel/ontology/utils.py +121 -0
  53. cartography/intel/pagerduty/escalation_policies.py +13 -6
  54. cartography/intel/pagerduty/schedules.py +9 -4
  55. cartography/intel/pagerduty/services.py +7 -3
  56. cartography/intel/pagerduty/teams.py +5 -2
  57. cartography/intel/pagerduty/users.py +3 -1
  58. cartography/intel/pagerduty/vendors.py +3 -1
  59. cartography/intel/trivy/__init__.py +109 -58
  60. cartography/models/airbyte/user.py +4 -0
  61. cartography/models/anthropic/user.py +4 -0
  62. cartography/models/aws/ec2/networkinterfaces.py +2 -0
  63. cartography/models/aws/ecr/image.py +55 -0
  64. cartography/models/aws/ecr/repository_image.py +1 -1
  65. cartography/models/aws/iam/group_membership.py +3 -2
  66. cartography/models/aws/identitycenter/awsssouser.py +3 -1
  67. cartography/models/bigfix/bigfix_computer.py +1 -1
  68. cartography/models/cloudflare/member.py +4 -0
  69. cartography/models/crowdstrike/hosts.py +1 -1
  70. cartography/models/duo/endpoint.py +1 -1
  71. cartography/models/duo/phone.py +2 -2
  72. cartography/models/duo/user.py +4 -0
  73. cartography/models/entra/user.py +2 -1
  74. cartography/models/github/users.py +4 -0
  75. cartography/models/gsuite/__init__.py +0 -0
  76. cartography/models/gsuite/group.py +218 -0
  77. cartography/models/gsuite/tenant.py +29 -0
  78. cartography/models/gsuite/user.py +107 -0
  79. cartography/models/kandji/device.py +1 -2
  80. cartography/models/keycloak/user.py +4 -0
  81. cartography/models/lastpass/user.py +4 -0
  82. cartography/models/ontology/__init__.py +0 -0
  83. cartography/models/ontology/device.py +125 -0
  84. cartography/models/ontology/mapping/__init__.py +16 -0
  85. cartography/models/ontology/mapping/data/__init__.py +1 -0
  86. cartography/models/ontology/mapping/data/devices.py +160 -0
  87. cartography/models/ontology/mapping/data/users.py +239 -0
  88. cartography/models/ontology/mapping/specs.py +65 -0
  89. cartography/models/ontology/user.py +52 -0
  90. cartography/models/openai/user.py +4 -0
  91. cartography/models/scaleway/iam/user.py +4 -0
  92. cartography/models/snipeit/asset.py +1 -0
  93. cartography/models/snipeit/user.py +4 -0
  94. cartography/models/tailscale/device.py +1 -1
  95. cartography/models/tailscale/user.py +6 -1
  96. cartography/rules/data/frameworks/mitre_attack/requirements/t1098_account_manipulation/__init__.py +176 -89
  97. cartography/sync.py +4 -1
  98. cartography/util.py +49 -18
  99. {cartography-0.117.0.dist-info → cartography-0.119.0.dist-info}/METADATA +3 -3
  100. {cartography-0.117.0.dist-info → cartography-0.119.0.dist-info}/RECORD +104 -89
  101. cartography/data/jobs/cleanup/gsuite_ingest_groups_cleanup.json +0 -23
  102. cartography/data/jobs/cleanup/gsuite_ingest_users_cleanup.json +0 -11
  103. cartography/intel/gsuite/api.py +0 -355
  104. {cartography-0.117.0.dist-info → cartography-0.119.0.dist-info}/WHEEL +0 -0
  105. {cartography-0.117.0.dist-info → cartography-0.119.0.dist-info}/entry_points.txt +0 -0
  106. {cartography-0.117.0.dist-info → cartography-0.119.0.dist-info}/licenses/LICENSE +0 -0
  107. {cartography-0.117.0.dist-info → cartography-0.119.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,44 @@
1
+ import logging
2
+
3
+ import neo4j
4
+
5
+ import cartography.intel.ontology.devices
6
+ import cartography.intel.ontology.users
7
+ from cartography.config import Config
8
+ from cartography.util import timeit
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ @timeit
14
+ def run(neo4j_session: neo4j.Session, config: Config) -> None:
15
+ common_job_parameters = {
16
+ "UPDATE_TAG": config.update_tag,
17
+ }
18
+
19
+ # Get source of truth from config
20
+ if config.ontology_users_source:
21
+ users_source_of_truth = [
22
+ source.strip() for source in config.ontology_users_source.split(",")
23
+ ]
24
+ else:
25
+ users_source_of_truth = []
26
+ if config.ontology_devices_source:
27
+ computers_source_of_truth = [
28
+ source.strip() for source in config.ontology_devices_source.split(",")
29
+ ]
30
+ else:
31
+ computers_source_of_truth = []
32
+
33
+ cartography.intel.ontology.users.sync(
34
+ neo4j_session,
35
+ users_source_of_truth,
36
+ config.update_tag,
37
+ common_job_parameters,
38
+ )
39
+ cartography.intel.ontology.devices.sync(
40
+ neo4j_session,
41
+ computers_source_of_truth,
42
+ config.update_tag,
43
+ common_job_parameters,
44
+ )
@@ -0,0 +1,54 @@
1
+ import logging
2
+ from typing import Any
3
+
4
+ import neo4j
5
+
6
+ from cartography.client.core.tx import load
7
+ from cartography.graph.job import GraphJob
8
+ from cartography.intel.ontology.utils import get_source_nodes_from_graph
9
+ from cartography.intel.ontology.utils import link_ontology_nodes
10
+ from cartography.models.ontology.device import DeviceSchema
11
+ from cartography.util import timeit
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ @timeit
17
+ def sync(
18
+ neo4j_session: neo4j.Session,
19
+ source_of_truth: list[str],
20
+ update_tag: int,
21
+ common_job_parameters: dict[str, Any],
22
+ ) -> None:
23
+ data = get_source_nodes_from_graph(neo4j_session, source_of_truth, "devices")
24
+ load_devices(
25
+ neo4j_session,
26
+ data,
27
+ update_tag,
28
+ )
29
+ link_ontology_nodes(neo4j_session, "devices", update_tag)
30
+ cleanup(neo4j_session, common_job_parameters)
31
+
32
+
33
+ @timeit
34
+ def load_devices(
35
+ neo4j_session: neo4j.Session,
36
+ data: list[dict[str, Any]],
37
+ update_tag: int,
38
+ ) -> None:
39
+ load(
40
+ neo4j_session,
41
+ DeviceSchema(),
42
+ data,
43
+ lastupdated=update_tag,
44
+ )
45
+
46
+
47
+ @timeit
48
+ def cleanup(
49
+ neo4j_session: neo4j.Session,
50
+ common_job_parameters: dict[str, Any],
51
+ ) -> None:
52
+ GraphJob.from_node_schema(DeviceSchema(), common_job_parameters).run(
53
+ neo4j_session,
54
+ )
@@ -0,0 +1,54 @@
1
+ import logging
2
+ from typing import Any
3
+
4
+ import neo4j
5
+
6
+ from cartography.client.core.tx import load
7
+ from cartography.graph.job import GraphJob
8
+ from cartography.intel.ontology.utils import get_source_nodes_from_graph
9
+ from cartography.intel.ontology.utils import link_ontology_nodes
10
+ from cartography.models.ontology.user import UserSchema
11
+ from cartography.util import timeit
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ @timeit
17
+ def sync(
18
+ neo4j_session: neo4j.Session,
19
+ source_of_truth: list[str],
20
+ update_tag: int,
21
+ common_job_parameters: dict[str, Any],
22
+ ) -> None:
23
+ data = get_source_nodes_from_graph(neo4j_session, source_of_truth, "users")
24
+ load_users(
25
+ neo4j_session,
26
+ data,
27
+ update_tag,
28
+ )
29
+ link_ontology_nodes(neo4j_session, "users", update_tag)
30
+ cleanup(neo4j_session, common_job_parameters)
31
+
32
+
33
+ @timeit
34
+ def load_users(
35
+ neo4j_session: neo4j.Session,
36
+ data: list[dict[str, Any]],
37
+ update_tag: int,
38
+ ) -> None:
39
+ load(
40
+ neo4j_session,
41
+ UserSchema(),
42
+ data,
43
+ lastupdated=update_tag,
44
+ )
45
+
46
+
47
+ @timeit
48
+ def cleanup(
49
+ neo4j_session: neo4j.Session,
50
+ common_job_parameters: dict[str, Any],
51
+ ) -> None:
52
+ GraphJob.from_node_schema(UserSchema(), common_job_parameters).run(
53
+ neo4j_session,
54
+ )
@@ -0,0 +1,121 @@
1
+ import logging
2
+ from dataclasses import asdict
3
+ from typing import Any
4
+
5
+ import neo4j
6
+
7
+ from cartography.client.core.tx import read_list_of_dicts_tx
8
+ from cartography.graph.job import GraphJob
9
+ from cartography.models.ontology.mapping import ONTOLOGY_MAPPING
10
+ from cartography.models.ontology.mapping import ONTOLOGY_MODELS
11
+ from cartography.util import timeit
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ @timeit
17
+ def get_source_nodes_from_graph(
18
+ neo4j_session: neo4j.Session,
19
+ source_of_truth: list[str],
20
+ module_name: str,
21
+ ) -> list[dict[str, Any]]:
22
+ """Retrieve source nodes from the Neo4j graph database based on the ontology mapping.
23
+
24
+ This function queries the Neo4j database for nodes that match the labels
25
+ defined in the ontology mapping for the specified module and source of truth.
26
+ It returns a list of dictionaries containing the relevant fields for each node.
27
+
28
+ If no source of truth is provided, default to all sources defined in the mapping.
29
+
30
+ Args:
31
+ neo4j_session (neo4j.Session): The Neo4j session to use for querying the database.
32
+ source_of_truth (list[str]): A list of source of truth identifiers to filter the modules.
33
+ module_name (str): The name of the ontology module to use for the mapping (eg. users, devices, etc.).
34
+
35
+ Returns:
36
+ list[dict[str, Any]]: A list of dictionaries, each containing a node details formatted according to the ontology mapping.
37
+ """
38
+ results: dict[str, dict[str, Any]] = {}
39
+ modules_mapping = ONTOLOGY_MAPPING[module_name]
40
+ if len(source_of_truth) == 0:
41
+ source_of_truth = list(modules_mapping.keys())
42
+ for source in source_of_truth:
43
+ if source not in modules_mapping:
44
+ logger.warning(
45
+ "Source of truth '%s' is not supported for '%s'.", source, module_name
46
+ )
47
+ continue
48
+ for node in modules_mapping[source].nodes:
49
+ query = f"MATCH (n:{node.node_label}) RETURN n"
50
+ for row in neo4j_session.execute_read(read_list_of_dicts_tx, query):
51
+ node_data = row["n"]
52
+ result: dict[str, Any] = {}
53
+ skip_node: bool = False
54
+
55
+ # Extract only the fields defined in the ontology mapping
56
+ for field in node.fields:
57
+ value = node_data.get(field.node_field)
58
+ # Skip nodes missing required fields
59
+ if field.required and value is None:
60
+ logger.debug(
61
+ "Skipping node with label '%s' due to missing required field '%s'.",
62
+ node.node_label,
63
+ field.node_field,
64
+ )
65
+ skip_node = True
66
+ break
67
+ result[field.ontology_field] = value
68
+ if skip_node:
69
+ continue
70
+
71
+ # Merge results based on the node's id field to avoid duplicates
72
+ id_field = ONTOLOGY_MODELS[module_name]().properties.id.name
73
+ existing = results.get(result[id_field])
74
+ if existing:
75
+ logger.debug(
76
+ "Merging node: %s to %s", result[id_field], existing[id_field]
77
+ )
78
+ # Merge existing data with new data, prioritizing non-None values
79
+ for key, value in result.items():
80
+ if existing.get(key) is None and value is not None:
81
+ existing[key] = value
82
+ else:
83
+ logger.debug("Adding new node: %s", result[id_field])
84
+ results[result[id_field]] = result
85
+ return list(results.values())
86
+
87
+
88
+ @timeit
89
+ def link_ontology_nodes(
90
+ neo4j_session: neo4j.Session,
91
+ module_name: str,
92
+ update_tag: int,
93
+ ) -> None:
94
+ """Link ontology nodes in the Neo4j graph database based on the ontology mapping.
95
+
96
+ This function retrieves the ontology mapping for the specified module and
97
+ executes the relationship statements defined in the mapping to link nodes
98
+ in the Neo4j graph database.
99
+
100
+ Args:
101
+ neo4j_session (neo4j.Session): The Neo4j session to use for executing the relationship statements.
102
+ module_name (str): The name of the ontology module for which to link nodes (eg. users, devices, etc.).
103
+ update_tag (int): The update tag of the current run, used to tag the changes in the graph.
104
+ """
105
+ modules_mapping = ONTOLOGY_MAPPING.get(module_name)
106
+ if modules_mapping is None:
107
+ logger.warning("No ontology mapping found for module '%s'.", module_name)
108
+ return
109
+ for source, mapping in modules_mapping.items():
110
+ if len(mapping.rels) == 0:
111
+ continue
112
+ formated_json = {
113
+ "name": f"Linking ontology nodes for {module_name} for source {source}",
114
+ "statements": [asdict(rel) for rel in mapping.rels],
115
+ }
116
+ GraphJob.run_from_json(
117
+ neo4j_session,
118
+ formated_json,
119
+ {"UPDATE_TAG": update_tag},
120
+ short_name=f"ontology.{module_name}.{source}.linking",
121
+ )
@@ -6,6 +6,7 @@ from typing import List
6
6
  import neo4j
7
7
  from pdpyras import APISession
8
8
 
9
+ from cartography.client.core.tx import run_write_query
9
10
  from cartography.util import timeit
10
11
 
11
12
  logger = logging.getLogger(__name__)
@@ -72,7 +73,8 @@ def load_escalation_policy_data(
72
73
  for team in policy["teams"]:
73
74
  teams.append({"escalation_policy": policy["id"], "team": team["id"]})
74
75
 
75
- neo4j_session.run(
76
+ run_write_query(
77
+ neo4j_session,
76
78
  ingestion_cypher_query,
77
79
  EscalationPolicies=data,
78
80
  update_tag=update_tag,
@@ -115,7 +117,8 @@ def _attach_rules(
115
117
  elif target["type"] == "schedule":
116
118
  schedules.append({"rule": rule["id"], "schedule": target["id"]})
117
119
 
118
- neo4j_session.run(
120
+ run_write_query(
121
+ neo4j_session,
119
122
  ingestion_cypher_query,
120
123
  Rules=data,
121
124
  update_tag=update_tag,
@@ -140,7 +143,8 @@ def _attach_user_targets(
140
143
  MERGE (p)-[r:ASSOCIATED_WITH]->(u)
141
144
  ON CREATE SET r.firstseen = timestamp()
142
145
  """
143
- neo4j_session.run(
146
+ run_write_query(
147
+ neo4j_session,
144
148
  ingestion_cypher_query,
145
149
  Relations=data,
146
150
  update_tag=update_tag,
@@ -162,7 +166,8 @@ def _attach_schedule_targets(
162
166
  MERGE (p)-[r:ASSOCIATED_WITH]->(s)
163
167
  ON CREATE SET r.firstseen = timestamp()
164
168
  """
165
- neo4j_session.run(
169
+ run_write_query(
170
+ neo4j_session,
166
171
  ingestion_cypher_query,
167
172
  Relations=data,
168
173
  update_tag=update_tag,
@@ -184,7 +189,8 @@ def _attach_services(
184
189
  MERGE (s)-[r:ASSOCIATED_WITH]->(p)
185
190
  ON CREATE SET r.firstseen = timestamp()
186
191
  """
187
- neo4j_session.run(
192
+ run_write_query(
193
+ neo4j_session,
188
194
  ingestion_cypher_query,
189
195
  Relations=data,
190
196
  update_tag=update_tag,
@@ -206,7 +212,8 @@ def _attach_teams(
206
212
  MERGE (t)-[r:ASSOCIATED_WITH]->(p)
207
213
  ON CREATE SET r.firstseen = timestamp()
208
214
  """
209
- neo4j_session.run(
215
+ run_write_query(
216
+ neo4j_session,
210
217
  ingestion_cypher_query,
211
218
  Relations=data,
212
219
  update_tag=update_tag,
@@ -7,6 +7,7 @@ import dateutil.parser
7
7
  import neo4j
8
8
  from pdpyras import APISession
9
9
 
10
+ from cartography.client.core.tx import run_write_query
10
11
  from cartography.util import timeit
11
12
 
12
13
  logger = logging.getLogger(__name__)
@@ -63,7 +64,8 @@ def load_schedule_data(
63
64
  layer["_schedule_id"] = schedule["id"]
64
65
  layers.append(layer)
65
66
 
66
- neo4j_session.run(
67
+ run_write_query(
68
+ neo4j_session,
67
69
  ingestion_cypher_query,
68
70
  Schedules=data,
69
71
  update_tag=update_tag,
@@ -87,7 +89,8 @@ def _attach_users(
87
89
  MERGE (u)-[r:MEMBER_OF]->(s)
88
90
  ON CREATE SET r.firstseen = timestamp()
89
91
  """
90
- neo4j_session.run(
92
+ run_write_query(
93
+ neo4j_session,
91
94
  ingestion_cypher_query,
92
95
  Relations=data,
93
96
  update_tag=update_tag,
@@ -129,7 +132,8 @@ def _attach_layers(
129
132
  users.append(
130
133
  {"layer_id": layer["_layer_id"], "user": user["user"]["id"]},
131
134
  )
132
- neo4j_session.run(
135
+ run_write_query(
136
+ neo4j_session,
133
137
  ingestion_cypher_query,
134
138
  Layers=data,
135
139
  update_tag=update_tag,
@@ -152,7 +156,8 @@ def _attach_layer_users(
152
156
  MERGE (u)-[r:MEMBER_OF]->(l)
153
157
  ON CREATE SET r.firstseen = timestamp()
154
158
  """
155
- neo4j_session.run(
159
+ run_write_query(
160
+ neo4j_session,
156
161
  ingestion_cypher_query,
157
162
  Relations=data,
158
163
  update_tag=update_tag,
@@ -7,6 +7,7 @@ import dateutil.parser
7
7
  import neo4j
8
8
  from pdpyras import APISession
9
9
 
10
+ from cartography.client.core.tx import run_write_query
10
11
  from cartography.util import timeit
11
12
 
12
13
  logger = logging.getLogger(__name__)
@@ -96,7 +97,8 @@ def load_service_data(
96
97
  for team in service["teams"]:
97
98
  team_relations.append({"service": service["id"], "team": team["id"]})
98
99
 
99
- neo4j_session.run(
100
+ run_write_query(
101
+ neo4j_session,
100
102
  ingestion_cypher_query,
101
103
  Services=data,
102
104
  update_tag=update_tag,
@@ -120,7 +122,8 @@ def _attach_teams(
120
122
  MERGE (t)-[r:ASSOCIATED_WITH]->(s)
121
123
  ON CREATE SET r.firstseen = timestamp()
122
124
  """
123
- neo4j_session.run(
125
+ run_write_query(
126
+ neo4j_session,
124
127
  ingestion_cypher_query,
125
128
  Relations=data,
126
129
  update_tag=update_tag,
@@ -162,7 +165,8 @@ def load_integration_data(
162
165
  created_at = dateutil.parser.parse(integration["created_at"])
163
166
  integration["created_at"] = int(created_at.timestamp())
164
167
 
165
- neo4j_session.run(
168
+ run_write_query(
169
+ neo4j_session,
166
170
  ingestion_cypher_query,
167
171
  Integrations=data,
168
172
  update_tag=update_tag,
@@ -6,6 +6,7 @@ from typing import List
6
6
  import neo4j
7
7
  from pdpyras import APISession
8
8
 
9
+ from cartography.client.core.tx import run_write_query
9
10
  from cartography.util import timeit
10
11
 
11
12
  logger = logging.getLogger(__name__)
@@ -68,7 +69,8 @@ def load_team_data(
68
69
  """
69
70
  logger.info(f"Loading {len(data)} pagerduty teams.")
70
71
 
71
- neo4j_session.run(
72
+ run_write_query(
73
+ neo4j_session,
72
74
  ingestion_cypher_query,
73
75
  Teams=data,
74
76
  update_tag=update_tag,
@@ -90,7 +92,8 @@ def load_team_relations(
90
92
  ON CREATE SET r.firstseen = timestamp()
91
93
  SET r.role = relation.role
92
94
  """
93
- neo4j_session.run(
95
+ run_write_query(
96
+ neo4j_session,
94
97
  ingestion_cypher_query,
95
98
  Relations=data,
96
99
  update_tag=update_tag,
@@ -6,6 +6,7 @@ from typing import List
6
6
  import neo4j
7
7
  from pdpyras import APISession
8
8
 
9
+ from cartography.client.core.tx import run_write_query
9
10
  from cartography.util import timeit
10
11
 
11
12
  logger = logging.getLogger(__name__)
@@ -57,7 +58,8 @@ def load_user_data(
57
58
  """
58
59
  logger.info(f"Loading {len(data)} pagerduty users.")
59
60
 
60
- neo4j_session.run(
61
+ run_write_query(
62
+ neo4j_session,
61
63
  ingestion_cypher_query,
62
64
  Users=data,
63
65
  update_tag=update_tag,
@@ -6,6 +6,7 @@ from typing import List
6
6
  import neo4j
7
7
  from pdpyras import APISession
8
8
 
9
+ from cartography.client.core.tx import run_write_query
9
10
  from cartography.util import timeit
10
11
 
11
12
  logger = logging.getLogger(__name__)
@@ -53,7 +54,8 @@ def load_vendor_data(
53
54
  """
54
55
  logger.info(f"Loading {len(data)} pagerduty vendors.")
55
56
 
56
- neo4j_session.run(
57
+ run_write_query(
58
+ neo4j_session,
57
59
  ingestion_cypher_query,
58
60
  Vendors=data,
59
61
  update_tag=update_tag,