DEV Community

WindwhisperBoren33
WindwhisperBoren33

Posted on

Sizing Credential Blast Radius Across a Live Key Inventory (and the Quarterly Review)

Pick the least complex thing that still produces dated evidence: a scheduled job that reads the live key inventory, resolves every credential to an identity, and archives the rendered result. That is a quarterly access review. The spreadsheet somebody assembles the week before the SOC 2 fieldwork is a one-time event wearing the costume of a control, and no auditor can reproduce it.

The system I'm costing here is a marketplace metering pipeline — every seller's API usage lands in a monthly invoice, so a credential is an access path and a billing attribution at once. That changes which question matters. Not "who has a key", but how far one key reaches before the invoice stops being defensible.

Blast radius is the axis. Retention is the bill.

What the quarterly review actually costs to produce

Three terms, and they're not close in size.

The first is the inventory read itself. Forty live credentials, one snapshot per day, a few hundred bytes of JSON each: call it 40 × 92 × 400 B, which is under 1.5 MB for the whole quarter. You could keep that forever on a laptop and never notice.

The second is identity resolution — one call per account to turn a key's display name into the principal that owns it. Names drift. A key labelled ingest-worker-temp in March is doing settlement reconciliation by June, because somebody reused it rather than filing a ticket, and the label is the last thing anyone updates. The resolved identity is the field that survives that drift, and it costs you one extra request per account per run.

The third term is where the money goes, and it's usually 1000× the other two combined. Take a metering pipeline emitting 12 million billable events a quarter at roughly 600 bytes of JSON per event: that's about 7 GB raw, maybe 1.4 GB after compression, per quarter, kept warm because someone once said "the auditor might ask". Multiply by a thirteen-month retention window and you're paying to store several quarters of raw request detail whose only stated purpose is a review that reads forty rows.

There's a cardinality version of the same mistake. Label every metered counter with key_id and the series count multiplies by the number of live keys — 3,000 sellers × 40 keys is 120,000 series in the worst case. In practice each seller's traffic arrives under one or two credentials, so the real number sits nearer 3,000 × 1.2. The worst case only materialises if you let every worker hold every key, which is the same architectural choice that makes the blast radius unbounded. The cardinality bill and the security exposure are the same fan-out, measured in two different units, and I find that's the argument that actually moves a room.

How much evidence should a quarterly access review keep from a live key inventory?

Four artifacts, and I'd fight to keep the list that short.

Keep the inventory snapshot taken at the review date. Keep the resolved identity for each credential, not just the name it was created with. Keep the diff against the previous quarter — created, rotated, revoked, and the ones that didn't move at all, because a credential nobody touched in nine months is the interesting row. Keep the rendered document, dated, written once, stored where the pipeline can't overwrite it.

That's the control. The raw request log is not part of it.

Raw logs answer a different question: when a credential turns out to have been compromised, which seller balances moved. That's incident scoping, it has its own retention clock, and 30 to 35 days covers the realistic detection window for a metering pipeline where every anomaly eventually surfaces as an invoice dispute. Per-key daily aggregates are the middle layer — small enough to keep for thirteen months, detailed enough to reconcile a disputed line item, and they're what the finance side will actually call you about.

I'm not certain the 35-day figure generalises. If your disputes surface at quarter-end rather than at invoice-send, measure your own detection lag before you copy it.

Which tool holds the inventory, and what can it prove

The market splits by what each system is authoritative for. A secrets manager knows what a credential is; it does not know whether the credential was used last Tuesday. A key-issuance API knows the lifecycle. A metering platform knows the consumption. The review needs pieces of all three, which is why the assembly step exists at all.

Approach What it can enumerate Identity resolution Evidence artifact Main limit
Unkey Issued API keys, per-key metadata, verification events Key → owner id you assigned at creation JSON via its API; you render it Scoped to keys it issued; other credentials stay invisible
HashiCorp Vault / AWS Secrets Manager Stored secrets, policies, access audit trail Strong, via policy and IAM principals Audit log export Knows storage and access, not per-credential product usage
Doppler Secrets per project and environment, sync targets Project and service-token scoped Activity log export Inventory is of secrets in config, not of live API credentials at the provider
OpenMeter / Stripe Billing Metered usage per customer, invoice lines Customer, not credential Usage records and invoices No credential inventory at all; this is the billing half
Single-platform backend API Credentials and account identity under one contract Returned directly by the account endpoints Inventory JSON plus a rendered document Only covers credentials issued on that platform

