Incidents are when production data access is most needed and least controlled. A settlement batch is late, merchants are waiting, and the engineer on call needs to look at the invoices table now. In most organisations the answer is a shared read-only database account whose password lives in a vault, a wiki page or someone’s shell history, and which nobody has rotated since it was created. It works. It also means that anyone who has ever been on call can still read payment data, and the auditor’s question “who read this table, and why?” has no good answer.

This post follows one request in the demo organisation, Northwind Labs, from the sentence an SRE types to a PostgreSQL login that exists for sixty minutes, can read one table, and is removed by software running inside Northwind’s own network. Alongside it we look at a riskier neighbour, a request to change an invoice, to show where the same machinery slows down on purpose.

In short

  • One sentence becomes one database login. “Read the invoices table in the payments database for INC-4412, 1h” becomes a PostgreSQL role with VALID UNTIL and SELECT on billing.invoices only.
  • The narrowing is automatic. The entitlement allowed SELECT on every table; the least-privilege scoper cut it to the one table the sentence named.
  • Low risk is approved on the spot. Risk 30 on a production database holding payment data: the on-call and open-incident credits bring it to the auto-approve line, and flow #10 allows it.
  • The credential is shown once, then wiped. The password is sealed at rest, displayed to the requester once and deleted. At expiry the broker terminates sessions and drops the role.
  • Writes are treated differently. A 30-minute write request on the same table scores 62, needs a second factor and goes to the data owner.

The business problem: shared accounts and long memories

Production databases that hold payment or personal data are where a leaked credential costs the most. The exposure does not come from the one engineer who needs access tonight. It comes from the accumulated access of everyone who needed it on some night in the past three years and still has it. Shared accounts make that worse in two ways: they cannot be attributed to a person, and they cannot be removed from one person without breaking everyone else.

Industry
data

Stolen or misused credentials are consistently reported among the most common ways attackers get in, and breaches involving personal and payment data are among the most expensive to handle. IBM’s 2024 report put the global average cost of a data breach at around US$4.9 million.

Sources: Verizon Data Breach Investigations Report (credentials among the top initial access vectors); IBM Cost of a Data Breach Report 2024. Figures vary by year, region and industry.

The operating cost is real too. When access goes through a ticket queue, the incident waits for a database administrator to read the ticket, create a login, and remember to drop it afterwards. The usual outcome is that the step is skipped, or the login is created “for the week” and forgotten. Auditors testing PCI DSS or SOC 2 access controls then sample those logins and find that approval and removal are not evidenced.

TODAY Shared read-only account

One password, many people, no expiry. Who read billing.invoices on Tuesday cannot be answered, and removing one person means rotating for everyone.

WITH INSIGHTKEYS A login per grant

A PostgreSQL role created for one person, for one hour, on one table, with the reason and ticket attached, and dropped by the broker at expiry.

Step 1: Priya asks in one sentence

Priya is an SRE and is on call for the payments team. Incident INC-4412, “Settlement batch delayed for EU merchants”, is open at severity 2. Priya wants to see whether invoice statuses for the affected merchants are stuck. In the console, in Slack or with the command-line tool Priya types:

insightkeys request "read the invoices table in the payments database for INC-4412, 1 hour"

The intake agent drafts the request. It matches “payments database” to payments-db (Postgres), a real PostgreSQL database in the catalogue, rated critical, holding payment and personal data and owned by payments.lead@. It picks the Read entitlement, takes one hour as the duration and INC-4412 as the ticket, and uses the sentence as the reason. It only ever picks resources from the catalogue; it never invents one. Priya sees the draft and confirms it. This becomes request AR-0008.

Step 2: the scoper narrows, the risk agent scores

The Read entitlement on payments-db has a ceiling of SELECT on *.*: every table in every schema. That ceiling is what the data owner agreed anyone might ever be given for reading. The least-privilege scoper narrows inside it. The sentence named the invoices table, so the permission becomes SELECT on billing.invoices and nothing else. The scoper runs at autonomy level L3, “act and tell”: it applies the narrowing, records it on the request timeline, and the change can be pulled back by a person.

The risk agent then scores the narrowed request. Every point is explained:

