cartography 0.91.0__py3-none-any.whl → 0.92.0rc1__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.
- cartography/intel/okta/__init__.py +1 -1
- cartography/intel/okta/awssaml.py +117 -9
- {cartography-0.91.0.dist-info → cartography-0.92.0rc1.dist-info}/METADATA +1 -1
- {cartography-0.91.0.dist-info → cartography-0.92.0rc1.dist-info}/RECORD +9 -9
- {cartography-0.91.0.dist-info → cartography-0.92.0rc1.dist-info}/LICENSE +0 -0
- {cartography-0.91.0.dist-info → cartography-0.92.0rc1.dist-info}/NOTICE +0 -0
- {cartography-0.91.0.dist-info → cartography-0.92.0rc1.dist-info}/WHEEL +0 -0
- {cartography-0.91.0.dist-info → cartography-0.92.0rc1.dist-info}/entry_points.txt +0 -0
- {cartography-0.91.0.dist-info → cartography-0.92.0rc1.dist-info}/top_level.txt +0 -0
|
@@ -68,7 +68,7 @@ def start_okta_ingestion(neo4j_session: neo4j.Session, config: Config) -> None:
|
|
|
68
68
|
applications.sync_okta_applications(neo4j_session, config.okta_org_id, config.update_tag, config.okta_api_key)
|
|
69
69
|
factors.sync_users_factors(neo4j_session, config.okta_org_id, config.update_tag, config.okta_api_key, state)
|
|
70
70
|
origins.sync_trusted_origins(neo4j_session, config.okta_org_id, config.update_tag, config.okta_api_key)
|
|
71
|
-
awssaml.sync_okta_aws_saml(neo4j_session, config.okta_saml_role_regex, config.update_tag)
|
|
71
|
+
awssaml.sync_okta_aws_saml(neo4j_session, config.okta_saml_role_regex, config.update_tag, config.okta_org_id)
|
|
72
72
|
|
|
73
73
|
# need creds with permission
|
|
74
74
|
# soft fail as some won't be able to get such high priv token
|
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
# Okta intel module - AWS SAML
|
|
2
2
|
import logging
|
|
3
3
|
import re
|
|
4
|
+
from collections import namedtuple
|
|
4
5
|
from typing import Dict
|
|
5
6
|
from typing import List
|
|
6
7
|
from typing import Optional
|
|
7
8
|
|
|
8
9
|
import neo4j
|
|
9
10
|
|
|
11
|
+
from cartography.client.core.tx import read_list_of_dicts_tx
|
|
12
|
+
from cartography.client.core.tx import read_single_value_tx
|
|
10
13
|
from cartography.util import timeit
|
|
11
14
|
|
|
12
15
|
|
|
16
|
+
AccountRole = namedtuple('AccountRole', ['account_id', 'role_name'])
|
|
17
|
+
OktaGroup = namedtuple('OktaGroup', ['group_id', 'group_name'])
|
|
18
|
+
GroupRole = namedtuple('GroupRole', ['okta_group_id', 'aws_role_arn'])
|
|
19
|
+
|
|
13
20
|
logger = logging.getLogger(__name__)
|
|
14
21
|
|
|
15
22
|
|
|
@@ -17,17 +24,25 @@ def _parse_regex(regex_string: str) -> str:
|
|
|
17
24
|
return regex_string.replace("{{accountid}}", "P<accountid>").replace("{{role}}", "P<role>").strip()
|
|
18
25
|
|
|
19
26
|
|
|
20
|
-
|
|
21
|
-
|
|
27
|
+
def _parse_okta_group_name(okta_group_name: str, mapping_regex: str) -> AccountRole | None:
|
|
28
|
+
"""
|
|
29
|
+
Extract AWS account id and AWS role name from the given Okta group name using the given mapping regex.
|
|
30
|
+
"""
|
|
22
31
|
regex = _parse_regex(mapping_regex)
|
|
23
|
-
matches = re.search(regex,
|
|
32
|
+
matches = re.search(regex, okta_group_name)
|
|
24
33
|
if matches:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
34
|
+
account_id = matches.group("accountid")
|
|
35
|
+
role_name = matches.group("role")
|
|
36
|
+
return AccountRole(account_id, role_name)
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def transform_okta_group_to_aws_role(group_id: str, group_name: str, mapping_regex: str) -> Optional[Dict]:
|
|
41
|
+
account_role = _parse_okta_group_name(group_name, mapping_regex)
|
|
42
|
+
if account_role:
|
|
43
|
+
role_arn = f"arn:aws:iam::{account_role.account_id}:role/{account_role.role_name}"
|
|
28
44
|
return {"groupid": group_id, "role": role_arn}
|
|
29
|
-
|
|
30
|
-
return None
|
|
45
|
+
return None
|
|
31
46
|
|
|
32
47
|
|
|
33
48
|
@timeit
|
|
@@ -45,6 +60,7 @@ def query_for_okta_to_aws_role_mapping(neo4j_session: neo4j.Session, mapping_reg
|
|
|
45
60
|
|
|
46
61
|
for res in results:
|
|
47
62
|
has_results = True
|
|
63
|
+
# input: okta group id, okta group name. output: aws role arn.
|
|
48
64
|
mapping = transform_okta_group_to_aws_role(res["group.id"], res["group.name"], mapping_regex)
|
|
49
65
|
if mapping:
|
|
50
66
|
group_to_role_mapping.append(mapping)
|
|
@@ -107,8 +123,96 @@ def _load_human_can_assume_role(neo4j_session: neo4j.Session, okta_update_tag: i
|
|
|
107
123
|
)
|
|
108
124
|
|
|
109
125
|
|
|
126
|
+
def get_awssso_okta_groups(neo4j_session: neo4j.Session, okta_org_id: str) -> list[OktaGroup]:
|
|
127
|
+
"""
|
|
128
|
+
Return list of all Okta group ids in the current Okta organization tied to Okta Applications with name
|
|
129
|
+
"amazon_aws_sso".
|
|
130
|
+
"""
|
|
131
|
+
query = """
|
|
132
|
+
MATCH (g:OktaGroup)-[:APPLICATION]->(a:OktaApplication{name:"amazon_aws_sso"})
|
|
133
|
+
<-[:RESOURCE]-(:OktaOrganization{id: $okta_org_id})
|
|
134
|
+
RETURN g.id as group_id, g.name as group_name
|
|
135
|
+
"""
|
|
136
|
+
result = neo4j_session.read_transaction(read_list_of_dicts_tx, query, okta_org_id=okta_org_id)
|
|
137
|
+
return [OktaGroup(group_name=og['group_name'], group_id=og['group_id']) for og in result]
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def get_awssso_role_arn(account_id: str, role_hint: str, neo4j_session: neo4j.Session) -> str | None:
|
|
141
|
+
"""
|
|
142
|
+
Attempt to return the AWS role ARN for the given AWS account ID and role hint string.
|
|
143
|
+
This function exists to handle that AWS SSO roles have a 'AWSReservedSSO' prefix and a hashed suffix
|
|
144
|
+
Input:
|
|
145
|
+
- account_id: AWS account ID
|
|
146
|
+
- role_hint (str): The `AccountRole.role_name` returned by _parse_okta_group_name(). This is the part of the Okta
|
|
147
|
+
group name that refers to the AWS role name.
|
|
148
|
+
Output:
|
|
149
|
+
- If we are able to find it, returns the matching AWS role ARN.
|
|
150
|
+
"""
|
|
151
|
+
query = """
|
|
152
|
+
MATCH (:AWSAccount{id:$account_id})-[:RESOURCE]->(role:AWSRole{path:"/aws-reserved/sso.amazonaws.com/"})
|
|
153
|
+
WHERE SPLIT(role.name, '_')[1..-1][0] = $role_hint
|
|
154
|
+
RETURN role.arn AS role_arn
|
|
155
|
+
"""
|
|
156
|
+
return neo4j_session.read_transaction(read_single_value_tx, query, account_id=account_id, role_hint=role_hint)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def query_for_okta_to_awssso_role_mapping(
|
|
160
|
+
neo4j_session: neo4j.Session,
|
|
161
|
+
awssso_okta_groups: list[OktaGroup],
|
|
162
|
+
mapping_regex: str,
|
|
163
|
+
) -> list[GroupRole]:
|
|
164
|
+
"""
|
|
165
|
+
Input:
|
|
166
|
+
- neo4j session
|
|
167
|
+
- str list of Okta group names
|
|
168
|
+
- str regex that tells us how to find the AWS role name and account when given an Okta group name
|
|
169
|
+
Output:
|
|
170
|
+
- list of OktaGroup id to AWSRole arn pairs.
|
|
171
|
+
"""
|
|
172
|
+
result = []
|
|
173
|
+
for group in awssso_okta_groups:
|
|
174
|
+
account_role = _parse_okta_group_name(group.group_name, mapping_regex)
|
|
175
|
+
if not account_role:
|
|
176
|
+
logger.info(f"Okta group {group.group_name} has no associated AWS SSO role")
|
|
177
|
+
continue
|
|
178
|
+
|
|
179
|
+
role_arn = get_awssso_role_arn(account_role.account_id, account_role.role_name, neo4j_session)
|
|
180
|
+
if role_arn:
|
|
181
|
+
result.append(GroupRole(group.group_id, role_arn))
|
|
182
|
+
return result
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _load_awssso_tx(tx: neo4j.Transaction, group_to_role: list[GroupRole], okta_update_tag: int) -> None:
|
|
186
|
+
ingest_statement = """
|
|
187
|
+
UNWIND $GROUP_TO_ROLE as app_data
|
|
188
|
+
MATCH (role:AWSRole{arn: app_data.aws_role_arn})
|
|
189
|
+
MATCH (group:OktaGroup{id: app_data.okta_group_id})
|
|
190
|
+
MERGE (role)<-[r:ALLOWED_BY]-(group)
|
|
191
|
+
ON CREATE SET r.firstseen = timestamp()
|
|
192
|
+
SET r.lastupdated = $okta_update_tag
|
|
193
|
+
"""
|
|
194
|
+
tx.run(
|
|
195
|
+
ingest_statement,
|
|
196
|
+
GROUP_TO_ROLE=[g._asdict() for g in group_to_role],
|
|
197
|
+
okta_update_tag=okta_update_tag,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _load_okta_group_to_awssso_roles(
|
|
202
|
+
neo4j_session: neo4j.Session,
|
|
203
|
+
group_to_role: list[GroupRole],
|
|
204
|
+
okta_update_tag: int,
|
|
205
|
+
) -> None:
|
|
206
|
+
neo4j_session.write_transaction(_load_awssso_tx, group_to_role, okta_update_tag)
|
|
207
|
+
|
|
208
|
+
|
|
110
209
|
@timeit
|
|
111
|
-
def sync_okta_aws_saml(
|
|
210
|
+
def sync_okta_aws_saml(
|
|
211
|
+
neo4j_session: neo4j.Session,
|
|
212
|
+
mapping_regex: str,
|
|
213
|
+
okta_update_tag: int,
|
|
214
|
+
okta_org_id: str,
|
|
215
|
+
) -> None:
|
|
112
216
|
"""
|
|
113
217
|
Sync okta integration with saml. This will link OktaGroups to the AWSRoles they enable.
|
|
114
218
|
This is for people who use the okta saml provider for AWS
|
|
@@ -127,3 +231,7 @@ def sync_okta_aws_saml(neo4j_session: neo4j.Session, mapping_regex: str, okta_up
|
|
|
127
231
|
group_to_role_mapping = query_for_okta_to_aws_role_mapping(neo4j_session, mapping_regex)
|
|
128
232
|
_load_okta_group_to_aws_roles(neo4j_session, group_to_role_mapping, okta_update_tag)
|
|
129
233
|
_load_human_can_assume_role(neo4j_session, okta_update_tag)
|
|
234
|
+
|
|
235
|
+
sso_okta_groups = get_awssso_okta_groups(neo4j_session, okta_org_id)
|
|
236
|
+
group_to_ssorole_mapping = query_for_okta_to_awssso_role_mapping(neo4j_session, sso_okta_groups, mapping_regex)
|
|
237
|
+
_load_okta_group_to_awssso_roles(neo4j_session, group_to_ssorole_mapping, okta_update_tag)
|
|
@@ -253,9 +253,9 @@ cartography/intel/oci/__init__.py,sha256=AZmRX6EO4LUnynDtIKHxtZ_Ab2-CYPPc2u5d0Q2
|
|
|
253
253
|
cartography/intel/oci/iam.py,sha256=zPrJeoMoO3ZkjBfWbTttjrcUvxxMuWquLTmsDH5MgOI,17712
|
|
254
254
|
cartography/intel/oci/organizations.py,sha256=tzQkZfE4LPoS-6lXBRQGyhq8aJLZUJ1_q75Q9eTBke0,4086
|
|
255
255
|
cartography/intel/oci/utils.py,sha256=UbX9jib4sWEdKeAt2CeCo4k9shUiWY08oTfQz_nDvjA,3223
|
|
256
|
-
cartography/intel/okta/__init__.py,sha256=
|
|
256
|
+
cartography/intel/okta/__init__.py,sha256=i5YY9mIDQ2-IBnCSWf4rToYMa9fQQIxucCnl0TXK2Uc,3833
|
|
257
257
|
cartography/intel/okta/applications.py,sha256=ZqUn-bru6Kh75vpUeRnMurUBh0rGBRpI2b2V09HOOQw,12866
|
|
258
|
-
cartography/intel/okta/awssaml.py,sha256=
|
|
258
|
+
cartography/intel/okta/awssaml.py,sha256=Rw0mrJ7NY5xjfEO_ijMqi1VEbr0FSasfrvGtoCPy1aU,9136
|
|
259
259
|
cartography/intel/okta/factors.py,sha256=1bLnF4MRf0MYzzhT2tfM4jdfkjE1bkQn6_WuOqED2K4,4955
|
|
260
260
|
cartography/intel/okta/groups.py,sha256=GxaixbY5KWkalj2rY6nWwe_IskVVowOAPo88OZIGcPY,10172
|
|
261
261
|
cartography/intel/okta/organization.py,sha256=YLQc7ETdtf8Vc-CRCYivV_xmVl2Oz0Px53anJHYp-p8,821
|
|
@@ -331,10 +331,10 @@ cartography/models/semgrep/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
|
|
|
331
331
|
cartography/models/semgrep/deployment.py,sha256=or5qZDuR51MXzINpH15jZrqmSUvXQevCNYWJ7D6v-JI,745
|
|
332
332
|
cartography/models/semgrep/findings.py,sha256=xrn8sgXpNMrNJbKQagaAVxaCG9bVjTATSRR2XRBR4rg,5386
|
|
333
333
|
cartography/models/semgrep/locations.py,sha256=kSk7Nn5Mn4Ob84MVZOo2GR0YFi-9Okq9pgA3FfC6_bk,3061
|
|
334
|
-
cartography-0.
|
|
335
|
-
cartography-0.
|
|
336
|
-
cartography-0.
|
|
337
|
-
cartography-0.
|
|
338
|
-
cartography-0.
|
|
339
|
-
cartography-0.
|
|
340
|
-
cartography-0.
|
|
334
|
+
cartography-0.92.0rc1.dist-info/LICENSE,sha256=489ZXeW9G90up6ep-D1n-lJgk9ciNT2yxXpFgRSidtk,11341
|
|
335
|
+
cartography-0.92.0rc1.dist-info/METADATA,sha256=Ascw5OW3AX47QCK7O2ftYRY4BRVycgrTVfIZqMe0O6g,1991
|
|
336
|
+
cartography-0.92.0rc1.dist-info/NOTICE,sha256=YOGAsjFtbyKj5tslYIg6V5jEYRuEvnSsIuDOUKj0Qj4,97
|
|
337
|
+
cartography-0.92.0rc1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
338
|
+
cartography-0.92.0rc1.dist-info/entry_points.txt,sha256=GVIAWD0o0_K077qMA_k1oZU4v-M0a8GLKGJR8tZ-qH8,112
|
|
339
|
+
cartography-0.92.0rc1.dist-info/top_level.txt,sha256=BHqsNJQiI6Q72DeypC1IINQJE59SLhU4nllbQjgJi9g,12
|
|
340
|
+
cartography-0.92.0rc1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|