A production database access review is the list of human logins and application logins that can still read or change production Postgres and production MySQL. Auditors sample both. The failure we see is a spreadsheet of people, with the application roles left off because "those are just services," plus a second failure where inherited grants never appear because the export listed only the login role itself.
Rutagon builds one population for both engines and files it in the GRC platform you already run. The client is the one in the audit. Rutagon builds the evidence pipeline. Moving database passwords onto IAM authentication is a different cutover, described in RDS IAM database authentication. This article is who can use the database, human or application, before and after that cutover.
Which databases are in the population
Production is the set of instances named in the system description. A read endpoint that accepts a human login is in the population when that login can see production data. A migration host that is not production stays out. Staging stays out unless the same credentials also open production, in which case the production row records that reuse and the shared-login problem is handled with a retire date on the shared-account review.
Every login is either a person or an application. A person maps to a workforce id. An application maps to a service name and a repository. A login that several people know the password for is still a database row here, and it is also a shared account. We do not let the service label hide a shared password.
Postgres and MySQL do not share a catalog. We do not flatten them into one fake privilege name. The reviewer sees the engine, the instance, the login, and the grants in that engine's own terms. The decision column is the same shape so the GRC sample is one pack.
Production database access review of humans and apps
On Postgres, privileges often sit on a group role that logins inherit. PostgreSQL documents role membership as GRANT of one role to another. Membership with inherit lets the session use the group privileges immediately. Membership without inherit does not. Attributes such as superuser, create-database, create-role, and login are not inherited the way ordinary table grants are. Using those attributes requires SET ROLE into a role that has them. The membership page is Role Membership.
That distinction changes the row. A human who is not a superuser, but who can SET ROLE into a superuser, is a privileged human. A review that prints rolsuper and stops will mark them clean. We record direct attributes, inherit paths, and set-role paths. Application logins get the same walk. An application that inherits a reporting role is a different risk from an application that can become a database owner.
engine: postgres
instance: production-writer
login: app-or-human
owner: workforce-id-or-service
attributes: [LOGIN]
inherit_roles: [reporting]
set_role_targets: [break-glass-dba]
superuser_attribute: false
decision: pending
On MySQL, global privileges are visible in INFORMATION_SCHEMA.USER_PRIVILEGES. The grantee is user_name at host_name. Each row is one privilege type, and grant option is a separate flag. The USER_PRIVILEGES table documents those columns and states that the table is not equivalent to SHOW GRANTS. Database and table grants live outside it. A collector that files only USER_PRIVILEGES will tell an auditor the account has no global superuser bit and will hide a full grant on the production schema. We run SHOW GRANTS for each production account and we keep the host part. The account app from one network is not the account app from localhost.
Anonymous accounts, the ones with an empty user name, are rows. They are not skipped as junk. An application account whose owner field is blank stays open until a service owner is named. The same rule we use for privileged cloud roles in privileged access review automation applies here: an unowned privileged login does not close.
What the reviewer is deciding
Human rows ask whether this person still needs production data, and whether the path is inherit-only or includes an attribute they must set role to reach. Application rows ask whether the service still needs that grant, and whether a broader role was copied forward when the service was created. A keep on a set-role path to superuser needs a named owner and a reason. We do not accept "the platform uses it" without a repository.
Removal is a change in the database, or in the identity store that maps to the database, tracked by a ticket. The next export is the check. A grant that remains after the ticket closed reopens the row. We store the ticket id from the GRC you already run on the pack so the sample is a lookup.
Read-only human access is still access. Analysts with inherit into a wide reporting role are in the population. The decision can be keep. The decision cannot be "out of scope because they cannot write." The auditor's question is who can see production data.
Break-glass database roles stay in the population. They need an owner and a reason, and they need the set-role path written down so a later sample can see who was able to become that role. A break-glass role with login enabled for a shared password is the wrong shape. The login should be individual, and the shared password belongs on the shared-account list until it is retired.
Filing the pack
We pull from the production endpoints you named, on the review cadence, and we can pull between decision cycles so the grants match the database rather than a quarter-old dump. The file is one pack with an engine column, not two unrelated spreadsheets that drift apart.
The upload goes to the access-review control already named in the GRC you already run. When the GRC cannot store one row per login, we attach the file and keep the ticket id. The columns stay stable: engine, instance, login, owner, inherit path, set-role path, and the MySQL grant text.
The collector is part of the AWS cloud infrastructure work when the databases run there, and the same row shape holds for engines you run yourselves. We do not replace the database. We read the catalog the auditor will eventually ask to see, including the grants a global-privilege table omits.
A production lesson we keep: the first export is longer than the team expects, because inherited roles and host-qualified MySQL accounts were never on the spreadsheet. That length is the review. Shortening it by dropping application logins recreates the gap the sample will find.
Frequently Asked Questions
Are application logins part of a production database access review?
Human logins and application logins on production Postgres and MySQL are one population. Application rows carry a service owner and a repository. A login with no owner stays open, and a shared password on that login is tracked as a shared account.
Why is rolsuper not enough on Postgres?
Superuser and the other role attributes are not inherited the way table grants are. A member may need SET ROLE to use them, or may already inherit ordinary privileges from a group. We record attributes, inherit paths, and set-role paths so a privileged path is visible.
Why not stop at MySQL USER_PRIVILEGES?
That table lists global privileges and MySQL documents that it is not the same as SHOW GRANTS. Schema and table grants would be missing. We collect SHOW GRANTS per account and we keep the host part of the account name.
Does this replace IAM database authentication?
IAM authentication is a credential cutover for clients that can use it. This review is the entitlement list: which humans and which applications can use production. The packs stay separate so a sample does not mix them.
How do you show a removal worked?
The ticket id sits on the row in the GRC you already run. The next catalog export must omit the grant or the set-role path that was revoked. If it is still there, the row reopens. A closed ticket by itself is not the catalog.
See what an Access & Credential Governance Diagnostic finds in your environment. Contact Rutagon at rutagon.com/contact, 907-841-8407, or contact@rutagon.com.