bt-cli 0.4.7__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.
Files changed (121) hide show
  1. bt_cli/__init__.py +3 -0
  2. bt_cli/cli.py +830 -0
  3. bt_cli/commands/__init__.py +1 -0
  4. bt_cli/commands/configure.py +415 -0
  5. bt_cli/commands/learn.py +229 -0
  6. bt_cli/commands/quick.py +784 -0
  7. bt_cli/core/__init__.py +1 -0
  8. bt_cli/core/auth.py +213 -0
  9. bt_cli/core/client.py +313 -0
  10. bt_cli/core/config.py +393 -0
  11. bt_cli/core/config_file.py +420 -0
  12. bt_cli/core/csv_utils.py +91 -0
  13. bt_cli/core/errors.py +247 -0
  14. bt_cli/core/output.py +205 -0
  15. bt_cli/core/prompts.py +87 -0
  16. bt_cli/core/rest_debug.py +221 -0
  17. bt_cli/data/CLAUDE.md +88 -0
  18. bt_cli/data/__init__.py +0 -0
  19. bt_cli/data/skills/bt/SKILL.md +98 -0
  20. bt_cli/data/skills/entitle/SKILL.md +159 -0
  21. bt_cli/data/skills/epmw/SKILL.md +145 -0
  22. bt_cli/data/skills/pra/SKILL.md +149 -0
  23. bt_cli/data/skills/pws/SKILL.md +197 -0
  24. bt_cli/entitle/__init__.py +1 -0
  25. bt_cli/entitle/client/__init__.py +5 -0
  26. bt_cli/entitle/client/base.py +443 -0
  27. bt_cli/entitle/commands/__init__.py +24 -0
  28. bt_cli/entitle/commands/accounts.py +53 -0
  29. bt_cli/entitle/commands/applications.py +39 -0
  30. bt_cli/entitle/commands/auth.py +68 -0
  31. bt_cli/entitle/commands/bundles.py +218 -0
  32. bt_cli/entitle/commands/integrations.py +60 -0
  33. bt_cli/entitle/commands/permissions.py +70 -0
  34. bt_cli/entitle/commands/policies.py +97 -0
  35. bt_cli/entitle/commands/resources.py +131 -0
  36. bt_cli/entitle/commands/roles.py +74 -0
  37. bt_cli/entitle/commands/users.py +123 -0
  38. bt_cli/entitle/commands/workflows.py +187 -0
  39. bt_cli/entitle/models/__init__.py +31 -0
  40. bt_cli/entitle/models/bundle.py +28 -0
  41. bt_cli/entitle/models/common.py +37 -0
  42. bt_cli/entitle/models/integration.py +30 -0
  43. bt_cli/entitle/models/permission.py +27 -0
  44. bt_cli/entitle/models/policy.py +25 -0
  45. bt_cli/entitle/models/resource.py +29 -0
  46. bt_cli/entitle/models/role.py +28 -0
  47. bt_cli/entitle/models/user.py +24 -0
  48. bt_cli/entitle/models/workflow.py +55 -0
  49. bt_cli/epmw/__init__.py +1 -0
  50. bt_cli/epmw/client/__init__.py +5 -0
  51. bt_cli/epmw/client/base.py +848 -0
  52. bt_cli/epmw/commands/__init__.py +33 -0
  53. bt_cli/epmw/commands/audits.py +250 -0
  54. bt_cli/epmw/commands/auth.py +55 -0
  55. bt_cli/epmw/commands/computers.py +140 -0
  56. bt_cli/epmw/commands/events.py +233 -0
  57. bt_cli/epmw/commands/groups.py +215 -0
  58. bt_cli/epmw/commands/policies.py +673 -0
  59. bt_cli/epmw/commands/quick.py +348 -0
  60. bt_cli/epmw/commands/requests.py +224 -0
  61. bt_cli/epmw/commands/roles.py +78 -0
  62. bt_cli/epmw/commands/tasks.py +38 -0
  63. bt_cli/epmw/commands/users.py +219 -0
  64. bt_cli/epmw/models/__init__.py +1 -0
  65. bt_cli/pra/__init__.py +1 -0
  66. bt_cli/pra/client/__init__.py +5 -0
  67. bt_cli/pra/client/base.py +618 -0
  68. bt_cli/pra/commands/__init__.py +30 -0
  69. bt_cli/pra/commands/auth.py +55 -0
  70. bt_cli/pra/commands/import_export.py +442 -0
  71. bt_cli/pra/commands/jump_clients.py +139 -0
  72. bt_cli/pra/commands/jump_groups.py +146 -0
  73. bt_cli/pra/commands/jump_items.py +638 -0
  74. bt_cli/pra/commands/jumpoints.py +95 -0
  75. bt_cli/pra/commands/policies.py +197 -0
  76. bt_cli/pra/commands/quick.py +470 -0
  77. bt_cli/pra/commands/teams.py +81 -0
  78. bt_cli/pra/commands/users.py +87 -0
  79. bt_cli/pra/commands/vault.py +564 -0
  80. bt_cli/pra/models/__init__.py +27 -0
  81. bt_cli/pra/models/common.py +12 -0
  82. bt_cli/pra/models/jump_client.py +25 -0
  83. bt_cli/pra/models/jump_group.py +15 -0
  84. bt_cli/pra/models/jump_item.py +72 -0
  85. bt_cli/pra/models/jumpoint.py +19 -0
  86. bt_cli/pra/models/team.py +14 -0
  87. bt_cli/pra/models/user.py +17 -0
  88. bt_cli/pra/models/vault.py +45 -0
  89. bt_cli/pws/__init__.py +1 -0
  90. bt_cli/pws/client/__init__.py +5 -0
  91. bt_cli/pws/client/base.py +356 -0
  92. bt_cli/pws/client/beyondinsight.py +869 -0
  93. bt_cli/pws/client/passwordsafe.py +1786 -0
  94. bt_cli/pws/commands/__init__.py +33 -0
  95. bt_cli/pws/commands/accounts.py +372 -0
  96. bt_cli/pws/commands/assets.py +311 -0
  97. bt_cli/pws/commands/auth.py +166 -0
  98. bt_cli/pws/commands/clouds.py +221 -0
  99. bt_cli/pws/commands/config.py +344 -0
  100. bt_cli/pws/commands/credentials.py +347 -0
  101. bt_cli/pws/commands/databases.py +306 -0
  102. bt_cli/pws/commands/directories.py +199 -0
  103. bt_cli/pws/commands/functional.py +298 -0
  104. bt_cli/pws/commands/import_export.py +452 -0
  105. bt_cli/pws/commands/platforms.py +118 -0
  106. bt_cli/pws/commands/quick.py +1646 -0
  107. bt_cli/pws/commands/search.py +256 -0
  108. bt_cli/pws/commands/secrets.py +1343 -0
  109. bt_cli/pws/commands/systems.py +389 -0
  110. bt_cli/pws/commands/users.py +415 -0
  111. bt_cli/pws/commands/workgroups.py +166 -0
  112. bt_cli/pws/config.py +18 -0
  113. bt_cli/pws/models/__init__.py +19 -0
  114. bt_cli/pws/models/account.py +186 -0
  115. bt_cli/pws/models/asset.py +102 -0
  116. bt_cli/pws/models/common.py +132 -0
  117. bt_cli/pws/models/system.py +121 -0
  118. bt_cli-0.4.7.dist-info/METADATA +172 -0
  119. bt_cli-0.4.7.dist-info/RECORD +121 -0
  120. bt_cli-0.4.7.dist-info/WHEEL +4 -0
  121. bt_cli-0.4.7.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,564 @@
