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
@@ -1,355 +0,0 @@
1
- import logging
2
- from typing import Dict
3
- from typing import List
4
-
5
- import neo4j
6
- from googleapiclient.discovery import Resource
7
- from googleapiclient.errors import HttpError
8
-
9
- from cartography.client.core.tx import run_write_query
10
- from cartography.util import run_cleanup_job
11
- from cartography.util import timeit
12
-
13
- logger = logging.getLogger(__name__)
14
-
15
-
16
- GOOGLE_API_NUM_RETRIES = 5
17
-
18
-
19
- @timeit
20
- def get_all_groups(admin: Resource) -> List[Dict]:
21
- """
22
- Return list of Google Groups in your organization
23
- Returns empty list if we are unable to enumerate the groups for any reasons
24
-
25
- googleapiclient.discovery.build('admin', 'directory_v1', credentials=credentials, cache_discovery=False)
26
-
27
- :param admin: google's apiclient discovery resource object. From googleapiclient.discovery.build
28
- See https://googleapis.github.io/google-api-python-client/docs/epy/googleapiclient.discovery-module.html#build.
29
- :return: List of Google groups in domain
30
- """
31
- request = admin.groups().list(
32
- customer="my_customer",
33
- maxResults=20,
34
- orderBy="email",
35
- )
36
- response_objects = []
37
- while request is not None:
38
- try:
39
- resp = request.execute(num_retries=GOOGLE_API_NUM_RETRIES)
40
- response_objects.append(resp)
41
- request = admin.groups().list_next(request, resp)
42
- except HttpError as e:
43
- if (
44
- e.resp.status == 403
45
- and "Request had insufficient authentication scopes" in str(e)
46
- ):
47
- logger.error(
48
- "Missing required GSuite scopes. If using the gcloud CLI, ",
49
- "run: gcloud auth application-default login --scopes="
50
- '"https://www.googleapis.com/auth/admin.directory.user.readonly,'
51
- "https://www.googleapis.com/auth/admin.directory.group.readonly,"
52
- "https://www.googleapis.com/auth/admin.directory.group.member.readonly,"
53
- 'https://www.googleapis.com/auth/cloud-platform"',
54
- )
55
- raise
56
- return response_objects
57
-
58
-
59
- @timeit
60
- def transform_groups(response_objects: List[Dict]) -> List[Dict]:
61
- """Strips list of API response objects to return list of group objects only
62
-
63
- :param response_objects:
64
- :return: list of dictionary objects as defined in /docs/root/modules/gsuite/schema.md
65
- """
66
- groups: List[Dict] = []
67
- for response_object in response_objects:
68
- for group in response_object["groups"]:
69
- groups.append(group)
70
- return groups
71
-
72
-
73
- @timeit
74
- def transform_users(response_objects: List[Dict]) -> List[Dict]:
75
- """Strips list of API response objects to return list of group objects only
76
- :param response_objects:
77
- :return: list of dictionary objects as defined in /docs/root/modules/gsuite/schema.md
78
- """
79
- users: List[Dict] = []
80
- for response_object in response_objects:
81
- for user in response_object["users"]:
82
- users.append(user)
83
- return users
84
-
85
-
86
- @timeit
87
- def get_all_groups_for_email(admin: Resource, email: str) -> List[Dict]:
88
- """Fetch all groups of which the given group is a member
89
-
90
- Arguments:
91
- email: A string representing the email address for the group
92
-
93
- Returns a list of Group models
94
- Throws GoogleException
95
- """
96
- request = admin.groups().list(userKey=email, maxResults=500)
97
- groups: List[Dict] = []
98
- while request is not None:
99
- resp = request.execute(num_retries=GOOGLE_API_NUM_RETRIES)
100
- groups = groups + resp.get("groups", [])
101
- request = admin.groups().list_next(request, resp)
102
- return groups
103
-
104
-
105
- @timeit
106
- def get_members_for_group(admin: Resource, group_email: str) -> List[Dict]:
107
- """Get all members for a google group
108
-
109
- :param group_email: A string representing the email address for the group
110
-
111
- :return: List of dictionaries representing Users or Groups.
112
- """
113
- request = admin.members().list(
114
- groupKey=group_email,
115
- maxResults=500,
116
- )
117
- members: List[Dict] = []
118
- while request is not None:
119
- resp = request.execute(num_retries=GOOGLE_API_NUM_RETRIES)
120
- members = members + resp.get("members", [])
121
- request = admin.members().list_next(request, resp)
122
-
123
- return members
124
-
125
-
126
- @timeit
127
- def get_all_users(admin: Resource) -> List[Dict]:
128
- """
129
- Return list of Google Users in your organization
130
- Returns empty list if we are unable to enumerate the groups for any reasons
131
- https://developers.google.com/admin-sdk/directory/v1/guides/manage-users
132
-
133
- :param admin: apiclient discovery resource object
134
- see
135
- :return: List of Google users in domain
136
- see https://developers.google.com/admin-sdk/directory/v1/guides/manage-users#get_all_domain_users
137
- """
138
- request = admin.users().list(
139
- customer="my_customer",
140
- maxResults=500,
141
- orderBy="email",
142
- )
143
- response_objects = []
144
- while request is not None:
145
- resp = request.execute(num_retries=GOOGLE_API_NUM_RETRIES)
146
- response_objects.append(resp)
147
- request = admin.users().list_next(request, resp)
148
- return response_objects
149
-
150
-
151
- @timeit
152
- def load_gsuite_groups(
153
- neo4j_session: neo4j.Session,
154
- groups: List[Dict],
155
- gsuite_update_tag: int,
156
- ) -> None:
157
- ingestion_qry = """
158
- UNWIND $GroupData as group
159
- MERGE (g:GSuiteGroup{id: group.id})
160
- ON CREATE SET
161
- g.firstseen = $UpdateTag,
162
- g.group_id = group.id
163
- SET
164
- g.admin_created = group.adminCreated,
165
- g.description = group.description,
166
- g.direct_members_count = group.directMembersCount,
167
- g.email = group.email,
168
- g.etag = group.etag,
169
- g.kind = group.kind,
170
- g.name = group.name,
171
- g:GCPPrincipal,
172
- g.lastupdated = $UpdateTag
173
- """
174
- logger.info(f"Ingesting {len(groups)} gsuite groups")
175
- run_write_query(
176
- neo4j_session,
177
- ingestion_qry,
178
- GroupData=groups,
179
- UpdateTag=gsuite_update_tag,
180
- )
181
-
182
-
183
- @timeit
184
- def load_gsuite_users(
185
- neo4j_session: neo4j.Session,
186
- users: List[Dict],
187
- gsuite_update_tag: int,
188
- ) -> None:
189
- ingestion_qry = """
190
- UNWIND $UserData as user
191
- MERGE (u:GSuiteUser{id: user.id})
192
- ON CREATE SET
193
- u.user_id = user.id,
194
- u.firstseen = $UpdateTag
195
- SET
196
- u.agreed_to_terms = user.agreedToTerms,
197
- u.archived = user.archived,
198
- u.change_password_at_next_login = user.changePasswordAtNextLogin,
199
- u.creation_time = user.creationTime,
200
- u.customer_id = user.customerId,
201
- u.etag = user.etag,
202
- u.include_in_global_address_list = user.includeInGlobalAddressList,
203
- u.ip_whitelisted = user.ipWhitelisted,
204
- u.is_admin = user.isAdmin,
205
- u.is_delegated_admin = user.isDelegatedAdmin,
206
- u.is_enforced_in_2_sv = user.isEnforcedIn2Sv,
207
- u.is_enrolled_in_2_sv = user.isEnrolledIn2Sv,
208
- u.is_mailbox_setup = user.isMailboxSetup,
209
- u.kind = user.kind,
210
- u.last_login_time = user.lastLoginTime,
211
- u.name = user.name.fullName,
212
- u.family_name = user.name.familyName,
213
- u.given_name = user.name.givenName,
214
- u.org_unit_path = user.orgUnitPath,
215
- u.primary_email = user.primaryEmail,
216
- u.email = user.primaryEmail,
217
- u.suspended = user.suspended,
218
- u.thumbnail_photo_etag = user.thumbnailPhotoEtag,
219
- u.thumbnail_photo_url = user.thumbnailPhotoUrl,
220
- u:GCPPrincipal,
221
- u.lastupdated = $UpdateTag
222
- """
223
- logger.info(f"Ingesting {len(users)} gsuite users")
224
- run_write_query(
225
- neo4j_session,
226
- ingestion_qry,
227
- UserData=users,
228
- UpdateTag=gsuite_update_tag,
229
- )
230
-
231
-
232
- @timeit
233
- def load_gsuite_members(
234
- neo4j_session: neo4j.Session,
235
- group: Dict,
236
- members: List[Dict],
237
- gsuite_update_tag: int,
238
- ) -> None:
239
- ingestion_qry = """
240
- UNWIND $MemberData as member
241
- MATCH (user:GSuiteUser {id: member.id}),(group:GSuiteGroup {id: $GroupID })
242
- MERGE (user)-[r:MEMBER_GSUITE_GROUP]->(group)
243
- ON CREATE SET
244
- r.firstseen = $UpdateTag
245
- SET
246
- r.lastupdated = $UpdateTag
247
- """
248
- run_write_query(
249
- neo4j_session,
250
- ingestion_qry,
251
- MemberData=members,
252
- GroupID=group.get("id"),
253
- UpdateTag=gsuite_update_tag,
254
- )
255
- membership_qry = """
256
- UNWIND $MemberData as member
257
- MATCH(group_1: GSuiteGroup{id: member.id}), (group_2:GSuiteGroup {id: $GroupID})
258
- MERGE (group_1)-[r:MEMBER_GSUITE_GROUP]->(group_2)
259
- ON CREATE SET
260
- r.firstseen = $UpdateTag
261
- SET
262
- r.lastupdated = $UpdateTag
263
- """
264
- run_write_query(
265
- neo4j_session,
266
- membership_qry,
267
- MemberData=members,
268
- GroupID=group.get("id"),
269
- UpdateTag=gsuite_update_tag,
270
- )
271
-
272
-
273
- @timeit
274
- def cleanup_gsuite_users(
275
- neo4j_session: neo4j.Session,
276
- common_job_parameters: Dict,
277
- ) -> None:
278
- run_cleanup_job(
279
- "gsuite_ingest_users_cleanup.json",
280
- neo4j_session,
281
- common_job_parameters,
282
- )
283
-
284
-
285
- @timeit
286
- def cleanup_gsuite_groups(
287
- neo4j_session: neo4j.Session,
288
- common_job_parameters: Dict,
289
- ) -> None:
290
- run_cleanup_job(
291
- "gsuite_ingest_groups_cleanup.json",
292
- neo4j_session,
293
- common_job_parameters,
294
- )
295
-
296
-
297
- @timeit
298
- def sync_gsuite_users(
299
- neo4j_session: neo4j.Session,
300
- admin: Resource,
301
- gsuite_update_tag: int,
302
- common_job_parameters: Dict,
303
- ) -> None:
304
- """
305
- GET GSuite user objects using the google admin api resource, load the data into Neo4j and clean up stale nodes.
306
-
307
- :param session: The Neo4j session
308
- :param admin: Google admin resource object created by `googleapiclient.discovery.build()`.
309
- See https://googleapis.github.io/google-api-python-client/docs/epy/googleapiclient.discovery-module.html#build.
310
- :param gcp_update_tag: The timestamp value to set our new Neo4j nodes with
311
- :param common_job_parameters: Parameters to carry to the Neo4j jobs
312
- :return: Nothing
313
- """
314
- logger.debug("Syncing GSuite Users")
315
- resp_objs = get_all_users(admin)
316
- users = transform_users(resp_objs)
317
- load_gsuite_users(neo4j_session, users, gsuite_update_tag)
318
- cleanup_gsuite_users(neo4j_session, common_job_parameters)
319
-
320
-
321
- @timeit
322
- def sync_gsuite_groups(
323
- neo4j_session: neo4j.Session,
324
- admin: Resource,
325
- gsuite_update_tag: int,
326
- common_job_parameters: Dict,
327
- ) -> None:
328
- """
329
- GET GSuite group objects using the google admin api resource, load the data into Neo4j and clean up stale nodes.
330
-
331
- :param neo4j_session: The Neo4j session
332
- :param admin: Google admin resource object created by `googleapiclient.discovery.build()`.
333
- See https://googleapis.github.io/google-api-python-client/docs/epy/googleapiclient.discovery-module.html#build.
334
- :param gcp_update_tag: The timestamp value to set our new Neo4j nodes with
335
- :param common_job_parameters: Parameters to carry to the Neo4j jobs
336
- :return: Nothing
337
- """
338
- logger.debug("Syncing GSuite Groups")
339
- resp_objs = get_all_groups(admin)
340
- groups = transform_groups(resp_objs)
341
- load_gsuite_groups(neo4j_session, groups, gsuite_update_tag)
342
- cleanup_gsuite_groups(neo4j_session, common_job_parameters)
343
- sync_gsuite_members(groups, neo4j_session, admin, gsuite_update_tag)
344
-
345
-
346
- @timeit
347
- def sync_gsuite_members(
348
- groups: List[Dict],
349
- neo4j_session: neo4j.Session,
350
- admin: Resource,
351
- gsuite_update_tag: int,
352
- ) -> None:
353
- for group in groups:
354
- members = get_members_for_group(admin, group["email"])
355
- load_gsuite_members(neo4j_session, group, members, gsuite_update_tag)