FactorPointsWhy
Environment+30Production
Resource sensitivity+20payments-db is rated critical
Sensitive data+8Holds payment and personal data
Duration−3One hour or less
First time+5Priya has not had this access before
Narrow scope−10Named resources only: one table
On call−10Priya is on call for the payments team
Open incident−10INC-4412 is open and referenced
Total30Medium, at the auto-approve threshold

Read access adds nothing. Northwind’s routing thresholds are the defaults: auto-approve at 30 or below where the flow allows it, step-up at 55, two approvers at 75. The first flow that matches is #10, “Read access: automatic when the risk is low”, with a four-hour maximum. A score of 30 is within it, so the request is approved automatically. Notice what earned the approval: not Priya’s seniority, but the narrow scope, the short window, the open incident and the fact that Priya is the person on call. Ask for every table for eight hours on a quiet Sunday and the same flow would send it to the owner.

Step 3: the broker creates a login in Northwind’s network

InsightKeys’ servers do not hold a credential that can create database logins. The broker, a small service Northwind runs next to its databases, does. It holds a PostgreSQL role with CREATEROLE, connects out over HTTPS to collect instructions, and accepts only instructions signed with Northwind’s organisation key. Before acting, it checks its own local policy file, broker.yaml, which Northwind owns: is this integration allowed, is the duration within the longest grant it will create, is the broker paused.

sequenceDiagram participant P as Priya (SRE) participant I as Intake and scoper agents participant R as Risk agent and flow #10 participant C as Control plane participant B as Broker in Northwind network participant D as payments-db (PostgreSQL) P->>I: read the invoices table for INC-4412, 1 hour I->>R: SELECT on billing.invoices, 1h, ticket INC-4412 R->>C: Risk 30, approved automatically C->>B: Signed grant instruction B->>B: Verify signature, nonce, expiry and local policy B->>D: Create login role with VALID UNTIL, grant SELECT on billing.invoices B-->>C: Grant active, credential sealed C-->>P: Credential shown once Note over B,D: At expiry the broker acts on its own clock B->>D: Terminate sessions, drop the role B-->>C: Reports expired

AR-0008 from sentence to removal. The granting credential stays with the broker inside the customer network.

In effect, the broker runs the equivalent of the following against payments-db. The role name in the demo is ik_165ab58803c0; the password is random and never chosen by a person:

CREATE ROLE ik_165ab58803c0 LOGIN PASSWORD '…' VALID UNTIL '<one hour from now>';
GRANT USAGE ON SCHEMA billing TO ik_165ab58803c0;
GRANT SELECT ON billing.invoices TO ik_165ab58803c0;

The password is sealed at rest, shown to Priya once on the grant page, then wiped. If it is lost, Priya asks again; there is no “show password” button a day later, because there is nothing left to show. VALID UNTIL is the database’s own expiry on the password, a second lock behind the broker’s clock.

The AR-0008 grant page: payments-db (Postgres) Read, active, ends in 59 minutes, permission SELECT on billing.invoices, and what the broker created: role ik_165ab58803c0 in database payments
The grant, as the admin sees it. SELECT on billing.invoices only, about an hour left, and exactly what the broker created in the database: one role, one schema, one table.

The grant page is also the record. It shows who holds the access and why, when it started and ends, the permission, what the broker created, and every signed broker instruction with its status. An admin can revoke it from the same page with one click; so can Priya when the work is done.

The neighbour: fixing one invoice is a different request

The next day Sam, an engineer on the platform team, needs to correct one invoice status. Sam asks for “write access to fix one invoice status in the payments database for 30 minutes, OPS-219”. This is AR-0004. The Write entitlement’s ceiling is DELETE, INSERT, SELECT and UPDATE on *.*; the scoper narrows it to INSERT, SELECT and UPDATE on billing.invoices. No DELETE, no other tables.

The score is 62, High: production +30, critical +20, write +12, sensitive data +8, 30 minutes −3, first time +5, narrow scope −10. Sam is not on call for payments and OPS-219 is not an open incident, so neither credit applies. At 55 or more, step-up is required: Sam confirms it is Sam with an authenticator app before anyone is asked to approve. Then flow #20, “Write access: the owner approves”, sends it to payments.lead@, who owns the resource.

