@zincapp/znvault-cli 2.16.4 → 2.17.0
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.
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +6 -0
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/apikey/conditions.d.ts +6 -0
- package/dist/commands/apikey/conditions.d.ts.map +1 -0
- package/dist/commands/apikey/conditions.js +57 -0
- package/dist/commands/apikey/conditions.js.map +1 -0
- package/dist/commands/apikey/create.d.ts +6 -0
- package/dist/commands/apikey/create.d.ts.map +1 -0
- package/dist/commands/apikey/create.js +106 -0
- package/dist/commands/apikey/create.js.map +1 -0
- package/dist/commands/apikey/delete.d.ts +6 -0
- package/dist/commands/apikey/delete.d.ts.map +1 -0
- package/dist/commands/apikey/delete.js +29 -0
- package/dist/commands/apikey/delete.js.map +1 -0
- package/dist/commands/apikey/enable-disable.d.ts +6 -0
- package/dist/commands/apikey/enable-disable.d.ts.map +1 -0
- package/dist/commands/apikey/enable-disable.js +44 -0
- package/dist/commands/apikey/enable-disable.js.map +1 -0
- package/dist/commands/apikey/helpers.d.ts +23 -0
- package/dist/commands/apikey/helpers.d.ts.map +1 -0
- package/dist/commands/apikey/helpers.js +135 -0
- package/dist/commands/apikey/helpers.js.map +1 -0
- package/dist/commands/apikey/index.d.ts +10 -0
- package/dist/commands/apikey/index.d.ts.map +1 -0
- package/dist/commands/apikey/index.js +33 -0
- package/dist/commands/apikey/index.js.map +1 -0
- package/dist/commands/apikey/list.d.ts +6 -0
- package/dist/commands/apikey/list.d.ts.map +1 -0
- package/dist/commands/apikey/list.js +74 -0
- package/dist/commands/apikey/list.js.map +1 -0
- package/dist/commands/apikey/managed/bind.d.ts +6 -0
- package/dist/commands/apikey/managed/bind.d.ts.map +1 -0
- package/dist/commands/apikey/managed/bind.js +52 -0
- package/dist/commands/apikey/managed/bind.js.map +1 -0
- package/dist/commands/apikey/managed/conditions.d.ts +6 -0
- package/dist/commands/apikey/managed/conditions.d.ts.map +1 -0
- package/dist/commands/apikey/managed/conditions.js +62 -0
- package/dist/commands/apikey/managed/conditions.js.map +1 -0
- package/dist/commands/apikey/managed/config.d.ts +6 -0
- package/dist/commands/apikey/managed/config.d.ts.map +1 -0
- package/dist/commands/apikey/managed/config.js +52 -0
- package/dist/commands/apikey/managed/config.js.map +1 -0
- package/dist/commands/apikey/managed/create.d.ts +6 -0
- package/dist/commands/apikey/managed/create.d.ts.map +1 -0
- package/dist/commands/apikey/managed/create.js +82 -0
- package/dist/commands/apikey/managed/create.js.map +1 -0
- package/dist/commands/apikey/managed/delete.d.ts +6 -0
- package/dist/commands/apikey/managed/delete.d.ts.map +1 -0
- package/dist/commands/apikey/managed/delete.js +29 -0
- package/dist/commands/apikey/managed/delete.js.map +1 -0
- package/dist/commands/apikey/managed/get.d.ts +6 -0
- package/dist/commands/apikey/managed/get.d.ts.map +1 -0
- package/dist/commands/apikey/managed/get.js +31 -0
- package/dist/commands/apikey/managed/get.js.map +1 -0
- package/dist/commands/apikey/managed/helpers.d.ts +5 -0
- package/dist/commands/apikey/managed/helpers.d.ts.map +1 -0
- package/dist/commands/apikey/managed/helpers.js +70 -0
- package/dist/commands/apikey/managed/helpers.js.map +1 -0
- package/dist/commands/apikey/managed/index.d.ts +7 -0
- package/dist/commands/apikey/managed/index.d.ts.map +1 -0
- package/dist/commands/apikey/managed/index.js +27 -0
- package/dist/commands/apikey/managed/index.js.map +1 -0
- package/dist/commands/apikey/managed/list.d.ts +6 -0
- package/dist/commands/apikey/managed/list.d.ts.map +1 -0
- package/dist/commands/apikey/managed/list.js +58 -0
- package/dist/commands/apikey/managed/list.js.map +1 -0
- package/dist/commands/apikey/managed/permissions.d.ts +6 -0
- package/dist/commands/apikey/managed/permissions.d.ts.map +1 -0
- package/dist/commands/apikey/managed/permissions.js +73 -0
- package/dist/commands/apikey/managed/permissions.js.map +1 -0
- package/dist/commands/apikey/managed/rotate.d.ts +6 -0
- package/dist/commands/apikey/managed/rotate.d.ts.map +1 -0
- package/dist/commands/apikey/managed/rotate.js +29 -0
- package/dist/commands/apikey/managed/rotate.js.map +1 -0
- package/dist/commands/apikey/managed/types.d.ts +62 -0
- package/dist/commands/apikey/managed/types.d.ts.map +1 -0
- package/dist/commands/apikey/managed/types.js +3 -0
- package/dist/commands/apikey/managed/types.js.map +1 -0
- package/dist/commands/apikey/permissions.d.ts +6 -0
- package/dist/commands/apikey/permissions.d.ts.map +1 -0
- package/dist/commands/apikey/permissions.js +70 -0
- package/dist/commands/apikey/permissions.js.map +1 -0
- package/dist/commands/apikey/policies.d.ts +6 -0
- package/dist/commands/apikey/policies.d.ts.map +1 -0
- package/dist/commands/apikey/policies.js +82 -0
- package/dist/commands/apikey/policies.js.map +1 -0
- package/dist/commands/apikey/rotate.d.ts +6 -0
- package/dist/commands/apikey/rotate.d.ts.map +1 -0
- package/dist/commands/apikey/rotate.js +42 -0
- package/dist/commands/apikey/rotate.js.map +1 -0
- package/dist/commands/apikey/self.d.ts +6 -0
- package/dist/commands/apikey/self.d.ts.map +1 -0
- package/dist/commands/apikey/self.js +96 -0
- package/dist/commands/apikey/self.js.map +1 -0
- package/dist/commands/apikey/show.d.ts +6 -0
- package/dist/commands/apikey/show.d.ts.map +1 -0
- package/dist/commands/apikey/show.js +79 -0
- package/dist/commands/apikey/show.js.map +1 -0
- package/dist/commands/apikey/types.d.ts +83 -0
- package/dist/commands/apikey/types.d.ts.map +1 -0
- package/dist/commands/apikey/types.js +3 -0
- package/dist/commands/apikey/types.js.map +1 -0
- package/dist/commands/apikey.d.ts +8 -2
- package/dist/commands/apikey.d.ts.map +1 -1
- package/dist/commands/apikey.js +9 -1296
- package/dist/commands/apikey.js.map +1 -1
- package/dist/commands/device.d.ts.map +1 -1
- package/dist/commands/device.js +8 -5
- package/dist/commands/device.js.map +1 -1
- package/dist/commands/plugin.d.ts.map +1 -1
- package/dist/commands/plugin.js +29 -7
- package/dist/commands/plugin.js.map +1 -1
- package/dist/commands/secret/copy.d.ts +6 -0
- package/dist/commands/secret/copy.d.ts.map +1 -0
- package/dist/commands/secret/copy.js +43 -0
- package/dist/commands/secret/copy.js.map +1 -0
- package/dist/commands/secret/create.d.ts +6 -0
- package/dist/commands/secret/create.d.ts.map +1 -0
- package/dist/commands/secret/create.js +297 -0
- package/dist/commands/secret/create.js.map +1 -0
- package/dist/commands/secret/decrypt.d.ts +6 -0
- package/dist/commands/secret/decrypt.d.ts.map +1 -0
- package/dist/commands/secret/decrypt.js +104 -0
- package/dist/commands/secret/decrypt.js.map +1 -0
- package/dist/commands/secret/delete.d.ts +6 -0
- package/dist/commands/secret/delete.d.ts.map +1 -0
- package/dist/commands/secret/delete.js +60 -0
- package/dist/commands/secret/delete.js.map +1 -0
- package/dist/commands/secret/get.d.ts +6 -0
- package/dist/commands/secret/get.d.ts.map +1 -0
- package/dist/commands/secret/get.js +60 -0
- package/dist/commands/secret/get.js.map +1 -0
- package/dist/commands/secret/helpers.d.ts +11 -0
- package/dist/commands/secret/helpers.d.ts.map +1 -0
- package/dist/commands/secret/helpers.js +59 -0
- package/dist/commands/secret/helpers.js.map +1 -0
- package/dist/commands/secret/history.d.ts +6 -0
- package/dist/commands/secret/history.d.ts.map +1 -0
- package/dist/commands/secret/history.js +52 -0
- package/dist/commands/secret/history.js.map +1 -0
- package/dist/commands/secret/index.d.ts +12 -0
- package/dist/commands/secret/index.d.ts.map +1 -0
- package/dist/commands/secret/index.js +49 -0
- package/dist/commands/secret/index.js.map +1 -0
- package/dist/commands/secret/list.d.ts +6 -0
- package/dist/commands/secret/list.d.ts.map +1 -0
- package/dist/commands/secret/list.js +72 -0
- package/dist/commands/secret/list.js.map +1 -0
- package/dist/commands/secret/pem-analysis.d.ts +32 -0
- package/dist/commands/secret/pem-analysis.d.ts.map +1 -0
- package/dist/commands/secret/pem-analysis.js +190 -0
- package/dist/commands/secret/pem-analysis.js.map +1 -0
- package/dist/commands/secret/resolve.d.ts +17 -0
- package/dist/commands/secret/resolve.d.ts.map +1 -0
- package/dist/commands/secret/resolve.js +36 -0
- package/dist/commands/secret/resolve.js.map +1 -0
- package/dist/commands/secret/rotate.d.ts +6 -0
- package/dist/commands/secret/rotate.d.ts.map +1 -0
- package/dist/commands/secret/rotate.js +72 -0
- package/dist/commands/secret/rotate.js.map +1 -0
- package/dist/commands/secret/types.d.ts +123 -0
- package/dist/commands/secret/types.d.ts.map +1 -0
- package/dist/commands/secret/types.js +3 -0
- package/dist/commands/secret/types.js.map +1 -0
- package/dist/commands/secret/update.d.ts +6 -0
- package/dist/commands/secret/update.d.ts.map +1 -0
- package/dist/commands/secret/update.js +124 -0
- package/dist/commands/secret/update.js.map +1 -0
- package/dist/commands/secret.d.ts +8 -2
- package/dist/commands/secret.d.ts.map +1 -1
- package/dist/commands/secret.js +6 -1131
- package/dist/commands/secret.js.map +1 -1
- package/dist/index.js +48 -4
- package/dist/index.js.map +1 -1
- package/dist/lib/client/apikeys.d.ts +34 -0
- package/dist/lib/client/apikeys.d.ts.map +1 -0
- package/dist/lib/client/apikeys.js +113 -0
- package/dist/lib/client/apikeys.js.map +1 -0
- package/dist/lib/client/audit.d.ts +21 -0
- package/dist/lib/client/audit.d.ts.map +1 -0
- package/dist/lib/client/audit.js +40 -0
- package/dist/lib/client/audit.js.map +1 -0
- package/dist/lib/client/health.d.ts +30 -0
- package/dist/lib/client/health.d.ts.map +1 -0
- package/dist/lib/client/health.js +55 -0
- package/dist/lib/client/health.js.map +1 -0
- package/dist/lib/client/http.d.ts +50 -0
- package/dist/lib/client/http.d.ts.map +1 -0
- package/dist/lib/client/http.js +333 -0
- package/dist/lib/client/http.js.map +1 -0
- package/dist/lib/client/index.d.ts +156 -0
- package/dist/lib/client/index.d.ts.map +1 -0
- package/dist/lib/client/index.js +172 -0
- package/dist/lib/client/index.js.map +1 -0
- package/dist/lib/client/lockdown.d.ts +23 -0
- package/dist/lib/client/lockdown.d.ts.map +1 -0
- package/dist/lib/client/lockdown.js +48 -0
- package/dist/lib/client/lockdown.js.map +1 -0
- package/dist/lib/client/managed-keys.d.ts +18 -0
- package/dist/lib/client/managed-keys.d.ts.map +1 -0
- package/dist/lib/client/managed-keys.js +190 -0
- package/dist/lib/client/managed-keys.js.map +1 -0
- package/dist/lib/client/policies.d.ts +35 -0
- package/dist/lib/client/policies.d.ts.map +1 -0
- package/dist/lib/client/policies.js +131 -0
- package/dist/lib/client/policies.js.map +1 -0
- package/dist/lib/client/tenants.d.ts +29 -0
- package/dist/lib/client/tenants.d.ts.map +1 -0
- package/dist/lib/client/tenants.js +56 -0
- package/dist/lib/client/tenants.js.map +1 -0
- package/dist/lib/client/types.d.ts +45 -0
- package/dist/lib/client/types.d.ts.map +1 -0
- package/dist/lib/client/types.js +3 -0
- package/dist/lib/client/types.js.map +1 -0
- package/dist/lib/client/users.d.ts +44 -0
- package/dist/lib/client/users.d.ts.map +1 -0
- package/dist/lib/client/users.js +116 -0
- package/dist/lib/client/users.js.map +1 -0
- package/dist/lib/client.d.ts +11 -246
- package/dist/lib/client.d.ts.map +1 -1
- package/dist/lib/client.js +10 -996
- package/dist/lib/client.js.map +1 -1
- package/dist/lib/command-error-handler.d.ts +99 -0
- package/dist/lib/command-error-handler.d.ts.map +1 -0
- package/dist/lib/command-error-handler.js +108 -0
- package/dist/lib/command-error-handler.js.map +1 -0
- package/dist/lib/config/apikey.d.ts +29 -0
- package/dist/lib/config/apikey.d.ts.map +1 -0
- package/dist/lib/config/apikey.js +58 -0
- package/dist/lib/config/apikey.js.map +1 -0
- package/dist/lib/config/cache.d.ts +47 -0
- package/dist/lib/config/cache.d.ts.map +1 -0
- package/dist/lib/config/cache.js +84 -0
- package/dist/lib/config/cache.js.map +1 -0
- package/dist/lib/config/credentials.d.ts +33 -0
- package/dist/lib/config/credentials.d.ts.map +1 -0
- package/dist/lib/config/credentials.js +70 -0
- package/dist/lib/config/credentials.js.map +1 -0
- package/dist/lib/config/getters.d.ts +28 -0
- package/dist/lib/config/getters.d.ts.map +1 -0
- package/dist/lib/config/getters.js +65 -0
- package/dist/lib/config/getters.js.map +1 -0
- package/dist/lib/config/index.d.ts +17 -0
- package/dist/lib/config/index.d.ts.map +1 -0
- package/dist/lib/config/index.js +21 -0
- package/dist/lib/config/index.js.map +1 -0
- package/dist/lib/config/migration.d.ts +10 -0
- package/dist/lib/config/migration.d.ts.map +1 -0
- package/dist/lib/config/migration.js +59 -0
- package/dist/lib/config/migration.js.map +1 -0
- package/dist/lib/config/plugins.d.ts +25 -0
- package/dist/lib/config/plugins.d.ts.map +1 -0
- package/dist/lib/config/plugins.js +58 -0
- package/dist/lib/config/plugins.js.map +1 -0
- package/dist/lib/config/profile.d.ts +42 -0
- package/dist/lib/config/profile.d.ts.map +1 -0
- package/dist/lib/config/profile.js +154 -0
- package/dist/lib/config/profile.js.map +1 -0
- package/dist/lib/config/store.d.ts +23 -0
- package/dist/lib/config/store.d.ts.map +1 -0
- package/dist/lib/config/store.js +41 -0
- package/dist/lib/config/store.js.map +1 -0
- package/dist/lib/config/types.d.ts +44 -0
- package/dist/lib/config/types.d.ts.map +1 -0
- package/dist/lib/config/types.js +8 -0
- package/dist/lib/config/types.js.map +1 -0
- package/dist/lib/config/validation.d.ts +38 -0
- package/dist/lib/config/validation.d.ts.map +1 -0
- package/dist/lib/config/validation.js +146 -0
- package/dist/lib/config/validation.js.map +1 -0
- package/dist/lib/config.d.ts +5 -158
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +26 -424
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/constants.d.ts +65 -0
- package/dist/lib/constants.d.ts.map +1 -0
- package/dist/lib/constants.js +90 -0
- package/dist/lib/constants.js.map +1 -0
- package/dist/lib/db.d.ts +4 -0
- package/dist/lib/db.d.ts.map +1 -1
- package/dist/lib/db.js +77 -46
- package/dist/lib/db.js.map +1 -1
- package/dist/lib/format-helpers.d.ts +63 -0
- package/dist/lib/format-helpers.d.ts.map +1 -0
- package/dist/lib/format-helpers.js +219 -0
- package/dist/lib/format-helpers.js.map +1 -0
- package/dist/lib/visual.d.ts +22 -0
- package/dist/lib/visual.d.ts.map +1 -1
- package/dist/lib/visual.js +83 -0
- package/dist/lib/visual.js.map +1 -1
- package/dist/services/signature-verifier.d.ts.map +1 -1
- package/dist/services/signature-verifier.js +11 -4
- package/dist/services/signature-verifier.js.map +1 -1
- package/dist/services/update-installer.d.ts +7 -0
- package/dist/services/update-installer.d.ts.map +1 -1
- package/dist/services/update-installer.js +58 -19
- package/dist/services/update-installer.js.map +1 -1
- package/package.json +1 -1
package/dist/commands/apikey.js
CHANGED
|
@@ -1,1297 +1,10 @@
|
|
|
1
|
-
// Path:
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
function formatDate(dateStr) {
|
|
11
|
-
return new Date(dateStr).toLocaleString();
|
|
12
|
-
}
|
|
13
|
-
function getDaysUntilExpiry(expiresAt) {
|
|
14
|
-
const expires = new Date(expiresAt);
|
|
15
|
-
const now = new Date();
|
|
16
|
-
return Math.ceil((expires.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
|
|
17
|
-
}
|
|
18
|
-
function formatSecondsToHuman(seconds) {
|
|
19
|
-
if (seconds < 60)
|
|
20
|
-
return `${seconds}s`;
|
|
21
|
-
if (seconds < 3600)
|
|
22
|
-
return `${Math.floor(seconds / 60)}m`;
|
|
23
|
-
if (seconds < 86400)
|
|
24
|
-
return `${Math.floor(seconds / 3600)}h`;
|
|
25
|
-
return `${Math.floor(seconds / 86400)}d`;
|
|
26
|
-
}
|
|
27
|
-
function formatExpiry(expiresAt) {
|
|
28
|
-
const days = getDaysUntilExpiry(expiresAt);
|
|
29
|
-
if (days < 0)
|
|
30
|
-
return `Expired ${Math.abs(days)} days ago`;
|
|
31
|
-
if (days === 0)
|
|
32
|
-
return 'Expires today';
|
|
33
|
-
if (days === 1)
|
|
34
|
-
return 'Expires tomorrow';
|
|
35
|
-
if (days <= 7)
|
|
36
|
-
return `Expires in ${days} days (!)`;
|
|
37
|
-
if (days <= 30)
|
|
38
|
-
return `Expires in ${days} days`;
|
|
39
|
-
return `Expires in ${days} days`;
|
|
40
|
-
}
|
|
41
|
-
function formatPermissions(permissions) {
|
|
42
|
-
if (permissions.length === 0)
|
|
43
|
-
return 'None';
|
|
44
|
-
if (permissions.length <= 3)
|
|
45
|
-
return permissions.join(', ');
|
|
46
|
-
return `${permissions.slice(0, 2).join(', ')} +${permissions.length - 2} more`;
|
|
47
|
-
}
|
|
48
|
-
function formatConditionsSummary(conditions) {
|
|
49
|
-
if (!conditions || Object.keys(conditions).length === 0)
|
|
50
|
-
return '-';
|
|
51
|
-
const parts = [];
|
|
52
|
-
if (conditions.ip)
|
|
53
|
-
parts.push('IP');
|
|
54
|
-
if (conditions.timeRange)
|
|
55
|
-
parts.push('Time');
|
|
56
|
-
if (conditions.methods)
|
|
57
|
-
parts.push('Methods');
|
|
58
|
-
if (conditions.resources)
|
|
59
|
-
parts.push('Resources');
|
|
60
|
-
if (conditions.aliases)
|
|
61
|
-
parts.push('Aliases');
|
|
62
|
-
if (conditions.resourceTags)
|
|
63
|
-
parts.push('Tags');
|
|
64
|
-
if (parts.length === 0)
|
|
65
|
-
return '-';
|
|
66
|
-
if (parts.length <= 2)
|
|
67
|
-
return parts.join(', ');
|
|
68
|
-
return `${parts.slice(0, 2).join(', ')} +${parts.length - 2}`;
|
|
69
|
-
}
|
|
70
|
-
function displayConditions(cond) {
|
|
71
|
-
if (cond.ip)
|
|
72
|
-
console.log(` - IP Allowlist: ${cond.ip.join(', ')}`);
|
|
73
|
-
if (cond.timeRange) {
|
|
74
|
-
const tr = cond.timeRange;
|
|
75
|
-
console.log(` - Time Range: ${tr.start}-${tr.end} ${tr.timezone ?? 'UTC'}`);
|
|
76
|
-
}
|
|
77
|
-
if (cond.methods)
|
|
78
|
-
console.log(` - Methods: ${cond.methods.join(', ')}`);
|
|
79
|
-
if (cond.resources)
|
|
80
|
-
console.log(` - Resources: ${JSON.stringify(cond.resources)}`);
|
|
81
|
-
if (cond.aliases)
|
|
82
|
-
console.log(` - Aliases: ${cond.aliases.join(', ')}`);
|
|
83
|
-
if (cond.resourceTags)
|
|
84
|
-
console.log(` - Tags: ${JSON.stringify(cond.resourceTags)}`);
|
|
85
|
-
}
|
|
86
|
-
// Managed key helper functions
|
|
87
|
-
function formatRotationMode(mode) {
|
|
88
|
-
switch (mode) {
|
|
89
|
-
case 'scheduled': return 'Scheduled';
|
|
90
|
-
case 'on-use': return 'On Use';
|
|
91
|
-
case 'on-bind': return 'On Bind';
|
|
92
|
-
default: return mode;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
function formatTimeUntil(dateStr) {
|
|
96
|
-
if (!dateStr)
|
|
97
|
-
return '-';
|
|
98
|
-
const date = new Date(dateStr);
|
|
99
|
-
const now = new Date();
|
|
100
|
-
const diffMs = date.getTime() - now.getTime();
|
|
101
|
-
if (diffMs <= 0)
|
|
102
|
-
return 'Now';
|
|
103
|
-
const diffMins = Math.floor(diffMs / 60000);
|
|
104
|
-
if (diffMins < 60)
|
|
105
|
-
return `${diffMins}m`;
|
|
106
|
-
const diffHours = Math.floor(diffMins / 60);
|
|
107
|
-
if (diffHours < 24)
|
|
108
|
-
return `${diffHours}h ${diffMins % 60}m`;
|
|
109
|
-
const diffDays = Math.floor(diffHours / 24);
|
|
110
|
-
return `${diffDays}d ${diffHours % 24}h`;
|
|
111
|
-
}
|
|
112
|
-
function displayManagedKeyDetails(key) {
|
|
113
|
-
const statusIcon = key.enabled ? '\x1b[32m●\x1b[0m Active' : '\x1b[31m○\x1b[0m Disabled';
|
|
114
|
-
output.keyValue({
|
|
115
|
-
'Name': key.name,
|
|
116
|
-
'Key ID': key.id,
|
|
117
|
-
'Prefix': key.prefix,
|
|
118
|
-
'Status': statusIcon,
|
|
119
|
-
'Tenant': key.tenant_id,
|
|
120
|
-
'Description': key.description ?? 'None',
|
|
121
|
-
'Rotation Mode': formatRotationMode(key.rotation_mode),
|
|
122
|
-
'Rotation Interval': key.rotation_interval ?? '-',
|
|
123
|
-
'Grace Period': key.grace_period,
|
|
124
|
-
'Next Rotation': key.next_rotation_at ? `${formatDate(key.next_rotation_at)} (${formatTimeUntil(key.next_rotation_at)})` : '-',
|
|
125
|
-
'Last Bound': key.last_bound_at ? formatDate(key.last_bound_at) : 'Never',
|
|
126
|
-
'Rotation Count': key.rotation_count,
|
|
127
|
-
'Last Rotation': key.last_rotation ? formatDate(key.last_rotation) : 'Never',
|
|
128
|
-
'Expires': formatDate(key.expires_at),
|
|
129
|
-
'Created': formatDate(key.created_at),
|
|
130
|
-
'Created By': key.created_by_username ?? key.created_by ?? 'Unknown',
|
|
131
|
-
});
|
|
132
|
-
if (key.notify_before) {
|
|
133
|
-
console.log(`\nNotifications: ${key.notify_before} before rotation`);
|
|
134
|
-
}
|
|
135
|
-
if (key.webhook_url) {
|
|
136
|
-
console.log(`Webhook: ${key.webhook_url}`);
|
|
137
|
-
}
|
|
138
|
-
if (key.permissions.length > 0) {
|
|
139
|
-
console.log('\nPermissions:');
|
|
140
|
-
for (const perm of key.permissions) {
|
|
141
|
-
console.log(` - ${perm}`);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
const keyConditions = key.conditions;
|
|
145
|
-
if (keyConditions && Object.keys(keyConditions).length > 0) {
|
|
146
|
-
console.log('\nConditions:');
|
|
147
|
-
displayConditions(keyConditions);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
// ============================================================================
|
|
151
|
-
// Command Registration
|
|
152
|
-
// ============================================================================
|
|
153
|
-
export function registerApiKeyCommands(program) {
|
|
154
|
-
const apiKeyCmd = program
|
|
155
|
-
.command('apikey')
|
|
156
|
-
.alias('api-key')
|
|
157
|
-
.description('API key management (independent, tenant-scoped)');
|
|
158
|
-
// List API keys
|
|
159
|
-
apiKeyCmd
|
|
160
|
-
.command('list')
|
|
161
|
-
.alias('ls')
|
|
162
|
-
.description('List API keys')
|
|
163
|
-
.option('-t, --tenant <id>', 'Tenant ID (superadmin only)')
|
|
164
|
-
.option('--json', 'Output as JSON')
|
|
165
|
-
.action(async (options) => {
|
|
166
|
-
const spinner = ora('Fetching API keys...').start();
|
|
167
|
-
try {
|
|
168
|
-
const result = await client.listApiKeys(options.tenant);
|
|
169
|
-
spinner.stop();
|
|
170
|
-
if (options.json) {
|
|
171
|
-
output.json(result);
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
if (result.items.length === 0) {
|
|
175
|
-
output.warn('No API keys found');
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
// Show expiring soon warning
|
|
179
|
-
if (result.expiringSoon.length > 0) {
|
|
180
|
-
console.log(`\n⚠️ ${result.expiringSoon.length} key(s) expiring within 7 days\n`);
|
|
181
|
-
}
|
|
182
|
-
const table = new Table({
|
|
183
|
-
head: ['Name', 'Prefix', 'Type', 'Status', 'Tenant', 'Permissions', 'Expires', 'Rotations'],
|
|
184
|
-
style: { head: ['cyan'] },
|
|
185
|
-
});
|
|
186
|
-
for (const key of result.items) {
|
|
187
|
-
const daysLeft = getDaysUntilExpiry(key.expires_at);
|
|
188
|
-
const expiryColor = daysLeft <= 7 ? '\x1b[31m' : daysLeft <= 30 ? '\x1b[33m' : '';
|
|
189
|
-
const reset = expiryColor ? '\x1b[0m' : '';
|
|
190
|
-
const statusIcon = key.enabled ? '\x1b[32m●\x1b[0m' : '\x1b[31m○\x1b[0m';
|
|
191
|
-
const statusText = key.enabled ? 'Active' : 'Disabled';
|
|
192
|
-
// Format key type (static vs managed with rotation info)
|
|
193
|
-
let keyType = 'Static';
|
|
194
|
-
if (key.is_managed && key.rotation_mode) {
|
|
195
|
-
const mode = key.rotation_mode.replace('on-', '');
|
|
196
|
-
const interval = key.rotation_interval_seconds
|
|
197
|
-
? formatSecondsToHuman(key.rotation_interval_seconds)
|
|
198
|
-
: '';
|
|
199
|
-
keyType = interval ? `${mode}/${interval}` : mode;
|
|
200
|
-
}
|
|
201
|
-
table.push([
|
|
202
|
-
key.name,
|
|
203
|
-
key.prefix,
|
|
204
|
-
keyType,
|
|
205
|
-
`${statusIcon} ${statusText}`,
|
|
206
|
-
key.tenant_id,
|
|
207
|
-
formatPermissions(key.permissions),
|
|
208
|
-
`${expiryColor}${formatExpiry(key.expires_at)}${reset}`,
|
|
209
|
-
key.rotation_count > 0 ? `${key.rotation_count}x` : '-',
|
|
210
|
-
]);
|
|
211
|
-
}
|
|
212
|
-
console.log(table.toString());
|
|
213
|
-
const showingInfo = result.pagination.hasMore
|
|
214
|
-
? `Showing ${result.items.length} of ${result.pagination.total}`
|
|
215
|
-
: `Total: ${result.pagination.total}`;
|
|
216
|
-
console.log(`\n${showingInfo} API key(s)`);
|
|
217
|
-
}
|
|
218
|
-
catch (err) {
|
|
219
|
-
spinner.fail('Failed to list API keys');
|
|
220
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
221
|
-
process.exit(1);
|
|
222
|
-
}
|
|
223
|
-
});
|
|
224
|
-
// Create API key
|
|
225
|
-
apiKeyCmd
|
|
226
|
-
.command('create <name>')
|
|
227
|
-
.description('Create a new API key with direct permissions')
|
|
228
|
-
.option('-e, --expires <days>', 'Days until expiration (1-3650, default: 90)', '90')
|
|
229
|
-
.option('-p, --permissions <perms>', 'Comma-separated permissions (required)')
|
|
230
|
-
.option('-d, --description <desc>', 'Description')
|
|
231
|
-
.option('--ip <ips>', 'Comma-separated IP allowlist (CIDR supported)')
|
|
232
|
-
.option('--time-range <range>', 'Time range restriction: "HH:MM-HH:MM [TIMEZONE]"')
|
|
233
|
-
.option('--methods <methods>', 'Comma-separated allowed HTTP methods: GET,POST,etc')
|
|
234
|
-
.option('--resources <ids>', 'Specific resource IDs (type:id,...): secrets:id1,certificates:id2')
|
|
235
|
-
.option('--aliases <patterns>', 'Comma-separated alias patterns (glob): prod/*,api/*')
|
|
236
|
-
.option('--tags <tags>', 'Required resource tags: key=value,key2=value2')
|
|
237
|
-
.option('-t, --tenant <id>', 'Tenant ID (superadmin only)')
|
|
238
|
-
.option('--json', 'Output as JSON')
|
|
239
|
-
.action(async (name, options) => {
|
|
240
|
-
// Validate permissions
|
|
241
|
-
if (!options.permissions) {
|
|
242
|
-
output.error('--permissions is required. Use comma-separated permission strings.');
|
|
243
|
-
output.info('Example: --permissions "secret:read:value,secret:list:values"');
|
|
244
|
-
process.exit(1);
|
|
245
|
-
}
|
|
246
|
-
const permissions = options.permissions.split(',').map((p) => p.trim());
|
|
247
|
-
const spinner = ora('Creating API key...').start();
|
|
248
|
-
try {
|
|
249
|
-
// Parse options
|
|
250
|
-
const expiresInDays = parseInt(options.expires, 10);
|
|
251
|
-
if (isNaN(expiresInDays) || expiresInDays < 1 || expiresInDays > 3650) {
|
|
252
|
-
spinner.fail('Invalid expiration');
|
|
253
|
-
output.error('Expiration must be between 1 and 3650 days');
|
|
254
|
-
process.exit(1);
|
|
255
|
-
}
|
|
256
|
-
let ipAllowlist;
|
|
257
|
-
if (options.ip) {
|
|
258
|
-
ipAllowlist = options.ip.split(',').map((ip) => ip.trim());
|
|
259
|
-
}
|
|
260
|
-
// Parse conditions
|
|
261
|
-
const conditions = {};
|
|
262
|
-
// IP condition (from --ip flag, now also stored in conditions)
|
|
263
|
-
if (ipAllowlist) {
|
|
264
|
-
conditions.ip = ipAllowlist;
|
|
265
|
-
}
|
|
266
|
-
// Time range condition
|
|
267
|
-
if (options.timeRange) {
|
|
268
|
-
const match = /^(\d{2}:\d{2})-(\d{2}:\d{2})(?:\s+(.+))?$/.exec(options.timeRange);
|
|
269
|
-
if (!match) {
|
|
270
|
-
spinner.fail('Invalid time range format');
|
|
271
|
-
output.error('Use format: "HH:MM-HH:MM [TIMEZONE]"');
|
|
272
|
-
output.info('Example: --time-range "09:00-17:00 America/New_York"');
|
|
273
|
-
process.exit(1);
|
|
274
|
-
}
|
|
275
|
-
conditions.timeRange = {
|
|
276
|
-
start: match[1],
|
|
277
|
-
end: match[2],
|
|
278
|
-
timezone: match[3] || 'UTC',
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
// HTTP methods condition
|
|
282
|
-
if (options.methods) {
|
|
283
|
-
conditions.methods = options.methods.split(',').map((m) => m.trim().toUpperCase());
|
|
284
|
-
}
|
|
285
|
-
// Resource IDs condition
|
|
286
|
-
if (options.resources) {
|
|
287
|
-
const resources = {};
|
|
288
|
-
for (const part of options.resources.split(',')) {
|
|
289
|
-
const [type, id] = part.split(':');
|
|
290
|
-
if (type && id) {
|
|
291
|
-
resources[type] = resources[type] ?? [];
|
|
292
|
-
resources[type].push(id);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
if (Object.keys(resources).length > 0) {
|
|
296
|
-
conditions.resources = resources;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
// Alias patterns condition
|
|
300
|
-
if (options.aliases) {
|
|
301
|
-
conditions.aliases = options.aliases.split(',').map((a) => a.trim());
|
|
302
|
-
}
|
|
303
|
-
// Resource tags condition
|
|
304
|
-
if (options.tags) {
|
|
305
|
-
const tags = {};
|
|
306
|
-
for (const part of options.tags.split(',')) {
|
|
307
|
-
const [key, value] = part.split('=');
|
|
308
|
-
if (key && value) {
|
|
309
|
-
tags[key.trim()] = value.trim();
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
if (Object.keys(tags).length > 0) {
|
|
313
|
-
conditions.resourceTags = tags;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
const result = await client.createApiKey({
|
|
317
|
-
name,
|
|
318
|
-
description: options.description,
|
|
319
|
-
expiresInDays,
|
|
320
|
-
permissions,
|
|
321
|
-
ipAllowlist,
|
|
322
|
-
conditions: Object.keys(conditions).length > 0 ? conditions : undefined,
|
|
323
|
-
tenantId: options.tenant,
|
|
324
|
-
});
|
|
325
|
-
spinner.succeed('API key created');
|
|
326
|
-
if (options.json) {
|
|
327
|
-
output.json(result);
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
|
-
console.log('\n⚠️ IMPORTANT: Save this key now - it will not be shown again!\n');
|
|
331
|
-
console.log('────────────────────────────────────────────────────────────────');
|
|
332
|
-
console.log(`API Key: ${result.key}`);
|
|
333
|
-
console.log('────────────────────────────────────────────────────────────────\n');
|
|
334
|
-
output.keyValue({
|
|
335
|
-
'Key ID': result.apiKey.id,
|
|
336
|
-
'Name': result.apiKey.name,
|
|
337
|
-
'Prefix': result.apiKey.prefix,
|
|
338
|
-
'Status': result.apiKey.enabled ? '\x1b[32m●\x1b[0m Active' : '\x1b[31m○\x1b[0m Disabled',
|
|
339
|
-
'Tenant': result.apiKey.tenant_id,
|
|
340
|
-
'Description': result.apiKey.description ?? 'None',
|
|
341
|
-
'Expires': formatDate(result.apiKey.expires_at),
|
|
342
|
-
'IP Allowlist': result.apiKey.ip_allowlist?.join(', ') ?? 'None',
|
|
343
|
-
});
|
|
344
|
-
if (result.apiKey.permissions.length > 0) {
|
|
345
|
-
console.log('\nPermissions:');
|
|
346
|
-
for (const perm of result.apiKey.permissions) {
|
|
347
|
-
console.log(` - ${perm}`);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
// Display conditions if any
|
|
351
|
-
const apiKeyConditions = result.apiKey.conditions;
|
|
352
|
-
if (apiKeyConditions && Object.keys(apiKeyConditions).length > 0) {
|
|
353
|
-
console.log('\nConditions:');
|
|
354
|
-
displayConditions(apiKeyConditions);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
catch (err) {
|
|
358
|
-
spinner.fail('Failed to create API key');
|
|
359
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
360
|
-
process.exit(1);
|
|
361
|
-
}
|
|
362
|
-
});
|
|
363
|
-
// Show API key details
|
|
364
|
-
apiKeyCmd
|
|
365
|
-
.command('show <id>')
|
|
366
|
-
.description('Show API key details')
|
|
367
|
-
.option('-t, --tenant <id>', 'Tenant ID')
|
|
368
|
-
.option('--json', 'Output as JSON')
|
|
369
|
-
.action(async (id, options) => {
|
|
370
|
-
const spinner = ora('Fetching API key...').start();
|
|
371
|
-
try {
|
|
372
|
-
// First try to get by ID directly
|
|
373
|
-
let key;
|
|
374
|
-
try {
|
|
375
|
-
key = await client.getApiKey(id, options.tenant);
|
|
376
|
-
}
|
|
377
|
-
catch {
|
|
378
|
-
// Fall back to list and search
|
|
379
|
-
const result = await client.listApiKeys(options.tenant);
|
|
380
|
-
key = result.items.find(k => k.id === id || k.prefix === id || k.name === id);
|
|
381
|
-
}
|
|
382
|
-
if (!key) {
|
|
383
|
-
spinner.fail('API key not found');
|
|
384
|
-
output.error(`No API key found matching: ${id}`);
|
|
385
|
-
process.exit(1);
|
|
386
|
-
}
|
|
387
|
-
spinner.stop();
|
|
388
|
-
if (options.json) {
|
|
389
|
-
output.json(key);
|
|
390
|
-
return;
|
|
391
|
-
}
|
|
392
|
-
const daysLeft = getDaysUntilExpiry(key.expires_at);
|
|
393
|
-
const statusIcon = key.enabled ? '\x1b[32m●\x1b[0m Active' : '\x1b[31m○\x1b[0m Disabled';
|
|
394
|
-
output.keyValue({
|
|
395
|
-
'Key ID': key.id,
|
|
396
|
-
'Name': key.name,
|
|
397
|
-
'Prefix': key.prefix,
|
|
398
|
-
'Status': statusIcon,
|
|
399
|
-
'Tenant': key.tenant_id,
|
|
400
|
-
'Description': key.description ?? 'None',
|
|
401
|
-
'Created By': key.created_by_username ?? key.created_by ?? 'Unknown',
|
|
402
|
-
'Created': formatDate(key.created_at),
|
|
403
|
-
'Expires': formatDate(key.expires_at),
|
|
404
|
-
'Days Until Expiry': daysLeft,
|
|
405
|
-
'Last Used': key.last_used ? formatDate(key.last_used) : 'Never',
|
|
406
|
-
'Rotation Count': key.rotation_count,
|
|
407
|
-
'Last Rotation': key.last_rotation ? formatDate(key.last_rotation) : 'Never',
|
|
408
|
-
'IP Allowlist': key.ip_allowlist?.join(', ') ?? 'None (any IP)',
|
|
409
|
-
});
|
|
410
|
-
if (key.permissions.length > 0) {
|
|
411
|
-
console.log('\nPermissions:');
|
|
412
|
-
for (const perm of key.permissions) {
|
|
413
|
-
console.log(` - ${perm}`);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
// Display conditions if any
|
|
417
|
-
const keyConditions = key.conditions;
|
|
418
|
-
if (keyConditions && Object.keys(keyConditions).length > 0) {
|
|
419
|
-
console.log('\nConditions:');
|
|
420
|
-
displayConditions(keyConditions);
|
|
421
|
-
}
|
|
422
|
-
if (!key.enabled) {
|
|
423
|
-
console.log('\n⚠️ This key is disabled and cannot be used for authentication.');
|
|
424
|
-
}
|
|
425
|
-
else if (daysLeft <= 7) {
|
|
426
|
-
console.log('\n⚠️ This key is expiring soon! Consider rotating it.');
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
catch (err) {
|
|
430
|
-
spinner.fail('Failed to fetch API key');
|
|
431
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
432
|
-
process.exit(1);
|
|
433
|
-
}
|
|
434
|
-
});
|
|
435
|
-
// Delete API key
|
|
436
|
-
apiKeyCmd
|
|
437
|
-
.command('delete <id>')
|
|
438
|
-
.alias('rm')
|
|
439
|
-
.description('Delete an API key')
|
|
440
|
-
.option('-t, --tenant <id>', 'Tenant ID')
|
|
441
|
-
.option('-f, --force', 'Skip confirmation')
|
|
442
|
-
.action(async (id, options) => {
|
|
443
|
-
if (!options.force) {
|
|
444
|
-
output.warn(`This will permanently delete API key: ${id}`);
|
|
445
|
-
output.warn('The key will stop working immediately.');
|
|
446
|
-
}
|
|
447
|
-
const spinner = ora('Deleting API key...').start();
|
|
448
|
-
try {
|
|
449
|
-
await client.deleteApiKey(id, options.tenant);
|
|
450
|
-
spinner.succeed(`API key deleted: ${id}`);
|
|
451
|
-
}
|
|
452
|
-
catch (err) {
|
|
453
|
-
spinner.fail('Failed to delete API key');
|
|
454
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
455
|
-
process.exit(1);
|
|
456
|
-
}
|
|
457
|
-
});
|
|
458
|
-
// Rotate API key
|
|
459
|
-
apiKeyCmd
|
|
460
|
-
.command('rotate <id>')
|
|
461
|
-
.description('Rotate an API key (creates new key, invalidates old)')
|
|
462
|
-
.option('-n, --name <name>', 'New name for the rotated key')
|
|
463
|
-
.option('-t, --tenant <id>', 'Tenant ID')
|
|
464
|
-
.option('--json', 'Output as JSON')
|
|
465
|
-
.action(async (id, options) => {
|
|
466
|
-
const spinner = ora('Rotating API key...').start();
|
|
467
|
-
try {
|
|
468
|
-
const result = await client.rotateApiKey(id, options.name, options.tenant);
|
|
469
|
-
spinner.succeed('API key rotated');
|
|
470
|
-
if (options.json) {
|
|
471
|
-
output.json(result);
|
|
472
|
-
return;
|
|
473
|
-
}
|
|
474
|
-
console.log('\n⚠️ IMPORTANT: Save this new key now - it will not be shown again!');
|
|
475
|
-
console.log('The old key has been invalidated.\n');
|
|
476
|
-
console.log('────────────────────────────────────────────────────────────────');
|
|
477
|
-
console.log(`New API Key: ${result.key}`);
|
|
478
|
-
console.log('────────────────────────────────────────────────────────────────\n');
|
|
479
|
-
output.keyValue({
|
|
480
|
-
'New Key ID': result.apiKey.id,
|
|
481
|
-
'Name': result.apiKey.name,
|
|
482
|
-
'Prefix': result.apiKey.prefix,
|
|
483
|
-
'Expires': formatDate(result.apiKey.expires_at),
|
|
484
|
-
'Rotation Count': result.apiKey.rotation_count,
|
|
485
|
-
});
|
|
486
|
-
}
|
|
487
|
-
catch (err) {
|
|
488
|
-
spinner.fail('Failed to rotate API key');
|
|
489
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
490
|
-
process.exit(1);
|
|
491
|
-
}
|
|
492
|
-
});
|
|
493
|
-
// Enable API key
|
|
494
|
-
apiKeyCmd
|
|
495
|
-
.command('enable <id>')
|
|
496
|
-
.description('Enable an API key (allow authentication)')
|
|
497
|
-
.option('-t, --tenant <id>', 'Tenant ID')
|
|
498
|
-
.action(async (id, options) => {
|
|
499
|
-
const spinner = ora('Enabling API key...').start();
|
|
500
|
-
try {
|
|
501
|
-
const key = await client.setApiKeyEnabled(id, true, options.tenant);
|
|
502
|
-
spinner.succeed(`API key enabled: ${key.name}`);
|
|
503
|
-
console.log('\nThe key can now be used for authentication.');
|
|
504
|
-
}
|
|
505
|
-
catch (err) {
|
|
506
|
-
spinner.fail('Failed to enable API key');
|
|
507
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
508
|
-
process.exit(1);
|
|
509
|
-
}
|
|
510
|
-
});
|
|
511
|
-
// Disable API key
|
|
512
|
-
apiKeyCmd
|
|
513
|
-
.command('disable <id>')
|
|
514
|
-
.description('Disable an API key (block authentication without deleting)')
|
|
515
|
-
.option('-t, --tenant <id>', 'Tenant ID')
|
|
516
|
-
.action(async (id, options) => {
|
|
517
|
-
const spinner = ora('Disabling API key...').start();
|
|
518
|
-
try {
|
|
519
|
-
const key = await client.setApiKeyEnabled(id, false, options.tenant);
|
|
520
|
-
spinner.succeed(`API key disabled: ${key.name}`);
|
|
521
|
-
console.log('\nThe key is now blocked from authentication.');
|
|
522
|
-
console.log('Use "znvault apikey enable" to re-enable it.');
|
|
523
|
-
}
|
|
524
|
-
catch (err) {
|
|
525
|
-
spinner.fail('Failed to disable API key');
|
|
526
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
527
|
-
process.exit(1);
|
|
528
|
-
}
|
|
529
|
-
});
|
|
530
|
-
// Update permissions
|
|
531
|
-
apiKeyCmd
|
|
532
|
-
.command('update-permissions <id>')
|
|
533
|
-
.description('Update API key permissions')
|
|
534
|
-
.option('-s, --set <perms>', 'Set permissions (comma-separated, replaces all)')
|
|
535
|
-
.option('-a, --add <perms>', 'Add permissions (comma-separated)')
|
|
536
|
-
.option('-r, --remove <perms>', 'Remove permissions (comma-separated)')
|
|
537
|
-
.option('-t, --tenant <id>', 'Tenant ID (superadmin only)')
|
|
538
|
-
.option('--json', 'Output as JSON')
|
|
539
|
-
.action(async (id, options) => {
|
|
540
|
-
if (!options.set && !options.add && !options.remove) {
|
|
541
|
-
output.error('At least one of --set, --add, or --remove is required');
|
|
542
|
-
output.info('Examples:');
|
|
543
|
-
output.info(' znvault apikey update-permissions <id> --set "secret:read,secret:list"');
|
|
544
|
-
output.info(' znvault apikey update-permissions <id> --add "kms:encrypt"');
|
|
545
|
-
output.info(' znvault apikey update-permissions <id> --remove "secret:delete"');
|
|
546
|
-
process.exit(1);
|
|
547
|
-
}
|
|
548
|
-
const spinner = ora('Updating permissions...').start();
|
|
549
|
-
try {
|
|
550
|
-
let newPermissions;
|
|
551
|
-
if (options.set) {
|
|
552
|
-
// Replace all permissions
|
|
553
|
-
newPermissions = options.set.split(',').map((p) => p.trim());
|
|
554
|
-
}
|
|
555
|
-
else {
|
|
556
|
-
// Need to get current permissions first
|
|
557
|
-
const currentKey = await client.getApiKey(id, options.tenant);
|
|
558
|
-
newPermissions = [...currentKey.permissions];
|
|
559
|
-
if (options.add) {
|
|
560
|
-
const toAdd = options.add.split(',').map((p) => p.trim());
|
|
561
|
-
for (const perm of toAdd) {
|
|
562
|
-
if (!newPermissions.includes(perm)) {
|
|
563
|
-
newPermissions.push(perm);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
if (options.remove) {
|
|
568
|
-
const toRemove = options.remove.split(',').map((p) => p.trim());
|
|
569
|
-
newPermissions = newPermissions.filter((p) => !toRemove.includes(p));
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
if (newPermissions.length === 0) {
|
|
573
|
-
spinner.fail('Cannot remove all permissions');
|
|
574
|
-
output.error('API key must have at least one permission');
|
|
575
|
-
process.exit(1);
|
|
576
|
-
}
|
|
577
|
-
const key = await client.updateApiKeyPermissions(id, newPermissions, options.tenant);
|
|
578
|
-
spinner.succeed('Permissions updated');
|
|
579
|
-
if (options.json) {
|
|
580
|
-
output.json(key);
|
|
581
|
-
return;
|
|
582
|
-
}
|
|
583
|
-
console.log('\nUpdated permissions:');
|
|
584
|
-
for (const perm of key.permissions) {
|
|
585
|
-
console.log(` - ${perm}`);
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
catch (err) {
|
|
589
|
-
spinner.fail('Failed to update permissions');
|
|
590
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
591
|
-
process.exit(1);
|
|
592
|
-
}
|
|
593
|
-
});
|
|
594
|
-
// Update conditions
|
|
595
|
-
apiKeyCmd
|
|
596
|
-
.command('update-conditions <id>')
|
|
597
|
-
.description('Update API key inline ABAC conditions')
|
|
598
|
-
.option('--ip <ips>', 'Comma-separated IP allowlist (CIDR supported)')
|
|
599
|
-
.option('--time-range <range>', 'Time range: "HH:MM-HH:MM [TIMEZONE]" or "clear"')
|
|
600
|
-
.option('--methods <methods>', 'Comma-separated HTTP methods or "clear"')
|
|
601
|
-
.option('--resources <ids>', 'Resource IDs (type:id,...) or "clear"')
|
|
602
|
-
.option('--aliases <patterns>', 'Alias patterns (glob) or "clear"')
|
|
603
|
-
.option('--tags <tags>', 'Resource tags (key=value,...) or "clear"')
|
|
604
|
-
.option('--clear-all', 'Remove all conditions')
|
|
605
|
-
.option('-t, --tenant <id>', 'Tenant ID')
|
|
606
|
-
.option('--json', 'Output as JSON')
|
|
607
|
-
.action(async (id, options) => {
|
|
608
|
-
const conditions = {};
|
|
609
|
-
// Handle --clear-all
|
|
610
|
-
if (!options.clearAll) {
|
|
611
|
-
// Parse individual conditions
|
|
612
|
-
if (options.ip && options.ip !== 'clear') {
|
|
613
|
-
conditions.ip = options.ip.split(',').map((ip) => ip.trim());
|
|
614
|
-
}
|
|
615
|
-
if (options.timeRange) {
|
|
616
|
-
if (options.timeRange !== 'clear') {
|
|
617
|
-
const match = /^(\d{2}:\d{2})-(\d{2}:\d{2})(?:\s+(.+))?$/.exec(options.timeRange);
|
|
618
|
-
if (!match) {
|
|
619
|
-
output.error('Invalid time range format. Use: "HH:MM-HH:MM [TIMEZONE]"');
|
|
620
|
-
process.exit(1);
|
|
621
|
-
}
|
|
622
|
-
conditions.timeRange = {
|
|
623
|
-
start: match[1],
|
|
624
|
-
end: match[2],
|
|
625
|
-
timezone: match[3] || 'UTC',
|
|
626
|
-
};
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
if (options.methods && options.methods !== 'clear') {
|
|
630
|
-
conditions.methods = options.methods.split(',').map((m) => m.trim().toUpperCase());
|
|
631
|
-
}
|
|
632
|
-
if (options.resources && options.resources !== 'clear') {
|
|
633
|
-
const resources = {};
|
|
634
|
-
for (const part of options.resources.split(',')) {
|
|
635
|
-
const [type, resId] = part.split(':');
|
|
636
|
-
if (type && resId) {
|
|
637
|
-
resources[type] = resources[type] ?? [];
|
|
638
|
-
resources[type].push(resId);
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
if (Object.keys(resources).length > 0) {
|
|
642
|
-
conditions.resources = resources;
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
if (options.aliases && options.aliases !== 'clear') {
|
|
646
|
-
conditions.aliases = options.aliases.split(',').map((a) => a.trim());
|
|
647
|
-
}
|
|
648
|
-
if (options.tags && options.tags !== 'clear') {
|
|
649
|
-
const tags = {};
|
|
650
|
-
for (const part of options.tags.split(',')) {
|
|
651
|
-
const [key, value] = part.split('=');
|
|
652
|
-
if (key && value) {
|
|
653
|
-
tags[key.trim()] = value.trim();
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
if (Object.keys(tags).length > 0) {
|
|
657
|
-
conditions.resourceTags = tags;
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
const spinner = ora('Updating conditions...').start();
|
|
662
|
-
try {
|
|
663
|
-
const key = await client.updateApiKeyConditions(id, conditions, options.tenant);
|
|
664
|
-
spinner.succeed('Conditions updated');
|
|
665
|
-
if (options.json) {
|
|
666
|
-
output.json(key);
|
|
667
|
-
return;
|
|
668
|
-
}
|
|
669
|
-
const keyConditions = key.conditions;
|
|
670
|
-
if (keyConditions && Object.keys(keyConditions).length > 0) {
|
|
671
|
-
console.log('\nUpdated conditions:');
|
|
672
|
-
displayConditions(keyConditions);
|
|
673
|
-
}
|
|
674
|
-
else {
|
|
675
|
-
console.log('\nAll conditions cleared.');
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
catch (err) {
|
|
679
|
-
spinner.fail('Failed to update conditions');
|
|
680
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
681
|
-
process.exit(1);
|
|
682
|
-
}
|
|
683
|
-
});
|
|
684
|
-
// List policies attached to an API key
|
|
685
|
-
apiKeyCmd
|
|
686
|
-
.command('list-policies <id>')
|
|
687
|
-
.description('List ABAC policies attached to an API key')
|
|
688
|
-
.option('-t, --tenant <id>', 'Tenant ID')
|
|
689
|
-
.option('--json', 'Output as JSON')
|
|
690
|
-
.action(async (id, options) => {
|
|
691
|
-
const spinner = ora('Fetching policies...').start();
|
|
692
|
-
try {
|
|
693
|
-
const result = await client.getApiKeyPolicies(id, options.tenant);
|
|
694
|
-
spinner.stop();
|
|
695
|
-
if (options.json) {
|
|
696
|
-
output.json(result);
|
|
697
|
-
return;
|
|
698
|
-
}
|
|
699
|
-
if (result.policies.length === 0) {
|
|
700
|
-
output.info('No ABAC policies attached to this API key');
|
|
701
|
-
return;
|
|
702
|
-
}
|
|
703
|
-
const table = new Table({
|
|
704
|
-
head: ['Policy ID', 'Policy Name', 'Attached At'],
|
|
705
|
-
style: { head: ['cyan'] },
|
|
706
|
-
});
|
|
707
|
-
for (const policy of result.policies) {
|
|
708
|
-
table.push([
|
|
709
|
-
policy.policyId,
|
|
710
|
-
policy.policyName,
|
|
711
|
-
formatDate(policy.attachedAt),
|
|
712
|
-
]);
|
|
713
|
-
}
|
|
714
|
-
console.log(table.toString());
|
|
715
|
-
console.log(`\nTotal: ${result.policies.length} policy/policies`);
|
|
716
|
-
}
|
|
717
|
-
catch (err) {
|
|
718
|
-
spinner.fail('Failed to fetch policies');
|
|
719
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
720
|
-
process.exit(1);
|
|
721
|
-
}
|
|
722
|
-
});
|
|
723
|
-
// Attach policy to API key
|
|
724
|
-
apiKeyCmd
|
|
725
|
-
.command('attach-policy <keyId> <policyId>')
|
|
726
|
-
.description('Attach an ABAC policy to an API key')
|
|
727
|
-
.option('-t, --tenant <id>', 'Tenant ID')
|
|
728
|
-
.action(async (keyId, policyId, options) => {
|
|
729
|
-
const spinner = ora('Attaching policy...').start();
|
|
730
|
-
try {
|
|
731
|
-
await client.attachApiKeyPolicy(keyId, policyId, options.tenant);
|
|
732
|
-
spinner.succeed(`Policy ${policyId} attached to API key ${keyId}`);
|
|
733
|
-
}
|
|
734
|
-
catch (err) {
|
|
735
|
-
spinner.fail('Failed to attach policy');
|
|
736
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
737
|
-
process.exit(1);
|
|
738
|
-
}
|
|
739
|
-
});
|
|
740
|
-
// Detach policy from API key
|
|
741
|
-
apiKeyCmd
|
|
742
|
-
.command('detach-policy <keyId> <policyId>')
|
|
743
|
-
.description('Detach an ABAC policy from an API key')
|
|
744
|
-
.option('-t, --tenant <id>', 'Tenant ID')
|
|
745
|
-
.action(async (keyId, policyId, options) => {
|
|
746
|
-
const spinner = ora('Detaching policy...').start();
|
|
747
|
-
try {
|
|
748
|
-
await client.detachApiKeyPolicy(keyId, policyId, options.tenant);
|
|
749
|
-
spinner.succeed(`Policy ${policyId} detached from API key ${keyId}`);
|
|
750
|
-
}
|
|
751
|
-
catch (err) {
|
|
752
|
-
spinner.fail('Failed to detach policy');
|
|
753
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
754
|
-
process.exit(1);
|
|
755
|
-
}
|
|
756
|
-
});
|
|
757
|
-
// Self-info (when using API key auth)
|
|
758
|
-
apiKeyCmd
|
|
759
|
-
.command('self')
|
|
760
|
-
.description('Show info about the currently used API key')
|
|
761
|
-
.option('--json', 'Output as JSON')
|
|
762
|
-
.action(async (options) => {
|
|
763
|
-
const spinner = ora('Fetching API key info...').start();
|
|
764
|
-
try {
|
|
765
|
-
const result = await client.getApiKeySelf();
|
|
766
|
-
spinner.stop();
|
|
767
|
-
if (options.json) {
|
|
768
|
-
output.json(result);
|
|
769
|
-
return;
|
|
770
|
-
}
|
|
771
|
-
const statusIcon = result.apiKey.enabled ? '\x1b[32m●\x1b[0m Active' : '\x1b[31m○\x1b[0m Disabled';
|
|
772
|
-
output.keyValue({
|
|
773
|
-
'Key ID': result.apiKey.id,
|
|
774
|
-
'Name': result.apiKey.name,
|
|
775
|
-
'Prefix': result.apiKey.prefix,
|
|
776
|
-
'Status': statusIcon,
|
|
777
|
-
'Tenant': result.apiKey.tenant_id,
|
|
778
|
-
'Description': result.apiKey.description ?? 'None',
|
|
779
|
-
'Expires': formatDate(result.apiKey.expires_at),
|
|
780
|
-
'Days Until Expiry': result.expiresInDays,
|
|
781
|
-
'Expiring Soon': result.isExpiringSoon ? 'Yes (!)' : 'No',
|
|
782
|
-
'Last Used': result.apiKey.last_used ? formatDate(result.apiKey.last_used) : 'Never',
|
|
783
|
-
'Rotation Count': result.apiKey.rotation_count,
|
|
784
|
-
'Last Rotation': result.apiKey.last_rotation ? formatDate(result.apiKey.last_rotation) : 'Never',
|
|
785
|
-
});
|
|
786
|
-
if (result.apiKey.permissions.length > 0) {
|
|
787
|
-
console.log('\nPermissions:');
|
|
788
|
-
for (const perm of result.apiKey.permissions) {
|
|
789
|
-
console.log(` - ${perm}`);
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
// Display conditions if any
|
|
793
|
-
const apiKeyConditions = result.apiKey.conditions;
|
|
794
|
-
if (apiKeyConditions && Object.keys(apiKeyConditions).length > 0) {
|
|
795
|
-
console.log('\nConditions:');
|
|
796
|
-
displayConditions(apiKeyConditions);
|
|
797
|
-
}
|
|
798
|
-
if (result.isExpiringSoon) {
|
|
799
|
-
console.log('\n⚠️ This key is expiring soon! Run "znvault apikey self-rotate" to rotate it.');
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
catch (err) {
|
|
803
|
-
spinner.fail('Failed to fetch API key info');
|
|
804
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
805
|
-
console.log('\nNote: This command only works when authenticated via API key (X-API-Key header).');
|
|
806
|
-
process.exit(1);
|
|
807
|
-
}
|
|
808
|
-
});
|
|
809
|
-
// Self-rotate (when using API key auth)
|
|
810
|
-
apiKeyCmd
|
|
811
|
-
.command('self-rotate')
|
|
812
|
-
.description('Rotate the currently used API key')
|
|
813
|
-
.option('-n, --name <name>', 'New name for the rotated key')
|
|
814
|
-
.option('--json', 'Output as JSON')
|
|
815
|
-
.action(async (options) => {
|
|
816
|
-
const spinner = ora('Rotating API key...').start();
|
|
817
|
-
try {
|
|
818
|
-
const result = await client.rotateApiKeySelf(options.name);
|
|
819
|
-
spinner.succeed('API key rotated');
|
|
820
|
-
if (options.json) {
|
|
821
|
-
output.json(result);
|
|
822
|
-
return;
|
|
823
|
-
}
|
|
824
|
-
console.log('\n⚠️ IMPORTANT: Save this new key now - it will not be shown again!');
|
|
825
|
-
console.log('The old key has been invalidated.\n');
|
|
826
|
-
console.log('────────────────────────────────────────────────────────────────');
|
|
827
|
-
console.log(`New API Key: ${result.key}`);
|
|
828
|
-
console.log('────────────────────────────────────────────────────────────────\n');
|
|
829
|
-
output.keyValue({
|
|
830
|
-
'New Key ID': result.apiKey.id,
|
|
831
|
-
'Name': result.apiKey.name,
|
|
832
|
-
'Prefix': result.apiKey.prefix,
|
|
833
|
-
'Expires': formatDate(result.apiKey.expires_at),
|
|
834
|
-
'Days Until Expiry': result.expiresInDays,
|
|
835
|
-
'Rotation Count': result.apiKey.rotation_count,
|
|
836
|
-
});
|
|
837
|
-
}
|
|
838
|
-
catch (err) {
|
|
839
|
-
spinner.fail('Failed to rotate API key');
|
|
840
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
841
|
-
console.log('\nNote: This command only works when authenticated via API key (X-API-Key header).');
|
|
842
|
-
process.exit(1);
|
|
843
|
-
}
|
|
844
|
-
});
|
|
845
|
-
// ============================================================================
|
|
846
|
-
// Managed API Key Commands
|
|
847
|
-
// ============================================================================
|
|
848
|
-
const managedCmd = apiKeyCmd
|
|
849
|
-
.command('managed')
|
|
850
|
-
.description('Managed API key operations (auto-rotating keys)');
|
|
851
|
-
// List managed keys
|
|
852
|
-
managedCmd
|
|
853
|
-
.command('list')
|
|
854
|
-
.alias('ls')
|
|
855
|
-
.description('List all managed API keys')
|
|
856
|
-
.option('-t, --tenant <id>', 'Tenant ID (superadmin only)')
|
|
857
|
-
.option('--json', 'Output as JSON')
|
|
858
|
-
.action(async (options) => {
|
|
859
|
-
const spinner = ora('Fetching managed API keys...').start();
|
|
860
|
-
try {
|
|
861
|
-
const result = await client.listManagedApiKeys(options.tenant);
|
|
862
|
-
spinner.stop();
|
|
863
|
-
if (options.json) {
|
|
864
|
-
output.json(result);
|
|
865
|
-
return;
|
|
866
|
-
}
|
|
867
|
-
if (result.items.length === 0) {
|
|
868
|
-
output.warn('No managed API keys found');
|
|
869
|
-
return;
|
|
870
|
-
}
|
|
871
|
-
const table = new Table({
|
|
872
|
-
head: ['Name', 'Mode', 'Interval', 'Grace', 'Next Rotation', 'Status', 'Tenant', 'Rotations'],
|
|
873
|
-
style: { head: ['cyan'] },
|
|
874
|
-
});
|
|
875
|
-
for (const key of result.items) {
|
|
876
|
-
const statusIcon = key.enabled ? '\x1b[32m●\x1b[0m' : '\x1b[31m○\x1b[0m';
|
|
877
|
-
const nextRotation = key.next_rotation_at ? formatTimeUntil(key.next_rotation_at) : '-';
|
|
878
|
-
table.push([
|
|
879
|
-
key.name,
|
|
880
|
-
formatRotationMode(key.rotation_mode),
|
|
881
|
-
key.rotation_interval ?? '-',
|
|
882
|
-
key.grace_period,
|
|
883
|
-
nextRotation,
|
|
884
|
-
`${statusIcon} ${key.enabled ? 'Active' : 'Disabled'}`,
|
|
885
|
-
key.tenant_id,
|
|
886
|
-
key.rotation_count > 0 ? `${key.rotation_count}x` : '-',
|
|
887
|
-
]);
|
|
888
|
-
}
|
|
889
|
-
console.log(table.toString());
|
|
890
|
-
const showingInfo = result.pagination.hasMore
|
|
891
|
-
? `Showing ${result.items.length} of ${result.pagination.total}`
|
|
892
|
-
: `Total: ${result.pagination.total}`;
|
|
893
|
-
console.log(`\n${showingInfo} managed API key(s)`);
|
|
894
|
-
}
|
|
895
|
-
catch (err) {
|
|
896
|
-
spinner.fail('Failed to list managed API keys');
|
|
897
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
898
|
-
process.exit(1);
|
|
899
|
-
}
|
|
900
|
-
});
|
|
901
|
-
// Create managed key
|
|
902
|
-
managedCmd
|
|
903
|
-
.command('create <name>')
|
|
904
|
-
.description('Create a new managed API key with auto-rotation')
|
|
905
|
-
.option('-e, --expires <days>', 'Days until expiration (1-3650, default: 365)', '365')
|
|
906
|
-
.option('-p, --permissions <perms>', 'Comma-separated permissions (required)')
|
|
907
|
-
.option('-d, --description <desc>', 'Description')
|
|
908
|
-
.option('-m, --rotation-mode <mode>', 'Rotation mode: scheduled, on-use, on-bind (required)')
|
|
909
|
-
.option('-i, --rotation-interval <interval>', 'Rotation interval (e.g., 24h, 7d) - required for scheduled mode')
|
|
910
|
-
.option('-g, --grace-period <period>', 'Grace period (e.g., 5m, 1h)', '5m')
|
|
911
|
-
.option('--notify-before <duration>', 'Notify before rotation (e.g., 1h)')
|
|
912
|
-
.option('--webhook-url <url>', 'Webhook URL for rotation notifications')
|
|
913
|
-
.option('--ip <ips>', 'Comma-separated IP allowlist (CIDR supported)')
|
|
914
|
-
.option('-t, --tenant <id>', 'Tenant ID (superadmin only)')
|
|
915
|
-
.option('--json', 'Output as JSON')
|
|
916
|
-
.action(async (name, options) => {
|
|
917
|
-
// Validate required options
|
|
918
|
-
if (!options.permissions) {
|
|
919
|
-
output.error('--permissions is required. Use comma-separated permission strings.');
|
|
920
|
-
process.exit(1);
|
|
921
|
-
}
|
|
922
|
-
if (!options.rotationMode) {
|
|
923
|
-
output.error('--rotation-mode is required. Use: scheduled, on-use, or on-bind');
|
|
924
|
-
process.exit(1);
|
|
925
|
-
}
|
|
926
|
-
const rotationMode = options.rotationMode;
|
|
927
|
-
if (!['scheduled', 'on-use', 'on-bind'].includes(rotationMode)) {
|
|
928
|
-
output.error('Invalid rotation mode. Use: scheduled, on-use, or on-bind');
|
|
929
|
-
process.exit(1);
|
|
930
|
-
}
|
|
931
|
-
if (rotationMode === 'scheduled' && !options.rotationInterval) {
|
|
932
|
-
output.error('--rotation-interval is required for scheduled rotation mode');
|
|
933
|
-
output.info('Example: --rotation-interval 24h');
|
|
934
|
-
process.exit(1);
|
|
935
|
-
}
|
|
936
|
-
const permissions = options.permissions.split(',').map((p) => p.trim());
|
|
937
|
-
const spinner = ora('Creating managed API key...').start();
|
|
938
|
-
try {
|
|
939
|
-
const expiresInDays = parseInt(options.expires, 10);
|
|
940
|
-
if (isNaN(expiresInDays) || expiresInDays < 1 || expiresInDays > 3650) {
|
|
941
|
-
spinner.fail('Invalid expiration');
|
|
942
|
-
output.error('Expiration must be between 1 and 3650 days');
|
|
943
|
-
process.exit(1);
|
|
944
|
-
}
|
|
945
|
-
const result = await client.createManagedApiKey({
|
|
946
|
-
name,
|
|
947
|
-
description: options.description,
|
|
948
|
-
expiresInDays,
|
|
949
|
-
permissions,
|
|
950
|
-
tenantId: options.tenant,
|
|
951
|
-
ipAllowlist: options.ip?.split(',').map((ip) => ip.trim()),
|
|
952
|
-
managed: {
|
|
953
|
-
rotationMode,
|
|
954
|
-
rotationInterval: options.rotationInterval,
|
|
955
|
-
gracePeriod: options.gracePeriod,
|
|
956
|
-
notifyBefore: options.notifyBefore,
|
|
957
|
-
webhookUrl: options.webhookUrl,
|
|
958
|
-
},
|
|
959
|
-
});
|
|
960
|
-
spinner.succeed('Managed API key created');
|
|
961
|
-
if (options.json) {
|
|
962
|
-
output.json(result);
|
|
963
|
-
return;
|
|
964
|
-
}
|
|
965
|
-
console.log('\n✓ Managed API key created successfully');
|
|
966
|
-
console.log('\nNote: Managed keys do not show the key value at creation.');
|
|
967
|
-
console.log('Use "znvault apikey managed bind <name>" to get the current key value.\n');
|
|
968
|
-
displayManagedKeyDetails(result.apiKey);
|
|
969
|
-
}
|
|
970
|
-
catch (err) {
|
|
971
|
-
spinner.fail('Failed to create managed API key');
|
|
972
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
973
|
-
process.exit(1);
|
|
974
|
-
}
|
|
975
|
-
});
|
|
976
|
-
// Get managed key details
|
|
977
|
-
managedCmd
|
|
978
|
-
.command('get <name>')
|
|
979
|
-
.alias('show')
|
|
980
|
-
.description('Show managed API key details')
|
|
981
|
-
.option('-t, --tenant <id>', 'Tenant ID (superadmin only)')
|
|
982
|
-
.option('--json', 'Output as JSON')
|
|
983
|
-
.action(async (name, options) => {
|
|
984
|
-
const spinner = ora('Fetching managed API key...').start();
|
|
985
|
-
try {
|
|
986
|
-
const key = await client.getManagedApiKey(name, options.tenant);
|
|
987
|
-
spinner.stop();
|
|
988
|
-
if (options.json) {
|
|
989
|
-
output.json(key);
|
|
990
|
-
return;
|
|
991
|
-
}
|
|
992
|
-
displayManagedKeyDetails(key);
|
|
993
|
-
}
|
|
994
|
-
catch (err) {
|
|
995
|
-
spinner.fail('Failed to fetch managed API key');
|
|
996
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
997
|
-
process.exit(1);
|
|
998
|
-
}
|
|
999
|
-
});
|
|
1000
|
-
// Bind to managed key (get current key value)
|
|
1001
|
-
managedCmd
|
|
1002
|
-
.command('bind <name>')
|
|
1003
|
-
.description('Bind to a managed key and get the current key value')
|
|
1004
|
-
.option('-t, --tenant <id>', 'Tenant ID (superadmin only)')
|
|
1005
|
-
.option('--json', 'Output as JSON')
|
|
1006
|
-
.action(async (name, options) => {
|
|
1007
|
-
const spinner = ora('Binding to managed API key...').start();
|
|
1008
|
-
try {
|
|
1009
|
-
const result = await client.bindManagedApiKey(name, options.tenant);
|
|
1010
|
-
spinner.succeed('Bound to managed API key');
|
|
1011
|
-
if (options.json) {
|
|
1012
|
-
output.json(result);
|
|
1013
|
-
return;
|
|
1014
|
-
}
|
|
1015
|
-
console.log('\n────────────────────────────────────────────────────────────────');
|
|
1016
|
-
console.log(`API Key: ${result.key}`);
|
|
1017
|
-
console.log('────────────────────────────────────────────────────────────────\n');
|
|
1018
|
-
output.keyValue({
|
|
1019
|
-
'Name': result.name,
|
|
1020
|
-
'Key ID': result.id,
|
|
1021
|
-
'Prefix': result.prefix,
|
|
1022
|
-
'Rotation Mode': formatRotationMode(result.rotationMode),
|
|
1023
|
-
'Next Rotation': result.nextRotationAt ? `${formatDate(result.nextRotationAt)} (${formatTimeUntil(result.nextRotationAt)})` : '-',
|
|
1024
|
-
'Grace Period': result.gracePeriod,
|
|
1025
|
-
'Grace Expires': result.graceExpiresAt ? formatDate(result.graceExpiresAt) : '-',
|
|
1026
|
-
'Key Expires': formatDate(result.expiresAt),
|
|
1027
|
-
});
|
|
1028
|
-
if (result.permissions.length > 0) {
|
|
1029
|
-
console.log('\nPermissions:');
|
|
1030
|
-
for (const perm of result.permissions) {
|
|
1031
|
-
console.log(` - ${perm}`);
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
if (result._notice) {
|
|
1035
|
-
console.log(`\n⚠️ ${result._notice}`);
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
catch (err) {
|
|
1039
|
-
spinner.fail('Failed to bind to managed API key');
|
|
1040
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
1041
|
-
process.exit(1);
|
|
1042
|
-
}
|
|
1043
|
-
});
|
|
1044
|
-
// Force rotate managed key
|
|
1045
|
-
managedCmd
|
|
1046
|
-
.command('rotate <name>')
|
|
1047
|
-
.description('Force immediate rotation of a managed key')
|
|
1048
|
-
.option('-t, --tenant <id>', 'Tenant ID (superadmin only)')
|
|
1049
|
-
.action(async (name, options) => {
|
|
1050
|
-
const spinner = ora('Rotating managed API key...').start();
|
|
1051
|
-
try {
|
|
1052
|
-
const result = await client.rotateManagedApiKey(name, options.tenant);
|
|
1053
|
-
spinner.succeed('Managed API key rotated');
|
|
1054
|
-
console.log(`\n${result.message}`);
|
|
1055
|
-
if (result.nextRotationAt) {
|
|
1056
|
-
console.log(`Next scheduled rotation: ${formatDate(result.nextRotationAt)}`);
|
|
1057
|
-
}
|
|
1058
|
-
console.log('\nUse "znvault apikey managed bind <name>" to get the new key value.');
|
|
1059
|
-
}
|
|
1060
|
-
catch (err) {
|
|
1061
|
-
spinner.fail('Failed to rotate managed API key');
|
|
1062
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
1063
|
-
process.exit(1);
|
|
1064
|
-
}
|
|
1065
|
-
});
|
|
1066
|
-
// Update managed key config
|
|
1067
|
-
managedCmd
|
|
1068
|
-
.command('config <name>')
|
|
1069
|
-
.description('Update managed key rotation configuration')
|
|
1070
|
-
.option('-i, --rotation-interval <interval>', 'Rotation interval (e.g., 24h, 7d)')
|
|
1071
|
-
.option('-g, --grace-period <period>', 'Grace period (e.g., 5m, 1h)')
|
|
1072
|
-
.option('--notify-before <duration>', 'Notify before rotation (e.g., 1h)')
|
|
1073
|
-
.option('--webhook-url <url>', 'Webhook URL for rotation notifications')
|
|
1074
|
-
.option('-t, --tenant <id>', 'Tenant ID (superadmin only)')
|
|
1075
|
-
.option('--json', 'Output as JSON')
|
|
1076
|
-
.action(async (name, options) => {
|
|
1077
|
-
// Check if at least one config option is provided
|
|
1078
|
-
if (!options.rotationInterval && !options.gracePeriod && !options.notifyBefore && !options.webhookUrl) {
|
|
1079
|
-
output.error('At least one configuration option is required');
|
|
1080
|
-
output.info('Options: --rotation-interval, --grace-period, --notify-before, --webhook-url');
|
|
1081
|
-
process.exit(1);
|
|
1082
|
-
}
|
|
1083
|
-
const spinner = ora('Updating managed API key config...').start();
|
|
1084
|
-
try {
|
|
1085
|
-
const key = await client.updateManagedApiKeyConfig(name, {
|
|
1086
|
-
rotationInterval: options.rotationInterval,
|
|
1087
|
-
gracePeriod: options.gracePeriod,
|
|
1088
|
-
notifyBefore: options.notifyBefore,
|
|
1089
|
-
webhookUrl: options.webhookUrl,
|
|
1090
|
-
}, options.tenant);
|
|
1091
|
-
spinner.succeed('Configuration updated');
|
|
1092
|
-
if (options.json) {
|
|
1093
|
-
output.json(key);
|
|
1094
|
-
return;
|
|
1095
|
-
}
|
|
1096
|
-
console.log('\nUpdated configuration:');
|
|
1097
|
-
output.keyValue({
|
|
1098
|
-
'Rotation Interval': key.rotation_interval ?? '-',
|
|
1099
|
-
'Grace Period': key.grace_period,
|
|
1100
|
-
'Notify Before': key.notify_before ?? '-',
|
|
1101
|
-
'Webhook URL': key.webhook_url ?? '-',
|
|
1102
|
-
'Next Rotation': key.next_rotation_at ? formatDate(key.next_rotation_at) : '-',
|
|
1103
|
-
});
|
|
1104
|
-
}
|
|
1105
|
-
catch (err) {
|
|
1106
|
-
spinner.fail('Failed to update configuration');
|
|
1107
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
1108
|
-
process.exit(1);
|
|
1109
|
-
}
|
|
1110
|
-
});
|
|
1111
|
-
// Delete managed key
|
|
1112
|
-
managedCmd
|
|
1113
|
-
.command('delete <name>')
|
|
1114
|
-
.alias('rm')
|
|
1115
|
-
.description('Delete a managed API key')
|
|
1116
|
-
.option('-t, --tenant <id>', 'Tenant ID (superadmin only)')
|
|
1117
|
-
.option('-f, --force', 'Skip confirmation')
|
|
1118
|
-
.action(async (name, options) => {
|
|
1119
|
-
if (!options.force) {
|
|
1120
|
-
output.warn(`This will permanently delete managed API key: ${name}`);
|
|
1121
|
-
output.warn('All bound applications will lose access immediately.');
|
|
1122
|
-
}
|
|
1123
|
-
const spinner = ora('Deleting managed API key...').start();
|
|
1124
|
-
try {
|
|
1125
|
-
await client.deleteManagedApiKey(name, options.tenant);
|
|
1126
|
-
spinner.succeed(`Managed API key deleted: ${name}`);
|
|
1127
|
-
}
|
|
1128
|
-
catch (err) {
|
|
1129
|
-
spinner.fail('Failed to delete managed API key');
|
|
1130
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
1131
|
-
process.exit(1);
|
|
1132
|
-
}
|
|
1133
|
-
});
|
|
1134
|
-
// Update managed key permissions
|
|
1135
|
-
managedCmd
|
|
1136
|
-
.command('permissions <name>')
|
|
1137
|
-
.description('Update managed API key permissions')
|
|
1138
|
-
.option('-s, --set <perms>', 'Set permissions (comma-separated, replaces all)')
|
|
1139
|
-
.option('-a, --add <perms>', 'Add permissions (comma-separated)')
|
|
1140
|
-
.option('-r, --remove <perms>', 'Remove permissions (comma-separated)')
|
|
1141
|
-
.option('-t, --tenant <id>', 'Tenant ID (superadmin only)')
|
|
1142
|
-
.option('--json', 'Output as JSON')
|
|
1143
|
-
.action(async (name, options) => {
|
|
1144
|
-
if (!options.set && !options.add && !options.remove) {
|
|
1145
|
-
output.error('At least one of --set, --add, or --remove is required');
|
|
1146
|
-
output.info('Examples:');
|
|
1147
|
-
output.info(' znvault apikey managed permissions my-key --set "secret:read,secret:list"');
|
|
1148
|
-
output.info(' znvault apikey managed permissions my-key --add "kms:encrypt"');
|
|
1149
|
-
output.info(' znvault apikey managed permissions my-key --remove "secret:delete"');
|
|
1150
|
-
process.exit(1);
|
|
1151
|
-
}
|
|
1152
|
-
const spinner = ora('Updating managed API key permissions...').start();
|
|
1153
|
-
try {
|
|
1154
|
-
// First, get the managed key to find its ID and current permissions
|
|
1155
|
-
const managedKey = await client.getManagedApiKey(name, options.tenant);
|
|
1156
|
-
let newPermissions;
|
|
1157
|
-
if (options.set) {
|
|
1158
|
-
// Replace all permissions
|
|
1159
|
-
newPermissions = options.set.split(',').map((p) => p.trim());
|
|
1160
|
-
}
|
|
1161
|
-
else {
|
|
1162
|
-
// Start with current permissions
|
|
1163
|
-
newPermissions = [...managedKey.permissions];
|
|
1164
|
-
if (options.add) {
|
|
1165
|
-
const toAdd = options.add.split(',').map((p) => p.trim());
|
|
1166
|
-
for (const perm of toAdd) {
|
|
1167
|
-
if (!newPermissions.includes(perm)) {
|
|
1168
|
-
newPermissions.push(perm);
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
if (options.remove) {
|
|
1173
|
-
const toRemove = options.remove.split(',').map((p) => p.trim());
|
|
1174
|
-
newPermissions = newPermissions.filter((p) => !toRemove.includes(p));
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
if (newPermissions.length === 0) {
|
|
1178
|
-
spinner.fail('Cannot remove all permissions');
|
|
1179
|
-
output.error('API key must have at least one permission');
|
|
1180
|
-
process.exit(1);
|
|
1181
|
-
}
|
|
1182
|
-
// Update using the key ID
|
|
1183
|
-
const updatedKey = await client.updateApiKeyPermissions(managedKey.id, newPermissions, options.tenant);
|
|
1184
|
-
spinner.succeed('Permissions updated');
|
|
1185
|
-
if (options.json) {
|
|
1186
|
-
output.json(updatedKey);
|
|
1187
|
-
return;
|
|
1188
|
-
}
|
|
1189
|
-
console.log(`\nManaged key: ${name}`);
|
|
1190
|
-
console.log('Updated permissions:');
|
|
1191
|
-
for (const perm of updatedKey.permissions) {
|
|
1192
|
-
console.log(` - ${perm}`);
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1195
|
-
catch (err) {
|
|
1196
|
-
spinner.fail('Failed to update permissions');
|
|
1197
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
1198
|
-
process.exit(1);
|
|
1199
|
-
}
|
|
1200
|
-
});
|
|
1201
|
-
// Update managed key conditions
|
|
1202
|
-
managedCmd
|
|
1203
|
-
.command('conditions <name>')
|
|
1204
|
-
.description('Update managed API key ABAC conditions')
|
|
1205
|
-
.option('--ip <ips>', 'Comma-separated IP allowlist (CIDR supported)')
|
|
1206
|
-
.option('--time-range <range>', 'Time range: "HH:MM-HH:MM [TIMEZONE]" or "clear"')
|
|
1207
|
-
.option('--methods <methods>', 'Comma-separated HTTP methods or "clear"')
|
|
1208
|
-
.option('--resources <ids>', 'Resource IDs (type:id,...) or "clear"')
|
|
1209
|
-
.option('--aliases <patterns>', 'Alias patterns (glob) or "clear"')
|
|
1210
|
-
.option('--tags <tags>', 'Resource tags (key=value,...) or "clear"')
|
|
1211
|
-
.option('--clear-all', 'Remove all conditions')
|
|
1212
|
-
.option('-t, --tenant <id>', 'Tenant ID (superadmin only)')
|
|
1213
|
-
.option('--json', 'Output as JSON')
|
|
1214
|
-
.action(async (name, options) => {
|
|
1215
|
-
const conditions = {};
|
|
1216
|
-
// Handle --clear-all
|
|
1217
|
-
if (!options.clearAll) {
|
|
1218
|
-
// Parse individual conditions
|
|
1219
|
-
if (options.ip && options.ip !== 'clear') {
|
|
1220
|
-
conditions.ip = options.ip.split(',').map((ip) => ip.trim());
|
|
1221
|
-
}
|
|
1222
|
-
if (options.timeRange) {
|
|
1223
|
-
if (options.timeRange !== 'clear') {
|
|
1224
|
-
const match = /^(\d{2}:\d{2})-(\d{2}:\d{2})(?:\s+(.+))?$/.exec(options.timeRange);
|
|
1225
|
-
if (!match) {
|
|
1226
|
-
output.error('Invalid time range format. Use: "HH:MM-HH:MM [TIMEZONE]"');
|
|
1227
|
-
process.exit(1);
|
|
1228
|
-
}
|
|
1229
|
-
conditions.timeRange = {
|
|
1230
|
-
start: match[1],
|
|
1231
|
-
end: match[2],
|
|
1232
|
-
timezone: match[3] || 'UTC',
|
|
1233
|
-
};
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
if (options.methods && options.methods !== 'clear') {
|
|
1237
|
-
conditions.methods = options.methods.split(',').map((m) => m.trim().toUpperCase());
|
|
1238
|
-
}
|
|
1239
|
-
if (options.resources && options.resources !== 'clear') {
|
|
1240
|
-
const resources = {};
|
|
1241
|
-
for (const part of options.resources.split(',')) {
|
|
1242
|
-
const [type, resId] = part.split(':');
|
|
1243
|
-
if (type && resId) {
|
|
1244
|
-
resources[type] = resources[type] ?? [];
|
|
1245
|
-
resources[type].push(resId);
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
if (Object.keys(resources).length > 0) {
|
|
1249
|
-
conditions.resources = resources;
|
|
1250
|
-
}
|
|
1251
|
-
}
|
|
1252
|
-
if (options.aliases && options.aliases !== 'clear') {
|
|
1253
|
-
conditions.aliases = options.aliases.split(',').map((a) => a.trim());
|
|
1254
|
-
}
|
|
1255
|
-
if (options.tags && options.tags !== 'clear') {
|
|
1256
|
-
const tags = {};
|
|
1257
|
-
for (const part of options.tags.split(',')) {
|
|
1258
|
-
const [key, value] = part.split('=');
|
|
1259
|
-
if (key && value) {
|
|
1260
|
-
tags[key.trim()] = value.trim();
|
|
1261
|
-
}
|
|
1262
|
-
}
|
|
1263
|
-
if (Object.keys(tags).length > 0) {
|
|
1264
|
-
conditions.resourceTags = tags;
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
}
|
|
1268
|
-
const spinner = ora('Updating managed API key conditions...').start();
|
|
1269
|
-
try {
|
|
1270
|
-
// First, get the managed key to find its ID
|
|
1271
|
-
const managedKey = await client.getManagedApiKey(name, options.tenant);
|
|
1272
|
-
// Update using the key ID
|
|
1273
|
-
const key = await client.updateApiKeyConditions(managedKey.id, conditions, options.tenant);
|
|
1274
|
-
spinner.succeed('Conditions updated');
|
|
1275
|
-
if (options.json) {
|
|
1276
|
-
output.json(key);
|
|
1277
|
-
return;
|
|
1278
|
-
}
|
|
1279
|
-
console.log(`\nManaged key: ${name}`);
|
|
1280
|
-
console.log('Updated conditions:');
|
|
1281
|
-
if (key.conditions && Object.keys(key.conditions).length > 0) {
|
|
1282
|
-
for (const [condKey, condValue] of Object.entries(key.conditions)) {
|
|
1283
|
-
console.log(` ${condKey}: ${JSON.stringify(condValue)}`);
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
else {
|
|
1287
|
-
console.log(' (no conditions)');
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1290
|
-
catch (err) {
|
|
1291
|
-
spinner.fail('Failed to update conditions');
|
|
1292
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
1293
|
-
process.exit(1);
|
|
1294
|
-
}
|
|
1295
|
-
});
|
|
1296
|
-
}
|
|
1
|
+
// Path: src/commands/apikey.ts
|
|
2
|
+
/**
|
|
3
|
+
* API key commands - backward compatibility re-export
|
|
4
|
+
*
|
|
5
|
+
* This file maintains backward compatibility by re-exporting from the
|
|
6
|
+
* modularized apikey/ directory structure.
|
|
7
|
+
*/
|
|
8
|
+
export { registerApiKeyCommands } from './apikey/index.js';
|
|
9
|
+
export * from './apikey/types.js';
|
|
1297
10
|
//# sourceMappingURL=apikey.js.map
|