reverse-diagrams 1.3.4__py3-none-any.whl → 2.0.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.
- reverse_diagrams-2.0.0.dist-info/METADATA +706 -0
- reverse_diagrams-2.0.0.dist-info/RECORD +35 -0
- {reverse_diagrams-1.3.4.dist-info → reverse_diagrams-2.0.0.dist-info}/WHEEL +1 -1
- src/aws/client_manager.py +217 -0
- src/aws/describe_identity_store.py +8 -0
- src/aws/describe_organization.py +324 -445
- src/aws/describe_sso.py +170 -143
- src/aws/exceptions.py +26 -0
- src/banner/banner.py +43 -40
- src/config.py +153 -0
- src/models.py +242 -0
- src/plugins/__init__.py +12 -0
- src/plugins/base.py +292 -0
- src/plugins/builtin/__init__.py +12 -0
- src/plugins/builtin/ec2_plugin.py +228 -0
- src/plugins/builtin/identity_center_plugin.py +496 -0
- src/plugins/builtin/organizations_plugin.py +376 -0
- src/plugins/registry.py +126 -0
- src/reports/console_view.py +57 -19
- src/reports/save_results.py +210 -15
- src/reverse_diagrams.py +331 -38
- src/utils/__init__.py +1 -0
- src/utils/cache.py +274 -0
- src/utils/concurrent.py +361 -0
- src/utils/progress.py +257 -0
- src/version.py +1 -1
- reverse_diagrams-1.3.4.dist-info/METADATA +0 -247
- reverse_diagrams-1.3.4.dist-info/RECORD +0 -21
- src/reports/tes.py +0 -366
- {reverse_diagrams-1.3.4.dist-info → reverse_diagrams-2.0.0.dist-info}/entry_points.txt +0 -0
- {reverse_diagrams-1.3.4.dist-info → reverse_diagrams-2.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
"""AWS IAM Identity Center (SSO) plugin for generating identity and access diagrams."""
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Dict, Any, List
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from src.plugins.base import AWSServicePlugin, PluginMetadata
|
|
7
|
+
from src.aws.client_manager import AWSClientManager
|
|
8
|
+
from src.models import DiagramConfig
|
|
9
|
+
from src.utils.concurrent import get_concurrent_processor
|
|
10
|
+
from src.utils.progress import get_progress_tracker
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class IdentityCenterPlugin(AWSServicePlugin):
|
|
16
|
+
"""Plugin for AWS IAM Identity Center (SSO) diagram generation."""
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def metadata(self) -> PluginMetadata:
|
|
20
|
+
"""Get plugin metadata."""
|
|
21
|
+
return PluginMetadata(
|
|
22
|
+
name="identity-center",
|
|
23
|
+
version="1.0.0",
|
|
24
|
+
description="Generate diagrams for AWS IAM Identity Center (SSO) groups, users, and permissions",
|
|
25
|
+
author="Reverse Diagrams Team",
|
|
26
|
+
aws_services=["sso-admin", "identitystore", "organizations"],
|
|
27
|
+
dependencies=[]
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
def collect_data(self, client_manager: AWSClientManager, region: str, **kwargs) -> Dict[str, Any]:
|
|
31
|
+
"""
|
|
32
|
+
Collect AWS IAM Identity Center data.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
client_manager: AWS client manager
|
|
36
|
+
region: AWS region
|
|
37
|
+
**kwargs: Additional parameters
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Dictionary containing Identity Center data
|
|
41
|
+
"""
|
|
42
|
+
logger.debug(f"Collecting AWS IAM Identity Center data from region {region}")
|
|
43
|
+
progress = get_progress_tracker()
|
|
44
|
+
|
|
45
|
+
data = {
|
|
46
|
+
"region": region,
|
|
47
|
+
"sso_instances": [],
|
|
48
|
+
"identity_store_id": "",
|
|
49
|
+
"instance_arn": "",
|
|
50
|
+
"groups": [],
|
|
51
|
+
"users": [],
|
|
52
|
+
"group_memberships": [],
|
|
53
|
+
"permission_sets": [],
|
|
54
|
+
"permission_set_details": {},
|
|
55
|
+
"account_assignments": [],
|
|
56
|
+
"accounts": [],
|
|
57
|
+
"final_account_assignments": {}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
# Get SSO instances
|
|
62
|
+
progress.show_success("🔐 Getting Identity Store Instance Info")
|
|
63
|
+
instances_response = client_manager.call_api("sso-admin", "list_instances")
|
|
64
|
+
data["sso_instances"] = instances_response.get("Instances", [])
|
|
65
|
+
|
|
66
|
+
if not data["sso_instances"]:
|
|
67
|
+
raise ValueError("No SSO instances found")
|
|
68
|
+
|
|
69
|
+
data["identity_store_id"] = data["sso_instances"][0]["IdentityStoreId"]
|
|
70
|
+
data["instance_arn"] = data["sso_instances"][0]["InstanceArn"]
|
|
71
|
+
|
|
72
|
+
logger.debug(f"Using Identity Store ID: {data['identity_store_id']}")
|
|
73
|
+
|
|
74
|
+
# Get groups
|
|
75
|
+
progress.show_success("👥 Listing Groups")
|
|
76
|
+
data["groups"] = self._list_groups(client_manager, data["identity_store_id"])
|
|
77
|
+
logger.debug(f"Found {len(data['groups'])} groups")
|
|
78
|
+
|
|
79
|
+
# Get users
|
|
80
|
+
progress.show_success("👤 Listing Users")
|
|
81
|
+
data["users"] = self._list_users(client_manager, data["identity_store_id"])
|
|
82
|
+
logger.debug(f"Found {len(data['users'])} users")
|
|
83
|
+
|
|
84
|
+
# Get group memberships
|
|
85
|
+
progress.show_success("🔗 Getting Group Memberships")
|
|
86
|
+
data["group_memberships"] = self._get_group_memberships(
|
|
87
|
+
client_manager, data["identity_store_id"], data["groups"]
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Complete group members with user information
|
|
91
|
+
data["group_memberships"] = self._complete_group_members(
|
|
92
|
+
data["group_memberships"], data["users"]
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Get permission sets
|
|
96
|
+
progress.show_success("🛡️ Listing Permission Sets")
|
|
97
|
+
data["permission_sets"] = self._list_permission_sets(
|
|
98
|
+
client_manager, data["instance_arn"]
|
|
99
|
+
)
|
|
100
|
+
logger.debug(f"Found {len(data['permission_sets'])} permission sets")
|
|
101
|
+
|
|
102
|
+
# Get permission set details
|
|
103
|
+
data["permission_set_details"] = self._get_permission_set_details(
|
|
104
|
+
client_manager, data["instance_arn"], data["permission_sets"]
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Get organization accounts
|
|
108
|
+
progress.show_success("🏢 Getting Organization Accounts")
|
|
109
|
+
data["accounts"] = self._list_organization_accounts(client_manager)
|
|
110
|
+
logger.debug(f"Found {len(data['accounts'])} accounts")
|
|
111
|
+
|
|
112
|
+
# Get account assignments
|
|
113
|
+
progress.show_success("📋 Getting Account Assignments")
|
|
114
|
+
data["account_assignments"] = self._get_account_assignments(
|
|
115
|
+
client_manager,
|
|
116
|
+
data["instance_arn"],
|
|
117
|
+
data["accounts"],
|
|
118
|
+
data["permission_sets"]
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Add user and group information to assignments
|
|
122
|
+
data["account_assignments"] = self._enrich_account_assignments(
|
|
123
|
+
data["account_assignments"],
|
|
124
|
+
data["group_memberships"],
|
|
125
|
+
data["users"],
|
|
126
|
+
data["permission_set_details"]
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# Create final account assignments structure
|
|
130
|
+
data["final_account_assignments"] = self._organize_account_assignments(
|
|
131
|
+
data["accounts"], data["account_assignments"]
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
progress.show_summary(
|
|
135
|
+
"Identity Center Summary",
|
|
136
|
+
[
|
|
137
|
+
f"Identity Store ID: {data['identity_store_id']}",
|
|
138
|
+
f"Groups: {len(data['groups'])}",
|
|
139
|
+
f"Users: {len(data['users'])}",
|
|
140
|
+
f"Permission Sets: {len(data['permission_sets'])}",
|
|
141
|
+
f"Account Assignments: {len(data['account_assignments'])}"
|
|
142
|
+
]
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
except Exception as e:
|
|
146
|
+
logger.error(f"Failed to collect Identity Center data: {e}")
|
|
147
|
+
raise
|
|
148
|
+
|
|
149
|
+
return data
|
|
150
|
+
|
|
151
|
+
def _list_groups(self, client_manager: AWSClientManager, identity_store_id: str) -> List[Dict[str, Any]]:
|
|
152
|
+
"""List all groups in the identity store."""
|
|
153
|
+
try:
|
|
154
|
+
groups = client_manager.paginate_api_call(
|
|
155
|
+
"identitystore",
|
|
156
|
+
"list_groups",
|
|
157
|
+
"Groups",
|
|
158
|
+
IdentityStoreId=identity_store_id
|
|
159
|
+
)
|
|
160
|
+
return groups
|
|
161
|
+
except Exception as e:
|
|
162
|
+
logger.error(f"Failed to list groups: {e}")
|
|
163
|
+
return []
|
|
164
|
+
|
|
165
|
+
def _list_users(self, client_manager: AWSClientManager, identity_store_id: str) -> List[Dict[str, Any]]:
|
|
166
|
+
"""List all users in the identity store."""
|
|
167
|
+
try:
|
|
168
|
+
users = client_manager.paginate_api_call(
|
|
169
|
+
"identitystore",
|
|
170
|
+
"list_users",
|
|
171
|
+
"Users",
|
|
172
|
+
IdentityStoreId=identity_store_id
|
|
173
|
+
)
|
|
174
|
+
return users
|
|
175
|
+
except Exception as e:
|
|
176
|
+
logger.error(f"Failed to list users: {e}")
|
|
177
|
+
return []
|
|
178
|
+
|
|
179
|
+
def _get_group_memberships(
|
|
180
|
+
self,
|
|
181
|
+
client_manager: AWSClientManager,
|
|
182
|
+
identity_store_id: str,
|
|
183
|
+
groups: List[Dict[str, Any]]
|
|
184
|
+
) -> List[Dict[str, Any]]:
|
|
185
|
+
"""Get group memberships for all groups."""
|
|
186
|
+
group_memberships = []
|
|
187
|
+
progress = get_progress_tracker()
|
|
188
|
+
|
|
189
|
+
with progress.track_operation(f"Getting memberships for {len(groups)} groups", total=len(groups)) as task_id:
|
|
190
|
+
for group in groups:
|
|
191
|
+
try:
|
|
192
|
+
memberships = client_manager.paginate_api_call(
|
|
193
|
+
"identitystore",
|
|
194
|
+
"list_group_memberships",
|
|
195
|
+
"GroupMemberships",
|
|
196
|
+
IdentityStoreId=identity_store_id,
|
|
197
|
+
GroupId=group["GroupId"]
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
group_memberships.append({
|
|
201
|
+
"group_id": group["GroupId"],
|
|
202
|
+
"group_name": group["DisplayName"],
|
|
203
|
+
"members": memberships
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
progress.update_progress(task_id)
|
|
207
|
+
|
|
208
|
+
except Exception as e:
|
|
209
|
+
logger.warning(f"Failed to get memberships for group {group['GroupId']}: {e}")
|
|
210
|
+
group_memberships.append({
|
|
211
|
+
"group_id": group["GroupId"],
|
|
212
|
+
"group_name": group["DisplayName"],
|
|
213
|
+
"members": []
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
return group_memberships
|
|
217
|
+
|
|
218
|
+
def _complete_group_members(
|
|
219
|
+
self,
|
|
220
|
+
group_memberships: List[Dict[str, Any]],
|
|
221
|
+
users: List[Dict[str, Any]]
|
|
222
|
+
) -> List[Dict[str, Any]]:
|
|
223
|
+
"""Complete group member information with user details."""
|
|
224
|
+
user_lookup = {user["UserId"]: user for user in users}
|
|
225
|
+
|
|
226
|
+
for group_membership in group_memberships:
|
|
227
|
+
for member in group_membership["members"]:
|
|
228
|
+
user_id = member.get("MemberId", {}).get("UserId")
|
|
229
|
+
if user_id and user_id in user_lookup:
|
|
230
|
+
user = user_lookup[user_id]
|
|
231
|
+
member["MemberId"]["UserName"] = user.get("UserName", "Unknown")
|
|
232
|
+
|
|
233
|
+
return group_memberships
|
|
234
|
+
|
|
235
|
+
def _list_permission_sets(self, client_manager: AWSClientManager, instance_arn: str) -> List[str]:
|
|
236
|
+
"""List all permission sets."""
|
|
237
|
+
try:
|
|
238
|
+
permission_sets = client_manager.paginate_api_call(
|
|
239
|
+
"sso-admin",
|
|
240
|
+
"list_permission_sets",
|
|
241
|
+
"PermissionSets",
|
|
242
|
+
InstanceArn=instance_arn
|
|
243
|
+
)
|
|
244
|
+
return permission_sets
|
|
245
|
+
except Exception as e:
|
|
246
|
+
logger.error(f"Failed to list permission sets: {e}")
|
|
247
|
+
return []
|
|
248
|
+
|
|
249
|
+
def _get_permission_set_details(
|
|
250
|
+
self,
|
|
251
|
+
client_manager: AWSClientManager,
|
|
252
|
+
instance_arn: str,
|
|
253
|
+
permission_sets: List[str]
|
|
254
|
+
) -> Dict[str, str]:
|
|
255
|
+
"""Get detailed information for permission sets."""
|
|
256
|
+
permission_set_details = {}
|
|
257
|
+
progress = get_progress_tracker()
|
|
258
|
+
|
|
259
|
+
with progress.track_operation(f"Getting details for {len(permission_sets)} permission sets", total=len(permission_sets)) as task_id:
|
|
260
|
+
for permission_set_arn in permission_sets:
|
|
261
|
+
try:
|
|
262
|
+
response = client_manager.call_api(
|
|
263
|
+
"sso-admin",
|
|
264
|
+
"describe_permission_set",
|
|
265
|
+
InstanceArn=instance_arn,
|
|
266
|
+
PermissionSetArn=permission_set_arn
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
permission_set = response.get("PermissionSet", {})
|
|
270
|
+
permission_set_details[permission_set_arn] = permission_set.get("Name", "Unknown")
|
|
271
|
+
|
|
272
|
+
progress.update_progress(task_id)
|
|
273
|
+
|
|
274
|
+
except Exception as e:
|
|
275
|
+
logger.warning(f"Failed to describe permission set {permission_set_arn}: {e}")
|
|
276
|
+
permission_set_details[permission_set_arn] = "Unknown"
|
|
277
|
+
|
|
278
|
+
return permission_set_details
|
|
279
|
+
|
|
280
|
+
def _list_organization_accounts(self, client_manager: AWSClientManager) -> List[Dict[str, Any]]:
|
|
281
|
+
"""List organization accounts."""
|
|
282
|
+
try:
|
|
283
|
+
accounts = client_manager.paginate_api_call(
|
|
284
|
+
"organizations",
|
|
285
|
+
"list_accounts",
|
|
286
|
+
"Accounts"
|
|
287
|
+
)
|
|
288
|
+
return accounts
|
|
289
|
+
except Exception as e:
|
|
290
|
+
logger.warning(f"Failed to list organization accounts: {e}")
|
|
291
|
+
return []
|
|
292
|
+
|
|
293
|
+
def _get_account_assignments(
|
|
294
|
+
self,
|
|
295
|
+
client_manager: AWSClientManager,
|
|
296
|
+
instance_arn: str,
|
|
297
|
+
accounts: List[Dict[str, Any]],
|
|
298
|
+
permission_sets: List[str]
|
|
299
|
+
) -> List[Dict[str, Any]]:
|
|
300
|
+
"""Get account assignments for all accounts and permission sets."""
|
|
301
|
+
all_assignments = []
|
|
302
|
+
progress = get_progress_tracker()
|
|
303
|
+
|
|
304
|
+
total_operations = len(accounts) * len(permission_sets)
|
|
305
|
+
|
|
306
|
+
with progress.track_operation(f"Getting account assignments", total=total_operations) as task_id:
|
|
307
|
+
for account in accounts:
|
|
308
|
+
for permission_set_arn in permission_sets:
|
|
309
|
+
try:
|
|
310
|
+
assignments = client_manager.paginate_api_call(
|
|
311
|
+
"sso-admin",
|
|
312
|
+
"list_account_assignments",
|
|
313
|
+
"AccountAssignments",
|
|
314
|
+
InstanceArn=instance_arn,
|
|
315
|
+
AccountId=account["Id"],
|
|
316
|
+
PermissionSetArn=permission_set_arn
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
all_assignments.extend(assignments)
|
|
320
|
+
progress.update_progress(task_id)
|
|
321
|
+
|
|
322
|
+
except Exception as e:
|
|
323
|
+
logger.debug(f"No assignments for account {account['Id']} and permission set {permission_set_arn}: {e}")
|
|
324
|
+
progress.update_progress(task_id)
|
|
325
|
+
|
|
326
|
+
return all_assignments
|
|
327
|
+
|
|
328
|
+
def _enrich_account_assignments(
|
|
329
|
+
self,
|
|
330
|
+
account_assignments: List[Dict[str, Any]],
|
|
331
|
+
group_memberships: List[Dict[str, Any]],
|
|
332
|
+
users: List[Dict[str, Any]],
|
|
333
|
+
permission_set_details: Dict[str, str]
|
|
334
|
+
) -> List[Dict[str, Any]]:
|
|
335
|
+
"""Add user and group information to account assignments."""
|
|
336
|
+
# Create lookup dictionaries
|
|
337
|
+
group_lookup = {group["group_id"]: group for group in group_memberships}
|
|
338
|
+
user_lookup = {user["UserId"]: user for user in users}
|
|
339
|
+
|
|
340
|
+
progress = get_progress_tracker()
|
|
341
|
+
|
|
342
|
+
with progress.track_operation(f"Enriching {len(account_assignments)} assignments", total=len(account_assignments)) as task_id:
|
|
343
|
+
for assignment in account_assignments:
|
|
344
|
+
# Add permission set name
|
|
345
|
+
permission_set_arn = assignment.get("PermissionSetArn", "")
|
|
346
|
+
assignment["PermissionSetName"] = permission_set_details.get(permission_set_arn, "Unknown")
|
|
347
|
+
|
|
348
|
+
# Add group or user information
|
|
349
|
+
principal_type = assignment.get("PrincipalType", "")
|
|
350
|
+
principal_id = assignment.get("PrincipalId", "")
|
|
351
|
+
|
|
352
|
+
if principal_type == "GROUP" and principal_id in group_lookup:
|
|
353
|
+
assignment["GroupName"] = group_lookup[principal_id]["group_name"]
|
|
354
|
+
elif principal_type == "USER" and principal_id in user_lookup:
|
|
355
|
+
assignment["UserName"] = user_lookup[principal_id].get("UserName", "Unknown")
|
|
356
|
+
|
|
357
|
+
progress.update_progress(task_id)
|
|
358
|
+
|
|
359
|
+
return account_assignments
|
|
360
|
+
|
|
361
|
+
def _organize_account_assignments(
|
|
362
|
+
self,
|
|
363
|
+
accounts: List[Dict[str, Any]],
|
|
364
|
+
account_assignments: List[Dict[str, Any]]
|
|
365
|
+
) -> Dict[str, List[Dict[str, Any]]]:
|
|
366
|
+
"""Organize account assignments by account name."""
|
|
367
|
+
account_lookup = {account["Id"]: account["Name"] for account in accounts}
|
|
368
|
+
organized_assignments = {}
|
|
369
|
+
|
|
370
|
+
for assignment in account_assignments:
|
|
371
|
+
account_id = assignment.get("AccountId", "")
|
|
372
|
+
account_name = account_lookup.get(account_id, account_id)
|
|
373
|
+
|
|
374
|
+
if account_name not in organized_assignments:
|
|
375
|
+
organized_assignments[account_name] = []
|
|
376
|
+
|
|
377
|
+
organized_assignments[account_name].append(assignment)
|
|
378
|
+
|
|
379
|
+
return organized_assignments
|
|
380
|
+
|
|
381
|
+
def generate_diagram_code(self, data: Dict[str, Any], config: DiagramConfig) -> str:
|
|
382
|
+
"""
|
|
383
|
+
Generate diagram code for AWS IAM Identity Center.
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
data: Identity Center data collected from AWS
|
|
387
|
+
config: Diagram configuration
|
|
388
|
+
|
|
389
|
+
Returns:
|
|
390
|
+
Python code for generating Identity Center diagram
|
|
391
|
+
"""
|
|
392
|
+
logger.debug("Generating AWS IAM Identity Center diagram code")
|
|
393
|
+
|
|
394
|
+
code_lines = [
|
|
395
|
+
"from diagrams import Diagram, Cluster, Edge",
|
|
396
|
+
"from diagrams.aws.management import Organizations, OrganizationsAccount, OrganizationsOrganizationalUnit",
|
|
397
|
+
"from diagrams.aws.general import Users, User",
|
|
398
|
+
"from diagrams.aws.security import IAMPermissions",
|
|
399
|
+
"",
|
|
400
|
+
f'with Diagram("{config.title}", show=False, direction="{config.direction}"):'
|
|
401
|
+
]
|
|
402
|
+
|
|
403
|
+
# Get data
|
|
404
|
+
final_assignments = data.get("final_account_assignments", {})
|
|
405
|
+
group_memberships = data.get("group_memberships", [])
|
|
406
|
+
|
|
407
|
+
# Create group lookup for members
|
|
408
|
+
group_lookup = {group["group_name"]: group for group in group_memberships}
|
|
409
|
+
|
|
410
|
+
# Generate account assignment clusters
|
|
411
|
+
for account_name, assignments in final_assignments.items():
|
|
412
|
+
if not assignments:
|
|
413
|
+
continue
|
|
414
|
+
|
|
415
|
+
code_lines.append(f" with Cluster('Account: {account_name}'):")
|
|
416
|
+
|
|
417
|
+
# Group assignments by group/user
|
|
418
|
+
processed_principals = set()
|
|
419
|
+
|
|
420
|
+
for assignment in assignments:
|
|
421
|
+
principal_key = None
|
|
422
|
+
|
|
423
|
+
if "GroupName" in assignment and assignment["GroupName"] not in processed_principals:
|
|
424
|
+
group_name = assignment["GroupName"]
|
|
425
|
+
principal_key = f"group_{self._format_name_for_code(group_name)}"
|
|
426
|
+
processed_principals.add(group_name)
|
|
427
|
+
|
|
428
|
+
code_lines.append(f" with Cluster('Group: {group_name}'):")
|
|
429
|
+
code_lines.append(f" {principal_key} = Users(\"{self._split_long_name(group_name)}\")")
|
|
430
|
+
code_lines.append(f" {principal_key} \\")
|
|
431
|
+
code_lines.append(f" - Edge(color=\"brown\", style=\"dotted\", label=\"Permissions Set\") \\")
|
|
432
|
+
code_lines.append(f" - IAMPermissions(\"{self._split_long_name(assignment['PermissionSetName'])}\")")
|
|
433
|
+
|
|
434
|
+
# Add group members if available
|
|
435
|
+
if group_name in group_lookup:
|
|
436
|
+
members = group_lookup[group_name]["members"]
|
|
437
|
+
if members:
|
|
438
|
+
member_names = [m.get("MemberId", {}).get("UserName", "Unknown") for m in members]
|
|
439
|
+
members_code = self._create_users_list(member_names)
|
|
440
|
+
code_lines.append(f" members_{self._format_name_for_code(group_name)} = {members_code}")
|
|
441
|
+
code_lines.append(f" {principal_key} \\")
|
|
442
|
+
code_lines.append(f" - Edge(color=\"darkgreen\", style=\"dotted\", label=\"Member\") \\")
|
|
443
|
+
code_lines.append(f" - members_{self._format_name_for_code(group_name)}")
|
|
444
|
+
|
|
445
|
+
elif "UserName" in assignment and assignment["UserName"] not in processed_principals:
|
|
446
|
+
user_name = assignment["UserName"]
|
|
447
|
+
principal_key = f"user_{self._format_name_for_code(user_name)}"
|
|
448
|
+
processed_principals.add(user_name)
|
|
449
|
+
|
|
450
|
+
code_lines.append(f" with Cluster('User: {user_name}'):")
|
|
451
|
+
code_lines.append(f" {principal_key} = User(\"{self._split_long_name(user_name)}\")")
|
|
452
|
+
code_lines.append(f" {principal_key} \\")
|
|
453
|
+
code_lines.append(f" - Edge(color=\"brown\", style=\"dotted\") \\")
|
|
454
|
+
code_lines.append(f" - IAMPermissions(\"{self._split_long_name(assignment['PermissionSetName'])}\")")
|
|
455
|
+
|
|
456
|
+
return "\n".join(code_lines)
|
|
457
|
+
|
|
458
|
+
def _format_name_for_code(self, name: str) -> str:
|
|
459
|
+
"""Format name for use in Python code."""
|
|
460
|
+
import re
|
|
461
|
+
# Remove special characters and spaces
|
|
462
|
+
formatted = re.sub(r'[^\w]', '', name)
|
|
463
|
+
return formatted if formatted else "Unknown"
|
|
464
|
+
|
|
465
|
+
def _split_long_name(self, name: str) -> str:
|
|
466
|
+
"""Split long names for better display."""
|
|
467
|
+
if len(name) > 17:
|
|
468
|
+
return name[:16] + "\\n" + name[16:]
|
|
469
|
+
return name
|
|
470
|
+
|
|
471
|
+
def _create_users_list(self, user_names: List[str]) -> str:
|
|
472
|
+
"""Create a list of User objects for diagram code."""
|
|
473
|
+
if not user_names:
|
|
474
|
+
return "[]"
|
|
475
|
+
|
|
476
|
+
user_objects = []
|
|
477
|
+
for user_name in user_names[:5]: # Limit to 5 users to avoid clutter
|
|
478
|
+
user_objects.append(f'User("{self._split_long_name(user_name)}")')
|
|
479
|
+
|
|
480
|
+
if len(user_names) > 5:
|
|
481
|
+
user_objects.append(f'User("... and {len(user_names) - 5} more")')
|
|
482
|
+
|
|
483
|
+
return "[" + ", ".join(user_objects) + "]"
|
|
484
|
+
|
|
485
|
+
def get_required_permissions(self) -> List[str]:
|
|
486
|
+
"""Get required AWS permissions for Identity Center plugin."""
|
|
487
|
+
return [
|
|
488
|
+
"sso:ListInstances",
|
|
489
|
+
"sso:ListPermissionSets",
|
|
490
|
+
"sso:DescribePermissionSet",
|
|
491
|
+
"sso:ListAccountAssignments",
|
|
492
|
+
"identitystore:ListGroups",
|
|
493
|
+
"identitystore:ListUsers",
|
|
494
|
+
"identitystore:ListGroupMemberships",
|
|
495
|
+
"organizations:ListAccounts"
|
|
496
|
+
]
|