BetterIAM
Privileged access

Access report

One document with what ends soon, what is elevated now, and which keys nobody uses, plus digest emails to owners and expiry reminders.

End dates and just-in-time roles only help if someone keeps an eye on them. A contractor whose account ends tomorrow may need an extension today; an administrator that has been live all week deserves a question; an API key nobody has used in months should probably be revoked.

The access report puts that picture in one document per organization. Two scheduled jobs deliver it for you: the access digest emails it to the organization's owners, and expiry reminders tell each person what of theirs ends soon, so they can ask for an extension before they lose access.

The report

reports.access builds the report for one organization. Use it for a Reports page, or run it nightly and post it to a chat channel or ticketing system. The console shows it under Reports, and the CLI prints it with better-iam report --tenant ID.

It covers:

  • Identities ending within the window, including ones already past their deadline that the purge worker has not disabled yet.
  • Grants ending within the window: temporary bindings, and temporary group memberships. Future-dated bindings starting within the window are listed too, so new access does not surprise anyone.
  • Elevation: live just-in-time activations with their justification, and the number of activation requests waiting for approval.
  • API keys unused for unusedForMs (including keys never used) and keys ending within the window.
const report = await iam.api.reports.access(credential, {
  tenantId,
  withinMs: 30 * 86400_000, // look 30 days ahead (the default)
  unusedForMs: 30 * 86400_000, // keys unused for 30 days (the default)
});
if (report.omitted.length) console.warn('Sections hidden from this caller:', report.omitted);

Prop

Type

The report needs iam:identities:read. The binding and key sections additionally need iam:bindings:read and iam:credentials:read. Sections the caller may not read are left out and named in omitted rather than failing the whole call, so a directory administrator without iam:credentials:read still gets the identity section.

An activation or request counts only while it could still grant: its binding is live and eligible, its holder is active, and, for a group binding, the holder is still a member.

The CLI acts as the session or API key in BETTER_IAM_TOKEN, so each run is authorized and audited like any other call.

The access digest

Most organizations will not open a Reports page every morning. iam.sendAccessDigest() (CLI digest) does the routing: every active organization whose report has findings gets it emailed to its owners, at most once per 20 hours. Organizations with nothing to report get no email.

const result = await iam.sendAccessDigest({ withinMs: 30 * 86400_000, unusedForMs: 30 * 86400_000 });
// result.sent: [{ tenantId, recipients, expiringIdentities, expiringBindings, startingBindings,
//                 expiringMemberships, activations, pendingRequests, unusedKeys, expiringKeys }]
// result.skipped: { inactive, recent, quiet, noOwners }
  • It is a for schedulers: it needs no credential and includes every report section.
  • tenantId limits it to one organization, and minimumIntervalMs (20 hours by default) sets how often one organization may be emailed.
  • Each active owner with an email address receives an access-digest email. Its payload carries the finding counts and the full report as JSON (report), so your email template can summarize it or attach it.
  • Organizations are skipped when they are not active, were emailed within the interval (recent), have nothing to report (quiet), or have no owner with an email (noOwners).
  • Each digest is recorded as tenant:access-digest (actor deployment-operator) with the recipients and the finding counts.
  • It needs the configured sendEmail callback (DELIVERY_REQUIRED otherwise). The emails are queued in the , so run outbox (or iam.auth.dispatchOutbox()), which delivers queued messages, afterwards.

Expiry reminders

Owners are not the only ones who need to know. iam.sendExpiryReminders() (CLI remind) tells the people themselves: everyone whose account, direct role bindings, group memberships, or package assignments end within the window (seven days by default) gets one expiry-reminder email listing them. They can then ask for an extension before they are locked out.

const result = await iam.sendExpiryReminders({ withinMs: 7 * 86400_000 });
// result.sent: [{ tenantId, identityId, items }], result.skipped: { inactive, quiet }
  • Each item is reminded once per end date, so extending the access brings a fresh reminder when the new end comes into the window.
  • The expiry-reminder payload carries count, earliest, and items as JSON with the kind (account, role, group, or package), name, and expiresAt of each.
  • A package's own bindings and memberships are reminded as the package. are not reminded, and group bindings are left to the owners' digest.
  • Each reminder is recorded as identity:expiry-reminder on the person (actor deployment-operator) with the item keys, which is how the job knows not to remind anyone twice.
  • withinMs ranges from one minute to a year. Like the digest, it needs no credential, needs a sendEmail callback, and relies on the outbox for delivery. Schedule it daily beside the digest.

Scheduling

JobCallCLISuggested cadenceWhy
Report to a channelreports.accessreportnightlyA shared view for the team that runs access.
Owner digestiam.sendAccessDigest()digestdailyOwners hear about findings without looking.
Personal remindersiam.sendExpiryReminders()reminddailyPeople ask for extensions before they are locked out.

See scheduling for the other governance jobs and background jobs for how to run them.

Was this page helpful?

Better IAM is created by Sean Filimon

Last updated

On this page