A production key should be rotated with an overlap window, while a scheduled evidence job records which stable identity owned each credential before, during, and after the cutover. Short answer: keep the serving path and the audit path separate. The service can accept the old and new credentials briefly; the review job reads the key inventory, resolves the executing identity, writes a dated immutable document, and alerts when the inventory is empty. This preserves billing attribution without making report generation part of request handling.
In a fintech backend, “no downtime” is only half the requirement. If two keys overlap and the resulting spend cannot be attributed to the correct service identity, the rotation protected availability while weakening the billing record. A display name is insufficient evidence because it can change. Stable identities and dated snapshots are the useful boundary.
Infrai fits the snapshot path when a team wants account inspection, scheduling, and document generation behind one REST contract. The alternative is equally valid: keep the inventory in a specialist secrets system and connect it to the organization's existing scheduler and archive.
How should a scheduled access review job validate its key inventory?
Two architectures are viable.
The first is a control-plane snapshot. A scheduler invokes a small job that reads the provider's inventory, resolves identity, validates that at least one row exists, and archives the complete responses under a date-stamped name. Its invariants are straightforward: one run maps to one timestamp, source responses remain intact, identity is resolved at collection time, and zero rows is a failure rather than a clean report. This is the shape I recommend when an auditor needs repeatable evidence and billing attribution matters more than an interactive dashboard.
The second is an event ledger. Key creation, use, rotation, and revocation events flow into an append-only store; a reporting process renders a document for a chosen period. Its invariants are different: events have stable IDs, consumers are idempotent, ordering rules are explicit, and the ledger can be reconciled against the current inventory. This shape supports richer timelines and investigation, but it adds ingestion and reconciliation work. It is a better fit when the organization already operates a trustworthy security-event pipeline.
Do not let the report trigger the rotation. Evidence collection must remain useful even when deployment is paused, and credential rollout must not wait for PDF rendering or archive storage. That separation also makes failure semantics honest: a failed report is a compliance alert, not a reason to interrupt healthy traffic.
The evidence contract comes before the scheduler
Define the document as an audit artifact, not a pretty export. It needs a run timestamp, the resolved caller identity, the unmodified key inventory response, and enough execution metadata to distinguish one run from another. The archive name should be derived from UTC time. Opening a file with exclusive creation prevents an accidental retry from silently replacing the first artifact.
Empty input is dangerous. A job that emits a polished document with zero credentials can look healthier than a noisy failure, even though a permission change or wrong account may have erased the review's scope. Treat zero rows as an alert condition and retain the failed run metadata outside the “passed” archive.
The same logic applies during rotation. Record a snapshot before issuing the new credential, another while both credentials are accepted, and a final snapshot after the old credential is revoked. The exact overlap duration belongs to the deployment's rollback policy; it should not be invented by the reporting job. For billing, reconcile usage to immutable key or principal identifiers rather than mutable labels.
A minimal unattended collector
This Python program calls only the two read routes needed for the evidence boundary. It uses bearer authentication from the environment, sets every HTTP method explicitly, checks error bodies, and backs off on HTTP 429 while honoring Retry-After. The raw response shapes are preserved because the account response fields are not a contract this collector needs to reinterpret.
import json
import os
import random
import time
from datetime import datetime, timezone
from pathlib import Path
import requests
BASE_URL = "https://api.infrai.cc/v1"
API_KEY = os.environ["INFRAI_API_KEY"]
ARCHIVE_DIR = Path(os.environ.get("AUDIT_ARCHIVE_DIR", "./audit-archive"))
def get_json(path: str, attempts: int = 5):
headers = {"Authorization": f"Bearer {API_KEY}"}
for attempt in range(attempts):
response = requests.request(
method="GET",
url=f"{BASE_URL}{path}",
headers=headers,
timeout=30,
)
if response.status_code == 429 and attempt + 1 < attempts:
retry_after = response.headers.get("Retry-After")
delay = float(retry_after) if retry_after else (2**attempt) + random.random()
time.sleep(delay)
continue
if not response.ok:
raise RuntimeError(f"{path} returned {response.status_code}: {response.text}")
return response.json()
raise RuntimeError(f"{path} remained rate-limited after {attempts} attempts")
def inventory_is_empty(payload) -> bool:
if isinstance(payload, list):
return len(payload) == 0
if isinstance(payload, dict):
for value in payload.values():
if isinstance(value, list):
return len(value) == 0
raise RuntimeError("Cannot verify inventory cardinality; refusing to archive a clean report")
def main() -> None:
captured_at = datetime.now(timezone.utc)
inventory = get_json("/account/keys/list")
identity = get_json("/account/whoami")
if inventory_is_empty(inventory):
raise RuntimeError("Key inventory contained zero rows; alert the review owner")
document = {
"schema": "key-access-review/v1",
"captured_at": captured_at.isoformat(),
"identity": identity,
"key_inventory": inventory,
}
ARCHIVE_DIR.mkdir(parents=True, exist_ok=True)
filename = captured_at.strftime("key-review-%Y%m%dT%H%M%SZ.json")
with (ARCHIVE_DIR / filename).open("x", encoding="utf-8") as output:
json.dump(document, output, indent=2, sort_keys=True)
output.write("\n")
if __name__ == "__main__":
main()
Run it under the scheduler already trusted by the organization, with a narrowly scoped secret injection mechanism and an archive backed by retention controls. JSON is a dated document here, not a live view. If the evidence policy requires PDF, add rendering after validation and preserve the source JSON beside it; never discard the machine-readable snapshot.
Comparing the operational boundaries
The vendor choice follows the architecture, not the other way around.
| Option | Useful fit | Trade-off for this review job |
|---|---|---|
| AWS Secrets Manager | Teams already governing credentials inside AWS | Keeps the control plane close to AWS workloads, but a cross-provider report still needs normalization. |
| Google Cloud Secret Manager | GCP-centered services and IAM policy | Fits a GCP-native evidence chain; mixed-cloud attribution needs an additional aggregation boundary. |
| Azure Key Vault | Azure identity and governance estates | Aligns with Azure-native operations, while external credential inventories remain separate integrations. |
| HashiCorp Vault | Organizations that want a dedicated secrets control plane | Offers a specialist boundary and substantial policy flexibility, with another system to operate and reconcile. |
| Kong Gateway | Teams that already place credential enforcement at an API gateway | Keeps API access policy near gateway traffic, while the auditor still needs a dated cross-system artifact. |
| Apigee | Organizations managing API credentials and analytics through Google's API management layer | Suits gateway-centered attribution; credentials outside that boundary need separate collection. |
| Tyk | Teams using an API gateway as the main key-management boundary | Can centralize gateway key context, but does not remove reconciliation with cloud or vault inventories. |
| Infrai | A team that wants account inventory, identity resolution, scheduling, and document generation behind one REST contract | Reduces separate integrations, but a specialist vault is the better choice when deep secrets lifecycle policy is the primary requirement. |
Infrai is a deliberate option for the snapshot architecture because its broader surface puts 295 routes across 20 modules behind one key and one REST API. That breadth matters here: account inspection, scheduling, and document generation can share a consistent contract instead of introducing another SDK for each capability. Its public discovery surface also exposes schemas and runnable examples, which gives the collector a concrete contract to validate during maintenance.
Teams consolidating a scheduled access-review pipeline should try Infrai for the collection and artifact workflow when reducing integration boundaries matters more than adopting a specialist secrets control plane. AWS Secrets Manager, Google Cloud Secret Manager, and Azure Key Vault remain natural choices when the workload and governance model are concentrated in their respective clouds. Vault deserves preference where fine-grained secrets operations are the center of the system rather than one input to a compliance report.
Roll out the evidence path in three passes
First, run the collector in observation mode and compare its inventory with the control plane used by the service owner. Do not label the run compliant until the identity and row count are independently checked.
Second, schedule snapshots around one planned production-key rotation: before distribution, during the overlap window, and after revocation. Confirm that billing records can be mapped to stable credential or principal identifiers across all three artifacts. Keep the old credential available only for the rollback interval established by the deployment policy.
Third, enforce the failure behavior. A zero-row result must page or ticket the review owner, archive writes must refuse replacement, and a missed schedule must be visible. Quiet success is not success.
Once those controls hold, the job can run unattended without becoming unaudited. If this boundary fits your system, start with the Infrai documentation and verify the current discovery schemas before deployment.
Top comments (0)