cartography 0.113.0__py3-none-any.whl → 0.115.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 (96) hide show
  1. cartography/_version.py +2 -2
  2. cartography/cli.py +10 -2
  3. cartography/client/core/tx.py +11 -0
  4. cartography/config.py +4 -0
  5. cartography/data/indexes.cypher +0 -27
  6. cartography/intel/aws/config.py +7 -3
  7. cartography/intel/aws/ecr.py +9 -9
  8. cartography/intel/aws/iam.py +741 -492
  9. cartography/intel/aws/identitycenter.py +240 -13
  10. cartography/intel/aws/lambda_function.py +69 -2
  11. cartography/intel/aws/organizations.py +10 -9
  12. cartography/intel/aws/permission_relationships.py +7 -17
  13. cartography/intel/aws/redshift.py +9 -4
  14. cartography/intel/aws/route53.py +53 -3
  15. cartography/intel/aws/securityhub.py +3 -1
  16. cartography/intel/azure/__init__.py +24 -0
  17. cartography/intel/azure/app_service.py +105 -0
  18. cartography/intel/azure/functions.py +124 -0
  19. cartography/intel/azure/logic_apps.py +101 -0
  20. cartography/intel/create_indexes.py +2 -1
  21. cartography/intel/dns.py +5 -2
  22. cartography/intel/entra/__init__.py +31 -0
  23. cartography/intel/entra/app_role_assignments.py +277 -0
  24. cartography/intel/entra/applications.py +4 -238
  25. cartography/intel/entra/federation/__init__.py +0 -0
  26. cartography/intel/entra/federation/aws_identity_center.py +77 -0
  27. cartography/intel/entra/service_principals.py +217 -0
  28. cartography/intel/gcp/__init__.py +136 -440
  29. cartography/intel/gcp/clients.py +65 -0
  30. cartography/intel/gcp/compute.py +18 -44
  31. cartography/intel/gcp/crm/__init__.py +0 -0
  32. cartography/intel/gcp/crm/folders.py +108 -0
  33. cartography/intel/gcp/crm/orgs.py +65 -0
  34. cartography/intel/gcp/crm/projects.py +109 -0
  35. cartography/intel/gcp/dns.py +2 -1
  36. cartography/intel/gcp/gke.py +72 -113
  37. cartography/intel/github/__init__.py +41 -0
  38. cartography/intel/github/commits.py +423 -0
  39. cartography/intel/github/repos.py +76 -45
  40. cartography/intel/gsuite/api.py +17 -4
  41. cartography/intel/okta/applications.py +9 -4
  42. cartography/intel/okta/awssaml.py +5 -2
  43. cartography/intel/okta/factors.py +3 -1
  44. cartography/intel/okta/groups.py +5 -2
  45. cartography/intel/okta/organization.py +3 -1
  46. cartography/intel/okta/origins.py +3 -1
  47. cartography/intel/okta/roles.py +5 -2
  48. cartography/intel/okta/users.py +3 -1
  49. cartography/models/aws/iam/access_key.py +103 -0
  50. cartography/models/aws/iam/account_role.py +24 -0
  51. cartography/models/aws/iam/federated_principal.py +60 -0
  52. cartography/models/aws/iam/group.py +60 -0
  53. cartography/models/aws/iam/group_membership.py +26 -0
  54. cartography/models/aws/iam/inline_policy.py +78 -0
  55. cartography/models/aws/iam/managed_policy.py +51 -0
  56. cartography/models/aws/iam/policy_statement.py +57 -0
  57. cartography/models/aws/iam/role.py +83 -0
  58. cartography/models/aws/iam/root_principal.py +52 -0
  59. cartography/models/aws/iam/service_principal.py +30 -0
  60. cartography/models/aws/iam/sts_assumerole_allow.py +38 -0
  61. cartography/models/aws/iam/user.py +54 -0
  62. cartography/models/aws/identitycenter/awspermissionset.py +24 -1
  63. cartography/models/aws/identitycenter/awssogroup.py +70 -0
  64. cartography/models/aws/identitycenter/awsssouser.py +37 -1
  65. cartography/models/aws/lambda_function/lambda_function.py +2 -0
  66. cartography/models/azure/__init__.py +0 -0
  67. cartography/models/azure/app_service.py +59 -0
  68. cartography/models/azure/function_app.py +59 -0
  69. cartography/models/azure/logic_apps.py +56 -0
  70. cartography/models/entra/entra_user_to_aws_sso.py +41 -0
  71. cartography/models/entra/service_principal.py +104 -0
  72. cartography/models/entra/user.py +18 -0
  73. cartography/models/gcp/compute/subnet.py +74 -0
  74. cartography/models/gcp/crm/__init__.py +0 -0
  75. cartography/models/gcp/crm/folders.py +98 -0
  76. cartography/models/gcp/crm/organizations.py +21 -0
  77. cartography/models/gcp/crm/projects.py +100 -0
  78. cartography/models/gcp/gke.py +69 -0
  79. cartography/models/github/commits.py +63 -0
  80. {cartography-0.113.0.dist-info → cartography-0.115.0.dist-info}/METADATA +8 -5
  81. {cartography-0.113.0.dist-info → cartography-0.115.0.dist-info}/RECORD +85 -56
  82. cartography/data/jobs/cleanup/aws_import_account_access_key_cleanup.json +0 -17
  83. cartography/data/jobs/cleanup/aws_import_groups_cleanup.json +0 -13
  84. cartography/data/jobs/cleanup/aws_import_principals_cleanup.json +0 -30
  85. cartography/data/jobs/cleanup/aws_import_roles_cleanup.json +0 -13
  86. cartography/data/jobs/cleanup/aws_import_users_cleanup.json +0 -8
  87. cartography/data/jobs/cleanup/gcp_compute_vpc_subnet_cleanup.json +0 -35
  88. cartography/data/jobs/cleanup/gcp_crm_folder_cleanup.json +0 -23
  89. cartography/data/jobs/cleanup/gcp_crm_organization_cleanup.json +0 -17
  90. cartography/data/jobs/cleanup/gcp_crm_project_cleanup.json +0 -23
  91. cartography/data/jobs/cleanup/gcp_gke_cluster_cleanup.json +0 -17
  92. cartography/intel/gcp/crm.py +0 -355
  93. {cartography-0.113.0.dist-info → cartography-0.115.0.dist-info}/WHEEL +0 -0
  94. {cartography-0.113.0.dist-info → cartography-0.115.0.dist-info}/entry_points.txt +0 -0
  95. {cartography-0.113.0.dist-info → cartography-0.115.0.dist-info}/licenses/LICENSE +0 -0
  96. {cartography-0.113.0.dist-info → cartography-0.115.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,217 @@
1
+ import logging
2
+ import re
3
+ from typing import Any
4
+ from typing import AsyncGenerator
5
+
6
+ import neo4j
7
+ from azure.identity import ClientSecretCredential
8
+ from msgraph import GraphServiceClient
9
+ from msgraph.generated.models.service_principal import ServicePrincipal
10
+
11
+ from cartography.client.core.tx import load
12
+ from cartography.graph.job import GraphJob
13
+ from cartography.models.entra.service_principal import EntraServicePrincipalSchema
14
+ from cartography.util import timeit
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ SERVICE_PRINCIPALS_PAGE_SIZE = 999
19
+
20
+
21
+ @timeit
22
+ async def get_entra_service_principals(
23
+ client: GraphServiceClient,
24
+ ) -> AsyncGenerator[ServicePrincipal, None]:
25
+ """
26
+ Gets Entra service principals using the Microsoft Graph API with a generator.
27
+
28
+ :param client: GraphServiceClient
29
+ :return: Generator of raw ServicePrincipal objects from Microsoft Graph
30
+ """
31
+ count = 0
32
+ # Get all service principals with pagination
33
+ request_configuration = client.service_principals.ServicePrincipalsRequestBuilderGetRequestConfiguration(
34
+ query_parameters=client.service_principals.ServicePrincipalsRequestBuilderGetQueryParameters(
35
+ top=SERVICE_PRINCIPALS_PAGE_SIZE
36
+ )
37
+ )
38
+ page = await client.service_principals.get(
39
+ request_configuration=request_configuration
40
+ )
41
+
42
+ while page:
43
+ if page.value:
44
+ for spn in page.value:
45
+ count += 1
46
+ yield spn
47
+
48
+ if not page.odata_next_link:
49
+ break
50
+ page = await client.service_principals.with_url(page.odata_next_link).get()
51
+
52
+ logger.info(f"Retrieved {count} Entra service principals total")
53
+
54
+
55
+ async def get_service_principal_by_app_id(
56
+ client: GraphServiceClient, app_id: str
57
+ ) -> ServicePrincipal | None:
58
+ """
59
+ Gets a service principal by app_id using the Microsoft Graph API.
60
+ This function is extracted from the original app_role_assignments logic.
61
+
62
+ :param client: GraphServiceClient
63
+ :param app_id: Application ID to search for
64
+ :return: ServicePrincipal object or None if not found
65
+ """
66
+ service_principals_page = await client.service_principals.get(
67
+ request_configuration=client.service_principals.ServicePrincipalsRequestBuilderGetRequestConfiguration(
68
+ query_parameters=client.service_principals.ServicePrincipalsRequestBuilderGetQueryParameters(
69
+ filter=f"appId eq '{app_id}'"
70
+ )
71
+ )
72
+ )
73
+
74
+ if not service_principals_page or not service_principals_page.value:
75
+ logger.warning(
76
+ f"No service principal found for application {app_id}. Continuing."
77
+ )
78
+ return None
79
+
80
+ return service_principals_page.value[0]
81
+
82
+
83
+ def transform_service_principals(
84
+ service_principals: list[ServicePrincipal],
85
+ ) -> list[dict[str, Any]]:
86
+ result = []
87
+ for spn in service_principals:
88
+ aws_identity_center_instance_id = None
89
+ match = re.search(r"d-[a-z0-9]{10}", spn.login_url or "")
90
+ aws_identity_center_instance_id = match.group(0) if match else None
91
+ transformed = {
92
+ "id": spn.id,
93
+ "app_id": spn.app_id,
94
+ "account_enabled": spn.account_enabled,
95
+ # uuid.UUID to string
96
+ "app_owner_organization_id": (
97
+ str(spn.app_owner_organization_id)
98
+ if spn.app_owner_organization_id
99
+ else None
100
+ ),
101
+ "aws_identity_center_instance_id": aws_identity_center_instance_id,
102
+ "display_name": spn.display_name,
103
+ "login_url": spn.login_url,
104
+ "preferred_single_sign_on_mode": spn.preferred_single_sign_on_mode,
105
+ "preferred_token_signing_key_thumbprint": spn.preferred_token_signing_key_thumbprint,
106
+ "reply_urls": spn.reply_urls,
107
+ "service_principal_type": spn.service_principal_type,
108
+ "sign_in_audience": spn.sign_in_audience,
109
+ "tags": spn.tags,
110
+ # uuid.UUID to string
111
+ "token_encryption_key_id": (
112
+ str(spn.token_encryption_key_id)
113
+ if spn.token_encryption_key_id
114
+ else None
115
+ ),
116
+ }
117
+ result.append(transformed)
118
+ return result
119
+
120
+
121
+ @timeit
122
+ def load_service_principals(
123
+ neo4j_session: neo4j.Session,
124
+ service_principal_data: list[dict[str, Any]],
125
+ update_tag: int,
126
+ tenant_id: str,
127
+ ) -> None:
128
+ load(
129
+ neo4j_session,
130
+ EntraServicePrincipalSchema(),
131
+ service_principal_data,
132
+ lastupdated=update_tag,
133
+ TENANT_ID=tenant_id,
134
+ )
135
+
136
+
137
+ @timeit
138
+ def cleanup_service_principals(
139
+ neo4j_session: neo4j.Session, common_job_parameters: dict[str, Any]
140
+ ) -> None:
141
+ """
142
+ Delete Entra service principals from the graph if they were not updated in the last sync.
143
+
144
+ :param neo4j_session: Neo4j session
145
+ :param common_job_parameters: Common job parameters containing UPDATE_TAG and TENANT_ID
146
+ """
147
+ GraphJob.from_node_schema(EntraServicePrincipalSchema(), common_job_parameters).run(
148
+ neo4j_session
149
+ )
150
+
151
+
152
+ @timeit
153
+ async def sync_service_principals(
154
+ neo4j_session: neo4j.Session,
155
+ tenant_id: str,
156
+ client_id: str,
157
+ client_secret: str,
158
+ update_tag: int,
159
+ common_job_parameters: dict[str, Any],
160
+ ) -> None:
161
+ """
162
+ Sync Entra service principals to the graph.
163
+
164
+ :param neo4j_session: Neo4j session
165
+ :param tenant_id: Entra tenant ID
166
+ :param client_id: Azure application client ID
167
+ :param client_secret: Azure application client secret
168
+ :param update_tag: Update tag for tracking data freshness
169
+ :param common_job_parameters: Common job parameters for cleanup
170
+ """
171
+ # Create credentials and client
172
+ credential = ClientSecretCredential(
173
+ tenant_id=tenant_id,
174
+ client_id=client_id,
175
+ client_secret=client_secret,
176
+ )
177
+
178
+ client = GraphServiceClient(
179
+ credential,
180
+ scopes=["https://graph.microsoft.com/.default"],
181
+ )
182
+ service_principals_batch = []
183
+ batch_size = 50 # Batch size for service principals
184
+ total_count = 0
185
+
186
+ # Stream service principals and process in batches
187
+ async for spn in get_entra_service_principals(client):
188
+ service_principals_batch.append(spn)
189
+ total_count += 1
190
+
191
+ # Transform and load service principals in batches
192
+ if len(service_principals_batch) >= batch_size:
193
+ transformed_service_principals = transform_service_principals(
194
+ service_principals_batch
195
+ )
196
+ load_service_principals(
197
+ neo4j_session, transformed_service_principals, update_tag, tenant_id
198
+ )
199
+ logger.info(
200
+ f"Loaded batch of {len(service_principals_batch)} service principals (total: {total_count})"
201
+ )
202
+ service_principals_batch.clear()
203
+ transformed_service_principals.clear()
204
+
205
+ # Process remaining service principals
206
+ if service_principals_batch:
207
+ transformed_service_principals = transform_service_principals(
208
+ service_principals_batch
209
+ )
210
+ load_service_principals(
211
+ neo4j_session, transformed_service_principals, update_tag, tenant_id
212
+ )
213
+ service_principals_batch.clear()
214
+ transformed_service_principals.clear()
215
+
216
+ logger.info(f"Completed loading {total_count} service principals")
217
+ cleanup_service_principals(neo4j_session, common_job_parameters)