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,442 @@
|
|
|
1
|
+
"""Import/export commands for PRA bulk operations."""
|
|
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, print_error, print_success, print_warning
|
|
12
|
+
from ...core.csv_utils import read_csv, write_csv, validate_required_fields, parse_bool, parse_int
|
|
13
|
+
from ..client import get_client
|
|
14
|
+
|
|
15
|
+
import_app = typer.Typer(no_args_is_help=True, help="Import resources from CSV files")
|
|
16
|
+
export_app = typer.Typer(no_args_is_help=True, help="Export sample CSV templates")
|
|
17
|
+
console = Console()
|
|
18
|
+
|
|
19
|
+
# Column definitions for CSV formats
|
|
20
|
+
JUMP_ITEMS_COLUMNS = [
|
|
21
|
+
"type", "name", "hostname", "jumpoint_id", "jump_group_id",
|
|
22
|
+
"username", "port", "protocol", "domain", "tag"
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
VAULT_ACCOUNTS_COLUMNS = [
|
|
26
|
+
"name", "type", "username", "password", "description", "private_key"
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
# Sample data for templates
|
|
30
|
+
JUMP_ITEMS_SAMPLE = [
|
|
31
|
+
{
|
|
32
|
+
"type": "shell", "name": "web-server-01", "hostname": "10.0.1.50",
|
|
33
|
+
"jumpoint_id": "3", "jump_group_id": "24",
|
|
34
|
+
"username": "ec2-admin", "port": "22", "protocol": "ssh",
|
|
35
|
+
"domain": "", "tag": "linux"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"type": "shell", "name": "web-server-02", "hostname": "10.0.1.51",
|
|
39
|
+
"jumpoint_id": "3", "jump_group_id": "24",
|
|
40
|
+
"username": "ec2-admin", "port": "22", "protocol": "ssh",
|
|
41
|
+
"domain": "", "tag": "linux"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"type": "shell", "name": "db-server-01", "hostname": "10.0.1.60",
|
|
45
|
+
"jumpoint_id": "3", "jump_group_id": "24",
|
|
46
|
+
"username": "ec2-admin", "port": "22", "protocol": "ssh",
|
|
47
|
+
"domain": "", "tag": "database"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"type": "rdp", "name": "win-server-01", "hostname": "10.0.2.10",
|
|
51
|
+
"jumpoint_id": "3", "jump_group_id": "31",
|
|
52
|
+
"username": "", "port": "3389", "protocol": "",
|
|
53
|
+
"domain": "nexusdyn.corp", "tag": "windows"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"type": "rdp", "name": "win-server-02", "hostname": "10.0.2.11",
|
|
57
|
+
"jumpoint_id": "3", "jump_group_id": "31",
|
|
58
|
+
"username": "", "port": "3389", "protocol": "",
|
|
59
|
+
"domain": "nexusdyn.corp", "tag": "windows"
|
|
60
|
+
},
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
VAULT_ACCOUNTS_SAMPLE = [
|
|
64
|
+
{
|
|
65
|
+
"name": "server-admin", "type": "username_password",
|
|
66
|
+
"username": "admin@corp.local", "password": "ServerAdmin#2026!",
|
|
67
|
+
"description": "Domain admin", "private_key": ""
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"name": "postgres-prod", "type": "username_password",
|
|
71
|
+
"username": "postgres", "password": "PgProd#2026!",
|
|
72
|
+
"description": "Production DB", "private_key": ""
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"name": "ssh-deploy-key", "type": "ssh",
|
|
76
|
+
"username": "deploy", "password": "",
|
|
77
|
+
"description": "Deployment SSH key", "private_key": "-----BEGIN OPENSSH PRIVATE KEY-----\n..."
|
|
78
|
+
},
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@import_app.command("jump-items")
|
|
83
|
+
def import_jump_items(
|
|
84
|
+
file: str = typer.Option(..., "--file", "-f", help="CSV file path"),
|
|
85
|
+
dry_run: bool = typer.Option(False, "--dry-run", help="Validate without creating"),
|
|
86
|
+
jumpoint: Optional[int] = typer.Option(None, "--jumpoint", "-j", help="Override jumpoint ID for all rows"),
|
|
87
|
+
jump_group: Optional[int] = typer.Option(None, "--jump-group", "-g", help="Override jump group ID for all rows"),
|
|
88
|
+
) -> None:
|
|
89
|
+
"""Import jump items (shell and RDP) from CSV.
|
|
90
|
+
|
|
91
|
+
The 'type' column determines whether to create a shell or RDP jump item.
|
|
92
|
+
|
|
93
|
+
Required columns: type, name, hostname, jumpoint_id, jump_group_id
|
|
94
|
+
Optional: username, port, protocol, domain, tag
|
|
95
|
+
|
|
96
|
+
Examples:
|
|
97
|
+
bt pra import jump-items --file jump-items.csv --dry-run
|
|
98
|
+
bt pra import jump-items --file jump-items.csv
|
|
99
|
+
bt pra import jump-items --file jump-items.csv --jumpoint 3 --jump-group 24
|
|
100
|
+
"""
|
|
101
|
+
try:
|
|
102
|
+
rows = read_csv(file)
|
|
103
|
+
if not rows:
|
|
104
|
+
print_error("CSV file is empty")
|
|
105
|
+
raise typer.Exit(1)
|
|
106
|
+
|
|
107
|
+
console.print(f"[dim]Read {len(rows)} rows from {file}[/dim]")
|
|
108
|
+
|
|
109
|
+
# Validate all rows first
|
|
110
|
+
errors = []
|
|
111
|
+
required = ["type", "name", "hostname"]
|
|
112
|
+
if not jumpoint:
|
|
113
|
+
required.append("jumpoint_id")
|
|
114
|
+
if not jump_group:
|
|
115
|
+
required.append("jump_group_id")
|
|
116
|
+
|
|
117
|
+
for i, row in enumerate(rows, 1):
|
|
118
|
+
row_errors = validate_required_fields(row, required, i)
|
|
119
|
+
errors.extend(row_errors)
|
|
120
|
+
|
|
121
|
+
# Validate type
|
|
122
|
+
item_type = row.get("type", "").strip().lower()
|
|
123
|
+
if item_type and item_type not in ("shell", "rdp"):
|
|
124
|
+
errors.append(f"Row {i}: Invalid type '{item_type}' (must be 'shell' or 'rdp')")
|
|
125
|
+
|
|
126
|
+
if errors:
|
|
127
|
+
print_error("Validation errors:")
|
|
128
|
+
for err in errors[:20]:
|
|
129
|
+
console.print(f" [red]{err}[/red]")
|
|
130
|
+
if len(errors) > 20:
|
|
131
|
+
console.print(f" [red]... and {len(errors) - 20} more errors[/red]")
|
|
132
|
+
raise typer.Exit(1)
|
|
133
|
+
|
|
134
|
+
# Count by type
|
|
135
|
+
shell_count = sum(1 for r in rows if r.get("type", "").lower() == "shell")
|
|
136
|
+
rdp_count = sum(1 for r in rows if r.get("type", "").lower() == "rdp")
|
|
137
|
+
console.print(f"[dim]Found {shell_count} shell and {rdp_count} RDP jump items[/dim]")
|
|
138
|
+
|
|
139
|
+
if dry_run:
|
|
140
|
+
console.print("\n[yellow]DRY RUN - No changes will be made[/yellow]\n")
|
|
141
|
+
table = Table(title="Jump Items to Create")
|
|
142
|
+
table.add_column("Type", style="magenta")
|
|
143
|
+
table.add_column("Name", style="green")
|
|
144
|
+
table.add_column("Hostname", style="cyan")
|
|
145
|
+
table.add_column("Jumpoint", style="yellow")
|
|
146
|
+
table.add_column("Group", style="blue")
|
|
147
|
+
table.add_column("Username", style="dim")
|
|
148
|
+
|
|
149
|
+
for row in rows:
|
|
150
|
+
jp = jumpoint or parse_int(row.get("jumpoint_id", ""))
|
|
151
|
+
jg = jump_group or parse_int(row.get("jump_group_id", ""))
|
|
152
|
+
table.add_row(
|
|
153
|
+
row.get("type", "").upper(),
|
|
154
|
+
row.get("name", ""),
|
|
155
|
+
row.get("hostname", ""),
|
|
156
|
+
str(jp),
|
|
157
|
+
str(jg),
|
|
158
|
+
row.get("username", "") or "-"
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
console.print(table)
|
|
162
|
+
console.print(f"\n[dim]Would create {len(rows)} jump items[/dim]")
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
# Actually import
|
|
166
|
+
client = get_client()
|
|
167
|
+
created = 0
|
|
168
|
+
|
|
169
|
+
for row in rows:
|
|
170
|
+
item_type = row.get("type", "").strip().lower()
|
|
171
|
+
name = row.get("name", "").strip()
|
|
172
|
+
hostname = row.get("hostname", "").strip()
|
|
173
|
+
jp_id = jumpoint or parse_int(row.get("jumpoint_id", ""))
|
|
174
|
+
jg_id = jump_group or parse_int(row.get("jump_group_id", ""))
|
|
175
|
+
|
|
176
|
+
if not jp_id or not jg_id:
|
|
177
|
+
print_warning(f"Skipping {name}: Missing jumpoint or jump group ID")
|
|
178
|
+
continue
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
if item_type == "shell":
|
|
182
|
+
port = parse_int(row.get("port", ""), 22)
|
|
183
|
+
protocol = row.get("protocol", "").strip() or "ssh"
|
|
184
|
+
username = row.get("username", "").strip() or None
|
|
185
|
+
tag = row.get("tag", "").strip() or None
|
|
186
|
+
|
|
187
|
+
console.print(f"[dim]Creating shell jump item '{name}'...[/dim]")
|
|
188
|
+
result = client.create_shell_jump(
|
|
189
|
+
name=name,
|
|
190
|
+
hostname=hostname,
|
|
191
|
+
jumpoint_id=jp_id,
|
|
192
|
+
jump_group_id=jg_id,
|
|
193
|
+
port=port,
|
|
194
|
+
protocol=protocol,
|
|
195
|
+
username=username,
|
|
196
|
+
tag=tag,
|
|
197
|
+
)
|
|
198
|
+
item_id = result.get("id")
|
|
199
|
+
created += 1
|
|
200
|
+
console.print(f" [green]Created shell jump: {name} (ID: {item_id})[/green]")
|
|
201
|
+
|
|
202
|
+
elif item_type == "rdp":
|
|
203
|
+
port = parse_int(row.get("port", ""), 3389)
|
|
204
|
+
domain = row.get("domain", "").strip() or None
|
|
205
|
+
tag = row.get("tag", "").strip() or None
|
|
206
|
+
|
|
207
|
+
console.print(f"[dim]Creating RDP jump item '{name}'...[/dim]")
|
|
208
|
+
result = client.create_rdp_jump(
|
|
209
|
+
name=name,
|
|
210
|
+
hostname=hostname,
|
|
211
|
+
jumpoint_id=jp_id,
|
|
212
|
+
jump_group_id=jg_id,
|
|
213
|
+
rdp_port=port,
|
|
214
|
+
domain=domain,
|
|
215
|
+
tag=tag,
|
|
216
|
+
)
|
|
217
|
+
item_id = result.get("id")
|
|
218
|
+
created += 1
|
|
219
|
+
console.print(f" [green]Created RDP jump: {name} (ID: {item_id})[/green]")
|
|
220
|
+
|
|
221
|
+
except httpx.HTTPStatusError as e:
|
|
222
|
+
print_warning(f"Error creating {name}: {e.response.text}")
|
|
223
|
+
except Exception as e:
|
|
224
|
+
print_warning(f"Error creating {name}: {e}")
|
|
225
|
+
|
|
226
|
+
print_success(f"Import complete: {created} jump items created")
|
|
227
|
+
|
|
228
|
+
except FileNotFoundError as e:
|
|
229
|
+
print_error(str(e))
|
|
230
|
+
raise typer.Exit(1)
|
|
231
|
+
except typer.Exit:
|
|
232
|
+
raise
|
|
233
|
+
except Exception as e:
|
|
234
|
+
print_api_error(e, "import jump-items")
|
|
235
|
+
raise typer.Exit(1)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
@import_app.command("vault-accounts")
|
|
239
|
+
def import_vault_accounts(
|
|
240
|
+
file: str = typer.Option(..., "--file", "-f", help="CSV file path"),
|
|
241
|
+
dry_run: bool = typer.Option(False, "--dry-run", help="Validate without creating"),
|
|
242
|
+
) -> None:
|
|
243
|
+
"""Import vault accounts from CSV.
|
|
244
|
+
|
|
245
|
+
Required columns: name, type (username_password|ssh|ssh_ca)
|
|
246
|
+
Optional: username, password, description, private_key
|
|
247
|
+
|
|
248
|
+
Examples:
|
|
249
|
+
bt pra import vault-accounts --file vault-accounts.csv --dry-run
|
|
250
|
+
bt pra import vault-accounts --file vault-accounts.csv
|
|
251
|
+
"""
|
|
252
|
+
try:
|
|
253
|
+
rows = read_csv(file)
|
|
254
|
+
if not rows:
|
|
255
|
+
print_error("CSV file is empty")
|
|
256
|
+
raise typer.Exit(1)
|
|
257
|
+
|
|
258
|
+
console.print(f"[dim]Read {len(rows)} rows from {file}[/dim]")
|
|
259
|
+
|
|
260
|
+
# Validate
|
|
261
|
+
errors = []
|
|
262
|
+
required = ["name", "type"]
|
|
263
|
+
valid_types = ("username_password", "ssh", "ssh_ca")
|
|
264
|
+
|
|
265
|
+
for i, row in enumerate(rows, 1):
|
|
266
|
+
row_errors = validate_required_fields(row, required, i)
|
|
267
|
+
errors.extend(row_errors)
|
|
268
|
+
|
|
269
|
+
# Validate type
|
|
270
|
+
acct_type = row.get("type", "").strip().lower()
|
|
271
|
+
if acct_type and acct_type not in valid_types:
|
|
272
|
+
errors.append(f"Row {i}: Invalid type '{acct_type}' (must be one of: {', '.join(valid_types)})")
|
|
273
|
+
|
|
274
|
+
if errors:
|
|
275
|
+
print_error("Validation errors:")
|
|
276
|
+
for err in errors[:20]:
|
|
277
|
+
console.print(f" [red]{err}[/red]")
|
|
278
|
+
raise typer.Exit(1)
|
|
279
|
+
|
|
280
|
+
if dry_run:
|
|
281
|
+
console.print("\n[yellow]DRY RUN - No changes will be made[/yellow]\n")
|
|
282
|
+
table = Table(title="Vault Accounts to Create")
|
|
283
|
+
table.add_column("Name", style="green")
|
|
284
|
+
table.add_column("Type", style="magenta")
|
|
285
|
+
table.add_column("Username", style="cyan")
|
|
286
|
+
table.add_column("Description", style="dim")
|
|
287
|
+
|
|
288
|
+
for row in rows:
|
|
289
|
+
table.add_row(
|
|
290
|
+
row.get("name", ""),
|
|
291
|
+
row.get("type", ""),
|
|
292
|
+
row.get("username", "") or "-",
|
|
293
|
+
(row.get("description", "") or "-")[:40]
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
console.print(table)
|
|
297
|
+
console.print(f"\n[dim]Would create {len(rows)} vault accounts[/dim]")
|
|
298
|
+
return
|
|
299
|
+
|
|
300
|
+
# Actually import
|
|
301
|
+
client = get_client()
|
|
302
|
+
created = 0
|
|
303
|
+
|
|
304
|
+
for row in rows:
|
|
305
|
+
name = row.get("name", "").strip()
|
|
306
|
+
acct_type = row.get("type", "").strip().lower()
|
|
307
|
+
|
|
308
|
+
try:
|
|
309
|
+
console.print(f"[dim]Creating vault account '{name}'...[/dim]")
|
|
310
|
+
result = client.create_vault_account(
|
|
311
|
+
name=name,
|
|
312
|
+
account_type=acct_type,
|
|
313
|
+
username=row.get("username", "").strip() or None,
|
|
314
|
+
password=row.get("password", "").strip() or None,
|
|
315
|
+
description=row.get("description", "").strip() or None,
|
|
316
|
+
private_key=row.get("private_key", "").strip() or None,
|
|
317
|
+
)
|
|
318
|
+
acct_id = result.get("id")
|
|
319
|
+
created += 1
|
|
320
|
+
console.print(f" [green]Created vault account: {name} (ID: {acct_id})[/green]")
|
|
321
|
+
|
|
322
|
+
except httpx.HTTPStatusError as e:
|
|
323
|
+
print_warning(f"Error creating {name}: {e.response.text}")
|
|
324
|
+
except Exception as e:
|
|
325
|
+
print_warning(f"Error creating {name}: {e}")
|
|
326
|
+
|
|
327
|
+
print_success(f"Import complete: {created} vault accounts created")
|
|
328
|
+
|
|
329
|
+
except FileNotFoundError as e:
|
|
330
|
+
print_error(str(e))
|
|
331
|
+
raise typer.Exit(1)
|
|
332
|
+
except typer.Exit:
|
|
333
|
+
raise
|
|
334
|
+
except Exception as e:
|
|
335
|
+
print_api_error(e, "import vault-accounts")
|
|
336
|
+
raise typer.Exit(1)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
@export_app.command("jump-items")
|
|
340
|
+
def export_jump_items(
|
|
341
|
+
file: str = typer.Option("pra-jump-items-template.csv", "--file", "-f", help="Output file path"),
|
|
342
|
+
sample: bool = typer.Option(True, "--sample/--no-sample", help="Export sample template (default) or current data"),
|
|
343
|
+
) -> None:
|
|
344
|
+
"""Export sample jump items CSV template.
|
|
345
|
+
|
|
346
|
+
Examples:
|
|
347
|
+
bt pra export jump-items --file jump-items-template.csv
|
|
348
|
+
bt pra export jump-items --file jump-items-template.csv --no-sample
|
|
349
|
+
"""
|
|
350
|
+
try:
|
|
351
|
+
if sample:
|
|
352
|
+
write_csv(file, JUMP_ITEMS_SAMPLE, JUMP_ITEMS_COLUMNS)
|
|
353
|
+
print_success(f"Sample jump items template exported to: {file}")
|
|
354
|
+
console.print(f"[dim]Contains {len(JUMP_ITEMS_SAMPLE)} example rows[/dim]")
|
|
355
|
+
else:
|
|
356
|
+
# Export actual data from API
|
|
357
|
+
client = get_client()
|
|
358
|
+
|
|
359
|
+
rows = []
|
|
360
|
+
|
|
361
|
+
# Get shell jump items
|
|
362
|
+
shell_items = client.list_shell_jumps()
|
|
363
|
+
for item in shell_items:
|
|
364
|
+
rows.append({
|
|
365
|
+
"type": "shell",
|
|
366
|
+
"name": item.get("name", ""),
|
|
367
|
+
"hostname": item.get("hostname", ""),
|
|
368
|
+
"jumpoint_id": str(item.get("jumpoint_id", "")),
|
|
369
|
+
"jump_group_id": str(item.get("jump_group_id", "")),
|
|
370
|
+
"username": item.get("username", "") or "",
|
|
371
|
+
"port": str(item.get("port", "")),
|
|
372
|
+
"protocol": item.get("protocol", ""),
|
|
373
|
+
"domain": "",
|
|
374
|
+
"tag": item.get("tag", "") or "",
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
# Get RDP jump items
|
|
378
|
+
rdp_items = client.list_rdp_jumps()
|
|
379
|
+
for item in rdp_items:
|
|
380
|
+
rows.append({
|
|
381
|
+
"type": "rdp",
|
|
382
|
+
"name": item.get("name", ""),
|
|
383
|
+
"hostname": item.get("hostname", ""),
|
|
384
|
+
"jumpoint_id": str(item.get("jumpoint_id", "")),
|
|
385
|
+
"jump_group_id": str(item.get("jump_group_id", "")),
|
|
386
|
+
"username": "",
|
|
387
|
+
"port": str(item.get("rdp_port", item.get("port", ""))),
|
|
388
|
+
"protocol": "",
|
|
389
|
+
"domain": item.get("domain", "") or "",
|
|
390
|
+
"tag": item.get("tag", "") or "",
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
write_csv(file, rows, JUMP_ITEMS_COLUMNS)
|
|
394
|
+
print_success(f"Exported {len(rows)} jump items to: {file}")
|
|
395
|
+
|
|
396
|
+
except typer.Exit:
|
|
397
|
+
raise
|
|
398
|
+
except Exception as e:
|
|
399
|
+
print_api_error(e, "export jump-items")
|
|
400
|
+
raise typer.Exit(1)
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
@export_app.command("vault-accounts")
|
|
404
|
+
def export_vault_accounts(
|
|
405
|
+
file: str = typer.Option("pra-vault-accounts-template.csv", "--file", "-f", help="Output file path"),
|
|
406
|
+
sample: bool = typer.Option(True, "--sample/--no-sample", help="Export sample template (default) or current data"),
|
|
407
|
+
) -> None:
|
|
408
|
+
"""Export sample vault accounts CSV template.
|
|
409
|
+
|
|
410
|
+
Examples:
|
|
411
|
+
bt pra export vault-accounts --file vault-accounts-template.csv
|
|
412
|
+
bt pra export vault-accounts --file vault-accounts-template.csv --no-sample
|
|
413
|
+
"""
|
|
414
|
+
try:
|
|
415
|
+
if sample:
|
|
416
|
+
write_csv(file, VAULT_ACCOUNTS_SAMPLE, VAULT_ACCOUNTS_COLUMNS)
|
|
417
|
+
print_success(f"Sample vault accounts template exported to: {file}")
|
|
418
|
+
console.print(f"[dim]Contains {len(VAULT_ACCOUNTS_SAMPLE)} example rows[/dim]")
|
|
419
|
+
else:
|
|
420
|
+
# Export actual data from API
|
|
421
|
+
client = get_client()
|
|
422
|
+
|
|
423
|
+
accounts = client.list_vault_accounts()
|
|
424
|
+
rows = []
|
|
425
|
+
for acct in accounts:
|
|
426
|
+
rows.append({
|
|
427
|
+
"name": acct.get("name", ""),
|
|
428
|
+
"type": acct.get("type", ""),
|
|
429
|
+
"username": acct.get("username", "") or "",
|
|
430
|
+
"password": "", # Don't export passwords
|
|
431
|
+
"description": acct.get("description", "") or "",
|
|
432
|
+
"private_key": "", # Don't export private keys
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
write_csv(file, rows, VAULT_ACCOUNTS_COLUMNS)
|
|
436
|
+
print_success(f"Exported {len(rows)} vault accounts to: {file}")
|
|
437
|
+
|
|
438
|
+
except typer.Exit:
|
|
439
|
+
raise
|
|
440
|
+
except Exception as e:
|
|
441
|
+
print_api_error(e, "export vault-accounts")
|
|
442
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Jump Client commands."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from bt_cli.core.output import OutputFormat, print_table, print_json, print_error, print_success, print_api_error
|
|
9
|
+
|
|
10
|
+
app = typer.Typer(no_args_is_help=True)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@app.command("list")
|
|
14
|
+
def list_jump_clients(
|
|
15
|
+
jump_group_id: Optional[int] = typer.Option(None, "--jump-group", "-g", help="Filter by Jump Group ID"),
|
|
16
|
+
name: Optional[str] = typer.Option(None, "--name", "-n", help="Filter by name"),
|
|
17
|
+
hostname: Optional[str] = typer.Option(None, "--hostname", "-h", help="Filter by hostname"),
|
|
18
|
+
output: OutputFormat = typer.Option(
|
|
19
|
+
OutputFormat.TABLE, "--output", "-o", help="Output format"
|
|
20
|
+
),
|
|
21
|
+
):
|
|
22
|
+
"""List Jump Clients (agents)."""
|
|
23
|
+
from bt_cli.pra.client import get_client
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
client = get_client()
|
|
27
|
+
clients = client.list_jump_clients(
|
|
28
|
+
jump_group_id=jump_group_id,
|
|
29
|
+
name=name,
|
|
30
|
+
hostname=hostname,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
if output == OutputFormat.JSON:
|
|
34
|
+
print_json(clients)
|
|
35
|
+
else:
|
|
36
|
+
columns = [
|
|
37
|
+
("ID", "id"),
|
|
38
|
+
("Name", "name"),
|
|
39
|
+
("Hostname", "hostname"),
|
|
40
|
+
("Private IP", "private_ip"),
|
|
41
|
+
("Public IP", "public_ip"),
|
|
42
|
+
("Connection", "connection_type"),
|
|
43
|
+
("Jump Group", "jump_group_id"),
|
|
44
|
+
]
|
|
45
|
+
print_table(clients, columns, title="Jump Clients")
|
|
46
|
+
except httpx.HTTPStatusError as e:
|
|
47
|
+
print_api_error(e, "list jump clients")
|
|
48
|
+
raise typer.Exit(1)
|
|
49
|
+
except httpx.RequestError as e:
|
|
50
|
+
print_api_error(e, "list jump clients")
|
|
51
|
+
raise typer.Exit(1)
|
|
52
|
+
except Exception as e:
|
|
53
|
+
print_api_error(e, "list jump clients")
|
|
54
|
+
raise typer.Exit(1)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@app.command("get")
|
|
58
|
+
def get_jump_client(
|
|
59
|
+
client_id: int = typer.Argument(..., help="Jump Client ID"),
|
|
60
|
+
output: OutputFormat = typer.Option(
|
|
61
|
+
OutputFormat.TABLE, "--output", "-o", help="Output format"
|
|
62
|
+
),
|
|
63
|
+
):
|
|
64
|
+
"""Get Jump Client details."""
|
|
65
|
+
from bt_cli.pra.client import get_client
|
|
66
|
+
from rich.console import Console
|
|
67
|
+
from rich.panel import Panel
|
|
68
|
+
|
|
69
|
+
console = Console()
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
client = get_client()
|
|
73
|
+
jc = client.get_jump_client(client_id)
|
|
74
|
+
|
|
75
|
+
if output == OutputFormat.JSON:
|
|
76
|
+
print_json(jc)
|
|
77
|
+
else:
|
|
78
|
+
name = jc.get("name", "")
|
|
79
|
+
hostname = jc.get("hostname", "")
|
|
80
|
+
fqdn = jc.get("fqdn", "")
|
|
81
|
+
os_info = jc.get("operating_system", "")
|
|
82
|
+
private_ip = jc.get("private_ip", "")
|
|
83
|
+
public_ip = jc.get("public_ip", "")
|
|
84
|
+
connection = jc.get("connection_type", "")
|
|
85
|
+
console_user = jc.get("console_user", "") or "-"
|
|
86
|
+
last_connect = jc.get("last_connect_timestamp", "") or "-"
|
|
87
|
+
jump_group = jc.get("jump_group_id", "")
|
|
88
|
+
tag = jc.get("tag", "") or "-"
|
|
89
|
+
|
|
90
|
+
console.print(Panel(
|
|
91
|
+
f"[bold]{name}[/bold]\n\n"
|
|
92
|
+
f"[dim]Hostname:[/dim] {hostname}\n"
|
|
93
|
+
f"[dim]FQDN:[/dim] {fqdn}\n"
|
|
94
|
+
f"[dim]OS:[/dim] {os_info}\n"
|
|
95
|
+
f"[dim]Private IP:[/dim] {private_ip}\n"
|
|
96
|
+
f"[dim]Public IP:[/dim] {public_ip}\n"
|
|
97
|
+
f"[dim]Connection:[/dim] {connection}\n"
|
|
98
|
+
f"[dim]Console User:[/dim] {console_user}\n"
|
|
99
|
+
f"[dim]Last Connect:[/dim] {last_connect}\n"
|
|
100
|
+
f"[dim]Jump Group ID:[/dim] {jump_group}\n"
|
|
101
|
+
f"[dim]Tag:[/dim] {tag}",
|
|
102
|
+
title="Jump Client Details",
|
|
103
|
+
subtitle=f"ID: {jc.get('id', '')}",
|
|
104
|
+
))
|
|
105
|
+
except httpx.HTTPStatusError as e:
|
|
106
|
+
print_api_error(e, "get jump client")
|
|
107
|
+
raise typer.Exit(1)
|
|
108
|
+
except httpx.RequestError as e:
|
|
109
|
+
print_api_error(e, "get jump client")
|
|
110
|
+
raise typer.Exit(1)
|
|
111
|
+
except Exception as e:
|
|
112
|
+
print_api_error(e, "get jump client")
|
|
113
|
+
raise typer.Exit(1)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@app.command("delete")
|
|
117
|
+
def delete_jump_client(
|
|
118
|
+
client_id: int = typer.Argument(..., help="Jump Client ID"),
|
|
119
|
+
force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation"),
|
|
120
|
+
):
|
|
121
|
+
"""Delete a Jump Client."""
|
|
122
|
+
from bt_cli.pra.client import get_client
|
|
123
|
+
|
|
124
|
+
if not force:
|
|
125
|
+
typer.confirm(f"Delete jump client {client_id}?", abort=True)
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
client = get_client()
|
|
129
|
+
client.delete_jump_client(client_id)
|
|
130
|
+
print_success(f"Deleted jump client {client_id}")
|
|
131
|
+
except httpx.HTTPStatusError as e:
|
|
132
|
+
print_api_error(e, "delete jump client")
|
|
133
|
+
raise typer.Exit(1)
|
|
134
|
+
except httpx.RequestError as e:
|
|
135
|
+
print_api_error(e, "delete jump client")
|
|
136
|
+
raise typer.Exit(1)
|
|
137
|
+
except Exception as e:
|
|
138
|
+
print_api_error(e, "delete jump client")
|
|
139
|
+
raise typer.Exit(1)
|