1
+ """Vault account 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
+ # Account subcommands
13
+ accounts_app = typer.Typer(no_args_is_help=True, help="Vault accounts")
14
+ app.add_typer(accounts_app, name="accounts")
15
+
16
+
17
+ @accounts_app.command("list")
18
+ def list_vault_accounts(
19
+ account_type: Optional[str] = typer.Option(
20
+ None, "--type", "-t",
21
+ help="Filter by type: username_password, ssh, ssh_ca, windows_local, windows_domain, etc."
22
+ ),
23
+ name: Optional[str] = typer.Option(None, "--name", "-n", help="Filter by name"),
24
+ details: bool = typer.Option(False, "--details", "-d", help="Fetch full details (slower, includes username/checkout status)"),
25
+ output: OutputFormat = typer.Option(OutputFormat.TABLE, "--output", "-o"),
26
+ ):
27
+ """List Vault accounts."""
28
+ from bt_cli.pra.client import get_client
29
+ from rich.console import Console
30
+
31
+ console = Console()
32
+
33
+ try:
34
+ client = get_client()
35
+ accounts = client.list_vault_accounts(account_type=account_type, name=name)
36
+
37
+ # If details requested, fetch full info for each account
38
+ if details and accounts:
39
+ console.print(f"[dim]Fetching details for {len(accounts)} accounts...[/dim]")
40
+ detailed_accounts = []
41
+ for acc in accounts:
42
+ try:
43
+ full = client.get_vault_account(acc["id"])
44
+ # Add checkout status
45
+ if full.get("last_checkout_timestamp"):
46
+ full["checkout_status"] = "Checked Out"
47
+ else:
48
+ full["checkout_status"] = "-"
49
+ detailed_accounts.append(full)
50
+ except Exception:
51
+ detailed_accounts.append(acc)
52
+ accounts = detailed_accounts
53
+
54
+ if output == OutputFormat.JSON:
55
+ print_json(accounts)
56
+ else:
57
+ if details:
58
+ columns = [
59
+ ("ID", "id"),
60
+ ("Name", "name"),
61
+ ("Type", "type"),
62
+ ("Username", "username"),
63
+ ("Last Checkout", "last_checkout_timestamp"),
64
+ ("Description", "description"),
65
+ ]
66
+ else:
67
+ columns = [
68
+ ("ID", "id"),
69
+ ("Name", "name"),
70
+ ("Type", "type"),
71
+ ("Group ID", "account_group_id"),
72
+ ("Description", "description"),
73
+ ]
74
+ print_table(accounts, columns, title="Vault Accounts")
75
+ except httpx.HTTPStatusError as e:
76
+ print_api_error(e, "list vault accounts")
77
+ raise typer.Exit(1)
78
+ except httpx.RequestError as e:
79
+ print_api_error(e, "list vault accounts")
80
+ raise typer.Exit(1)
81
+ except Exception as e:
82
+ print_api_error(e, "list vault accounts")
83
+ raise typer.Exit(1)
84
+
85
+
86
+ @accounts_app.command("get")
87
+ def get_vault_account(
88
+ account_id: int = typer.Argument(..., help="Vault Account ID"),
89
+ output: OutputFormat = typer.Option(OutputFormat.TABLE, "--output", "-o"),
90
+ ):
91
+ """Get Vault account details."""
92
+ from bt_cli.pra.client import get_client
93
+ from rich.console import Console
94
+ from rich.panel import Panel
95
+
96
+ console = Console()
97
+
98
+ try:
99
+ client = get_client()
100
+ acc = client.get_vault_account(account_id)
101
+
102
+ if output == OutputFormat.JSON:
103
+ print_json(acc)
104
+ else:
105
+ name = acc.get("name", "")
106
+ acc_type = acc.get("type", "")
107
+ username = acc.get("username", "") or "-"
108
+ description = acc.get("description", "") or "-"
109
+ group_id = acc.get("account_group_id", "") or "-"
110
+ last_checkout = acc.get("last_checkout_timestamp", "") or "-"
111
+ personal = "Yes" if acc.get("personal") else "No"
112
+
113
+ console.print(Panel(
114
+ f"[bold]{name}[/bold]\n\n"
115
+ f"[dim]Type:[/dim] {acc_type}\n"
116
+ f"[dim]Username:[/dim] {username}\n"
117
+ f"[dim]Description:[/dim] {description}\n"
118
+ f"[dim]Group ID:[/dim] {group_id}\n"
119
+ f"[dim]Personal:[/dim] {personal}\n"
120
+ f"[dim]Last Checkout:[/dim] {last_checkout}",
121
+ title="Vault Account Details",
122
+ subtitle=f"ID: {acc.get('id', '')}",
123
+ ))
124
+ except httpx.HTTPStatusError as e:
125
+ print_api_error(e, "get vault account")
126
+ raise typer.Exit(1)
127
+ except httpx.RequestError as e:
128
+ print_api_error(e, "get vault account")
129
+ raise typer.Exit(1)
130
+ except Exception as e:
131
+ print_api_error(e, "get vault account")
132
+ raise typer.Exit(1)
133
+
134
+
135
+ @accounts_app.command("create")
136
+ def create_vault_account(
137
+ name: str = typer.Option(..., "--name", "-n", help="Account name"),
138
+ account_type: str = typer.Option(
139
+ ..., "--type", "-t",
140
+ help="Account type: username_password, ssh, or ssh_ca"
141
+ ),
142
+ username: Optional[str] = typer.Option(None, "--username", "-u", help="Username (for username_password)"),
143
+ password: Optional[str] = typer.Option(None, "--password", "-p", help="Password (for username_password)"),
144
+ private_key: Optional[str] = typer.Option(None, "--private-key", help="SSH private key (for ssh type)"),
145
+ private_key_file: Optional[str] = typer.Option(None, "--private-key-file", help="Path to SSH private key file"),
146
+ description: Optional[str] = typer.Option(None, "--description", "-d", help="Account description"),
147
+ personal: bool = typer.Option(False, "--personal", help="Mark as personal account"),
148
+ output: OutputFormat = typer.Option(OutputFormat.JSON, "--output", "-o"),
149
+ ):
150
+ """Create a Vault account.
151
+
152
+ Examples:
153
+ # Create username/password account
154
+ bt pra vault accounts create -n "db-admin" -t username_password -u "admin" -p "secret"
155
+
156
+ # Create SSH key account
157
+ bt pra vault accounts create -n "deploy-key" -t ssh --private-key-file ~/.ssh/id_rsa
158
+
159
+ # Create SSH CA account (for certificate-based auth)
160
+ bt pra vault accounts create -n "ssh-ca" -t ssh_ca --private-key-file /path/to/ca_key
161
+ """
162
+ from bt_cli.pra.client import get_client
163
+ from pathlib import Path
164
+
165
+ # Validate account type
166
+ valid_types = ["username_password", "ssh", "ssh_ca"]
167
+ if account_type not in valid_types:
168
+ print_error(f"Invalid account type '{account_type}'. Must be one of: {', '.join(valid_types)}")
169
+ raise typer.Exit(1)
170
+
171
+ # Build account data
172
+ data = {
173
+ "name": name,
174
+ "type": account_type,
175
+ }
176
+
177
+ if username:
178
+ data["username"] = username
179
+ if password:
180
+ data["password"] = password
181
+ if description:
182
+ data["description"] = description
183
+ if personal:
184
+ data["personal"] = True
185
+
186
+ # Handle private key (from file or direct)
187
+ if private_key_file:
188
+ key_path = Path(private_key_file).expanduser()
189
+ if not key_path.exists():
190
+ print_error(f"Private key file not found: {private_key_file}")
191
+ raise typer.Exit(1)
192
+ data["private_key"] = key_path.read_text()
193
+ elif private_key:
194
+ data["private_key"] = private_key
195
+
196
+ # Validate required fields per type
197
+ if account_type == "username_password":
198
+ if not username:
199
+ print_error("Username is required for username_password type")
200
+ raise typer.Exit(1)
201
+ elif account_type in ["ssh", "ssh_ca"]:
202
+ if not data.get("private_key"):
203
+ print_error(f"Private key is required for {account_type} type (use --private-key or --private-key-file)")
204
+ raise typer.Exit(1)
205
+
206
+ try:
207
+ client = get_client()
208
+ result = client.create_vault_account(data)
209
+ print_success(f"Created vault account: {result.get('name')} (ID: {result.get('id')})")
210
+
211
+ if output == OutputFormat.JSON:
212
+ print_json(result)
213
+ else:
214
+ typer.echo(f"ID: {result.get('id')}")
215
+ typer.echo(f"Name: {result.get('name')}")
216
+ typer.echo(f"Type: {result.get('type')}")
217
+ except httpx.HTTPStatusError as e:
218
+ print_api_error(e, "create vault account")
219
+ raise typer.Exit(1)
220
+ except httpx.RequestError as e:
221
+ print_api_error(e, "create vault account")
222
+ raise typer.Exit(1)
223
+ except Exception as e:
224
+ print_api_error(e, "create vault account")
225
+ raise typer.Exit(1)
226
+
227
+
228
+ @accounts_app.command("delete")
229
+ def delete_vault_account(
230
+ account_id: int = typer.Argument(..., help="Vault Account ID to delete"),
231
+ force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation"),
232
+ ):
233
+ """Delete a Vault account."""
234
+ from bt_cli.pra.client import get_client
235
+
236
+ try:
237
+ client = get_client()
238
+
239
+ # Get account details for confirmation
240
+ account = client.get_vault_account(account_id)
241
+ account_name = account.get("name", f"ID {account_id}")
242
+
243
+ if not force:
244
+ typer.confirm(f"Delete vault account '{account_name}'?", abort=True)
245
+
246
+ client.delete_vault_account(account_id)
247
+ print_success(f"Deleted vault account: {account_name}")
248
+ except httpx.HTTPStatusError as e:
249
+ print_api_error(e, "delete vault account")
250
+ raise typer.Exit(1)
251
+ except httpx.RequestError as e:
252
+ print_api_error(e, "delete vault account")
253
+ raise typer.Exit(1)
254
+ except Exception as e:
255
+ print_api_error(e, "delete vault account")
256
+ raise typer.Exit(1)
257
+
258
+
259
+ @accounts_app.command("get-user-data")
260
+ def get_user_data(
261
+ account_id: int = typer.Argument(..., help="SSH CA Vault Account ID"),
262
+ username: str = typer.Option("ec2-admin", "--username", "-u", help="Username to create on target host"),
263
+ sudo: bool = typer.Option(True, "--sudo/--no-sudo", help="Grant passwordless sudo"),
264
+ shell: str = typer.Option("/bin/bash", "--shell", "-s", help="User shell"),
265
+ ):
266
+ """Generate EC2 user-data script for SSH CA provisioning.
267
+
268
+ Creates a complete cloud-init script that:
269
+ - Creates the specified user
270
+ - Configures SSH to trust the CA public key
271
+ - Optionally grants passwordless sudo
272
+
273
+ Use this output as EC2 user-data when launching instances.
274
+
275
+ Example:
276
+ # Generate and save to file
277
+ bt pra vault accounts get-user-data 31 --username ec2-admin > user-data.sh
278
+
279
+ # Use with AWS CLI
280
+ aws ec2 run-instances ... --user-data file://user-data.sh
281
+ """
282
+ from bt_cli.pra.client import get_client
283
+
284
+ try:
285
+ client = get_client()
286
+ account = client.get_vault_account(account_id)
287
+
288
+ if account.get("type") != "ssh_ca":
289
+ print_error(f"Account {account_id} is type '{account.get('type')}', not 'ssh_ca'")
290
+ raise typer.Exit(1)
291
+
292
+ public_key = account.get("public_key")
293
+ if not public_key:
294
+ print_error(f"Account {account_id} has no public_key")
295
+ raise typer.Exit(1)
296
+
297
+ # Generate user-data script
298
+ sudo_line = f'echo "{username} ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/{username}' if sudo else "# sudo not configured"
299
+
300
+ script = f'''#!/bin/bash
301
+ # Generated by bt-cli for PRA SSH CA authentication
302
+ # Vault Account ID: {account_id}
303
+ # Account Name: {account.get("name", "unknown")}
304
+
305
+ set -e
306
+
307
+ # Create user
308
+ useradd -m -s {shell} {username} 2>/dev/null || true
309
+ mkdir -p /home/{username}/.ssh
310
+ chown {username}:{username} /home/{username}/.ssh
311
+ chmod 700 /home/{username}/.ssh
312
+
313
+ # Configure SSH CA trust
314
+ echo "TrustedUserCAKeys /etc/ssh/trusted-user-ca-keys.pub" >> /etc/ssh/sshd_config
315
+ cat > /etc/ssh/trusted-user-ca-keys.pub << 'CAKEY'
316
+ {public_key}
317
+ CAKEY
318
+
319
+ # Configure sudo
320
+ {sudo_line}
321
+
322
+ # Restart SSH
323
+ systemctl restart sshd || service sshd restart
324
+
325
+ echo "SSH CA provisioning complete for user: {username}"
326
+ '''
327
+ typer.echo(script)
328
+
329
+ except httpx.HTTPStatusError as e:
330
+ print_api_error(e, "get user-data")
331
+ raise typer.Exit(1)
332
+ except httpx.RequestError as e:
333
+ print_api_error(e, "get user-data")
334
+ raise typer.Exit(1)
335
+ except Exception as e:
336
+ print_api_error(e, "get user-data")
337
+ raise typer.Exit(1)
338
+
339
+
340
+ @accounts_app.command("get-public-key")
341
+ def get_public_key(
342
+ account_id: int = typer.Argument(..., help="SSH CA Vault Account ID"),
343
+ authorized_keys: bool = typer.Option(
344
+ True, "--authorized-keys/--raw",
345
+ help="Output in authorized_keys format (with cert-authority prefix)"
346
+ ),
347
+ ):
348
+ """Get SSH CA public key for provisioning hosts.
349
+
350
+ Retrieves the public key from an SSH CA vault account, ready to add
351
+ to a user's authorized_keys file for certificate-based authentication.
352
+
353
+ Example usage in EC2 user-data:
354
+
355
+ PRA_PUBLIC_KEY=$(bt pra vault accounts get-public-key 3)
356
+ echo "$PRA_PUBLIC_KEY" >> /home/admin/.ssh/authorized_keys
357
+
358
+ The output includes the 'cert-authority' prefix required for SSH CA auth.
359
+ """
360
+ from bt_cli.pra.client import get_client
361
+
362
+ try:
363
+ client = get_client()
364
+ account = client.get_vault_account(account_id)
365
+
366
+ if account.get("type") != "ssh_ca":
367
+ print_error(f"Account {account_id} is type '{account.get('type')}', not 'ssh_ca'")
368
+ raise typer.Exit(1)
369
+
370
+ public_key = account.get("public_key")
371
+ if not public_key:
372
+ print_error(f"Account {account_id} has no public_key")
373
+ raise typer.Exit(1)
374
+
375
+ # Output just the key - ready for authorized_keys or scripting
376
+ typer.echo(public_key)
377
+
378
+ except httpx.HTTPStatusError as e:
379
+ print_api_error(e, "get public key")
380
+ raise typer.Exit(1)
381
+ except httpx.RequestError as e:
382
+ print_api_error(e, "get public key")
383
+ raise typer.Exit(1)
384
+ except Exception as e:
385
+ print_api_error(e, "get public key")
386
+ raise typer.Exit(1)
387
+
388
+
389
+ @accounts_app.command("checkout")
390
+ def checkout_vault_account(
391
+ account_id: int = typer.Argument(..., help="Vault Account ID"),
392
+ output: OutputFormat = typer.Option(OutputFormat.JSON, "--output", "-o"),
393
+ ):
394
+ """Check out a Vault account's credentials."""
395
+ from bt_cli.pra.client import get_client
396
+
397
+ try:
398
+ client = get_client()
399
+ credential = client.checkout_vault_account(account_id)
400
+ print_success(f"Checked out vault account {account_id}")
401
+
402
+ if output == OutputFormat.JSON:
403
+ print_json(credential)
404
+ else:
405
+ if credential.get("password"):
406
+ typer.echo(f"Password: {credential['password']}")
407
+ if credential.get("private_key"):
408
+ typer.echo(f"Private Key:\n{credential['private_key']}")
409
+ if credential.get("passphrase"):
410
+ typer.echo(f"Passphrase: {credential['passphrase']}")
411
+ except httpx.HTTPStatusError as e:
412
+ print_api_error(e, "checkout vault account")
413
+ raise typer.Exit(1)
414
+ except httpx.RequestError as e:
415
+ print_api_error(e, "checkout vault account")
416
+ raise typer.Exit(1)
417
+ except Exception as e:
418
+ print_api_error(e, "checkout vault account")
419
+ raise typer.Exit(1)
420
+
421
+
422
+ @accounts_app.command("checkin")
423
+ def checkin_vault_account(
424
+ account_id: int = typer.Argument(..., help="Vault Account ID"),
425
+ ):
426
+ """Check in a Vault account."""
427
+ from bt_cli.pra.client import get_client
428
+
429
+ try:
430
+ client = get_client()
431
+ client.checkin_vault_account(account_id)
432
+ print_success(f"Checked in vault account {account_id}")
433
+ except httpx.HTTPStatusError as e:
434
+ print_api_error(e, "checkin vault account")
435
+ raise typer.Exit(1)
436
+ except httpx.RequestError as e:
437
+ print_api_error(e, "checkin vault account")
438
+ raise typer.Exit(1)
439
+ except Exception as e:
440
+ print_api_error(e, "checkin vault account")
441
+ raise typer.Exit(1)
442
+
443
+
444
+ @accounts_app.command("force-checkin")
445
+ def force_checkin_vault_account(
446
+ account_id: int = typer.Argument(..., help="Vault Account ID"),
447
+ force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation"),
448
+ ):
449
+ """Force check in a Vault account (admin operation)."""
450
+ from bt_cli.pra.client import get_client
451
+
452
+ if not force:
453
+ typer.confirm(f"Force checkin vault account {account_id}?", abort=True)
454
+
455
+ try:
456
+ client = get_client()
457
+ client.force_checkin_vault_account(account_id)
458
+ print_success(f"Force checked in vault account {account_id}")
459
+ except httpx.HTTPStatusError as e:
460
+ print_api_error(e, "force checkin vault account")
461
+ raise typer.Exit(1)
462
+ except httpx.RequestError as e:
463
+ print_api_error(e, "force checkin vault account")
464
+ raise typer.Exit(1)
465
+ except Exception as e:
466
+ print_api_error(e, "force checkin vault account")
467
+ raise typer.Exit(1)
468
+
469
+
470
+ @accounts_app.command("rotate")
471
+ def rotate_vault_account(
472
+ account_id: int = typer.Argument(..., help="Vault Account ID"),
473
+ ):
474
+ """Schedule credential rotation for a Vault account."""
475
+ from bt_cli.pra.client import get_client
476
+
477
+ try:
478
+ client = get_client()
479
+ client.rotate_vault_account(account_id)
480
+ print_success(f"Scheduled rotation for vault account {account_id}")
481
+ except httpx.HTTPStatusError as e:
482
+ print_api_error(e, "schedule rotation")
483
+ raise typer.Exit(1)
484
+ except httpx.RequestError as e:
485
+ print_api_error(e, "schedule rotation")
486
+ raise typer.Exit(1)
487
+ except Exception as e:
488
+ print_api_error(e, "schedule rotation")
489
+ raise typer.Exit(1)
490
+
491
+
492
+ # Account Groups subcommands
493
+ groups_app = typer.Typer(no_args_is_help=True, help="Vault account groups")
494
+ app.add_typer(groups_app, name="groups")
495
+
496
+
497
+ @groups_app.command("list")
498
+ def list_vault_account_groups(
499
+ output: OutputFormat = typer.Option(OutputFormat.TABLE, "--output", "-o"),
500
+ ):
501
+ """List Vault account groups."""
502
+ from bt_cli.pra.client import get_client
503
+
504
+ try:
505
+ client = get_client()
506
+ groups = client.list_vault_account_groups()
507
+
508
+ if output == OutputFormat.JSON:
509
+ print_json(groups)
510
+ else:
511
+ columns = [
512
+ ("ID", "id"),
513
+ ("Name", "name"),
514
+ ("Description", "description"),
515
+ ]
516
+ print_table(groups, columns, title="Vault Account Groups")
517
+ except httpx.HTTPStatusError as e:
518
+ print_api_error(e, "list vault account groups")
519
+ raise typer.Exit(1)
520
+ except httpx.RequestError as e:
521
+ print_api_error(e, "list vault account groups")
522
+ raise typer.Exit(1)
523
+ except Exception as e:
524
+ print_api_error(e, "list vault account groups")
525
+ raise typer.Exit(1)
526
+
527
+
528
+ @groups_app.command("get")
529
+ def get_vault_account_group(
530
+ group_id: int = typer.Argument(..., help="Vault Account Group ID"),
531
+ output: OutputFormat = typer.Option(OutputFormat.TABLE, "--output", "-o"),
532
+ ):
533
+ """Get Vault account group details."""
534
+ from bt_cli.pra.client import get_client
535
+ from rich.console import Console
536
+ from rich.panel import Panel
537
+
538
+ console = Console()
539
+
540
+ try:
541
+ client = get_client()
542
+ group = client.get_vault_account_group(group_id)
543
+
544
+ if output == OutputFormat.JSON:
545
+ print_json(group)
546
+ else:
547
+ name = group.get("name", "")
548
+ description = group.get("description", "") or "-"
549
+
550
+ console.print(Panel(
551
+ f"[bold]{name}[/bold]\n\n"
552
+ f"[dim]Description:[/dim] {description}",
553
+ title="Vault Account Group Details",
554
+ subtitle=f"ID: {group.get('id', '')}",
555
+ ))
556
+ except httpx.HTTPStatusError as e:
557
+ print_api_error(e, "get vault account group")
558
+ raise typer.Exit(1)
559
+ except httpx.RequestError as e:
560
+ print_api_error(e, "get vault account group")
561
+ raise typer.Exit(1)
562
+ except Exception as e:
563
+ print_api_error(e, "get vault account group")
564
+ raise typer.Exit(1)
@@ -0,0 +1,27 @@
1
+ """PRA Pydantic models."""
2
+
3
+ from .common import PRABaseModel
4
+ from .jumpoint import Jumpoint
5
+ from .jump_group import JumpGroup
6
+ from .jump_client import JumpClient
7
+ from .jump_item import ShellJump, RdpJump, VncJump, WebJump, ProtocolTunnel
8
+ from .vault import VaultAccount, VaultAccountGroup, VaultCredential
9
+ from .user import User
10
+ from .team import Team
11
+
12
+ __all__ = [
13
+ "PRABaseModel",
14
+ "Jumpoint",
15
+ "JumpGroup",
16
+ "JumpClient",
17
+ "ShellJump",
18
+ "RdpJump",
19
+ "VncJump",
20
+ "WebJump",
21
+ "ProtocolTunnel",
22
+ "VaultAccount",
23
+ "VaultAccountGroup",
24
+ "VaultCredential",
25
+ "User",
26
+ "Team",
27
+ ]
@@ -0,0 +1,12 @@
1
+ """Common base models for PRA."""
2
+
3
+ from pydantic import BaseModel, ConfigDict
4
+
5
+
6
+ class PRABaseModel(BaseModel):
7
+ """Base model for all PRA resources."""
8
+
9
+ model_config = ConfigDict(
10
+ populate_by_name=True,
11
+ extra="allow",
12
+ )
@@ -0,0 +1,25 @@
1
+ """Jump Client model."""
2
+
3
+ from typing import Optional
4
+
5
+ from .common import PRABaseModel
6
+
7
+
8
+ class JumpClient(PRABaseModel):
9
+ """Jump Client - agent installed on endpoint."""
10
+
11
+ id: int
12
+ name: str
13
+ hostname: Optional[str] = None
14
+ fqdn: Optional[str] = None
15
+ tag: Optional[str] = None
16
+ comments: Optional[str] = None
17
+ jump_group_id: Optional[int] = None
18
+ jump_group_type: Optional[str] = None
19
+ connection_type: Optional[str] = None
20
+ public_ip: Optional[str] = None
21
+ private_ip: Optional[str] = None
22
+ console_user: Optional[str] = None
23
+ os_name: Optional[str] = None
24
+ os_version: Optional[str] = None
25
+ last_connect_time: Optional[str] = None
@@ -0,0 +1,15 @@
1
+ """Jump Group model."""
2
+
3
+ from typing import Optional
4
+
5
+ from .common import PRABaseModel
6
+
7
+
8
+ class JumpGroup(PRABaseModel):
9
+ """Jump Group - organizes Jump Items."""
10
+
11
+ id: int
12
+ name: str
13
+ code_name: Optional[str] = None
14
+ comments: Optional[str] = None
15
+ ecm_group_id: Optional[int] = None