@zincapp/znvault-cli 2.19.0 → 2.19.1
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/dynamic-secrets/connection.d.ts +17 -0
- package/dist/commands/dynamic-secrets/connection.d.ts.map +1 -0
- package/dist/commands/dynamic-secrets/connection.js +217 -0
- package/dist/commands/dynamic-secrets/connection.js.map +1 -0
- package/dist/commands/dynamic-secrets/creds.d.ts +5 -0
- package/dist/commands/dynamic-secrets/creds.d.ts.map +1 -0
- package/dist/commands/dynamic-secrets/creds.js +39 -0
- package/dist/commands/dynamic-secrets/creds.js.map +1 -0
- package/dist/commands/dynamic-secrets/helpers.d.ts +5 -0
- package/dist/commands/dynamic-secrets/helpers.d.ts.map +1 -0
- package/dist/commands/dynamic-secrets/helpers.js +36 -0
- package/dist/commands/dynamic-secrets/helpers.js.map +1 -0
- package/dist/commands/dynamic-secrets/index.d.ts +7 -0
- package/dist/commands/dynamic-secrets/index.d.ts.map +1 -0
- package/dist/commands/dynamic-secrets/index.js +173 -0
- package/dist/commands/dynamic-secrets/index.js.map +1 -0
- package/dist/commands/dynamic-secrets/lease.d.ts +11 -0
- package/dist/commands/dynamic-secrets/lease.d.ts.map +1 -0
- package/dist/commands/dynamic-secrets/lease.js +137 -0
- package/dist/commands/dynamic-secrets/lease.js.map +1 -0
- package/dist/commands/dynamic-secrets/role.d.ts +15 -0
- package/dist/commands/dynamic-secrets/role.d.ts.map +1 -0
- package/dist/commands/dynamic-secrets/role.js +184 -0
- package/dist/commands/dynamic-secrets/role.js.map +1 -0
- package/dist/commands/dynamic-secrets/types.d.ts +125 -0
- package/dist/commands/dynamic-secrets/types.d.ts.map +1 -0
- package/dist/commands/dynamic-secrets/types.js +3 -0
- package/dist/commands/dynamic-secrets/types.js.map +1 -0
- package/dist/commands/dynamic-secrets.d.ts +6 -2
- package/dist/commands/dynamic-secrets.d.ts.map +1 -1
- package/dist/commands/dynamic-secrets.js +6 -754
- package/dist/commands/dynamic-secrets.js.map +1 -1
- package/dist/commands/policy/attachments.d.ts +9 -0
- package/dist/commands/policy/attachments.d.ts.map +1 -0
- package/dist/commands/policy/attachments.js +161 -0
- package/dist/commands/policy/attachments.js.map +1 -0
- package/dist/commands/policy/crud.d.ts +8 -0
- package/dist/commands/policy/crud.d.ts.map +1 -0
- package/dist/commands/policy/crud.js +232 -0
- package/dist/commands/policy/crud.js.map +1 -0
- package/dist/commands/policy/helpers.d.ts +13 -0
- package/dist/commands/policy/helpers.d.ts.map +1 -0
- package/dist/commands/policy/helpers.js +61 -0
- package/dist/commands/policy/helpers.js.map +1 -0
- package/dist/commands/policy/index.d.ts +7 -0
- package/dist/commands/policy/index.d.ts.map +1 -0
- package/dist/commands/policy/index.js +160 -0
- package/dist/commands/policy/index.js.map +1 -0
- package/dist/commands/policy/io.d.ts +4 -0
- package/dist/commands/policy/io.d.ts.map +1 -0
- package/dist/commands/policy/io.js +65 -0
- package/dist/commands/policy/io.js.map +1 -0
- package/dist/commands/policy/list.d.ts +4 -0
- package/dist/commands/policy/list.d.ts.map +1 -0
- package/dist/commands/policy/list.js +99 -0
- package/dist/commands/policy/list.js.map +1 -0
- package/dist/commands/policy/test.d.ts +3 -0
- package/dist/commands/policy/test.d.ts.map +1 -0
- package/dist/commands/policy/test.js +58 -0
- package/dist/commands/policy/test.js.map +1 -0
- package/dist/commands/policy/types.d.ts +84 -0
- package/dist/commands/policy/types.d.ts.map +1 -0
- package/dist/commands/policy/types.js +3 -0
- package/dist/commands/policy/types.js.map +1 -0
- package/dist/commands/policy.d.ts +6 -2
- package/dist/commands/policy.d.ts.map +1 -1
- package/dist/commands/policy.js +4 -770
- package/dist/commands/policy.js.map +1 -1
- package/dist/lib/db/audit.d.ts +16 -0
- package/dist/lib/db/audit.d.ts.map +1 -0
- package/dist/lib/db/audit.js +60 -0
- package/dist/lib/db/audit.js.map +1 -0
- package/dist/lib/db/client.d.ts +27 -0
- package/dist/lib/db/client.d.ts.map +1 -0
- package/dist/lib/db/client.js +70 -0
- package/dist/lib/db/client.js.map +1 -0
- package/dist/lib/db/emergency.d.ts +50 -0
- package/dist/lib/db/emergency.d.ts.map +1 -0
- package/dist/lib/db/emergency.js +180 -0
- package/dist/lib/db/emergency.js.map +1 -0
- package/dist/lib/db/health.d.ts +14 -0
- package/dist/lib/db/health.d.ts.map +1 -0
- package/dist/lib/db/health.js +177 -0
- package/dist/lib/db/health.js.map +1 -0
- package/dist/lib/db/index.d.ts +56 -0
- package/dist/lib/db/index.d.ts.map +1 -0
- package/dist/lib/db/index.js +107 -0
- package/dist/lib/db/index.js.map +1 -0
- package/dist/lib/db/lockdown.d.ts +15 -0
- package/dist/lib/db/lockdown.d.ts.map +1 -0
- package/dist/lib/db/lockdown.js +67 -0
- package/dist/lib/db/lockdown.js.map +1 -0
- package/dist/lib/db/tenants.d.ts +14 -0
- package/dist/lib/db/tenants.d.ts.map +1 -0
- package/dist/lib/db/tenants.js +88 -0
- package/dist/lib/db/tenants.js.map +1 -0
- package/dist/lib/db/types.d.ts +95 -0
- package/dist/lib/db/types.d.ts.map +1 -0
- package/dist/lib/db/types.js +3 -0
- package/dist/lib/db/types.js.map +1 -0
- package/dist/lib/db/users.d.ts +16 -0
- package/dist/lib/db/users.d.ts.map +1 -0
- package/dist/lib/db/users.js +95 -0
- package/dist/lib/db/users.js.map +1 -0
- package/dist/lib/db.d.ts +4 -112
- package/dist/lib/db.d.ts.map +1 -1
- package/dist/lib/db.js +4 -726
- package/dist/lib/db.js.map +1 -1
- package/package.json +1 -1
package/dist/commands/policy.js
CHANGED
|
@@ -1,773 +1,7 @@
|
|
|
1
|
-
// Path:
|
|
2
|
-
import ora from 'ora';
|
|
3
|
-
import * as fs from 'fs';
|
|
4
|
-
import { client } from '../lib/client.js';
|
|
5
|
-
import { promptConfirm } from '../lib/prompts.js';
|
|
6
|
-
import * as output from '../lib/output.js';
|
|
7
|
-
// ============ Helper Functions ============
|
|
1
|
+
// Path: src/commands/policy.ts
|
|
8
2
|
/**
|
|
9
|
-
*
|
|
3
|
+
* Policy command re-exports for backward compatibility.
|
|
4
|
+
* The actual implementation has been modularized into src/commands/policy/
|
|
10
5
|
*/
|
|
11
|
-
|
|
12
|
-
if (!fs.existsSync(filePath)) {
|
|
13
|
-
throw new Error(`File not found: ${filePath}`);
|
|
14
|
-
}
|
|
15
|
-
try {
|
|
16
|
-
return fs.readFileSync(filePath, 'utf-8');
|
|
17
|
-
}
|
|
18
|
-
catch (err) {
|
|
19
|
-
if (err instanceof Error) {
|
|
20
|
-
if (err.message.includes('EACCES')) {
|
|
21
|
-
throw new Error(`Permission denied: ${filePath}`);
|
|
22
|
-
}
|
|
23
|
-
if (err.message.includes('EISDIR')) {
|
|
24
|
-
throw new Error(`Path is a directory, not a file: ${filePath}`);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
throw new Error(`Failed to read file: ${filePath}`);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Safely parse JSON with proper error handling
|
|
32
|
-
*/
|
|
33
|
-
function safeParseJson(content, context) {
|
|
34
|
-
try {
|
|
35
|
-
return JSON.parse(content);
|
|
36
|
-
}
|
|
37
|
-
catch (err) {
|
|
38
|
-
if (err instanceof SyntaxError) {
|
|
39
|
-
throw new Error(`Invalid JSON in ${context}: ${err.message}`);
|
|
40
|
-
}
|
|
41
|
-
throw new Error(`Failed to parse ${context} as JSON`);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Safely write a file with proper error handling
|
|
46
|
-
*/
|
|
47
|
-
function safeWriteFile(filePath, content) {
|
|
48
|
-
try {
|
|
49
|
-
fs.writeFileSync(filePath, content);
|
|
50
|
-
}
|
|
51
|
-
catch (err) {
|
|
52
|
-
if (err instanceof Error) {
|
|
53
|
-
if (err.message.includes('EACCES')) {
|
|
54
|
-
throw new Error(`Permission denied: ${filePath}`);
|
|
55
|
-
}
|
|
56
|
-
if (err.message.includes('ENOENT')) {
|
|
57
|
-
throw new Error(`Directory does not exist: ${filePath}`);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
throw new Error(`Failed to write file: ${filePath}`);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
export function registerPolicyCommands(program) {
|
|
64
|
-
const policy = program
|
|
65
|
-
.command('policy')
|
|
66
|
-
.description('ABAC policy management commands');
|
|
67
|
-
// ============ List Policies ============
|
|
68
|
-
policy
|
|
69
|
-
.command('list')
|
|
70
|
-
.description('List ABAC policies')
|
|
71
|
-
.option('--tenant <id>', 'Filter by tenant ID (superadmin only)')
|
|
72
|
-
.option('--enabled', 'Show only enabled policies')
|
|
73
|
-
.option('--disabled', 'Show only disabled policies')
|
|
74
|
-
.option('--effect <effect>', 'Filter by effect (allow|deny)')
|
|
75
|
-
.option('--search <term>', 'Search by name or description')
|
|
76
|
-
.option('--json', 'Output as JSON')
|
|
77
|
-
.action(async (options) => {
|
|
78
|
-
const spinner = ora('Fetching policies...').start();
|
|
79
|
-
try {
|
|
80
|
-
const result = await client.listPolicies({
|
|
81
|
-
tenantId: options.tenant,
|
|
82
|
-
enabled: options.enabled ? true : options.disabled ? false : undefined,
|
|
83
|
-
effect: options.effect,
|
|
84
|
-
search: options.search,
|
|
85
|
-
});
|
|
86
|
-
spinner.stop();
|
|
87
|
-
if (options.json) {
|
|
88
|
-
output.json(result.items);
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
if (result.items.length === 0) {
|
|
92
|
-
output.info('No policies found');
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
output.table(['ID', 'Name', 'Effect', 'Priority', 'Actions', 'Status', 'Tenant'], result.items.map(p => [
|
|
96
|
-
p.id.substring(0, 8),
|
|
97
|
-
p.name.length > 25 ? p.name.substring(0, 22) + '...' : p.name,
|
|
98
|
-
p.effect.toUpperCase(),
|
|
99
|
-
p.priority.toString(),
|
|
100
|
-
p.actions.length > 2 ? `${p.actions.slice(0, 2).join(', ')}...` : p.actions.join(', '),
|
|
101
|
-
p.isActive ? 'Enabled' : 'Disabled',
|
|
102
|
-
p.tenantId ?? '-',
|
|
103
|
-
]));
|
|
104
|
-
output.info(`Total: ${result.pagination.total} policy(s)${result.pagination.hasMore ? ' (more available)' : ''}`);
|
|
105
|
-
}
|
|
106
|
-
catch (err) {
|
|
107
|
-
spinner.fail('Failed to list policies');
|
|
108
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
109
|
-
process.exit(1);
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
// ============ Get Policy ============
|
|
113
|
-
policy
|
|
114
|
-
.command('get <id>')
|
|
115
|
-
.description('Get policy details')
|
|
116
|
-
.option('--json', 'Output as JSON')
|
|
117
|
-
.action(async (id, options) => {
|
|
118
|
-
const spinner = ora('Fetching policy...').start();
|
|
119
|
-
try {
|
|
120
|
-
const result = await client.getPolicy(id);
|
|
121
|
-
spinner.stop();
|
|
122
|
-
if (options.json) {
|
|
123
|
-
output.json(result);
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
output.section('Policy Details');
|
|
127
|
-
output.keyValue({
|
|
128
|
-
'ID': result.id,
|
|
129
|
-
'Name': result.name,
|
|
130
|
-
'Description': result.description ?? '-',
|
|
131
|
-
'Effect': result.effect.toUpperCase(),
|
|
132
|
-
'Priority': result.priority.toString(),
|
|
133
|
-
'Status': result.isActive ? 'Enabled' : 'Disabled',
|
|
134
|
-
'Tenant': result.tenantId ?? 'Global',
|
|
135
|
-
'Created': output.formatDate(result.createdAt),
|
|
136
|
-
'Updated': output.formatDate(result.updatedAt),
|
|
137
|
-
});
|
|
138
|
-
console.log();
|
|
139
|
-
output.section('Actions');
|
|
140
|
-
for (const action of result.actions) {
|
|
141
|
-
console.log(` - ${action}`);
|
|
142
|
-
}
|
|
143
|
-
if (result.resources && result.resources.length > 0) {
|
|
144
|
-
console.log();
|
|
145
|
-
output.section('Resources');
|
|
146
|
-
for (const resource of result.resources) {
|
|
147
|
-
const parts = [`type: ${resource.type}`];
|
|
148
|
-
if (resource.id)
|
|
149
|
-
parts.push(`id: ${resource.id}`);
|
|
150
|
-
if (resource.tenantId)
|
|
151
|
-
parts.push(`tenant: ${resource.tenantId}`);
|
|
152
|
-
if (resource.tags)
|
|
153
|
-
parts.push(`tags: ${JSON.stringify(resource.tags)}`);
|
|
154
|
-
console.log(` - ${parts.join(', ')}`);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
if (result.conditions && result.conditions.length > 0) {
|
|
158
|
-
console.log();
|
|
159
|
-
output.section('Conditions');
|
|
160
|
-
for (const condition of result.conditions) {
|
|
161
|
-
const op = condition.operator ? ` ${condition.operator}` : '';
|
|
162
|
-
console.log(` - ${condition.type}${op}: ${JSON.stringify(condition.value)}`);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
console.log();
|
|
166
|
-
}
|
|
167
|
-
catch (err) {
|
|
168
|
-
spinner.fail('Failed to get policy');
|
|
169
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
170
|
-
process.exit(1);
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
// ============ Create Policy ============
|
|
174
|
-
policy
|
|
175
|
-
.command('create')
|
|
176
|
-
.description('Create a new ABAC policy')
|
|
177
|
-
.requiredOption('--name <name>', 'Policy name')
|
|
178
|
-
.requiredOption('--effect <effect>', 'Policy effect (allow|deny)')
|
|
179
|
-
.requiredOption('--actions <actions>', 'Comma-separated list of actions (e.g., secret:read:value,secret:update)')
|
|
180
|
-
.option('--description <desc>', 'Policy description')
|
|
181
|
-
.option('--priority <num>', 'Priority (higher = evaluated first)', '0')
|
|
182
|
-
.option('--tenant <id>', 'Tenant ID (omit for global policy)')
|
|
183
|
-
.option('--resources <json>', 'Resources JSON array')
|
|
184
|
-
.option('--conditions <json>', 'Conditions JSON array')
|
|
185
|
-
.option('--from-file <path>', 'Load policy definition from JSON file')
|
|
186
|
-
.option('--json', 'Output as JSON')
|
|
187
|
-
.action(async (options) => {
|
|
188
|
-
try {
|
|
189
|
-
let policyData;
|
|
190
|
-
if (options.fromFile) {
|
|
191
|
-
// Load from file
|
|
192
|
-
const content = safeReadFile(options.fromFile);
|
|
193
|
-
policyData = safeParseJson(content, options.fromFile);
|
|
194
|
-
}
|
|
195
|
-
else {
|
|
196
|
-
// Parse and validate priority
|
|
197
|
-
const priority = parseInt(options.priority, 10);
|
|
198
|
-
if (isNaN(priority) || priority < 0) {
|
|
199
|
-
output.error('Priority must be a non-negative number');
|
|
200
|
-
process.exit(1);
|
|
201
|
-
}
|
|
202
|
-
// Build from options
|
|
203
|
-
policyData = {
|
|
204
|
-
name: options.name,
|
|
205
|
-
description: options.description,
|
|
206
|
-
effect: options.effect,
|
|
207
|
-
actions: options.actions.split(',').map((a) => a.trim()),
|
|
208
|
-
priority,
|
|
209
|
-
tenantId: options.tenant,
|
|
210
|
-
};
|
|
211
|
-
if (options.resources) {
|
|
212
|
-
policyData.resources = safeParseJson(options.resources, '--resources');
|
|
213
|
-
}
|
|
214
|
-
if (options.conditions) {
|
|
215
|
-
policyData.conditions = safeParseJson(options.conditions, '--conditions');
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
// Validate priority from file if loaded (ensure default and validate)
|
|
219
|
-
if (policyData.priority === undefined) {
|
|
220
|
-
policyData.priority = 0;
|
|
221
|
-
}
|
|
222
|
-
else if (typeof policyData.priority !== 'number' || isNaN(policyData.priority) || policyData.priority < 0) {
|
|
223
|
-
output.error('Priority must be a non-negative number');
|
|
224
|
-
process.exit(1);
|
|
225
|
-
}
|
|
226
|
-
const spinner = ora('Creating policy...').start();
|
|
227
|
-
const result = await client.createPolicy(policyData);
|
|
228
|
-
spinner.succeed('Policy created successfully');
|
|
229
|
-
if (options.json) {
|
|
230
|
-
output.json(result);
|
|
231
|
-
}
|
|
232
|
-
else {
|
|
233
|
-
output.keyValue({
|
|
234
|
-
'ID': result.id,
|
|
235
|
-
'Name': result.name,
|
|
236
|
-
'Effect': result.effect.toUpperCase(),
|
|
237
|
-
'Priority': result.priority.toString(),
|
|
238
|
-
'Status': result.isActive ? 'Enabled' : 'Disabled',
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
catch (err) {
|
|
243
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
244
|
-
process.exit(1);
|
|
245
|
-
}
|
|
246
|
-
});
|
|
247
|
-
// ============ Update Policy ============
|
|
248
|
-
policy
|
|
249
|
-
.command('update <id>')
|
|
250
|
-
.description('Update an ABAC policy')
|
|
251
|
-
.option('--name <name>', 'New policy name')
|
|
252
|
-
.option('--description <desc>', 'New description')
|
|
253
|
-
.option('--effect <effect>', 'New effect (allow|deny)')
|
|
254
|
-
.option('--actions <actions>', 'New comma-separated list of actions')
|
|
255
|
-
.option('--priority <num>', 'New priority')
|
|
256
|
-
.option('--resources <json>', 'New resources JSON array')
|
|
257
|
-
.option('--conditions <json>', 'New conditions JSON array')
|
|
258
|
-
.option('--from-file <path>', 'Load updates from JSON file')
|
|
259
|
-
.option('--json', 'Output as JSON')
|
|
260
|
-
.action(async (id, options) => {
|
|
261
|
-
try {
|
|
262
|
-
let updates;
|
|
263
|
-
if (options.fromFile) {
|
|
264
|
-
const content = safeReadFile(options.fromFile);
|
|
265
|
-
updates = safeParseJson(content, options.fromFile);
|
|
266
|
-
}
|
|
267
|
-
else {
|
|
268
|
-
updates = {};
|
|
269
|
-
if (options.name)
|
|
270
|
-
updates.name = options.name;
|
|
271
|
-
if (options.description)
|
|
272
|
-
updates.description = options.description;
|
|
273
|
-
if (options.effect)
|
|
274
|
-
updates.effect = options.effect;
|
|
275
|
-
if (options.actions)
|
|
276
|
-
updates.actions = options.actions.split(',').map((a) => a.trim());
|
|
277
|
-
if (options.priority) {
|
|
278
|
-
const priority = parseInt(options.priority, 10);
|
|
279
|
-
if (isNaN(priority) || priority < 0) {
|
|
280
|
-
output.error('Priority must be a non-negative number');
|
|
281
|
-
process.exit(1);
|
|
282
|
-
}
|
|
283
|
-
updates.priority = priority;
|
|
284
|
-
}
|
|
285
|
-
if (options.resources)
|
|
286
|
-
updates.resources = safeParseJson(options.resources, '--resources');
|
|
287
|
-
if (options.conditions)
|
|
288
|
-
updates.conditions = safeParseJson(options.conditions, '--conditions');
|
|
289
|
-
}
|
|
290
|
-
if (Object.keys(updates).length === 0) {
|
|
291
|
-
output.error('No updates specified');
|
|
292
|
-
process.exit(1);
|
|
293
|
-
}
|
|
294
|
-
const spinner = ora('Updating policy...').start();
|
|
295
|
-
const result = await client.updatePolicy(id, updates);
|
|
296
|
-
spinner.succeed('Policy updated successfully');
|
|
297
|
-
if (options.json) {
|
|
298
|
-
output.json(result);
|
|
299
|
-
}
|
|
300
|
-
else {
|
|
301
|
-
output.keyValue({
|
|
302
|
-
'ID': result.id,
|
|
303
|
-
'Name': result.name,
|
|
304
|
-
'Effect': result.effect.toUpperCase(),
|
|
305
|
-
'Updated': output.formatDate(result.updatedAt),
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
catch (err) {
|
|
310
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
311
|
-
process.exit(1);
|
|
312
|
-
}
|
|
313
|
-
});
|
|
314
|
-
// ============ Delete Policy ============
|
|
315
|
-
policy
|
|
316
|
-
.command('delete <id>')
|
|
317
|
-
.description('Delete an ABAC policy')
|
|
318
|
-
.option('-y, --yes', 'Skip confirmation')
|
|
319
|
-
.option('--json', 'Output as JSON')
|
|
320
|
-
.action(async (id, options) => {
|
|
321
|
-
try {
|
|
322
|
-
if (!options.yes) {
|
|
323
|
-
const confirmed = await promptConfirm(`Are you sure you want to delete policy '${id}'? This cannot be undone.`);
|
|
324
|
-
if (!confirmed) {
|
|
325
|
-
output.info('Delete cancelled');
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
const spinner = ora('Deleting policy...').start();
|
|
330
|
-
await client.deletePolicy(id);
|
|
331
|
-
spinner.succeed(`Policy '${id}' deleted successfully`);
|
|
332
|
-
if (options.json) {
|
|
333
|
-
output.json({ success: true, id, message: 'Policy deleted successfully' });
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
catch (err) {
|
|
337
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
338
|
-
process.exit(1);
|
|
339
|
-
}
|
|
340
|
-
});
|
|
341
|
-
// ============ Enable Policy ============
|
|
342
|
-
policy
|
|
343
|
-
.command('enable <id>')
|
|
344
|
-
.description('Enable an ABAC policy')
|
|
345
|
-
.option('--json', 'Output as JSON')
|
|
346
|
-
.action(async (id, options) => {
|
|
347
|
-
const spinner = ora('Enabling policy...').start();
|
|
348
|
-
try {
|
|
349
|
-
const result = await client.togglePolicy(id, true);
|
|
350
|
-
spinner.succeed('Policy enabled successfully');
|
|
351
|
-
if (options.json) {
|
|
352
|
-
output.json(result);
|
|
353
|
-
}
|
|
354
|
-
else {
|
|
355
|
-
output.keyValue({
|
|
356
|
-
'ID': result.id,
|
|
357
|
-
'Name': result.name,
|
|
358
|
-
'Status': 'Enabled',
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
catch (err) {
|
|
363
|
-
spinner.fail('Failed to enable policy');
|
|
364
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
365
|
-
process.exit(1);
|
|
366
|
-
}
|
|
367
|
-
});
|
|
368
|
-
// ============ Disable Policy ============
|
|
369
|
-
policy
|
|
370
|
-
.command('disable <id>')
|
|
371
|
-
.description('Disable an ABAC policy')
|
|
372
|
-
.option('--json', 'Output as JSON')
|
|
373
|
-
.action(async (id, options) => {
|
|
374
|
-
const spinner = ora('Disabling policy...').start();
|
|
375
|
-
try {
|
|
376
|
-
const result = await client.togglePolicy(id, false);
|
|
377
|
-
spinner.succeed('Policy disabled successfully');
|
|
378
|
-
if (options.json) {
|
|
379
|
-
output.json(result);
|
|
380
|
-
}
|
|
381
|
-
else {
|
|
382
|
-
output.keyValue({
|
|
383
|
-
'ID': result.id,
|
|
384
|
-
'Name': result.name,
|
|
385
|
-
'Status': 'Disabled',
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
catch (err) {
|
|
390
|
-
spinner.fail('Failed to disable policy');
|
|
391
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
392
|
-
process.exit(1);
|
|
393
|
-
}
|
|
394
|
-
});
|
|
395
|
-
// ============ Validate Policy ============
|
|
396
|
-
policy
|
|
397
|
-
.command('validate')
|
|
398
|
-
.description('Validate a policy definition without creating it')
|
|
399
|
-
.requiredOption('--name <name>', 'Policy name')
|
|
400
|
-
.requiredOption('--effect <effect>', 'Policy effect (allow|deny)')
|
|
401
|
-
.requiredOption('--actions <actions>', 'Comma-separated list of actions')
|
|
402
|
-
.option('--description <desc>', 'Policy description')
|
|
403
|
-
.option('--priority <num>', 'Priority', '0')
|
|
404
|
-
.option('--resources <json>', 'Resources JSON array')
|
|
405
|
-
.option('--conditions <json>', 'Conditions JSON array')
|
|
406
|
-
.option('--from-file <path>', 'Load policy from JSON file')
|
|
407
|
-
.action(async (options) => {
|
|
408
|
-
try {
|
|
409
|
-
let policyData;
|
|
410
|
-
if (options.fromFile) {
|
|
411
|
-
const content = safeReadFile(options.fromFile);
|
|
412
|
-
policyData = safeParseJson(content, options.fromFile);
|
|
413
|
-
}
|
|
414
|
-
else {
|
|
415
|
-
const priority = parseInt(options.priority, 10);
|
|
416
|
-
if (isNaN(priority) || priority < 0) {
|
|
417
|
-
output.error('Priority must be a non-negative number');
|
|
418
|
-
process.exit(1);
|
|
419
|
-
}
|
|
420
|
-
policyData = {
|
|
421
|
-
name: options.name,
|
|
422
|
-
description: options.description,
|
|
423
|
-
effect: options.effect,
|
|
424
|
-
actions: options.actions.split(',').map((a) => a.trim()),
|
|
425
|
-
priority,
|
|
426
|
-
};
|
|
427
|
-
if (options.resources) {
|
|
428
|
-
policyData.resources = safeParseJson(options.resources, '--resources');
|
|
429
|
-
}
|
|
430
|
-
if (options.conditions) {
|
|
431
|
-
policyData.conditions = safeParseJson(options.conditions, '--conditions');
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
const spinner = ora('Validating policy...').start();
|
|
435
|
-
const result = await client.validatePolicy(policyData);
|
|
436
|
-
if (result.valid) {
|
|
437
|
-
spinner.succeed('Policy is valid');
|
|
438
|
-
}
|
|
439
|
-
else {
|
|
440
|
-
spinner.fail('Policy validation failed');
|
|
441
|
-
if (result.errors) {
|
|
442
|
-
for (const error of result.errors) {
|
|
443
|
-
output.error(` - ${error}`);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
process.exit(1);
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
catch (err) {
|
|
450
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
451
|
-
process.exit(1);
|
|
452
|
-
}
|
|
453
|
-
});
|
|
454
|
-
// ============ Show Policy Attachments ============
|
|
455
|
-
policy
|
|
456
|
-
.command('attachments <id>')
|
|
457
|
-
.description('Show users and roles attached to a policy')
|
|
458
|
-
.option('--json', 'Output as JSON')
|
|
459
|
-
.action(async (id, options) => {
|
|
460
|
-
const spinner = ora('Fetching attachments...').start();
|
|
461
|
-
try {
|
|
462
|
-
const result = await client.getPolicyAttachments(id);
|
|
463
|
-
spinner.stop();
|
|
464
|
-
if (options.json) {
|
|
465
|
-
output.json(result);
|
|
466
|
-
return;
|
|
467
|
-
}
|
|
468
|
-
if (result.users.length === 0 && result.roles.length === 0) {
|
|
469
|
-
output.info('No attachments found for this policy');
|
|
470
|
-
return;
|
|
471
|
-
}
|
|
472
|
-
if (result.users.length > 0) {
|
|
473
|
-
output.section('Attached Users');
|
|
474
|
-
output.table(['User ID', 'Username', 'Attached At'], result.users.map(a => [
|
|
475
|
-
a.userId?.substring(0, 8) ?? '-',
|
|
476
|
-
a.username ?? '-',
|
|
477
|
-
output.formatDate(a.attachedAt),
|
|
478
|
-
]));
|
|
479
|
-
}
|
|
480
|
-
if (result.roles.length > 0) {
|
|
481
|
-
console.log();
|
|
482
|
-
output.section('Attached Roles');
|
|
483
|
-
output.table(['Role ID', 'Role Name', 'Attached At'], result.roles.map(a => [
|
|
484
|
-
a.roleId?.substring(0, 8) ?? '-',
|
|
485
|
-
a.roleName ?? '-',
|
|
486
|
-
output.formatDate(a.attachedAt),
|
|
487
|
-
]));
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
catch (err) {
|
|
491
|
-
spinner.fail('Failed to get attachments');
|
|
492
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
493
|
-
process.exit(1);
|
|
494
|
-
}
|
|
495
|
-
});
|
|
496
|
-
// ============ Attach Policy to User ============
|
|
497
|
-
policy
|
|
498
|
-
.command('attach-user <policyId> <userId>')
|
|
499
|
-
.description('Attach a policy to a user')
|
|
500
|
-
.option('--json', 'Output as JSON')
|
|
501
|
-
.action(async (policyId, userId, options) => {
|
|
502
|
-
const spinner = ora('Attaching policy to user...').start();
|
|
503
|
-
try {
|
|
504
|
-
await client.attachPolicyToUser(policyId, userId);
|
|
505
|
-
spinner.succeed('Policy attached to user successfully');
|
|
506
|
-
if (options.json) {
|
|
507
|
-
output.json({ success: true, policyId, userId, message: 'Policy attached to user successfully' });
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
catch (err) {
|
|
511
|
-
spinner.fail('Failed to attach policy');
|
|
512
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
513
|
-
process.exit(1);
|
|
514
|
-
}
|
|
515
|
-
});
|
|
516
|
-
// ============ Attach Policy to Role ============
|
|
517
|
-
policy
|
|
518
|
-
.command('attach-role <policyId> <roleId>')
|
|
519
|
-
.description('Attach a policy to a role')
|
|
520
|
-
.option('--json', 'Output as JSON')
|
|
521
|
-
.action(async (policyId, roleId, options) => {
|
|
522
|
-
const spinner = ora('Attaching policy to role...').start();
|
|
523
|
-
try {
|
|
524
|
-
await client.attachPolicyToRole(policyId, roleId);
|
|
525
|
-
spinner.succeed('Policy attached to role successfully');
|
|
526
|
-
if (options.json) {
|
|
527
|
-
output.json({ success: true, policyId, roleId, message: 'Policy attached to role successfully' });
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
catch (err) {
|
|
531
|
-
spinner.fail('Failed to attach policy');
|
|
532
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
533
|
-
process.exit(1);
|
|
534
|
-
}
|
|
535
|
-
});
|
|
536
|
-
// ============ Detach Policy from User ============
|
|
537
|
-
policy
|
|
538
|
-
.command('detach-user <policyId> <userId>')
|
|
539
|
-
.description('Detach a policy from a user')
|
|
540
|
-
.option('--json', 'Output as JSON')
|
|
541
|
-
.action(async (policyId, userId, options) => {
|
|
542
|
-
const spinner = ora('Detaching policy from user...').start();
|
|
543
|
-
try {
|
|
544
|
-
await client.detachPolicyFromUser(policyId, userId);
|
|
545
|
-
spinner.succeed('Policy detached from user successfully');
|
|
546
|
-
if (options.json) {
|
|
547
|
-
output.json({ success: true, policyId, userId, message: 'Policy detached from user successfully' });
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
catch (err) {
|
|
551
|
-
spinner.fail('Failed to detach policy');
|
|
552
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
553
|
-
process.exit(1);
|
|
554
|
-
}
|
|
555
|
-
});
|
|
556
|
-
// ============ Detach Policy from Role ============
|
|
557
|
-
policy
|
|
558
|
-
.command('detach-role <policyId> <roleId>')
|
|
559
|
-
.description('Detach a policy from a role')
|
|
560
|
-
.option('--json', 'Output as JSON')
|
|
561
|
-
.action(async (policyId, roleId, options) => {
|
|
562
|
-
const spinner = ora('Detaching policy from role...').start();
|
|
563
|
-
try {
|
|
564
|
-
await client.detachPolicyFromRole(policyId, roleId);
|
|
565
|
-
spinner.succeed('Policy detached from role successfully');
|
|
566
|
-
if (options.json) {
|
|
567
|
-
output.json({ success: true, policyId, roleId, message: 'Policy detached from role successfully' });
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
catch (err) {
|
|
571
|
-
spinner.fail('Failed to detach policy');
|
|
572
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
573
|
-
process.exit(1);
|
|
574
|
-
}
|
|
575
|
-
});
|
|
576
|
-
// ============ List User's Policies ============
|
|
577
|
-
policy
|
|
578
|
-
.command('user-policies <userId>')
|
|
579
|
-
.description('List policies attached to a user (directly or via roles)')
|
|
580
|
-
.option('--json', 'Output as JSON')
|
|
581
|
-
.action(async (userId, options) => {
|
|
582
|
-
const spinner = ora('Fetching user policies...').start();
|
|
583
|
-
try {
|
|
584
|
-
const policies = await client.getUserPolicies(userId);
|
|
585
|
-
spinner.stop();
|
|
586
|
-
if (options.json) {
|
|
587
|
-
output.json(policies);
|
|
588
|
-
return;
|
|
589
|
-
}
|
|
590
|
-
if (policies.length === 0) {
|
|
591
|
-
output.info('No policies attached to this user');
|
|
592
|
-
return;
|
|
593
|
-
}
|
|
594
|
-
output.table(['ID', 'Name', 'Effect', 'Priority', 'Status'], policies.map(p => [
|
|
595
|
-
p.id.substring(0, 8),
|
|
596
|
-
p.name,
|
|
597
|
-
p.effect.toUpperCase(),
|
|
598
|
-
p.priority.toString(),
|
|
599
|
-
p.isActive ? 'Enabled' : 'Disabled',
|
|
600
|
-
]));
|
|
601
|
-
output.info(`Total: ${policies.length} policy(s)`);
|
|
602
|
-
}
|
|
603
|
-
catch (err) {
|
|
604
|
-
spinner.fail('Failed to get user policies');
|
|
605
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
606
|
-
process.exit(1);
|
|
607
|
-
}
|
|
608
|
-
});
|
|
609
|
-
// ============ List Role's Policies ============
|
|
610
|
-
policy
|
|
611
|
-
.command('role-policies <roleId>')
|
|
612
|
-
.description('List policies attached to a role')
|
|
613
|
-
.option('--json', 'Output as JSON')
|
|
614
|
-
.action(async (roleId, options) => {
|
|
615
|
-
const spinner = ora('Fetching role policies...').start();
|
|
616
|
-
try {
|
|
617
|
-
const policies = await client.getRolePolicies(roleId);
|
|
618
|
-
spinner.stop();
|
|
619
|
-
if (options.json) {
|
|
620
|
-
output.json(policies);
|
|
621
|
-
return;
|
|
622
|
-
}
|
|
623
|
-
if (policies.length === 0) {
|
|
624
|
-
output.info('No policies attached to this role');
|
|
625
|
-
return;
|
|
626
|
-
}
|
|
627
|
-
output.table(['ID', 'Name', 'Effect', 'Priority', 'Status'], policies.map(p => [
|
|
628
|
-
p.id.substring(0, 8),
|
|
629
|
-
p.name,
|
|
630
|
-
p.effect.toUpperCase(),
|
|
631
|
-
p.priority.toString(),
|
|
632
|
-
p.isActive ? 'Enabled' : 'Disabled',
|
|
633
|
-
]));
|
|
634
|
-
output.info(`Total: ${policies.length} policy(s)`);
|
|
635
|
-
}
|
|
636
|
-
catch (err) {
|
|
637
|
-
spinner.fail('Failed to get role policies');
|
|
638
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
639
|
-
process.exit(1);
|
|
640
|
-
}
|
|
641
|
-
});
|
|
642
|
-
// ============ Test Policy Evaluation ============
|
|
643
|
-
policy
|
|
644
|
-
.command('test')
|
|
645
|
-
.description('Test ABAC policy evaluation for a user and action')
|
|
646
|
-
.requiredOption('--user <userId>', 'User ID to test')
|
|
647
|
-
.requiredOption('--action <action>', 'Action to test (e.g., secret:read:value)')
|
|
648
|
-
.option('--resource-type <type>', 'Resource type (secret|kms_key|certificate|...)')
|
|
649
|
-
.option('--resource-id <id>', 'Resource ID')
|
|
650
|
-
.option('--resource-tenant <id>', 'Resource tenant ID')
|
|
651
|
-
.option('--ip <ip>', 'Simulated client IP address')
|
|
652
|
-
.option('--mfa', 'Simulate MFA verified')
|
|
653
|
-
.option('--json', 'Output as JSON')
|
|
654
|
-
.action(async (options) => {
|
|
655
|
-
const spinner = ora('Testing policy evaluation...').start();
|
|
656
|
-
try {
|
|
657
|
-
const request = {
|
|
658
|
-
userId: options.user,
|
|
659
|
-
action: options.action,
|
|
660
|
-
resource: options.resourceType ? {
|
|
661
|
-
type: options.resourceType,
|
|
662
|
-
id: options.resourceId,
|
|
663
|
-
tenantId: options.resourceTenant,
|
|
664
|
-
} : undefined,
|
|
665
|
-
requestContext: (options.ip !== undefined || options.mfa !== undefined) ? {
|
|
666
|
-
ip: options.ip,
|
|
667
|
-
mfaVerified: options.mfa ?? false,
|
|
668
|
-
} : undefined,
|
|
669
|
-
};
|
|
670
|
-
const result = await client.testPolicy(request);
|
|
671
|
-
spinner.stop();
|
|
672
|
-
if (options.json) {
|
|
673
|
-
output.json(result);
|
|
674
|
-
return;
|
|
675
|
-
}
|
|
676
|
-
// Display result with color
|
|
677
|
-
const statusIcon = result.allowed ? '[OK]' : '[X]';
|
|
678
|
-
const statusText = result.allowed ? 'ALLOWED' : 'DENIED';
|
|
679
|
-
console.log();
|
|
680
|
-
console.log(` ${statusIcon} Access: ${statusText}`);
|
|
681
|
-
console.log(` Effect: ${result.effect.toUpperCase()}`);
|
|
682
|
-
console.log(` Reason: ${result.reason}`);
|
|
683
|
-
console.log();
|
|
684
|
-
output.keyValue({
|
|
685
|
-
'Policies Evaluated': result.evaluatedPolicies.toString(),
|
|
686
|
-
'Policies Matched': result.matchedPolicies.length.toString(),
|
|
687
|
-
'Evaluation Time': `${result.evaluationTimeMs}ms`,
|
|
688
|
-
});
|
|
689
|
-
if (result.matchedPolicies.length > 0) {
|
|
690
|
-
console.log();
|
|
691
|
-
output.section('Matched Policies');
|
|
692
|
-
output.table(['Name', 'Effect', 'Priority'], result.matchedPolicies.map(p => [
|
|
693
|
-
p.name,
|
|
694
|
-
p.effect.toUpperCase(),
|
|
695
|
-
p.priority.toString(),
|
|
696
|
-
]));
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
catch (err) {
|
|
700
|
-
spinner.fail('Failed to test policy');
|
|
701
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
702
|
-
process.exit(1);
|
|
703
|
-
}
|
|
704
|
-
});
|
|
705
|
-
// ============ Export Policy ============
|
|
706
|
-
policy
|
|
707
|
-
.command('export <id>')
|
|
708
|
-
.description('Export a policy as JSON')
|
|
709
|
-
.option('-o, --output <path>', 'Output file path')
|
|
710
|
-
.action(async (id, options) => {
|
|
711
|
-
const spinner = ora('Exporting policy...').start();
|
|
712
|
-
try {
|
|
713
|
-
const result = await client.getPolicy(id);
|
|
714
|
-
spinner.stop();
|
|
715
|
-
const exportData = {
|
|
716
|
-
name: result.name,
|
|
717
|
-
description: result.description,
|
|
718
|
-
effect: result.effect,
|
|
719
|
-
actions: result.actions,
|
|
720
|
-
resources: result.resources,
|
|
721
|
-
conditions: result.conditions,
|
|
722
|
-
priority: result.priority,
|
|
723
|
-
};
|
|
724
|
-
const jsonString = JSON.stringify(exportData, null, 2);
|
|
725
|
-
if (options.output) {
|
|
726
|
-
safeWriteFile(options.output, jsonString);
|
|
727
|
-
output.success(`Policy exported to ${options.output}`);
|
|
728
|
-
}
|
|
729
|
-
else {
|
|
730
|
-
console.log(jsonString);
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
catch (err) {
|
|
734
|
-
spinner.fail('Failed to export policy');
|
|
735
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
736
|
-
process.exit(1);
|
|
737
|
-
}
|
|
738
|
-
});
|
|
739
|
-
// ============ Import Policy ============
|
|
740
|
-
policy
|
|
741
|
-
.command('import <path>')
|
|
742
|
-
.description('Import a policy from JSON file')
|
|
743
|
-
.option('--tenant <id>', 'Override tenant ID')
|
|
744
|
-
.option('--json', 'Output as JSON')
|
|
745
|
-
.action(async (path, options) => {
|
|
746
|
-
try {
|
|
747
|
-
const content = safeReadFile(path);
|
|
748
|
-
const policyData = safeParseJson(content, path);
|
|
749
|
-
if (options.tenant) {
|
|
750
|
-
policyData.tenantId = options.tenant;
|
|
751
|
-
}
|
|
752
|
-
const spinner = ora('Importing policy...').start();
|
|
753
|
-
const result = await client.createPolicy(policyData);
|
|
754
|
-
spinner.succeed('Policy imported successfully');
|
|
755
|
-
if (options.json) {
|
|
756
|
-
output.json(result);
|
|
757
|
-
}
|
|
758
|
-
else {
|
|
759
|
-
output.keyValue({
|
|
760
|
-
'ID': result.id,
|
|
761
|
-
'Name': result.name,
|
|
762
|
-
'Effect': result.effect.toUpperCase(),
|
|
763
|
-
'Status': result.isActive ? 'Enabled' : 'Disabled',
|
|
764
|
-
});
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
catch (err) {
|
|
768
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
769
|
-
process.exit(1);
|
|
770
|
-
}
|
|
771
|
-
});
|
|
772
|
-
}
|
|
6
|
+
export { registerPolicyCommands } from './policy/index.js';
|
|
773
7
|
//# sourceMappingURL=policy.js.map
|