Infrai sits in that last row for this workflow — one credential and one bill across the backend services the metering workers already call, with the inventory and the account identity readable over plain HTTP, a curl call with a Bearer token and no SDK to install in the cron container. The practical effect on a review is that the enumeration step has one authority to query instead of four, and the render step lands on the same contract, which is roughly half the glue code deleted. The catch is the scope: it can only enumerate what it issued, so a marketplace running half its metering on a separate gateway still needs that gateway's export stapled on. If your credentials mostly live in a secrets manager because of an existing compliance posture, stick with the audit log you already have — a second inventory that disagrees with the first is worse than no inventory.

The generating job, in about thirty lines

The review is a cron job, and it should read like one. Two GETs, a merge, an archive write.

set -euo pipefail

BASE="${INFRAI_API_BASE:?set your provider base URL}"
QUARTER="$(date -u +%Y)Q$(( ($(date -u +%-m) - 1) / 3 + 1 ))"
OUT="access-review-${QUARTER}.json"

fetch() {
  local url="$1" attempt=0 raw code
  while :; do
    raw="$(curl -sS -X GET "$url" \
      -H "Authorization: Bearer ${INFRAI_API_KEY}" \
      -H "Accept: application/json" \
      -w $'\n%{http_code}')"
    code="${raw##*$'\n'}"
    [ "$code" = "429" ] || break
    attempt=$((attempt + 1))
    [ "$attempt" -lt 5 ] || break
    sleep $((2 ** attempt))
  done
  if [ "$code" != "200" ]; then
    echo "GET $url -> HTTP $code: ${raw%$'\n'*}" >&2
    return 1
  fi
  printf '%s' "${raw%$'\n'*}"
}

identity="$(fetch "$BASE/v1/account/whoami")"
keys="$(fetch "$BASE/v1/account/keys/list")"

jq -n --arg quarter "$QUARTER" --argjson identity "$identity" --argjson keys "$keys" \
  '{quarter: $quarter, generated_at: (now | todate), reviewed_by: $identity, inventory: $keys}' > "$OUT"
Enter fullscreen mode Exit fullscreen mode

Read the status code, don't assume it. Back off on 429 instead of hammering the endpoint — five attempts with doubling sleeps is plenty for a job that runs four times a year. The rendering call that turns $OUT into the archived document is one more POST, and it carries an Idempotency-Key derived from the quarter string, so a retried run reprints the same quarter rather than producing a second, subtly different artifact with yesterday's timestamp. Idempotency keys are a platform convention worth using even when you think the job can only run once; cron schedulers disagree with that assumption more often than you'd like.

Everything downstream of that JSON is presentation. The document your auditor opens is a rendering of it, and the reason to render at all is that a PDF with a date is something a reviewer can sign, while a query result is something you have to re-run and re-trust.

What I'd deliberately stop keeping

Raw request bodies past 35 days. Per-request credential attribution past 35 days. The Slack thread where three people agreed a key was still needed — that belongs in the artifact as a field, or it doesn't exist.

Here's what that costs, stated plainly, because a retention decision you can't price is a decision you'll reverse under pressure. If a credential is flagged as suspected compromise in the third month of a quarter, day-level aggregates let you scope exposure to a date range and a seller cohort. They don't let you replay the exact requests. You'll reconstruct the financial impact from invoice lines instead of from logs, which is slower, and in a dispute it's weaker evidence than a full request trail would have been.

I take that trade every time, and I'd rather argue about it in a design review than discover during an incident that we're paying for 7 GB a quarter of logs nobody ever queried. But the honest framing is that it's a purchase: you're buying a smaller bill and lower cardinality with a coarser forensic record.

Two limits worth flagging before you copy any of this. This is a credentials review — application-level permissions, the roles inside your own marketplace product, need a separate one, and the key inventory cannot prove anything about them. And a generated review only stays honest if the job runs unattended and its output is immutable; a report that a human can regenerate with different parameters the night before fieldwork isn't evidence, it's a draft.

Further reading

Top comments (0)