AR-0004 as the admin sees it: risk 62 High with each factor, step-up verified, permission INSERT, SELECT and UPDATE on billing.invoices, awaiting the owner's approval
AR-0004: the same table, a different answer. Risk 62, step-up verified with TOTP, and the owner’s approval still outstanding. The narrowed permission is on the page the approver reads.
flowchart TD A["Request on payments-db"] --> B{"Level asked for"} B -- Read --> C["Scoper narrows SELECT to named tables"] B -- Write --> D["Scoper narrows to named verbs and tables"] C --> E{"Risk 30 or less and flow #10 allows?"} E -- Yes --> F["Approved automatically"] E -- No --> G["Resource owner approves"] D --> H{"Risk 55 or more?"} H -- Yes --> I["Step-up second factor first"] H -- No --> G I --> G F --> J["Broker creates login, VALID UNTIL, table grants"] G --> J

How Northwind’s flows route database requests. Thresholds are the defaults and are editable in Settings.

The approver sees the sentence, the ticket, the score with every factor, and the exact permission: three verbs on one table for thirty minutes. That is a decision a resource owner can make in a minute from Slack or the console. It is very different from “please add Sam to payments_rw”, which nobody can evaluate.

AR-0004 as payments.lead sees it, with the reason, risk 62 and the narrowed permission, and Approve and Deny buttons
The owner’s view. payments.lead@ sees what Sam asked, why, the score and the three verbs on one table, and approves or denies.

Step 4: expiry, with the network or without it

When the hour is up, the broker revokes on its own clock. It keeps a local ledger of every grant it made, so it does not need the control plane to remind it. It terminates any sessions the role has open, so a long-running query or an idle psql window does not outlive the grant, and drops the role. Then it reports the grant as expired. The control plane sends its own revoke as well; re-delivery is idempotent, so the second one is harmless.

This matters for two failure cases a security reviewer will ask about. If the InsightKeys service is unreachable, expiry still happens. If the control plane were compromised and stopped sending revokes, expiry would still happen. And even if the broker itself were down at the moment of expiry, the password stops working at VALID UNTIL; the broker cleans up the role when it returns. Revocation is never refused, even when the broker’s pause switch is on.

If Priya needs more time, the grant is not extended. Priya makes a new, smaller request, which is scored again with what is true now. If the incident has closed, the incident credit is gone and the answer may be different. That is intended.

Putting a production database behind on-request accessIllustrative runbook with typical owners
1
Run the broker next to the databasePlatform

docker run or Docker Compose inside the network; enrol with a single-use token. It connects out only.

2
Give the broker a role with CREATEROLEDatabase admin

This is the only granting credential, and it stays in your network. Set the longest grant and whether admin may be granted in broker.yaml.

3
Discover existing rolesSecurity

Discovery lists PostgreSQL roles and privileges; superusers and unused logins appear in the standing-access list.

4
Define entitlements and ceilingsData owner

Read: SELECT on the schemas you allow. Write: the verbs you allow. The owner of record approves writes.

5
Choose flowsSecurity

Low-risk reads automatic (four hours at most); writes to the owner; step-up above 55.

6
Retire the shared accountDatabase admin + security

Once people use per-grant logins, rotate and remove the shared read-only account, through the broker.

7
Review after a monthSecurity

Insights shows time to access and how many requests were automatic; the quarterly review is pre-filled from real grants.

What changes, and what to be careful about

For the SRE, the hour starts within moments of asking instead of after a ticket is read. For the data owner, the only requests that reach them are the ones that change data or reach further than the flow allows, and each arrives already narrowed and explained. For the auditor, every access to payments-db has a person, a reason, a ticket, an approval (automatic, with the rule that allowed it, or a named approver), a start and an end. Grants and reviews can be exported to InsightAssure as evidence for access-control requirements.

Limits worth knowing

Privileges are table-level: SELECT on billing.invoices means every row and column of that table. Row-level restrictions remain your database’s job. PostgreSQL is the only database driver in 1.0, and it has been tested against a real PostgreSQL database rather than a large production fleet; MySQL, MongoDB and Snowflake are on the roadmap. Session watch can only compare activity the broker is able to report; in the screenshot above, no activity has been recorded on the grant yet. And a credential shown once can still be copied by the person who sees it, which is why the window is short and the login is dropped at the end.

Nobody should be able to read the payments database tomorrow because they needed to read it last year.

One sentence, one table, one hour, one login, then nothing. The approval is explained, the removal does not depend on anyone remembering, and the evidence is written as it happens.

Keep reading