@brunsforge/aarm 0.1.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/README.md +1180 -0
- package/dist/aarm.cjs +39260 -0
- package/dist/aarm.mjs +39262 -0
- package/dist/commands/apps.d.ts +2 -0
- package/dist/commands/apps.js +32 -0
- package/dist/commands/apps.js.map +1 -0
- package/dist/commands/preflight.d.ts +2 -0
- package/dist/commands/preflight.js +160 -0
- package/dist/commands/preflight.js.map +1 -0
- package/dist/commands/report.d.ts +2 -0
- package/dist/commands/report.js +166 -0
- package/dist/commands/report.js.map +1 -0
- package/dist/commands/secrets.d.ts +2 -0
- package/dist/commands/secrets.js +88 -0
- package/dist/commands/secrets.js.map +1 -0
- package/dist/commands/tenants.d.ts +2 -0
- package/dist/commands/tenants.js +209 -0
- package/dist/commands/tenants.js.map +1 -0
- package/dist/commands/usage.d.ts +2 -0
- package/dist/commands/usage.js +203 -0
- package/dist/commands/usage.js.map +1 -0
- package/dist/config/ConfigStore.d.ts +12 -0
- package/dist/config/ConfigStore.js +53 -0
- package/dist/config/ConfigStore.js.map +1 -0
- package/dist/config/CredentialStore.d.ts +8 -0
- package/dist/config/CredentialStore.js +29 -0
- package/dist/config/CredentialStore.js.map +1 -0
- package/dist/config/HistoryStore.d.ts +19 -0
- package/dist/config/HistoryStore.js +64 -0
- package/dist/config/HistoryStore.js.map +1 -0
- package/dist/exitCodes.d.ts +9 -0
- package/dist/exitCodes.js +10 -0
- package/dist/exitCodes.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/output/formatters.d.ts +10 -0
- package/dist/output/formatters.js +129 -0
- package/dist/output/formatters.js.map +1 -0
- package/dist/shared/context.d.ts +31 -0
- package/dist/shared/context.js +124 -0
- package/dist/shared/context.js.map +1 -0
- package/package.json +43 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createResultEnvelope, envelopeToJson, } from '@brunsforge/azure-app-registration-monitor';
|
|
2
|
+
import { buildContext, handleError } from '../shared/context.js';
|
|
3
|
+
import { printAppsTable } from '../output/formatters.js';
|
|
4
|
+
export function registerAppsCommand(program) {
|
|
5
|
+
const apps = program
|
|
6
|
+
.command('apps')
|
|
7
|
+
.description('Query App Registrations')
|
|
8
|
+
.action(() => apps.help()); // show help when no subcommand given
|
|
9
|
+
apps
|
|
10
|
+
.command('list')
|
|
11
|
+
.description('List all App Registrations and their secret risk summary')
|
|
12
|
+
.option('--include-owners', 'Resolve application owners (requires Directory.Read.All)')
|
|
13
|
+
.action(async (cmdOpts) => {
|
|
14
|
+
try {
|
|
15
|
+
const ctx = await buildContext(program.opts());
|
|
16
|
+
const inventory = await ctx.inventoryService.getInventory({
|
|
17
|
+
includeOwners: cmdOpts.includeOwners,
|
|
18
|
+
});
|
|
19
|
+
if (ctx.isJson) {
|
|
20
|
+
const envelope = createResultEnvelope(inventory, ctx.tenantId, ctx.environmentName);
|
|
21
|
+
process.stdout.write(envelopeToJson(envelope) + '\n');
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
printAppsTable(inventory);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
handleError(err);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=apps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apps.js","sourceRoot":"","sources":["../../src/commands/apps.ts"],"names":[],"mappings":"AACA,OAAO,EACL,oBAAoB,EACpB,cAAc,GACf,MAAM,4CAA4C,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EAAE,cAAc,EAAa,MAAM,yBAAyB,CAAC;AAEpE,MAAM,UAAU,mBAAmB,CAAC,OAAgB;IAClD,MAAM,IAAI,GAAG,OAAO;SACjB,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,yBAAyB,CAAC;SACtC,MAAM,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,qCAAqC;IAEnE,IAAI;SACD,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,0DAA0D,CAAC;SACvE,MAAM,CAAC,kBAAkB,EAAE,0DAA0D,CAAC;SACtF,MAAM,CAAC,KAAK,EAAE,OAAoC,EAAE,EAAE;QACrD,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;YAC/C,MAAM,SAAS,GAAG,MAAM,GAAG,CAAC,gBAAgB,CAAC,YAAY,CAAC;gBACxD,aAAa,EAAE,OAAO,CAAC,aAAa;aACrC,CAAC,CAAC;YAEH,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;gBACf,MAAM,QAAQ,GAAG,oBAAoB,CACnC,SAAS,EACT,GAAG,CAAC,QAAQ,EACZ,GAAG,CAAC,eAAe,CACpB,CAAC;gBACF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC,CAAC;YACxD,CAAC;iBAAM,CAAC;gBACN,cAAc,CAAC,SAAS,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,WAAW,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import Table from 'cli-table3';
|
|
3
|
+
import { createResultEnvelope, envelopeToJson, isDelegatedMode, PERMISSION_DETAILS, } from '@brunsforge/azure-app-registration-monitor';
|
|
4
|
+
import { buildContext, handleError } from '../shared/context.js';
|
|
5
|
+
import { ConfigStore } from '../config/ConfigStore.js';
|
|
6
|
+
import { HistoryStore } from '../config/HistoryStore.js';
|
|
7
|
+
function tick(value) {
|
|
8
|
+
return value ? chalk.green('[✓]') : chalk.red('[ ]');
|
|
9
|
+
}
|
|
10
|
+
function printPreflightTable(result) {
|
|
11
|
+
process.stdout.write(`\nPreflight — ${chalk.bold(result.tenantId)}\n`);
|
|
12
|
+
process.stdout.write(`Checked at : ${result.checkedAt}\n\n`);
|
|
13
|
+
const statusTable = new Table({ style: { compact: true } });
|
|
14
|
+
statusTable.push(['Authentication', result.authValid ? chalk.green('OK') : chalk.red('FAILED')], ['Graph reachable', result.graphReachable ? chalk.green('OK') : chalk.red('FAILED')]);
|
|
15
|
+
process.stdout.write(statusTable.toString() + '\n\n');
|
|
16
|
+
process.stdout.write(chalk.bold('Capabilities\n'));
|
|
17
|
+
const capTable = new Table({
|
|
18
|
+
head: ['Capability', 'Status'],
|
|
19
|
+
style: { head: ['cyan'] },
|
|
20
|
+
});
|
|
21
|
+
for (const [key, value] of Object.entries(result.capabilities)) {
|
|
22
|
+
capTable.push([
|
|
23
|
+
key,
|
|
24
|
+
`${tick(value)} ${value ? 'Available' : chalk.dim('Unavailable')}`,
|
|
25
|
+
]);
|
|
26
|
+
}
|
|
27
|
+
process.stdout.write(capTable.toString() + '\n');
|
|
28
|
+
if (result.missingPermissions.length > 0) {
|
|
29
|
+
process.stdout.write(chalk.bold('\nMissing permissions\n'));
|
|
30
|
+
for (const p of result.missingPermissions) {
|
|
31
|
+
process.stdout.write(` ${chalk.red('✗')} ${p}\n`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (result.warnings.length > 0) {
|
|
35
|
+
process.stdout.write(chalk.bold('\nWarnings\n'));
|
|
36
|
+
for (const w of result.warnings) {
|
|
37
|
+
process.stdout.write(` ${chalk.yellow('!')} ${w}\n`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (result.errors.length > 0) {
|
|
41
|
+
process.stdout.write(chalk.bold('\nErrors\n'));
|
|
42
|
+
for (const e of result.errors) {
|
|
43
|
+
process.stdout.write(` ${chalk.red('✗')} ${e}\n`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function registerPreflightCommand(program) {
|
|
48
|
+
const preflight = program
|
|
49
|
+
.command('preflight')
|
|
50
|
+
.description('Run and display tenant capability preflight checks')
|
|
51
|
+
.action(() => preflight.help());
|
|
52
|
+
preflight
|
|
53
|
+
.command('run')
|
|
54
|
+
.description('Run a full preflight capability check against the tenant')
|
|
55
|
+
.action(async () => {
|
|
56
|
+
try {
|
|
57
|
+
const ctx = await buildContext(program.opts());
|
|
58
|
+
// Progress goes to stderr so stdout stays clean JSON when --output json
|
|
59
|
+
if (!ctx.isJson) {
|
|
60
|
+
process.stderr.write(chalk.dim(`Running preflight for ${ctx.tenantId}...\n`));
|
|
61
|
+
}
|
|
62
|
+
const result = await ctx.preflightService.run({
|
|
63
|
+
tenantId: ctx.tenantId,
|
|
64
|
+
environmentName: ctx.environmentName,
|
|
65
|
+
authMode: ctx.authMode,
|
|
66
|
+
logAnalyticsWorkspaceId: ctx.logAnalyticsWorkspaceId,
|
|
67
|
+
});
|
|
68
|
+
// Auto-save preflight result to history
|
|
69
|
+
const history = new HistoryStore(ctx.configStore.getConfigDir());
|
|
70
|
+
void history.save('preflight', ctx.tenantId, result);
|
|
71
|
+
// Update lastPreflightAt on the tenant profile
|
|
72
|
+
void ctx.configStore.upsertTenant({
|
|
73
|
+
...ctx.tenant,
|
|
74
|
+
lastPreflightAt: new Date().toISOString(),
|
|
75
|
+
updatedAt: new Date().toISOString(),
|
|
76
|
+
});
|
|
77
|
+
if (ctx.isJson) {
|
|
78
|
+
process.stdout.write(envelopeToJson(createResultEnvelope(result, ctx.tenantId, ctx.environmentName, {
|
|
79
|
+
warnings: result.warnings,
|
|
80
|
+
errors: result.errors,
|
|
81
|
+
})) + '\n');
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
printPreflightTable(result);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
handleError(err);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
preflight
|
|
92
|
+
.command('show')
|
|
93
|
+
.description('Show the last cached preflight result (no network call)')
|
|
94
|
+
.action(async () => {
|
|
95
|
+
const opts = program.opts();
|
|
96
|
+
if (!opts.tenant) {
|
|
97
|
+
process.stderr.write('Error: --tenant is required.\n');
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
const store = new ConfigStore(opts.configDir);
|
|
101
|
+
const tenant = await store.getTenant(opts.tenant);
|
|
102
|
+
if (!tenant) {
|
|
103
|
+
process.stderr.write(`Error: Tenant "${opts.tenant}" not found.\n`);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
const history = new HistoryStore(store.getConfigDir());
|
|
107
|
+
const cached = await history.loadLatest('preflight', tenant.tenantId);
|
|
108
|
+
if (!cached) {
|
|
109
|
+
process.stderr.write(chalk.dim('No cached preflight result. Run "aarm preflight run --tenant <name>" first.\n'));
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
if (opts.output === 'json') {
|
|
113
|
+
process.stdout.write(envelopeToJson(createResultEnvelope(cached, tenant.tenantId, tenant.defaultEnvironmentName ?? 'default')) + '\n');
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
process.stderr.write(chalk.dim(`Showing cached result from ${cached.checkedAt}\n\n`));
|
|
117
|
+
printPreflightTable(cached);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
preflight
|
|
121
|
+
.command('explain')
|
|
122
|
+
.description('List all permissions required and how to grant them')
|
|
123
|
+
.action(async () => {
|
|
124
|
+
const globalOpts = program.opts();
|
|
125
|
+
// Resolve auth mode from stored tenant profile if --tenant is given
|
|
126
|
+
let delegated = false;
|
|
127
|
+
if (globalOpts.tenant) {
|
|
128
|
+
const { ConfigStore } = await import('../config/ConfigStore.js');
|
|
129
|
+
const store = new ConfigStore(globalOpts.configDir);
|
|
130
|
+
const tenant = await store.getTenant(globalOpts.tenant);
|
|
131
|
+
if (tenant)
|
|
132
|
+
delegated = isDelegatedMode(tenant.authMode);
|
|
133
|
+
}
|
|
134
|
+
const showSection = (sectionDelegated) => {
|
|
135
|
+
const label = sectionDelegated
|
|
136
|
+
? 'Delegated modes (device-code · username-password · interactive-browser · azure-cli)'
|
|
137
|
+
: 'Application modes (client-secret · certificate)';
|
|
138
|
+
process.stdout.write(chalk.bold(`\n${label}\n`));
|
|
139
|
+
process.stdout.write(chalk.dim('─'.repeat(72) + '\n\n'));
|
|
140
|
+
for (const d of PERMISSION_DETAILS) {
|
|
141
|
+
const hint = sectionDelegated ? d.delegatedHint : d.applicationHint;
|
|
142
|
+
const phase = d.mvp ? '' : chalk.dim(' [post-MVP]');
|
|
143
|
+
const consent = d.requiresAdminConsent ? chalk.yellow(' [admin consent]') : '';
|
|
144
|
+
const role = d.requiresUserRole && sectionDelegated ? chalk.magenta(' [user role]') : '';
|
|
145
|
+
process.stdout.write(` ${chalk.cyan(d.capability)}${phase}${consent}${role}\n`);
|
|
146
|
+
process.stdout.write(` ${hint}\n\n`);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
if (globalOpts.tenant) {
|
|
150
|
+
// Show only the relevant mode for the configured tenant
|
|
151
|
+
showSection(delegated);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
process.stdout.write(chalk.dim('Tip: pass --tenant <name> to see only the hints relevant to your auth mode.\n'));
|
|
155
|
+
showSection(false);
|
|
156
|
+
showSection(true);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=preflight.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preflight.js","sourceRoot":"","sources":["../../src/commands/preflight.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { createResultEnvelope, envelopeToJson, riskLevelOrder, } from '@brunsforge/azure-app-registration-monitor';
|
|
3
|
+
import { buildContext, handleError } from '../shared/context.js';
|
|
4
|
+
import { printSecretsTable, secretsToMarkdown, secretsToCsv, } from '../output/formatters.js';
|
|
5
|
+
const RISK_LEVELS = ['Info', 'Low', 'Medium', 'High', 'Critical'];
|
|
6
|
+
function parseRiskLevel(raw) {
|
|
7
|
+
const level = RISK_LEVELS.find((r) => r.toLowerCase() === raw.toLowerCase());
|
|
8
|
+
if (!level) {
|
|
9
|
+
process.stderr.write(`Invalid severity "${raw}". Valid values: ${RISK_LEVELS.map((r) => r.toLowerCase()).join(', ')}\n`);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
return level;
|
|
13
|
+
}
|
|
14
|
+
function allSecrets(inventory) {
|
|
15
|
+
return inventory.flatMap((app) => app.secrets);
|
|
16
|
+
}
|
|
17
|
+
function outputSecrets(secrets, output, tenantId, envName) {
|
|
18
|
+
if (secrets.length === 0) {
|
|
19
|
+
process.stdout.write(chalk.dim('No matching secrets found.\n'));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
switch (output) {
|
|
23
|
+
case 'json':
|
|
24
|
+
process.stdout.write(envelopeToJson(createResultEnvelope(secrets, tenantId, envName)) + '\n');
|
|
25
|
+
break;
|
|
26
|
+
case 'markdown':
|
|
27
|
+
process.stdout.write(secretsToMarkdown(secrets, tenantId, envName));
|
|
28
|
+
break;
|
|
29
|
+
case 'csv':
|
|
30
|
+
process.stdout.write(secretsToCsv(secrets));
|
|
31
|
+
break;
|
|
32
|
+
default:
|
|
33
|
+
printSecretsTable(secrets);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function registerReportCommand(program) {
|
|
37
|
+
const report = program
|
|
38
|
+
.command('report')
|
|
39
|
+
.description('Generate formatted reports from the secret inventory')
|
|
40
|
+
.action(() => report.help());
|
|
41
|
+
report
|
|
42
|
+
.command('expiring')
|
|
43
|
+
.description('Report secrets expiring within a given window')
|
|
44
|
+
.option('--days <n>', 'Look-ahead window in days')
|
|
45
|
+
.option('--months <n>', 'Look-ahead window in months (converted to days × 30)')
|
|
46
|
+
.action(async (cmdOpts) => {
|
|
47
|
+
try {
|
|
48
|
+
const ctx = await buildContext(program.opts());
|
|
49
|
+
const days = cmdOpts.months
|
|
50
|
+
? parseInt(cmdOpts.months, 10) * 30
|
|
51
|
+
: parseInt(cmdOpts.days ?? '30', 10);
|
|
52
|
+
const inventory = await ctx.inventoryService.getInventory({
|
|
53
|
+
thresholds: { expiringWithinDays: days },
|
|
54
|
+
});
|
|
55
|
+
const expiring = allSecrets(inventory).filter((s) => s.status === 'ExpiringSoon' || s.status === 'Expired');
|
|
56
|
+
outputSecrets(expiring, program.opts().output, ctx.tenantId, ctx.environmentName);
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
handleError(err);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
report
|
|
63
|
+
.command('tenant-summary')
|
|
64
|
+
.description('Summary of App Registrations and secrets for the tenant')
|
|
65
|
+
.action(async () => {
|
|
66
|
+
try {
|
|
67
|
+
const ctx = await buildContext(program.opts());
|
|
68
|
+
const inventory = await ctx.inventoryService.getInventory();
|
|
69
|
+
const allSec = allSecrets(inventory);
|
|
70
|
+
const summary = {
|
|
71
|
+
tenantId: ctx.tenantId,
|
|
72
|
+
environmentName: ctx.environmentName,
|
|
73
|
+
generatedAt: new Date().toISOString(),
|
|
74
|
+
appCount: inventory.length,
|
|
75
|
+
secretCount: allSec.length,
|
|
76
|
+
expiredCount: allSec.filter((s) => s.status === 'Expired').length,
|
|
77
|
+
expiringIn30Days: allSec.filter((s) => s.daysUntilExpiry !== null && s.daysUntilExpiry >= 0 && s.daysUntilExpiry <= 30).length,
|
|
78
|
+
expiringIn90Days: allSec.filter((s) => s.daysUntilExpiry !== null && s.daysUntilExpiry >= 0 && s.daysUntilExpiry <= 90).length,
|
|
79
|
+
byRisk: Object.fromEntries(RISK_LEVELS.map((r) => [r, allSec.filter((s) => s.riskLevel === r).length])),
|
|
80
|
+
};
|
|
81
|
+
if (ctx.isJson) {
|
|
82
|
+
process.stdout.write(envelopeToJson(createResultEnvelope(summary, ctx.tenantId, ctx.environmentName)) + '\n');
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
process.stdout.write(`\nTenant Summary — ${chalk.bold(ctx.tenantId)}\n`);
|
|
86
|
+
process.stdout.write(`Generated at : ${summary.generatedAt}\n\n`);
|
|
87
|
+
process.stdout.write(`App Registrations : ${summary.appCount}\n`);
|
|
88
|
+
process.stdout.write(`Total secrets : ${summary.secretCount}\n`);
|
|
89
|
+
process.stdout.write(`Expired : ${summary.expiredCount > 0 ? chalk.red(String(summary.expiredCount)) : '0'}\n`);
|
|
90
|
+
process.stdout.write(`Expiring ≤30 days : ${summary.expiringIn30Days > 0 ? chalk.red(String(summary.expiringIn30Days)) : '0'}\n`);
|
|
91
|
+
process.stdout.write(`Expiring ≤90 days : ${summary.expiringIn90Days > 0 ? chalk.yellow(String(summary.expiringIn90Days)) : '0'}\n\n`);
|
|
92
|
+
process.stdout.write('By risk level:\n');
|
|
93
|
+
for (const r of [...RISK_LEVELS].reverse()) {
|
|
94
|
+
const count = summary.byRisk[r];
|
|
95
|
+
if (count > 0)
|
|
96
|
+
process.stdout.write(` ${r.padEnd(8)} ${count}\n`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
handleError(err);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
report
|
|
105
|
+
.command('findings')
|
|
106
|
+
.description('List secrets at or above a given risk level')
|
|
107
|
+
.option('--severity <level>', 'Minimum risk level: info, low, medium, high, critical', 'medium')
|
|
108
|
+
.action(async (cmdOpts) => {
|
|
109
|
+
try {
|
|
110
|
+
const ctx = await buildContext(program.opts());
|
|
111
|
+
const threshold = parseRiskLevel(cmdOpts.severity);
|
|
112
|
+
const thresholdOrder = riskLevelOrder(threshold);
|
|
113
|
+
const inventory = await ctx.inventoryService.getInventory();
|
|
114
|
+
const findings = allSecrets(inventory).filter((s) => riskLevelOrder(s.riskLevel) >= thresholdOrder);
|
|
115
|
+
outputSecrets(findings, program.opts().output, ctx.tenantId, ctx.environmentName);
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
handleError(err);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
report
|
|
122
|
+
.command('rotation-guide')
|
|
123
|
+
.description('Print a guided rotation checklist for a specific secret')
|
|
124
|
+
.requiredOption('--app-id <client-id>', 'App Registration client ID')
|
|
125
|
+
.requiredOption('--key-id <secret-key-id>', 'Key ID of the secret to rotate')
|
|
126
|
+
.action(async (cmdOpts) => {
|
|
127
|
+
try {
|
|
128
|
+
const ctx = await buildContext(program.opts());
|
|
129
|
+
const now = new Date().toISOString().slice(0, 10);
|
|
130
|
+
const guide = [
|
|
131
|
+
`# Secret Rotation Guide`,
|
|
132
|
+
``,
|
|
133
|
+
`**Tenant:** ${ctx.tenantId}`,
|
|
134
|
+
`**App ID:** ${cmdOpts.appId}`,
|
|
135
|
+
`**Old Key ID:** ${cmdOpts.keyId}`,
|
|
136
|
+
`**Generated:** ${now}`,
|
|
137
|
+
``,
|
|
138
|
+
`## Checklist`,
|
|
139
|
+
``,
|
|
140
|
+
`- [ ] 1. Create a new client secret in the Entra portal for this App Registration.`,
|
|
141
|
+
`- [ ] 2. Store the new secret value securely (OS Credential Manager, Key Vault, or secret manager).`,
|
|
142
|
+
`- [ ] 3. Identify all consumers of this secret (use \`aarm usage analyze --app-id ${cmdOpts.appId}\`).`,
|
|
143
|
+
`- [ ] 4. Update each consumer with the new secret value.`,
|
|
144
|
+
`- [ ] 5. Trigger smoke tests or verify each consumer is working.`,
|
|
145
|
+
`- [ ] 6. Monitor the old key ID for residual usage:`,
|
|
146
|
+
` \`aarm usage rotation-check --app-id ${cmdOpts.appId} --old-key-id ${cmdOpts.keyId} --days 14\``,
|
|
147
|
+
`- [ ] 7. After 7–14 days with zero usage, delete the old secret in Entra.`,
|
|
148
|
+
``,
|
|
149
|
+
`## Notes`,
|
|
150
|
+
``,
|
|
151
|
+
`- Do NOT delete the old secret before step 7 — doing so may break consumers that have not yet been updated.`,
|
|
152
|
+
`- Use \`aarm usage rotation-check\` repeatedly to confirm zero residual usage.`,
|
|
153
|
+
].join('\n');
|
|
154
|
+
if (ctx.isJson) {
|
|
155
|
+
process.stdout.write(envelopeToJson(createResultEnvelope({ appId: cmdOpts.appId, keyId: cmdOpts.keyId, guide }, ctx.tenantId, ctx.environmentName)) + '\n');
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
process.stdout.write(guide + '\n');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
handleError(err);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/commands/report.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { createResultEnvelope, envelopeToJson, } from '@brunsforge/azure-app-registration-monitor';
|
|
2
|
+
import { buildContext, handleError } from '../shared/context.js';
|
|
3
|
+
import { printSecretsTable } from '../output/formatters.js';
|
|
4
|
+
import { HistoryStore } from '../config/HistoryStore.js';
|
|
5
|
+
function allSecrets(inventory) {
|
|
6
|
+
return inventory.flatMap((app) => app.secrets);
|
|
7
|
+
}
|
|
8
|
+
export function registerSecretsCommand(program) {
|
|
9
|
+
const secrets = program
|
|
10
|
+
.command('secrets')
|
|
11
|
+
.description('Query client secrets across App Registrations')
|
|
12
|
+
.action(() => secrets.help());
|
|
13
|
+
secrets
|
|
14
|
+
.command('list')
|
|
15
|
+
.description('List all secrets')
|
|
16
|
+
.action(async () => {
|
|
17
|
+
try {
|
|
18
|
+
const ctx = await buildContext(program.opts());
|
|
19
|
+
const inventory = await ctx.inventoryService.getInventory();
|
|
20
|
+
const flat = allSecrets(inventory);
|
|
21
|
+
const envelope = createResultEnvelope(flat, ctx.tenantId, ctx.environmentName);
|
|
22
|
+
// Auto-save history — side effect, failures are swallowed inside HistoryStore
|
|
23
|
+
const history = new HistoryStore(ctx.configStore.getConfigDir());
|
|
24
|
+
void history.save('secrets', ctx.tenantId, envelope);
|
|
25
|
+
// Update lastSuccessfulScanAt on the tenant profile
|
|
26
|
+
void ctx.configStore.upsertTenant({
|
|
27
|
+
...ctx.tenant,
|
|
28
|
+
lastSuccessfulScanAt: new Date().toISOString(),
|
|
29
|
+
updatedAt: new Date().toISOString(),
|
|
30
|
+
});
|
|
31
|
+
if (ctx.isJson) {
|
|
32
|
+
process.stdout.write(envelopeToJson(envelope) + '\n');
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
printSecretsTable(flat);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
handleError(err);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
secrets
|
|
43
|
+
.command('expiring')
|
|
44
|
+
.description('List secrets expiring within a given window')
|
|
45
|
+
.option('--days <n>', 'Window in days', '30')
|
|
46
|
+
.option('--months <n>', 'Window in months (converted to days × 30)')
|
|
47
|
+
.action(async (cmdOpts) => {
|
|
48
|
+
try {
|
|
49
|
+
const ctx = await buildContext(program.opts());
|
|
50
|
+
const days = cmdOpts.months
|
|
51
|
+
? parseInt(cmdOpts.months, 10) * 30
|
|
52
|
+
: parseInt(cmdOpts.days, 10);
|
|
53
|
+
const inventory = await ctx.inventoryService.getInventory({
|
|
54
|
+
thresholds: { expiringWithinDays: days },
|
|
55
|
+
});
|
|
56
|
+
const flat = allSecrets(inventory).filter((s) => s.status === 'ExpiringSoon');
|
|
57
|
+
if (ctx.isJson) {
|
|
58
|
+
process.stdout.write(envelopeToJson(createResultEnvelope(flat, ctx.tenantId, ctx.environmentName)) + '\n');
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
printSecretsTable(flat);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
handleError(err);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
secrets
|
|
69
|
+
.command('expired')
|
|
70
|
+
.description('List all expired secrets')
|
|
71
|
+
.action(async () => {
|
|
72
|
+
try {
|
|
73
|
+
const ctx = await buildContext(program.opts());
|
|
74
|
+
const inventory = await ctx.inventoryService.getInventory();
|
|
75
|
+
const flat = allSecrets(inventory).filter((s) => s.status === 'Expired');
|
|
76
|
+
if (ctx.isJson) {
|
|
77
|
+
process.stdout.write(envelopeToJson(createResultEnvelope(flat, ctx.tenantId, ctx.environmentName)) + '\n');
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
printSecretsTable(flat);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
handleError(err);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=secrets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/commands/secrets.ts"],"names":[],"mappings":"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"}
|