bt-cli 0.4.13__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.
- bt_cli/__init__.py +3 -0
- bt_cli/cli.py +830 -0
- bt_cli/commands/__init__.py +1 -0
- bt_cli/commands/configure.py +415 -0
- bt_cli/commands/learn.py +229 -0
- bt_cli/commands/quick.py +784 -0
- bt_cli/core/__init__.py +1 -0
- bt_cli/core/auth.py +213 -0
- bt_cli/core/client.py +313 -0
- bt_cli/core/config.py +393 -0
- bt_cli/core/config_file.py +420 -0
- bt_cli/core/csv_utils.py +91 -0
- bt_cli/core/errors.py +247 -0
- bt_cli/core/output.py +205 -0
- bt_cli/core/prompts.py +87 -0
- bt_cli/core/rest_debug.py +221 -0
- bt_cli/data/CLAUDE.md +94 -0
- bt_cli/data/__init__.py +0 -0
- bt_cli/data/skills/bt/SKILL.md +108 -0
- bt_cli/data/skills/entitle/SKILL.md +170 -0
- bt_cli/data/skills/epmw/SKILL.md +144 -0
- bt_cli/data/skills/pra/SKILL.md +150 -0
- bt_cli/data/skills/pws/SKILL.md +198 -0
- bt_cli/entitle/__init__.py +1 -0
- bt_cli/entitle/client/__init__.py +5 -0
- bt_cli/entitle/client/base.py +443 -0
- bt_cli/entitle/commands/__init__.py +24 -0
- bt_cli/entitle/commands/accounts.py +53 -0
- bt_cli/entitle/commands/applications.py +39 -0
- bt_cli/entitle/commands/auth.py +68 -0
- bt_cli/entitle/commands/bundles.py +218 -0
- bt_cli/entitle/commands/integrations.py +60 -0
- bt_cli/entitle/commands/permissions.py +70 -0
- bt_cli/entitle/commands/policies.py +97 -0
- bt_cli/entitle/commands/resources.py +131 -0
- bt_cli/entitle/commands/roles.py +74 -0
- bt_cli/entitle/commands/users.py +123 -0
- bt_cli/entitle/commands/workflows.py +187 -0
- bt_cli/entitle/models/__init__.py +31 -0
- bt_cli/entitle/models/bundle.py +28 -0
- bt_cli/entitle/models/common.py +37 -0
- bt_cli/entitle/models/integration.py +30 -0
- bt_cli/entitle/models/permission.py +27 -0
- bt_cli/entitle/models/policy.py +25 -0
- bt_cli/entitle/models/resource.py +29 -0
- bt_cli/entitle/models/role.py +28 -0
- bt_cli/entitle/models/user.py +24 -0
- bt_cli/entitle/models/workflow.py +55 -0
- bt_cli/epmw/__init__.py +1 -0
- bt_cli/epmw/client/__init__.py +5 -0
- bt_cli/epmw/client/base.py +848 -0
- bt_cli/epmw/commands/__init__.py +33 -0
- bt_cli/epmw/commands/audits.py +250 -0
- bt_cli/epmw/commands/auth.py +55 -0
- bt_cli/epmw/commands/computers.py +140 -0
- bt_cli/epmw/commands/events.py +233 -0
- bt_cli/epmw/commands/groups.py +215 -0
- bt_cli/epmw/commands/policies.py +673 -0
- bt_cli/epmw/commands/quick.py +348 -0
- bt_cli/epmw/commands/requests.py +224 -0
- bt_cli/epmw/commands/roles.py +78 -0
- bt_cli/epmw/commands/tasks.py +38 -0
- bt_cli/epmw/commands/users.py +219 -0
- bt_cli/epmw/models/__init__.py +1 -0
- bt_cli/pra/__init__.py +1 -0
- bt_cli/pra/client/__init__.py +5 -0
- bt_cli/pra/client/base.py +618 -0
- bt_cli/pra/commands/__init__.py +30 -0
- bt_cli/pra/commands/auth.py +55 -0
- bt_cli/pra/commands/import_export.py +442 -0
- bt_cli/pra/commands/jump_clients.py +139 -0
- bt_cli/pra/commands/jump_groups.py +146 -0
- bt_cli/pra/commands/jump_items.py +638 -0
- bt_cli/pra/commands/jumpoints.py +95 -0
- bt_cli/pra/commands/policies.py +197 -0
- bt_cli/pra/commands/quick.py +470 -0
- bt_cli/pra/commands/teams.py +81 -0
- bt_cli/pra/commands/users.py +87 -0
- bt_cli/pra/commands/vault.py +564 -0
- bt_cli/pra/models/__init__.py +27 -0
- bt_cli/pra/models/common.py +12 -0
- bt_cli/pra/models/jump_client.py +25 -0
- bt_cli/pra/models/jump_group.py +15 -0
- bt_cli/pra/models/jump_item.py +72 -0
- bt_cli/pra/models/jumpoint.py +19 -0
- bt_cli/pra/models/team.py +14 -0
- bt_cli/pra/models/user.py +17 -0
- bt_cli/pra/models/vault.py +45 -0
- bt_cli/pws/__init__.py +1 -0
- bt_cli/pws/client/__init__.py +5 -0
- bt_cli/pws/client/base.py +356 -0
- bt_cli/pws/client/beyondinsight.py +869 -0
- bt_cli/pws/client/passwordsafe.py +1786 -0
- bt_cli/pws/commands/__init__.py +33 -0
- bt_cli/pws/commands/accounts.py +372 -0
- bt_cli/pws/commands/assets.py +311 -0
- bt_cli/pws/commands/auth.py +166 -0
- bt_cli/pws/commands/clouds.py +221 -0
- bt_cli/pws/commands/config.py +344 -0
- bt_cli/pws/commands/credentials.py +347 -0
- bt_cli/pws/commands/databases.py +306 -0
- bt_cli/pws/commands/directories.py +199 -0
- bt_cli/pws/commands/functional.py +298 -0
- bt_cli/pws/commands/import_export.py +452 -0
- bt_cli/pws/commands/platforms.py +118 -0
- bt_cli/pws/commands/quick.py +1646 -0
- bt_cli/pws/commands/search.py +256 -0
- bt_cli/pws/commands/secrets.py +1343 -0
- bt_cli/pws/commands/systems.py +389 -0
- bt_cli/pws/commands/users.py +415 -0
- bt_cli/pws/commands/workgroups.py +166 -0
- bt_cli/pws/config.py +18 -0
- bt_cli/pws/models/__init__.py +19 -0
- bt_cli/pws/models/account.py +186 -0
- bt_cli/pws/models/asset.py +102 -0
- bt_cli/pws/models/common.py +132 -0
- bt_cli/pws/models/system.py +121 -0
- bt_cli-0.4.13.dist-info/METADATA +417 -0
- bt_cli-0.4.13.dist-info/RECORD +121 -0
- bt_cli-0.4.13.dist-info/WHEEL +4 -0
- bt_cli-0.4.13.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""CLI commands for managing cloud systems (AWS, Azure, GCP)."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
|
|
11
|
+
from ...core.output import print_api_error
|
|
12
|
+
from ..client.base import get_client
|
|
13
|
+
|
|
14
|
+
app = typer.Typer(no_args_is_help=True, help="Manage cloud systems (AWS, Azure, GCP)")
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
# Cloud platform IDs
|
|
18
|
+
CLOUD_PLATFORMS = {
|
|
19
|
+
"aws": 47,
|
|
20
|
+
"amazon": 47,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def print_clouds_table(systems: list[dict], title: str = "Cloud Systems") -> None:
|
|
25
|
+
"""Print cloud systems in a formatted table."""
|
|
26
|
+
table = Table(title=title)
|
|
27
|
+
table.add_column("ID", style="cyan", no_wrap=True)
|
|
28
|
+
table.add_column("Name", style="green")
|
|
29
|
+
table.add_column("Platform", style="yellow")
|
|
30
|
+
table.add_column("Workgroup", style="magenta")
|
|
31
|
+
table.add_column("FA ID", style="blue")
|
|
32
|
+
table.add_column("Access URL", style="dim")
|
|
33
|
+
|
|
34
|
+
for s in systems:
|
|
35
|
+
table.add_row(
|
|
36
|
+
str(s.get("ManagedSystemID", "")),
|
|
37
|
+
s.get("SystemName", "-"),
|
|
38
|
+
str(s.get("PlatformID", "-")),
|
|
39
|
+
str(s.get("WorkgroupID", "-")),
|
|
40
|
+
str(s.get("FunctionalAccountID", "-")),
|
|
41
|
+
(s.get("AccessURL", "-") or "-")[:40],
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
console.print(table)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@app.command("list")
|
|
48
|
+
def list_clouds(
|
|
49
|
+
workgroup: Optional[int] = typer.Option(None, "--workgroup", "-w", help="Filter by workgroup ID"),
|
|
50
|
+
limit: int = typer.Option(50, "--limit", "-l", help="Maximum results (default: 50)"),
|
|
51
|
+
fetch_all: bool = typer.Option(False, "--all", help="Fetch all results (may be slow)"),
|
|
52
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json"),
|
|
53
|
+
) -> None:
|
|
54
|
+
"""List cloud managed systems (AWS, Azure, GCP).
|
|
55
|
+
|
|
56
|
+
Examples:
|
|
57
|
+
bt pws clouds list # First 50 cloud systems
|
|
58
|
+
bt pws clouds list --all # All cloud systems
|
|
59
|
+
bt pws clouds list -w 3 # Cloud systems in workgroup 3
|
|
60
|
+
"""
|
|
61
|
+
try:
|
|
62
|
+
with get_client() as client:
|
|
63
|
+
client.authenticate()
|
|
64
|
+
# Get all managed systems and filter for cloud (EntityTypeID=4)
|
|
65
|
+
systems = client.paginate("/ManagedSystems")
|
|
66
|
+
cloud_systems = [s for s in systems if s.get("EntityTypeID") == 4]
|
|
67
|
+
|
|
68
|
+
if workgroup:
|
|
69
|
+
cloud_systems = [s for s in cloud_systems if s.get("WorkgroupID") == workgroup]
|
|
70
|
+
|
|
71
|
+
# Apply limit after filtering
|
|
72
|
+
total_count = len(cloud_systems)
|
|
73
|
+
if not fetch_all and len(cloud_systems) > limit:
|
|
74
|
+
cloud_systems = cloud_systems[:limit]
|
|
75
|
+
|
|
76
|
+
if output == "json":
|
|
77
|
+
console.print_json(json.dumps(cloud_systems, default=str))
|
|
78
|
+
else:
|
|
79
|
+
if cloud_systems:
|
|
80
|
+
print_clouds_table(cloud_systems)
|
|
81
|
+
if not fetch_all and total_count > limit:
|
|
82
|
+
console.print(f"[dim]Showing {len(cloud_systems)} of {total_count} results. Use --all to fetch all results.[/dim]")
|
|
83
|
+
else:
|
|
84
|
+
console.print("[yellow]No cloud systems found.[/yellow]")
|
|
85
|
+
|
|
86
|
+
except httpx.HTTPStatusError as e:
|
|
87
|
+
print_api_error(e, "manage cloud systems")
|
|
88
|
+
raise typer.Exit(1)
|
|
89
|
+
except httpx.RequestError as e:
|
|
90
|
+
print_api_error(e, "manage cloud systems")
|
|
91
|
+
raise typer.Exit(1)
|
|
92
|
+
except Exception as e:
|
|
93
|
+
print_api_error(e, "manage cloud systems")
|
|
94
|
+
raise typer.Exit(1)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@app.command("get")
|
|
98
|
+
def get_cloud(
|
|
99
|
+
system_id: int = typer.Argument(..., help="Managed System ID"),
|
|
100
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json"),
|
|
101
|
+
) -> None:
|
|
102
|
+
"""Get a cloud managed system by ID."""
|
|
103
|
+
try:
|
|
104
|
+
with get_client() as client:
|
|
105
|
+
client.authenticate()
|
|
106
|
+
system = client.get_managed_system(system_id)
|
|
107
|
+
|
|
108
|
+
if system.get("EntityTypeID") != 4:
|
|
109
|
+
console.print(f"[yellow]Warning: System {system_id} is not a cloud system (EntityTypeID={system.get('EntityTypeID')})[/yellow]")
|
|
110
|
+
|
|
111
|
+
if output == "json":
|
|
112
|
+
console.print_json(json.dumps(system, default=str))
|
|
113
|
+
else:
|
|
114
|
+
console.print(f"\n[bold cyan]Cloud System: {system.get('SystemName', 'Unknown')}[/bold cyan]\n")
|
|
115
|
+
|
|
116
|
+
info_table = Table(show_header=False, box=None)
|
|
117
|
+
info_table.add_column("Field", style="dim")
|
|
118
|
+
info_table.add_column("Value")
|
|
119
|
+
|
|
120
|
+
fields = [
|
|
121
|
+
("ID", "ManagedSystemID"),
|
|
122
|
+
("Name", "SystemName"),
|
|
123
|
+
("Host Name", "HostName"),
|
|
124
|
+
("Platform ID", "PlatformID"),
|
|
125
|
+
("Cloud ID", "CloudID"),
|
|
126
|
+
("Workgroup ID", "WorkgroupID"),
|
|
127
|
+
("Access URL", "AccessURL"),
|
|
128
|
+
("Description", "Description"),
|
|
129
|
+
("Functional Account ID", "FunctionalAccountID"),
|
|
130
|
+
("Auto Management", "AutoManagementFlag"),
|
|
131
|
+
("Change Frequency", "ChangeFrequencyType"),
|
|
132
|
+
("Change Days", "ChangeFrequencyDays"),
|
|
133
|
+
("Change Time", "ChangeTime"),
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
for label, key in fields:
|
|
137
|
+
value = system.get(key)
|
|
138
|
+
if value is not None:
|
|
139
|
+
if isinstance(value, bool):
|
|
140
|
+
value = "Yes" if value else "No"
|
|
141
|
+
info_table.add_row(label, str(value))
|
|
142
|
+
|
|
143
|
+
console.print(info_table)
|
|
144
|
+
|
|
145
|
+
except httpx.HTTPStatusError as e:
|
|
146
|
+
print_api_error(e, "manage cloud systems")
|
|
147
|
+
raise typer.Exit(1)
|
|
148
|
+
except httpx.RequestError as e:
|
|
149
|
+
print_api_error(e, "manage cloud systems")
|
|
150
|
+
raise typer.Exit(1)
|
|
151
|
+
except Exception as e:
|
|
152
|
+
print_api_error(e, "manage cloud systems")
|
|
153
|
+
raise typer.Exit(1)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@app.command("add-system")
|
|
157
|
+
def add_managed_system(
|
|
158
|
+
workgroup_id: int = typer.Option(..., "--workgroup", "-w", help="Workgroup ID"),
|
|
159
|
+
platform: str = typer.Option(..., "--platform", "-p", help="Platform: aws, amazon, or numeric ID"),
|
|
160
|
+
host_name: str = typer.Option(..., "--host", "-h", help="Host name / identifier"),
|
|
161
|
+
name: Optional[str] = typer.Option(None, "--name", "-n", help="System name"),
|
|
162
|
+
access_url: Optional[str] = typer.Option(None, "--access-url", "-u", help="Access URL (e.g., https://account-id.signin.aws.amazon.com/console)"),
|
|
163
|
+
functional_account: Optional[int] = typer.Option(None, "--functional-account", "-f", help="Functional account ID"),
|
|
164
|
+
description: Optional[str] = typer.Option(None, "--description", help="Description"),
|
|
165
|
+
auto_manage: bool = typer.Option(False, "--auto-manage", help="Enable automatic password management"),
|
|
166
|
+
change_frequency: Optional[str] = typer.Option(None, "--change-frequency", help="Change frequency: first, last, or xdays"),
|
|
167
|
+
change_days: Optional[int] = typer.Option(90, "--change-days", help="Days between changes (if xdays)"),
|
|
168
|
+
change_time: Optional[str] = typer.Option("23:30", "--change-time", help="Time for changes (HH:MM)"),
|
|
169
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json"),
|
|
170
|
+
) -> None:
|
|
171
|
+
"""Create a managed system for a cloud platform (AWS).
|
|
172
|
+
|
|
173
|
+
Example for AWS:
|
|
174
|
+
pws clouds add-system -w 3 -p aws -h "aws-nexus-123456789" -u "https://123456789.signin.aws.amazon.com/console" -f 12 --auto-manage
|
|
175
|
+
"""
|
|
176
|
+
try:
|
|
177
|
+
# Resolve platform ID
|
|
178
|
+
if platform.isdigit():
|
|
179
|
+
platform_id = int(platform)
|
|
180
|
+
else:
|
|
181
|
+
platform_lower = platform.lower().replace("_", "").replace("-", "")
|
|
182
|
+
if platform_lower not in CLOUD_PLATFORMS:
|
|
183
|
+
console.print(f"[red]Unknown platform:[/red] {platform}")
|
|
184
|
+
console.print("Valid platforms: aws, amazon, or numeric ID")
|
|
185
|
+
raise typer.Exit(1)
|
|
186
|
+
platform_id = CLOUD_PLATFORMS[platform_lower]
|
|
187
|
+
|
|
188
|
+
with get_client() as client:
|
|
189
|
+
client.authenticate()
|
|
190
|
+
system = client.create_cloud_managed_system(
|
|
191
|
+
workgroup_id=workgroup_id,
|
|
192
|
+
platform_id=platform_id,
|
|
193
|
+
host_name=host_name,
|
|
194
|
+
system_name=name,
|
|
195
|
+
access_url=access_url,
|
|
196
|
+
functional_account_id=functional_account,
|
|
197
|
+
description=description,
|
|
198
|
+
auto_management_flag=auto_manage,
|
|
199
|
+
change_frequency_type=change_frequency,
|
|
200
|
+
change_frequency_days=change_days,
|
|
201
|
+
change_time=change_time,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
if output == "json":
|
|
205
|
+
console.print_json(json.dumps(system, default=str))
|
|
206
|
+
else:
|
|
207
|
+
console.print(f"[green]Created cloud managed system:[/green] {system.get('SystemName', 'Unknown')}")
|
|
208
|
+
console.print(f" ID: {system.get('ManagedSystemID', 'N/A')}")
|
|
209
|
+
console.print(f" Cloud ID: {system.get('CloudID', 'N/A')}")
|
|
210
|
+
console.print(f" Platform: {system.get('PlatformID', 'N/A')}")
|
|
211
|
+
console.print(f" Auto-Management: {'Yes' if system.get('AutoManagementFlag') else 'No'}")
|
|
212
|
+
|
|
213
|
+
except httpx.HTTPStatusError as e:
|
|
214
|
+
print_api_error(e, "manage cloud systems")
|
|
215
|
+
raise typer.Exit(1)
|
|
216
|
+
except httpx.RequestError as e:
|
|
217
|
+
print_api_error(e, "manage cloud systems")
|
|
218
|
+
raise typer.Exit(1)
|
|
219
|
+
except Exception as e:
|
|
220
|
+
print_api_error(e, "manage cloud systems")
|
|
221
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
"""CLI commands for Password Safe configuration (access policies, user groups, etc.)."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
|
|
11
|
+
from ...core.output import print_api_error
|
|
12
|
+
from ..client.base import get_client
|
|
13
|
+
|
|
14
|
+
app = typer.Typer(no_args_is_help=True, help="View Password Safe configuration")
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
# Sub-apps for different config areas
|
|
18
|
+
policies_app = typer.Typer(no_args_is_help=True, help="Access policies")
|
|
19
|
+
groups_app = typer.Typer(no_args_is_help=True, help="User groups")
|
|
20
|
+
rules_app = typer.Typer(no_args_is_help=True, help="Password rules")
|
|
21
|
+
|
|
22
|
+
app.add_typer(policies_app, name="policies")
|
|
23
|
+
app.add_typer(groups_app, name="groups")
|
|
24
|
+
app.add_typer(rules_app, name="rules")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# =========================================================================
|
|
28
|
+
# Access Policies
|
|
29
|
+
# =========================================================================
|
|
30
|
+
|
|
31
|
+
@policies_app.command("list")
|
|
32
|
+
def list_policies(
|
|
33
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json"),
|
|
34
|
+
) -> None:
|
|
35
|
+
"""List access policies."""
|
|
36
|
+
try:
|
|
37
|
+
with get_client() as client:
|
|
38
|
+
client.authenticate()
|
|
39
|
+
policies = client.list_access_policies()
|
|
40
|
+
|
|
41
|
+
if output == "json":
|
|
42
|
+
console.print_json(json.dumps(policies, default=str))
|
|
43
|
+
else:
|
|
44
|
+
table = Table(title="Access Policies")
|
|
45
|
+
table.add_column("ID", style="cyan")
|
|
46
|
+
table.add_column("Name", style="green")
|
|
47
|
+
table.add_column("Description", style="yellow")
|
|
48
|
+
table.add_column("Schedules", style="magenta")
|
|
49
|
+
|
|
50
|
+
for policy in policies:
|
|
51
|
+
schedules = policy.get("Schedules", [])
|
|
52
|
+
access_types = []
|
|
53
|
+
for sched in schedules:
|
|
54
|
+
for at in sched.get("AccessTypes", []):
|
|
55
|
+
access_types.append(at.get("AccessType", ""))
|
|
56
|
+
|
|
57
|
+
table.add_row(
|
|
58
|
+
str(policy.get("AccessPolicyID", "")),
|
|
59
|
+
policy.get("Name", ""),
|
|
60
|
+
(policy.get("Description", "") or "-")[:50],
|
|
61
|
+
", ".join(access_types) if access_types else "-",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
console.print(table)
|
|
65
|
+
|
|
66
|
+
except httpx.HTTPStatusError as e:
|
|
67
|
+
print_api_error(e, "manage configuration")
|
|
68
|
+
raise typer.Exit(1)
|
|
69
|
+
except httpx.RequestError as e:
|
|
70
|
+
print_api_error(e, "manage configuration")
|
|
71
|
+
raise typer.Exit(1)
|
|
72
|
+
except Exception as e:
|
|
73
|
+
print_api_error(e, "manage configuration")
|
|
74
|
+
raise typer.Exit(1)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@policies_app.command("get")
|
|
78
|
+
def get_policy(
|
|
79
|
+
policy_id: int = typer.Argument(..., help="Access policy ID"),
|
|
80
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json"),
|
|
81
|
+
) -> None:
|
|
82
|
+
"""Get an access policy by ID."""
|
|
83
|
+
try:
|
|
84
|
+
with get_client() as client:
|
|
85
|
+
client.authenticate()
|
|
86
|
+
policy = client.get_access_policy(policy_id)
|
|
87
|
+
|
|
88
|
+
if output == "json":
|
|
89
|
+
console.print_json(json.dumps(policy, default=str))
|
|
90
|
+
else:
|
|
91
|
+
console.print(f"\n[bold cyan]Access Policy: {policy.get('Name', 'Unknown')}[/bold cyan]\n")
|
|
92
|
+
console.print(f" ID: {policy.get('AccessPolicyID')}")
|
|
93
|
+
console.print(f" Description: {policy.get('Description', '-')}")
|
|
94
|
+
|
|
95
|
+
for sched in policy.get("Schedules", []):
|
|
96
|
+
console.print(f"\n [bold]Schedule {sched.get('ScheduleID')}:[/bold]")
|
|
97
|
+
console.print(f" Require Reason: {sched.get('RequireReason', False)}")
|
|
98
|
+
console.print(f" Require Ticket: {sched.get('RequireTicketSystem', False)}")
|
|
99
|
+
|
|
100
|
+
for at in sched.get("AccessTypes", []):
|
|
101
|
+
console.print(f"\n [yellow]{at.get('AccessType')}:[/yellow]")
|
|
102
|
+
console.print(f" Min Approvers: {at.get('MinApprovers', 0)}")
|
|
103
|
+
console.print(f" Is Session: {at.get('IsSession', False)}")
|
|
104
|
+
console.print(f" Record Session: {at.get('RecordSession', False)}")
|
|
105
|
+
console.print(f" Max Concurrent: {at.get('MaxConcurrent', 1)}")
|
|
106
|
+
|
|
107
|
+
except httpx.HTTPStatusError as e:
|
|
108
|
+
print_api_error(e, "manage configuration")
|
|
109
|
+
raise typer.Exit(1)
|
|
110
|
+
except httpx.RequestError as e:
|
|
111
|
+
print_api_error(e, "manage configuration")
|
|
112
|
+
raise typer.Exit(1)
|
|
113
|
+
except Exception as e:
|
|
114
|
+
print_api_error(e, "manage configuration")
|
|
115
|
+
raise typer.Exit(1)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@policies_app.command("assignees")
|
|
119
|
+
def get_policy_assignees(
|
|
120
|
+
policy_id: int = typer.Argument(..., help="Access policy ID"),
|
|
121
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json"),
|
|
122
|
+
) -> None:
|
|
123
|
+
"""Get assignees for an access policy.
|
|
124
|
+
|
|
125
|
+
Shows which user groups can access which managed accounts (smart rules)
|
|
126
|
+
and with what role permissions.
|
|
127
|
+
"""
|
|
128
|
+
try:
|
|
129
|
+
with get_client() as client:
|
|
130
|
+
client.authenticate()
|
|
131
|
+
assignees = client.get_access_policy_assignees(policy_id)
|
|
132
|
+
|
|
133
|
+
if output == "json":
|
|
134
|
+
console.print_json(json.dumps(assignees, default=str))
|
|
135
|
+
else:
|
|
136
|
+
table = Table(title=f"Assignees for Policy {policy_id}")
|
|
137
|
+
table.add_column("User Group", style="cyan")
|
|
138
|
+
table.add_column("Role", style="green")
|
|
139
|
+
table.add_column("Smart Rule (Accounts)", style="yellow")
|
|
140
|
+
|
|
141
|
+
for a in assignees:
|
|
142
|
+
group_name = a.get("UserGroupName", "-")
|
|
143
|
+
if not a.get("UserGroupIsActive", True):
|
|
144
|
+
group_name += " (inactive)"
|
|
145
|
+
role_name = a.get("RoleName", "-")
|
|
146
|
+
smart_rule = a.get("SmartRuleTitle", "-")
|
|
147
|
+
if not a.get("SmartRuleIsActive", True):
|
|
148
|
+
smart_rule += " (inactive)"
|
|
149
|
+
table.add_row(group_name, role_name, smart_rule)
|
|
150
|
+
|
|
151
|
+
console.print(table)
|
|
152
|
+
|
|
153
|
+
except httpx.HTTPStatusError as e:
|
|
154
|
+
print_api_error(e, "manage configuration")
|
|
155
|
+
raise typer.Exit(1)
|
|
156
|
+
except httpx.RequestError as e:
|
|
157
|
+
print_api_error(e, "manage configuration")
|
|
158
|
+
raise typer.Exit(1)
|
|
159
|
+
except Exception as e:
|
|
160
|
+
print_api_error(e, "manage configuration")
|
|
161
|
+
raise typer.Exit(1)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
# =========================================================================
|
|
165
|
+
# User Groups
|
|
166
|
+
# =========================================================================
|
|
167
|
+
|
|
168
|
+
@groups_app.command("list")
|
|
169
|
+
def list_groups(
|
|
170
|
+
search: Optional[str] = typer.Option(None, "--search", "-s", help="Search by name"),
|
|
171
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json"),
|
|
172
|
+
) -> None:
|
|
173
|
+
"""List user groups."""
|
|
174
|
+
try:
|
|
175
|
+
with get_client() as client:
|
|
176
|
+
client.authenticate()
|
|
177
|
+
groups = client.list_user_groups(search=search)
|
|
178
|
+
|
|
179
|
+
if output == "json":
|
|
180
|
+
console.print_json(json.dumps(groups, default=str))
|
|
181
|
+
else:
|
|
182
|
+
table = Table(title="User Groups")
|
|
183
|
+
table.add_column("ID", style="cyan")
|
|
184
|
+
table.add_column("Name", style="green")
|
|
185
|
+
table.add_column("Type", style="yellow")
|
|
186
|
+
table.add_column("Active", style="magenta")
|
|
187
|
+
table.add_column("API Reg IDs", style="blue")
|
|
188
|
+
|
|
189
|
+
for group in groups:
|
|
190
|
+
table.add_row(
|
|
191
|
+
str(group.get("GroupID", "")),
|
|
192
|
+
group.get("Name", ""),
|
|
193
|
+
group.get("GroupType", "-"),
|
|
194
|
+
"Yes" if group.get("IsActive") else "No",
|
|
195
|
+
group.get("ApplicationRegistrationIDs") or "-",
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
console.print(table)
|
|
199
|
+
|
|
200
|
+
except httpx.HTTPStatusError as e:
|
|
201
|
+
print_api_error(e, "manage configuration")
|
|
202
|
+
raise typer.Exit(1)
|
|
203
|
+
except httpx.RequestError as e:
|
|
204
|
+
print_api_error(e, "manage configuration")
|
|
205
|
+
raise typer.Exit(1)
|
|
206
|
+
except Exception as e:
|
|
207
|
+
print_api_error(e, "manage configuration")
|
|
208
|
+
raise typer.Exit(1)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@groups_app.command("get")
|
|
212
|
+
def get_group(
|
|
213
|
+
group_id: int = typer.Argument(..., help="User group ID"),
|
|
214
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json"),
|
|
215
|
+
) -> None:
|
|
216
|
+
"""Get a user group by ID."""
|
|
217
|
+
try:
|
|
218
|
+
with get_client() as client:
|
|
219
|
+
client.authenticate()
|
|
220
|
+
group = client.get_user_group(group_id)
|
|
221
|
+
|
|
222
|
+
if output == "json":
|
|
223
|
+
console.print_json(json.dumps(group, default=str))
|
|
224
|
+
else:
|
|
225
|
+
console.print(f"\n[bold cyan]User Group: {group.get('Name', 'Unknown')}[/bold cyan]\n")
|
|
226
|
+
|
|
227
|
+
fields = [
|
|
228
|
+
("ID", "GroupID"),
|
|
229
|
+
("Name", "Name"),
|
|
230
|
+
("Description", "Description"),
|
|
231
|
+
("Type", "GroupType"),
|
|
232
|
+
("Active", "IsActive"),
|
|
233
|
+
("Role Type", "RoleType"),
|
|
234
|
+
("API Registration IDs", "ApplicationRegistrationIDs"),
|
|
235
|
+
("Distinguished Name", "DistinguishedName"),
|
|
236
|
+
]
|
|
237
|
+
|
|
238
|
+
for label, key in fields:
|
|
239
|
+
value = group.get(key)
|
|
240
|
+
if value is not None:
|
|
241
|
+
if isinstance(value, bool):
|
|
242
|
+
value = "Yes" if value else "No"
|
|
243
|
+
console.print(f" {label}: {value}")
|
|
244
|
+
|
|
245
|
+
except httpx.HTTPStatusError as e:
|
|
246
|
+
print_api_error(e, "manage configuration")
|
|
247
|
+
raise typer.Exit(1)
|
|
248
|
+
except httpx.RequestError as e:
|
|
249
|
+
print_api_error(e, "manage configuration")
|
|
250
|
+
raise typer.Exit(1)
|
|
251
|
+
except Exception as e:
|
|
252
|
+
print_api_error(e, "manage configuration")
|
|
253
|
+
raise typer.Exit(1)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
# =========================================================================
|
|
257
|
+
# Password Rules
|
|
258
|
+
# =========================================================================
|
|
259
|
+
|
|
260
|
+
@rules_app.command("list")
|
|
261
|
+
def list_rules(
|
|
262
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json"),
|
|
263
|
+
) -> None:
|
|
264
|
+
"""List password rules."""
|
|
265
|
+
try:
|
|
266
|
+
with get_client() as client:
|
|
267
|
+
client.authenticate()
|
|
268
|
+
rules = client.list_password_rules()
|
|
269
|
+
|
|
270
|
+
if output == "json":
|
|
271
|
+
console.print_json(json.dumps(rules, default=str))
|
|
272
|
+
else:
|
|
273
|
+
table = Table(title="Password Rules")
|
|
274
|
+
table.add_column("ID", style="cyan")
|
|
275
|
+
table.add_column("Name", style="green")
|
|
276
|
+
table.add_column("Min Len", style="yellow")
|
|
277
|
+
table.add_column("Max Len", style="magenta")
|
|
278
|
+
table.add_column("Upper", style="blue")
|
|
279
|
+
table.add_column("Lower", style="blue")
|
|
280
|
+
table.add_column("Numeric", style="blue")
|
|
281
|
+
table.add_column("Symbol", style="blue")
|
|
282
|
+
|
|
283
|
+
for rule in rules:
|
|
284
|
+
table.add_row(
|
|
285
|
+
str(rule.get("PasswordRuleID", "")),
|
|
286
|
+
rule.get("Name", ""),
|
|
287
|
+
str(rule.get("MinimumLength", "-")),
|
|
288
|
+
str(rule.get("MaximumLength", "-")),
|
|
289
|
+
rule.get("UppercaseRequirement", "-"),
|
|
290
|
+
rule.get("LowercaseRequirement", "-"),
|
|
291
|
+
rule.get("NumericRequirement", "-"),
|
|
292
|
+
rule.get("SymbolRequirement", "-"),
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
console.print(table)
|
|
296
|
+
|
|
297
|
+
except httpx.HTTPStatusError as e:
|
|
298
|
+
print_api_error(e, "manage configuration")
|
|
299
|
+
raise typer.Exit(1)
|
|
300
|
+
except httpx.RequestError as e:
|
|
301
|
+
print_api_error(e, "manage configuration")
|
|
302
|
+
raise typer.Exit(1)
|
|
303
|
+
except Exception as e:
|
|
304
|
+
print_api_error(e, "manage configuration")
|
|
305
|
+
raise typer.Exit(1)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
@rules_app.command("get")
|
|
309
|
+
def get_rule(
|
|
310
|
+
rule_id: int = typer.Argument(..., help="Password rule ID"),
|
|
311
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json"),
|
|
312
|
+
) -> None:
|
|
313
|
+
"""Get a password rule by ID."""
|
|
314
|
+
try:
|
|
315
|
+
with get_client() as client:
|
|
316
|
+
client.authenticate()
|
|
317
|
+
rule = client.get_password_rule(rule_id)
|
|
318
|
+
|
|
319
|
+
if output == "json":
|
|
320
|
+
console.print_json(json.dumps(rule, default=str))
|
|
321
|
+
else:
|
|
322
|
+
console.print(f"\n[bold cyan]Password Rule: {rule.get('Name', 'Unknown')}[/bold cyan]\n")
|
|
323
|
+
console.print(f" ID: {rule.get('PasswordRuleID')}")
|
|
324
|
+
console.print(f" Description: {rule.get('Description', '-')}")
|
|
325
|
+
console.print(f" Min Length: {rule.get('MinimumLength')}")
|
|
326
|
+
console.print(f" Max Length: {rule.get('MaximumLength')}")
|
|
327
|
+
console.print(f" First Char: {rule.get('FirstCharacterRequirement')}")
|
|
328
|
+
console.print(f" Uppercase: {rule.get('UppercaseRequirement')}")
|
|
329
|
+
console.print(f" Lowercase: {rule.get('LowercaseRequirement')}")
|
|
330
|
+
console.print(f" Numeric: {rule.get('NumericRequirement')}")
|
|
331
|
+
console.print(f" Symbol: {rule.get('SymbolRequirement')}")
|
|
332
|
+
|
|
333
|
+
if rule.get("ValidSymbols"):
|
|
334
|
+
console.print(f" Valid Symbols: {''.join(rule.get('ValidSymbols', []))}")
|
|
335
|
+
|
|
336
|
+
except httpx.HTTPStatusError as e:
|
|
337
|
+
print_api_error(e, "manage configuration")
|
|
338
|
+
raise typer.Exit(1)
|
|
339
|
+
except httpx.RequestError as e:
|
|
340
|
+
print_api_error(e, "manage configuration")
|
|
341
|
+
raise typer.Exit(1)
|
|
342
|
+
except Exception as e:
|
|
343
|
+
print_api_error(e, "manage configuration")
|
|
344
|
+
raise typer.Exit(1)
|