akv-manager 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/server.js +25 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akv-manager",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "Local web console to browse and manage Azure Key Vaults — secrets, keys, certificates, expiry — using your own az login. No credentials are stored.",
5
5
  "type": "commonjs",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -274,11 +274,20 @@ const VAULT_QUERY =
274
274
  async function listVaultsRaw(force) {
275
275
  if (!force && vaultCache.data && Date.now() - vaultCache.at < VAULT_TTL) return vaultCache.data;
276
276
  const subIds = (await listSubscriptions(force)).map((s) => s.subscriptionId);
277
- const out = await armFetch('/providers/Microsoft.ResourceGraph/resources?api-version=2021-03-01', {
278
- method: 'POST',
279
- body: { subscriptions: subIds, query: VAULT_QUERY, options: { resultFormat: 'objectArray', $top: 1000 } },
280
- });
281
- const data = out.data || [];
277
+ // Resource Graph returns at most 1000 rows per page; follow $skipToken to get them all.
278
+ const data = [];
279
+ let skipToken;
280
+ for (let page = 0; page < 100; page++) { // hard stop at 100k vaults — a safety bound
281
+ const options = { resultFormat: 'objectArray', $top: 1000 };
282
+ if (skipToken) options.$skipToken = skipToken;
283
+ const out = await armFetch('/providers/Microsoft.ResourceGraph/resources?api-version=2021-03-01', {
284
+ method: 'POST',
285
+ body: { subscriptions: subIds, query: VAULT_QUERY, options },
286
+ });
287
+ if (out.data) data.push(...out.data);
288
+ skipToken = out.$skipToken;
289
+ if (!skipToken) break;
290
+ }
282
291
  vaultCache = { at: Date.now(), data };
283
292
  return data;
284
293
  }
@@ -535,6 +544,15 @@ app.patch('/api/vaults/:name/certificates/:cert', (req, res) => {
535
544
  send(res, kvWrite(name, 'PATCH', `/certificates/${cert}?api-version=${KV_API}`, { attributes: attrs }));
536
545
  });
537
546
 
547
+ // Turn a write failure into a precise, actionable reason. A 403 from Key Vault is
548
+ // either a network/firewall block (fix: VPN) or a real data-plane permission gap
549
+ // (fix: grant Set on secrets) — never conflate the two as a bare "forbidden".
550
+ function writeReason(e) {
551
+ if (e.firewall) return 'blocked by the vault firewall — your IP is not allowed (connect to the corporate VPN)';
552
+ if (e.forbidden) return 'no write permission — your identity can read but not Set secrets on this vault';
553
+ return e.message || 'failed';
554
+ }
555
+
538
556
  // Create one or many secrets in a vault. items: [{name, value}], optional common expires.
539
557
  app.post('/api/vaults/:name/secrets-create', async (req, res) => {
540
558
  const { name } = req.params;
@@ -552,7 +570,7 @@ app.post('/api/vaults/:name/secrets-create', async (req, res) => {
552
570
  if (Object.keys(attrs).length) body.attributes = attrs;
553
571
  await kvWrite(name, 'PUT', `/secrets/${it.name}?api-version=${KV_API}`, body);
554
572
  return { ...r, ok: true };
555
- } catch (e) { return { ...r, ok: false, error: e.forbidden ? 'forbidden' : (e.message || 'failed') }; }
573
+ } catch (e) { return { ...r, ok: false, error: writeReason(e), firewall: !!e.firewall, forbidden: !!e.forbidden }; }
556
574
  }, 10);
557
575
  res.json({ ok: true, results });
558
576
  });
@@ -577,7 +595,7 @@ app.post('/api/batch', async (req, res) => {
577
595
  await kvWrite(it.vault, 'PATCH', `/secrets/${it.name}?api-version=${KV_API}`, { attributes: attrs });
578
596
  }
579
597
  return { ...r, ok: true };
580
- } catch (e) { return { ...r, ok: false, error: e.forbidden ? 'forbidden' : (e.message || 'failed') }; }
598
+ } catch (e) { return { ...r, ok: false, error: writeReason(e), firewall: !!e.firewall, forbidden: !!e.forbidden }; }
581
599
  }, 12);
582
600
  res.json({ ok: true, results });
583
601
  });