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.
- cartography/_version.py +2 -2
- cartography/cli.py +10 -2
- cartography/client/core/tx.py +11 -0
- cartography/config.py +4 -0
- cartography/data/indexes.cypher +0 -27
- cartography/intel/aws/config.py +7 -3
- cartography/intel/aws/ecr.py +9 -9
- cartography/intel/aws/iam.py +741 -492
- cartography/intel/aws/identitycenter.py +240 -13
- cartography/intel/aws/lambda_function.py +69 -2
- cartography/intel/aws/organizations.py +10 -9
- cartography/intel/aws/permission_relationships.py +7 -17
- cartography/intel/aws/redshift.py +9 -4
- cartography/intel/aws/route53.py +53 -3
- cartography/intel/aws/securityhub.py +3 -1
- cartography/intel/azure/__init__.py +24 -0
- cartography/intel/azure/app_service.py +105 -0
- cartography/intel/azure/functions.py +124 -0
- cartography/intel/azure/logic_apps.py +101 -0
- cartography/intel/create_indexes.py +2 -1
- cartography/intel/dns.py +5 -2
- cartography/intel/entra/__init__.py +31 -0
- cartography/intel/entra/app_role_assignments.py +277 -0
- cartography/intel/entra/applications.py +4 -238
- cartography/intel/entra/federation/__init__.py +0 -0
- cartography/intel/entra/federation/aws_identity_center.py +77 -0
- cartography/intel/entra/service_principals.py +217 -0
- cartography/intel/gcp/__init__.py +136 -440
- cartography/intel/gcp/clients.py +65 -0
- cartography/intel/gcp/compute.py +18 -44
- cartography/intel/gcp/crm/__init__.py +0 -0
- cartography/intel/gcp/crm/folders.py +108 -0
- cartography/intel/gcp/crm/orgs.py +65 -0
- cartography/intel/gcp/crm/projects.py +109 -0
- cartography/intel/gcp/dns.py +2 -1
- cartography/intel/gcp/gke.py +72 -113
- cartography/intel/github/__init__.py +41 -0
- cartography/intel/github/commits.py +423 -0
- cartography/intel/github/repos.py +76 -45
- cartography/intel/gsuite/api.py +17 -4
- cartography/intel/okta/applications.py +9 -4
- cartography/intel/okta/awssaml.py +5 -2
- cartography/intel/okta/factors.py +3 -1
- cartography/intel/okta/groups.py +5 -2
- cartography/intel/okta/organization.py +3 -1
- cartography/intel/okta/origins.py +3 -1
- cartography/intel/okta/roles.py +5 -2
- cartography/intel/okta/users.py +3 -1
- cartography/models/aws/iam/access_key.py +103 -0
- cartography/models/aws/iam/account_role.py +24 -0
- cartography/models/aws/iam/federated_principal.py +60 -0
- cartography/models/aws/iam/group.py +60 -0
- cartography/models/aws/iam/group_membership.py +26 -0
- cartography/models/aws/iam/inline_policy.py +78 -0
- cartography/models/aws/iam/managed_policy.py +51 -0
- cartography/models/aws/iam/policy_statement.py +57 -0
- cartography/models/aws/iam/role.py +83 -0
- cartography/models/aws/iam/root_principal.py +52 -0
- cartography/models/aws/iam/service_principal.py +30 -0
- cartography/models/aws/iam/sts_assumerole_allow.py +38 -0
- cartography/models/aws/iam/user.py +54 -0
- cartography/models/aws/identitycenter/awspermissionset.py +24 -1
- cartography/models/aws/identitycenter/awssogroup.py +70 -0
- cartography/models/aws/identitycenter/awsssouser.py +37 -1
- cartography/models/aws/lambda_function/lambda_function.py +2 -0
- cartography/models/azure/__init__.py +0 -0
- cartography/models/azure/app_service.py +59 -0
- cartography/models/azure/function_app.py +59 -0
- cartography/models/azure/logic_apps.py +56 -0
- cartography/models/entra/entra_user_to_aws_sso.py +41 -0
- cartography/models/entra/service_principal.py +104 -0
- cartography/models/entra/user.py +18 -0
- cartography/models/gcp/compute/subnet.py +74 -0
- cartography/models/gcp/crm/__init__.py +0 -0
- cartography/models/gcp/crm/folders.py +98 -0
- cartography/models/gcp/crm/organizations.py +21 -0
- cartography/models/gcp/crm/projects.py +100 -0
- cartography/models/gcp/gke.py +69 -0
- cartography/models/github/commits.py +63 -0
- {cartography-0.113.0.dist-info → cartography-0.115.0.dist-info}/METADATA +8 -5
- {cartography-0.113.0.dist-info → cartography-0.115.0.dist-info}/RECORD +85 -56
- cartography/data/jobs/cleanup/aws_import_account_access_key_cleanup.json +0 -17
- cartography/data/jobs/cleanup/aws_import_groups_cleanup.json +0 -13
- cartography/data/jobs/cleanup/aws_import_principals_cleanup.json +0 -30
- cartography/data/jobs/cleanup/aws_import_roles_cleanup.json +0 -13
- cartography/data/jobs/cleanup/aws_import_users_cleanup.json +0 -8
- cartography/data/jobs/cleanup/gcp_compute_vpc_subnet_cleanup.json +0 -35
- cartography/data/jobs/cleanup/gcp_crm_folder_cleanup.json +0 -23
- cartography/data/jobs/cleanup/gcp_crm_organization_cleanup.json +0 -17
- cartography/data/jobs/cleanup/gcp_crm_project_cleanup.json +0 -23
- cartography/data/jobs/cleanup/gcp_gke_cluster_cleanup.json +0 -17
- cartography/intel/gcp/crm.py +0 -355
- {cartography-0.113.0.dist-info → cartography-0.115.0.dist-info}/WHEEL +0 -0
- {cartography-0.113.0.dist-info → cartography-0.115.0.dist-info}/entry_points.txt +0 -0
- {cartography-0.113.0.dist-info → cartography-0.115.0.dist-info}/licenses/LICENSE +0 -0
- {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)
|