@prism-d1/cli 1.0.95 → 1.0.97
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/src/commands/bedrock-protection/scan-account.d.ts.map +1 -1
- package/dist/src/commands/bedrock-protection/scan-account.js +12 -10
- package/dist/src/commands/bedrock-protection/scan-account.js.map +1 -1
- package/dist/src/commands/bedrock-protection/scan-org.d.ts +23 -0
- package/dist/src/commands/bedrock-protection/scan-org.d.ts.map +1 -0
- package/dist/src/commands/bedrock-protection/scan-org.js +351 -0
- package/dist/src/commands/bedrock-protection/scan-org.js.map +1 -0
- package/dist/src/utils/audit-bedrock.d.ts +7 -5
- package/dist/src/utils/audit-bedrock.d.ts.map +1 -1
- package/dist/src/utils/audit-bedrock.js +46 -16
- package/dist/src/utils/audit-bedrock.js.map +1 -1
- package/dist/src/utils/audit-iam.d.ts +5 -4
- package/dist/src/utils/audit-iam.d.ts.map +1 -1
- package/dist/src/utils/audit-iam.js +13 -13
- package/dist/src/utils/audit-iam.js.map +1 -1
- package/dist/src/utils/audit-org.d.ts +84 -0
- package/dist/src/utils/audit-org.d.ts.map +1 -0
- package/dist/src/utils/audit-org.js +311 -0
- package/dist/src/utils/audit-org.js.map +1 -0
- package/dist/src/utils/aws.d.ts +33 -2
- package/dist/src/utils/aws.d.ts.map +1 -1
- package/dist/src/utils/aws.js +47 -5
- package/dist/src/utils/aws.js.map +1 -1
- package/dist/src/utils/exec.d.ts +12 -1
- package/dist/src/utils/exec.d.ts.map +1 -1
- package/dist/src/utils/exec.js +23 -2
- package/dist/src/utils/exec.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { aws } from './aws.js';
|
|
2
|
+
function asArray(v) {
|
|
3
|
+
if (!v)
|
|
4
|
+
return [];
|
|
5
|
+
return Array.isArray(v) ? v : [v];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* IAM action glob matching. `bedrock:*` covers
|
|
9
|
+
* `bedrock:CreateProvisionedModelThroughput`; a bare `*` covers everything.
|
|
10
|
+
* Without this, a broad deny would read as no coverage at all.
|
|
11
|
+
*/
|
|
12
|
+
function actionMatches(pattern, action) {
|
|
13
|
+
if (pattern === '*')
|
|
14
|
+
return true;
|
|
15
|
+
const rx = new RegExp('^' + pattern.split('*').map((s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('.*') + '$', 'i');
|
|
16
|
+
return rx.test(action);
|
|
17
|
+
}
|
|
18
|
+
function parseStatements(content) {
|
|
19
|
+
try {
|
|
20
|
+
const doc = JSON.parse(content);
|
|
21
|
+
const st = doc?.Statement;
|
|
22
|
+
if (!st)
|
|
23
|
+
return [];
|
|
24
|
+
return Array.isArray(st) ? st : [st];
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Names of policies containing a Deny that covers any of `actions`. */
|
|
31
|
+
export function policiesDenying(policies, actions) {
|
|
32
|
+
return policies.filter((p) => p.statements.some((s) => {
|
|
33
|
+
if (s.Effect !== 'Deny')
|
|
34
|
+
return false;
|
|
35
|
+
const pats = asArray(s.Action);
|
|
36
|
+
return actions.some((a) => pats.some((pat) => actionMatches(pat, a)));
|
|
37
|
+
})).map((p) => p.name);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Names of policies whose Deny is region-conditioned in a way that would
|
|
41
|
+
* constrain Bedrock.
|
|
42
|
+
*
|
|
43
|
+
* The common shape is a broad `Deny *` with
|
|
44
|
+
* `StringNotEquals: { "aws:RequestedRegion": [...] }` -- everything outside the
|
|
45
|
+
* listed regions is refused. A Bedrock-specific variant denies `bedrock:*` under
|
|
46
|
+
* the same condition. Both count; a region condition that cannot reach Bedrock
|
|
47
|
+
* (e.g. `Action: s3:*`) does not.
|
|
48
|
+
*/
|
|
49
|
+
export function policiesRestrictingBedrockRegion(policies) {
|
|
50
|
+
return policies.filter((p) => p.statements.some((s) => {
|
|
51
|
+
if (s.Effect !== 'Deny')
|
|
52
|
+
return false;
|
|
53
|
+
const cond = s.Condition ?? {};
|
|
54
|
+
const mentionsRegion = Object.values(cond).some((kv) => Object.keys(kv).some((k) => k.toLowerCase() === 'aws:requestedregion'));
|
|
55
|
+
if (!mentionsRegion)
|
|
56
|
+
return false;
|
|
57
|
+
const pats = asArray(s.Action);
|
|
58
|
+
// Must be able to reach a Bedrock call to be relevant here.
|
|
59
|
+
return pats.some((pat) => actionMatches(pat, 'bedrock:InvokeModel'));
|
|
60
|
+
})).map((p) => p.name);
|
|
61
|
+
}
|
|
62
|
+
const ENFORCEMENT_CAVEAT = 'Detected structurally, not proven: inheritance, NotAction and principal-tag exemptions can neutralise a policy that reads correctly. Confirm with a live call from a representative role.';
|
|
63
|
+
/**
|
|
64
|
+
* Expand each audited target to itself plus every ancestor up to the root.
|
|
65
|
+
*
|
|
66
|
+
* SCPs INHERIT downward: a policy attached to the root constrains every account
|
|
67
|
+
* in every OU beneath it. Judging attachment against the audited OU alone
|
|
68
|
+
* therefore ignores the most common place guardrails actually live, and would
|
|
69
|
+
* report a root-attached Bedrock region deny as absent -- a false FAIL on the
|
|
70
|
+
* highest-leverage control in this file. Verified against a live org where two
|
|
71
|
+
* of four customer SCPs sat at the root and were invisible to the OU-only view.
|
|
72
|
+
*/
|
|
73
|
+
export function expandToAncestors(target, scopeTargetIds) {
|
|
74
|
+
const ids = new Set(scopeTargetIds);
|
|
75
|
+
const errors = [];
|
|
76
|
+
for (const start of scopeTargetIds) {
|
|
77
|
+
let child = start;
|
|
78
|
+
// Bounded rather than while(true): OU nesting is capped at 5 levels, and a
|
|
79
|
+
// cycle here would otherwise hang the audit.
|
|
80
|
+
for (let depth = 0; depth < 10; depth++) {
|
|
81
|
+
if (/^r-/.test(child))
|
|
82
|
+
break;
|
|
83
|
+
const parents = aws(['organizations', 'list-parents', '--child-id', child], target);
|
|
84
|
+
if (!parents.ok) {
|
|
85
|
+
errors.push(`${child}: could not resolve parents (${parents.errorCode || 'unknown error'}), so inherited SCPs may be missed`);
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
const p = (parents.json?.Parents ?? [])[0];
|
|
89
|
+
if (!p?.Id)
|
|
90
|
+
break;
|
|
91
|
+
ids.add(p.Id);
|
|
92
|
+
child = p.Id;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return { ids: [...ids], errors };
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Managed Config rule identifiers that continuously assert what the IAM checks
|
|
99
|
+
* in this audit only sample. Keyed by the check they correspond to, so the
|
|
100
|
+
* output can name the gap in terms the reader already recognises.
|
|
101
|
+
*/
|
|
102
|
+
const IAM_CONFIG_RULES = [
|
|
103
|
+
['ACCESS_KEYS_ROTATED', 'access-key-age'],
|
|
104
|
+
['IAM_USER_UNUSED_CREDENTIALS_CHECK', 'unused-credentials'],
|
|
105
|
+
['ROOT_ACCOUNT_MFA_ENABLED', 'root-mfa'],
|
|
106
|
+
['MFA_ENABLED_FOR_IAM_CONSOLE_ACCESS', 'console-user-no-mfa'],
|
|
107
|
+
['IAM_PASSWORD_POLICY', 'password-policy'],
|
|
108
|
+
['IAM_USER_NO_POLICIES_CHECK', 'user-admin-policy'],
|
|
109
|
+
];
|
|
110
|
+
/** The four SCP checks whose verdict depends on knowing the audited scope. */
|
|
111
|
+
const SCOPE_DEPENDENT_SCP_CHECKS = [
|
|
112
|
+
['scp-attached', 'SCPs attached to the audited scope', 'HIGH'],
|
|
113
|
+
['scp-detection-tamper', 'SCP preventing tampering with detection', 'HIGH'],
|
|
114
|
+
['scp-bedrock-region', 'SCP restricting which regions Bedrock can be used in', 'MEDIUM'],
|
|
115
|
+
['scp-bedrock-provisioned-throughput', 'SCP denying Provisioned Throughput creation', 'MEDIUM'],
|
|
116
|
+
];
|
|
117
|
+
function scopeDependentIndeterminate(detail, remediation) {
|
|
118
|
+
return SCOPE_DEPENDENT_SCP_CHECKS.map(([id, title, severity]) => ({
|
|
119
|
+
id, category: 'preventive', title,
|
|
120
|
+
status: 'INDETERMINATE', severity,
|
|
121
|
+
detail, remediation,
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* @param scopeTargetIds Root and OU ids in scope, so attachment is judged
|
|
126
|
+
* against what is actually being audited rather than the org at large.
|
|
127
|
+
* `null` means the caller could not resolve the scope -- every
|
|
128
|
+
* attachment-dependent check is then INDETERMINATE, because an empty
|
|
129
|
+
* scope would otherwise make all four FAIL and present a permissions
|
|
130
|
+
* problem as four real absences.
|
|
131
|
+
*/
|
|
132
|
+
export function auditServiceControlPolicies(target, scopeTargetIds, scopeError) {
|
|
133
|
+
const out = [];
|
|
134
|
+
// SCPs must be enabled on the root before any can take effect. This gates the
|
|
135
|
+
// rest, the same way Cost Explorer gates the anomaly check.
|
|
136
|
+
const roots = aws(['organizations', 'list-roots'], target);
|
|
137
|
+
if (!roots.ok) {
|
|
138
|
+
const why = `Could not read organization roots (${roots.errorCode || 'unknown error'}).`;
|
|
139
|
+
const fix = 'Grant organizations:ListRoots and re-run.';
|
|
140
|
+
// Emit EVERY SCP check, not just this one. Returning a single finding here
|
|
141
|
+
// would silently shrink the check count, so a reader comparing runs would
|
|
142
|
+
// see four checks disappear rather than four checks it could not answer.
|
|
143
|
+
return [
|
|
144
|
+
{
|
|
145
|
+
id: 'scp-enabled', category: 'preventive', title: 'Service Control Policies enabled',
|
|
146
|
+
status: 'INDETERMINATE', severity: 'HIGH', detail: why, remediation: fix,
|
|
147
|
+
},
|
|
148
|
+
...scopeDependentIndeterminate(why, fix),
|
|
149
|
+
];
|
|
150
|
+
}
|
|
151
|
+
const rootList = roots.json?.Roots ?? [];
|
|
152
|
+
const scpEnabled = rootList.some((r) => (r.PolicyTypes ?? [])
|
|
153
|
+
.some((p) => p.Type === 'SERVICE_CONTROL_POLICY' && p.Status === 'ENABLED'));
|
|
154
|
+
out.push({
|
|
155
|
+
id: 'scp-enabled', category: 'preventive', title: 'Service Control Policies enabled',
|
|
156
|
+
status: scpEnabled ? 'PASS' : 'FAIL', severity: 'HIGH',
|
|
157
|
+
detail: scpEnabled
|
|
158
|
+
? 'SERVICE_CONTROL_POLICY is enabled on the organization root.'
|
|
159
|
+
: 'SERVICE_CONTROL_POLICY is not enabled, so no preventive guardrail can exist at any level. Every control in this organization is therefore detective — it can tell you a stolen key was used, never refuse the call.',
|
|
160
|
+
remediation: scpEnabled ? undefined : 'Enable the SERVICE_CONTROL_POLICY type on the root (Organizations → Policies). Enabling it alone changes nothing: FullAWSAccess stays attached and no action is newly denied.',
|
|
161
|
+
});
|
|
162
|
+
if (!scpEnabled) {
|
|
163
|
+
out.push(...scopeDependentIndeterminate('Cannot be evaluated while Service Control Policies are disabled.'));
|
|
164
|
+
return out;
|
|
165
|
+
}
|
|
166
|
+
// Scope unknown: an empty target list would make all four attachment-dependent
|
|
167
|
+
// checks FAIL, dressing a permissions problem up as four real absences.
|
|
168
|
+
if (scopeTargetIds === null) {
|
|
169
|
+
out.push(...scopeDependentIndeterminate(`The audited scope could not be resolved${scopeError ? ` (${scopeError})` : ''}, so it is unknown which SCPs apply to it.`, 'Grant organizations:ListRoots and re-run.'));
|
|
170
|
+
return out;
|
|
171
|
+
}
|
|
172
|
+
// Include every ancestor: SCPs attached above the audited OU still apply to it.
|
|
173
|
+
const { ids: effectiveTargets, errors: ancestorErrors } = expandToAncestors(target, scopeTargetIds);
|
|
174
|
+
const list = aws(['organizations', 'list-policies', '--filter', 'SERVICE_CONTROL_POLICY'], target);
|
|
175
|
+
if (!list.ok) {
|
|
176
|
+
out.push(...scopeDependentIndeterminate(`Could not list SCPs (${list.errorCode || 'unknown error'}).`, 'Grant organizations:ListPolicies, DescribePolicy and ListTargetsForPolicy, then re-run.'));
|
|
177
|
+
return out;
|
|
178
|
+
}
|
|
179
|
+
// FullAWSAccess is attached everywhere by default and allows everything, so it
|
|
180
|
+
// is never evidence of a guardrail. Excluding it keeps the attachment check
|
|
181
|
+
// from passing on AWS's own default.
|
|
182
|
+
const customer = (list.json?.Policies ?? []).filter((p) => p.AwsManaged !== true);
|
|
183
|
+
const policies = [];
|
|
184
|
+
let unreadable = 0;
|
|
185
|
+
for (const p of customer) {
|
|
186
|
+
const desc = aws(['organizations', 'describe-policy', '--policy-id', p.Id], target);
|
|
187
|
+
const targets = aws(['organizations', 'list-targets-for-policy', '--policy-id', p.Id], target);
|
|
188
|
+
if (!desc.ok || !targets.ok) {
|
|
189
|
+
unreadable++;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
policies.push({
|
|
193
|
+
name: p.Name ?? p.Id,
|
|
194
|
+
id: p.Id,
|
|
195
|
+
statements: parseStatements(desc.json?.Policy?.Content ?? ''),
|
|
196
|
+
targets: (targets.json?.Targets ?? []).map((t) => t.TargetId),
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
// An SCP attached nowhere in the audited scope constrains nothing there --
|
|
200
|
+
// the same shape as a budget with no subscriber, and just as easy to mistake
|
|
201
|
+
// for protection.
|
|
202
|
+
const inScope = policies.filter((p) => p.targets.some((t) => effectiveTargets.includes(t)));
|
|
203
|
+
out.push({
|
|
204
|
+
id: 'scp-attached', category: 'preventive', title: 'SCPs attached to the audited scope',
|
|
205
|
+
status: inScope.length ? 'PASS' : 'FAIL', severity: 'HIGH',
|
|
206
|
+
detail: inScope.length
|
|
207
|
+
? `${inScope.length} customer-managed SCP(s) apply to the audited scope (including inheritance from ${effectiveTargets.length - scopeTargetIds.length} ancestor target(s)): ${inScope.map((p) => p.name).join(', ')}.`
|
|
208
|
+
: customer.length === 0
|
|
209
|
+
? 'No customer-managed SCPs exist. Only AWS\'s default FullAWSAccess is attached, which allows everything.'
|
|
210
|
+
: `${customer.length} customer-managed SCP(s) exist but none is attached to the audited target(s) or any ancestor, so none constrains the accounts in scope.`,
|
|
211
|
+
remediation: inScope.length ? undefined : 'Attach the relevant SCP to the root or the OUs being audited. An unattached policy is inert.',
|
|
212
|
+
});
|
|
213
|
+
// Region restriction: the highest-leverage preventive control for LLMjacking.
|
|
214
|
+
const regionScoped = policiesRestrictingBedrockRegion(inScope);
|
|
215
|
+
out.push({
|
|
216
|
+
id: 'scp-bedrock-region', category: 'preventive', title: 'SCP restricting which regions Bedrock can be used in',
|
|
217
|
+
status: regionScoped.length ? 'PASS' : 'FAIL', severity: 'MEDIUM',
|
|
218
|
+
detail: regionScoped.length
|
|
219
|
+
? `${regionScoped.join(', ')} denies Bedrock-reaching actions outside an allowed region set. ${ENFORCEMENT_CAVEAT}`
|
|
220
|
+
: 'No in-scope SCP constrains Bedrock by region. A stolen credential can call InvokeModel in every enabled region, so spend is not bounded by the regions you actually operate in — and per-region CloudWatch alarms and Provisioned Throughput checks all miss activity elsewhere.',
|
|
221
|
+
remediation: regionScoped.length ? undefined : 'Add a Deny on `*` (or `bedrock:*`) with StringNotEquals on aws:RequestedRegion listing only the regions you use. This is the cheapest large reduction in blast radius available, and it costs nothing to run.',
|
|
222
|
+
});
|
|
223
|
+
const ptDenied = policiesDenying(inScope, ['bedrock:CreateProvisionedModelThroughput']);
|
|
224
|
+
out.push({
|
|
225
|
+
id: 'scp-bedrock-provisioned-throughput', category: 'preventive', title: 'SCP denying Provisioned Throughput creation',
|
|
226
|
+
status: ptDenied.length ? 'PASS' : 'FAIL', severity: 'MEDIUM',
|
|
227
|
+
detail: ptDenied.length
|
|
228
|
+
? `${ptDenied.join(', ')} denies bedrock:CreateProvisionedModelThroughput. ${ENFORCEMENT_CAVEAT}`
|
|
229
|
+
: 'No in-scope SCP denies bedrock:CreateProvisionedModelThroughput. It is the largest single commitment available in Bedrock — billed monthly whether used or not — and one API call creates it.',
|
|
230
|
+
remediation: ptDenied.length ? undefined : 'Deny bedrock:CreateProvisionedModelThroughput org-wide and exempt only the accounts that genuinely need it. Most organizations need it nowhere.',
|
|
231
|
+
});
|
|
232
|
+
// Protecting the detection layer the rest of this audit depends on.
|
|
233
|
+
const tamperActions = ['cloudtrail:StopLogging', 'config:DeleteConfigRule', 'guardduty:DeleteDetector'];
|
|
234
|
+
const tamperDenied = policiesDenying(inScope, tamperActions);
|
|
235
|
+
out.push({
|
|
236
|
+
id: 'scp-detection-tamper', category: 'preventive', title: 'SCP preventing tampering with detection',
|
|
237
|
+
status: tamperDenied.length ? 'PASS' : 'FAIL', severity: 'HIGH',
|
|
238
|
+
detail: tamperDenied.length
|
|
239
|
+
? `${tamperDenied.join(', ')} denies at least one of CloudTrail/Config/GuardDuty teardown. ${ENFORCEMENT_CAVEAT}`
|
|
240
|
+
: 'No in-scope SCP prevents stopping CloudTrail, deleting Config rules or disabling GuardDuty. An attacker who reaches an admin credential can therefore remove the evidence trail before running up spend, which defeats every detective control this audit recommends.',
|
|
241
|
+
remediation: tamperDenied.length ? undefined : 'Deny cloudtrail:StopLogging/DeleteTrail, config:Delete*, guardduty:DeleteDetector org-wide. Nothing legitimate needs them from a member account.',
|
|
242
|
+
});
|
|
243
|
+
for (const e of ancestorErrors) {
|
|
244
|
+
out.push({
|
|
245
|
+
id: 'scp-inheritance-resolved', category: 'preventive', title: 'SCP inheritance fully resolved',
|
|
246
|
+
status: 'INDETERMINATE', severity: 'MEDIUM',
|
|
247
|
+
detail: e,
|
|
248
|
+
remediation: 'Grant organizations:ListParents and re-run; without it an inherited guardrail can read as absent.',
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
if (unreadable > 0) {
|
|
252
|
+
out.push({
|
|
253
|
+
id: 'scp-readable', category: 'preventive', title: 'All SCPs readable',
|
|
254
|
+
status: 'INDETERMINATE', severity: 'MEDIUM',
|
|
255
|
+
detail: `${unreadable} of ${customer.length} customer-managed SCP(s) could not be read, so the SCP findings above are based on a partial view.`,
|
|
256
|
+
remediation: 'Grant organizations:DescribePolicy and ListTargetsForPolicy, then re-run.',
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
return out;
|
|
260
|
+
}
|
|
261
|
+
export function auditConfigRules(target) {
|
|
262
|
+
const rules = aws(['configservice', 'describe-organization-config-rules'], target);
|
|
263
|
+
if (!rules.ok) {
|
|
264
|
+
// Config not being enabled for the org is a legitimate state, and distinct
|
|
265
|
+
// from a permissions problem. Both are reported, neither as a pass.
|
|
266
|
+
const notEnabled = /OrganizationAccessDenied|NoAvailableOrganization|AccessDenied/i.test(rules.errorCode || rules.raw);
|
|
267
|
+
return [{
|
|
268
|
+
id: 'config-org-rules', category: 'detective', title: 'Organization Config rules deployed',
|
|
269
|
+
status: 'INDETERMINATE', severity: 'MEDIUM',
|
|
270
|
+
detail: notEnabled
|
|
271
|
+
? `Could not read organization Config rules (${rules.errorCode || 'access denied'}). Either AWS Config is not enabled for the organization, or trusted access / delegated administration is not configured.`
|
|
272
|
+
: `Could not read organization Config rules (${rules.errorCode || 'unknown error'}).`,
|
|
273
|
+
remediation: 'Enable trusted access for config.amazonaws.com in Organizations, or grant config:DescribeOrganizationConfigRules, then re-run.',
|
|
274
|
+
}, {
|
|
275
|
+
id: 'config-iam-coverage', category: 'detective', title: 'Continuous coverage of the IAM hygiene checks',
|
|
276
|
+
status: 'INDETERMINATE', severity: 'MEDIUM',
|
|
277
|
+
detail: 'Cannot be evaluated without the organization Config rule list.',
|
|
278
|
+
}];
|
|
279
|
+
}
|
|
280
|
+
const list = rules.json?.OrganizationConfigRules ?? [];
|
|
281
|
+
const out = [];
|
|
282
|
+
out.push({
|
|
283
|
+
id: 'config-org-rules', category: 'detective', title: 'Organization Config rules deployed',
|
|
284
|
+
status: list.length ? 'PASS' : 'FAIL', severity: 'MEDIUM',
|
|
285
|
+
detail: list.length
|
|
286
|
+
? `${list.length} organization Config rule(s) deployed.`
|
|
287
|
+
: 'No organization Config rules. This audit is a point-in-time sample; without continuous rules, a key created the day after a run goes unnoticed until the next one.',
|
|
288
|
+
remediation: list.length ? undefined : 'Deploy an Organization Conformance Pack with the IAM managed rules. They evaluate on every configuration change rather than when someone remembers to audit.',
|
|
289
|
+
});
|
|
290
|
+
// Which of this audit's own IAM checks have a continuous equivalent deployed.
|
|
291
|
+
const deployed = new Set();
|
|
292
|
+
for (const r of list) {
|
|
293
|
+
const id = r?.OrganizationManagedRuleMetadata?.RuleIdentifier;
|
|
294
|
+
if (id)
|
|
295
|
+
deployed.add(String(id).toUpperCase());
|
|
296
|
+
}
|
|
297
|
+
const missing = IAM_CONFIG_RULES.filter(([ruleId]) => !deployed.has(ruleId));
|
|
298
|
+
const present = IAM_CONFIG_RULES.filter(([ruleId]) => deployed.has(ruleId));
|
|
299
|
+
out.push({
|
|
300
|
+
id: 'config-iam-coverage', category: 'detective', title: 'Continuous coverage of the IAM hygiene checks',
|
|
301
|
+
status: missing.length === 0 ? 'PASS' : list.length === 0 ? 'FAIL' : 'FAIL',
|
|
302
|
+
severity: 'MEDIUM',
|
|
303
|
+
detail: missing.length === 0
|
|
304
|
+
? `All ${IAM_CONFIG_RULES.length} IAM hygiene checks have a continuously-evaluating Config rule: ${present.map(([r]) => r).join(', ')}.`
|
|
305
|
+
: `${missing.length} of ${IAM_CONFIG_RULES.length} IAM checks in this audit have no continuous equivalent. Sampled here but not monitored: ${missing.map(([, check]) => check).join(', ')}.${present.length ? ` Covered: ${present.map(([, check]) => check).join(', ')}.` : ''}`,
|
|
306
|
+
remediation: missing.length === 0 ? undefined
|
|
307
|
+
: `Deploy the matching managed rules org-wide: ${missing.map(([r]) => r).join(', ')}. Note the ordering pitfall — a Config rule evaluates nothing until the configuration recorder is running in the target accounts.`,
|
|
308
|
+
});
|
|
309
|
+
return out;
|
|
310
|
+
}
|
|
311
|
+
//# sourceMappingURL=audit-org.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-org.js","sourceRoot":"","sources":["../../../src/utils/audit-org.ts"],"names":[],"mappings":"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"}
|
package/dist/src/utils/aws.d.ts
CHANGED
|
@@ -5,6 +5,23 @@
|
|
|
5
5
|
* exposed to shell interpretation even though callers pass a user-supplied
|
|
6
6
|
* --profile and --region.
|
|
7
7
|
*/
|
|
8
|
+
/**
|
|
9
|
+
* Where an AWS call should be made: a named CLI profile, or temporary
|
|
10
|
+
* credentials from an assumed role.
|
|
11
|
+
*
|
|
12
|
+
* Both are carried in one object so every check function is agnostic about how
|
|
13
|
+
* the caller authenticated. `scan-account` supplies a profile; `scan-org`
|
|
14
|
+
* supplies assumed-role credentials per member account. Credentials travel in
|
|
15
|
+
* the child process environment rather than being written to `~/.aws/config`,
|
|
16
|
+
* so nothing is persisted and the assumed role cannot leak into a later call.
|
|
17
|
+
*/
|
|
18
|
+
export interface AwsTarget {
|
|
19
|
+
/** AWS CLI profile name. */
|
|
20
|
+
profile?: string;
|
|
21
|
+
/** Temporary credentials injected into the child environment. A key mapped
|
|
22
|
+
* to undefined is removed from it. */
|
|
23
|
+
env?: Record<string, string | undefined>;
|
|
24
|
+
}
|
|
8
25
|
export interface AwsResult {
|
|
9
26
|
ok: boolean;
|
|
10
27
|
json: any;
|
|
@@ -21,7 +38,21 @@ export interface AwsResult {
|
|
|
21
38
|
* precisely the accounts that most need auditing. Empty output is normalised to
|
|
22
39
|
* {} here and interpreted by each individual check.
|
|
23
40
|
*/
|
|
24
|
-
export declare function aws(args: string[],
|
|
41
|
+
export declare function aws(args: string[], target?: AwsTarget): AwsResult;
|
|
42
|
+
/**
|
|
43
|
+
* Assume a role and return credentials as an env fragment, or an error string.
|
|
44
|
+
*
|
|
45
|
+
* Deliberately returns the failure rather than throwing: at org scale a role
|
|
46
|
+
* that cannot be assumed is an expected condition (an account onboarded without
|
|
47
|
+
* the role, an SCP denying it), and the caller must surface that account as
|
|
48
|
+
* INDETERMINATE rather than dropping it. A silently skipped account is exactly
|
|
49
|
+
* how an org audit ends up reporting on a subset while looking complete.
|
|
50
|
+
*/
|
|
51
|
+
export declare function assumeRole(accountId: string, roleName: string, sessionName: string, durationSeconds: number, from?: AwsTarget, region?: string): {
|
|
52
|
+
env: Record<string, string | undefined>;
|
|
53
|
+
} | {
|
|
54
|
+
error: string;
|
|
55
|
+
};
|
|
25
56
|
/**
|
|
26
57
|
* Resolve the caller's account id, or print guidance and return null.
|
|
27
58
|
*
|
|
@@ -29,7 +60,7 @@ export declare function aws(args: string[], profile?: string): AwsResult;
|
|
|
29
60
|
* --profile fails in the CLI's own config layer and never reaches the service,
|
|
30
61
|
* so fall back to its stderr rather than reporting "unknown error".
|
|
31
62
|
*/
|
|
32
|
-
export declare function resolveAccount(
|
|
63
|
+
export declare function resolveAccount(target?: AwsTarget): string | null;
|
|
33
64
|
export declare function daysSince(iso: string): number | null;
|
|
34
65
|
export declare const PROFILE_OPTION: {
|
|
35
66
|
flags: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aws.d.ts","sourceRoot":"","sources":["../../../src/utils/aws.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,EAAE,GAAG,CAAC;IACV,+EAA+E;IAC/E,SAAS,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;GAOG;AACH,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,
|
|
1
|
+
{"version":3,"file":"aws.d.ts","sourceRoot":"","sources":["../../../src/utils/aws.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH;;;;;;;;;GASG;AACH,MAAM,WAAW,SAAS;IACxB,4BAA4B;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;2CACuC;IACvC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,EAAE,GAAG,CAAC;IACV,+EAA+E;IAC/E,SAAS,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;GAOG;AACH,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,EAAE,SAAS,GAAG,SAAS,CAejE;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CACxB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,eAAe,EAAE,MAAM,EACvB,IAAI,CAAC,EAAE,SAAS,EAChB,MAAM,CAAC,EAAE,MAAM,GACd;IAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAiCjE;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,MAAM,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,IAAI,CAShE;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIpD;AAED,eAAO,MAAM,cAAc;;;CAG1B,CAAC"}
|
package/dist/src/utils/aws.js
CHANGED
|
@@ -14,9 +14,9 @@ import { run } from './exec.js';
|
|
|
14
14
|
* precisely the accounts that most need auditing. Empty output is normalised to
|
|
15
15
|
* {} here and interpreted by each individual check.
|
|
16
16
|
*/
|
|
17
|
-
export function aws(args,
|
|
18
|
-
const argv = profile ? [...args, '--profile', profile] : args;
|
|
19
|
-
const res = run('aws', [...argv, '--output', 'json']);
|
|
17
|
+
export function aws(args, target) {
|
|
18
|
+
const argv = target?.profile ? [...args, '--profile', target.profile] : args;
|
|
19
|
+
const res = run('aws', [...argv, '--output', 'json'], target?.env);
|
|
20
20
|
const body = (res.stdout || '').trim();
|
|
21
21
|
if (!res.ok) {
|
|
22
22
|
const m = /An error occurred \(([A-Za-z0-9.]+)\)/.exec(res.stderr || '');
|
|
@@ -31,6 +31,48 @@ export function aws(args, profile) {
|
|
|
31
31
|
return { ok: false, json: null, errorCode: 'UnparseableResponse', raw: body };
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Assume a role and return credentials as an env fragment, or an error string.
|
|
36
|
+
*
|
|
37
|
+
* Deliberately returns the failure rather than throwing: at org scale a role
|
|
38
|
+
* that cannot be assumed is an expected condition (an account onboarded without
|
|
39
|
+
* the role, an SCP denying it), and the caller must surface that account as
|
|
40
|
+
* INDETERMINATE rather than dropping it. A silently skipped account is exactly
|
|
41
|
+
* how an org audit ends up reporting on a subset while looking complete.
|
|
42
|
+
*/
|
|
43
|
+
export function assumeRole(accountId, roleName, sessionName, durationSeconds, from, region) {
|
|
44
|
+
const res = aws([
|
|
45
|
+
'sts', 'assume-role',
|
|
46
|
+
'--role-arn', `arn:aws:iam::${accountId}:role/${roleName}`,
|
|
47
|
+
'--role-session-name', sessionName,
|
|
48
|
+
'--duration-seconds', String(durationSeconds),
|
|
49
|
+
], from);
|
|
50
|
+
if (!res.ok) {
|
|
51
|
+
const reason = res.errorCode
|
|
52
|
+
|| (res.raw.split('\n').map((l) => l.trim()).filter(Boolean).pop() ?? 'unknown error');
|
|
53
|
+
return { error: reason };
|
|
54
|
+
}
|
|
55
|
+
const c = res.json?.Credentials;
|
|
56
|
+
if (!c?.AccessKeyId || !c?.SecretAccessKey || !c?.SessionToken) {
|
|
57
|
+
return { error: 'assume-role returned no usable credentials' };
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
env: {
|
|
61
|
+
AWS_ACCESS_KEY_ID: c.AccessKeyId,
|
|
62
|
+
AWS_SECRET_ACCESS_KEY: c.SecretAccessKey,
|
|
63
|
+
AWS_SESSION_TOKEN: c.SessionToken,
|
|
64
|
+
// REMOVED, not blanked. A profile left in the ambient environment takes
|
|
65
|
+
// precedence over the injected keys and would silently audit the WRONG
|
|
66
|
+
// account -- the worst failure available here, since every finding would
|
|
67
|
+
// be misattributed. Blanking instead of removing breaks the CLI outright.
|
|
68
|
+
AWS_PROFILE: undefined,
|
|
69
|
+
AWS_DEFAULT_PROFILE: undefined,
|
|
70
|
+
// The unset profile also took its region with it, so pin one explicitly
|
|
71
|
+
// rather than depending on whatever the CLI can still resolve.
|
|
72
|
+
...(region ? { AWS_REGION: region, AWS_DEFAULT_REGION: region } : {}),
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
34
76
|
/**
|
|
35
77
|
* Resolve the caller's account id, or print guidance and return null.
|
|
36
78
|
*
|
|
@@ -38,8 +80,8 @@ export function aws(args, profile) {
|
|
|
38
80
|
* --profile fails in the CLI's own config layer and never reaches the service,
|
|
39
81
|
* so fall back to its stderr rather than reporting "unknown error".
|
|
40
82
|
*/
|
|
41
|
-
export function resolveAccount(
|
|
42
|
-
const who = aws(['sts', 'get-caller-identity'],
|
|
83
|
+
export function resolveAccount(target) {
|
|
84
|
+
const who = aws(['sts', 'get-caller-identity'], target);
|
|
43
85
|
if (who.ok)
|
|
44
86
|
return who.json?.Account ?? null;
|
|
45
87
|
const reason = who.errorCode
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aws.js","sourceRoot":"","sources":["../../../src/utils/aws.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"aws.js","sourceRoot":"","sources":["../../../src/utils/aws.ts"],"names":[],"mappings":"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"}
|
package/dist/src/utils/exec.d.ts
CHANGED
|
@@ -42,10 +42,21 @@ export interface RunResult {
|
|
|
42
42
|
* latter would arrive as a single argument and the command would fail, which
|
|
43
43
|
* is a loud failure rather than a silent injection.
|
|
44
44
|
*
|
|
45
|
+
* `extraEnv` is merged over the inherited environment. It exists so a caller
|
|
46
|
+
* can hand a child process temporary STS credentials without writing them to
|
|
47
|
+
* disk or mutating this process's own environment -- the latter would leak the
|
|
48
|
+
* assumed role into every later call in the same run.
|
|
49
|
+
*
|
|
50
|
+
* A key mapped to `undefined` is REMOVED rather than set. Blanking is not
|
|
51
|
+
* equivalent: `AWS_PROFILE=''` makes the AWS CLI look for a profile literally
|
|
52
|
+
* named `()` and fail with "The config profile () could not be found", so
|
|
53
|
+
* injected credentials are ignored and every call errors. Measured, not
|
|
54
|
+
* theoretical.
|
|
55
|
+
*
|
|
45
56
|
* Never throws: a non-zero exit, a missing binary, and a signal kill all come
|
|
46
57
|
* back as `ok: false`. Callers branch on `ok` and surface `stderr`.
|
|
47
58
|
*/
|
|
48
|
-
export declare function run(file: string, args: string[]): RunResult;
|
|
59
|
+
export declare function run(file: string, args: string[], extraEnv?: Record<string, string | undefined>): RunResult;
|
|
49
60
|
/**
|
|
50
61
|
* Like run(), but inherits stdio so the child can drive the terminal.
|
|
51
62
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exec.d.ts","sourceRoot":"","sources":["../../../src/utils/exec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAIH,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED
|
|
1
|
+
{"version":3,"file":"exec.d.ts","sourceRoot":"","sources":["../../../src/utils/exec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAIH,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,SAAS,CAmB1G;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAOpE"}
|
package/dist/src/utils/exec.js
CHANGED
|
@@ -38,15 +38,36 @@ import { execFileSync } from 'node:child_process';
|
|
|
38
38
|
* latter would arrive as a single argument and the command would fail, which
|
|
39
39
|
* is a loud failure rather than a silent injection.
|
|
40
40
|
*
|
|
41
|
+
* `extraEnv` is merged over the inherited environment. It exists so a caller
|
|
42
|
+
* can hand a child process temporary STS credentials without writing them to
|
|
43
|
+
* disk or mutating this process's own environment -- the latter would leak the
|
|
44
|
+
* assumed role into every later call in the same run.
|
|
45
|
+
*
|
|
46
|
+
* A key mapped to `undefined` is REMOVED rather than set. Blanking is not
|
|
47
|
+
* equivalent: `AWS_PROFILE=''` makes the AWS CLI look for a profile literally
|
|
48
|
+
* named `()` and fail with "The config profile () could not be found", so
|
|
49
|
+
* injected credentials are ignored and every call errors. Measured, not
|
|
50
|
+
* theoretical.
|
|
51
|
+
*
|
|
41
52
|
* Never throws: a non-zero exit, a missing binary, and a signal kill all come
|
|
42
53
|
* back as `ok: false`. Callers branch on `ok` and surface `stderr`.
|
|
43
54
|
*/
|
|
44
|
-
export function run(file, args) {
|
|
55
|
+
export function run(file, args, extraEnv) {
|
|
56
|
+
let env = process.env;
|
|
57
|
+
if (extraEnv) {
|
|
58
|
+
env = { ...process.env };
|
|
59
|
+
for (const [k, v] of Object.entries(extraEnv)) {
|
|
60
|
+
if (v === undefined)
|
|
61
|
+
delete env[k];
|
|
62
|
+
else
|
|
63
|
+
env[k] = v;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
45
66
|
try {
|
|
46
67
|
const stdout = execFileSync(file, args, {
|
|
47
68
|
encoding: 'utf8',
|
|
48
69
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
49
|
-
env
|
|
70
|
+
env,
|
|
50
71
|
}).trim();
|
|
51
72
|
return { ok: true, stdout, stderr: '' };
|
|
52
73
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exec.js","sourceRoot":"","sources":["../../../src/utils/exec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAQlD
|
|
1
|
+
{"version":3,"file":"exec.js","sourceRoot":"","sources":["../../../src/utils/exec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAQlD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,GAAG,CAAC,IAAY,EAAE,IAAc,EAAE,QAA6C;IAC7F,IAAI,GAAG,GAAsB,OAAO,CAAC,GAAG,CAAC;IACzC,IAAI,QAAQ,EAAE,CAAC;QACb,GAAG,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;QACzB,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC9C,IAAI,CAAC,KAAK,SAAS;gBAAE,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC;;gBAC9B,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IACD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE;YACtC,QAAQ,EAAE,MAAM;YAChB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;YAC/B,GAAG;SACJ,CAAC,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAC1C,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;IACrF,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,IAAc;IACzD,IAAI,CAAC;QACH,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QACjE,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|