# Just-in-Time Database Access with AWS RDS IAM Authentication

> RDS IAM authentication replaces a stored database password with a 15-minute token. This reference implementation adds the rest of the JIT path: a scoped IAM role, verified identity, a bounded session, approval in Git, and an honest look at the four limits that remain.

Tianzhou | 2026-08-10 | Source: https://www.bytebase.com/blog/just-in-time-database-access-with-aws-rds-iam/

---

Just-in-Time (JIT) database access is a control on humans. No engineer can query production by default. Access is requested, granted for a window, and stops being obtainable when that window closes.

The human path needs three controls:

1. **Ephemeral credentials** that expire on their own.
1. **Request and approval** to decide who gets access.
1. **Audit** tying the person to what they did.

AWS RDS IAM authentication gives you the first control. It replaces a stored database password with a signed token that is valid for 15 minutes. The other two controls have to be assembled around it.

This reference implementation uses RDS for PostgreSQL. MySQL and MariaDB use the same IAM model with different database-side setup.

## Reference implementation

### 1. Create the database user

Enable IAM database authentication on the RDS instance, then create a database user for temporary read access.

```sql create-db-jit-read-user.sql
CREATE USER db_jit_read;
GRANT rds_iam TO db_jit_read;
GRANT USAGE ON SCHEMA public TO db_jit_read;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO db_jit_read;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO db_jit_read;
```

`GRANT ... ON ALL TABLES` covers only existing tables. Default privileges cover tables created later, but they belong to the role that creates the objects. If migrations run as another role, repeat the last statement with `FOR ROLE <migration_role>`.

IAM controls who may authenticate as `db_jit_read`; PostgreSQL privileges still control what that user may do. Audit any `SECURITY DEFINER` functions exposed to `PUBLIC` before calling the role read-only.

Enabling IAM authentication is an instance modification. RDS applies it in the next maintenance window unless you choose `--apply-immediately`. Check engine, version, and Region support first; AWS also recommends budgeting [300 to 1000 MiB of extra memory](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html) for the feature.

### 2. Allow one IAM role to connect as that user

The permissions policy binds an IAM role to the database user.

```json rds-connect-policy.json
{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Effect": "Allow",
			"Action": "rds-db:connect",
			"Resource": "arn:aws:rds-db:us-east-2:123456789012:dbuser:db-ABCDEFGHIJKL01234/db_jit_read"
		}
	]
}
```

`db-ABCDEFGHIJKL01234` is the instance's `DbiResourceId`, not its console name:

```bash get-dbi-resource-id.sh
aws rds describe-db-instances \
  --db-instance-identifier mydb \
  --query "DBInstances[0].DbiResourceId" \
  --output text
```

The last segment is the PostgreSQL user. Together they mean: this role may authenticate to this RDS instance as `db_jit_read`.

### 3. Make the role temporary and attributable

The role's trust policy controls who may assume it and until when.

```json iam-jit-read-trust-policy.json
{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Effect": "Allow",
			"Principal": { "AWS": "arn:aws:iam::123456789012:user/alice" },
			"Action": ["sts:AssumeRole", "sts:SetSourceIdentity"],
			"Condition": {
				"StringEquals": { "sts:SourceIdentity": "alice@example.com" },
				"Bool": { "aws:MultiFactorAuthPresent": "true" },
				"DateLessThan": { "aws:CurrentTime": "2026-08-10T18:00:00Z" }
			}
		}
	]
}
```

Three details matter:

- The principal is Alice, not the whole AWS account.
- `SourceIdentity` is pinned to Alice's identity. A wildcard such as `*@example.com` would let her claim to be someone else.
- `DateLessThan` stops new role sessions after the approved window.

The example uses an IAM user to keep the mechanism visible. For production workforce access, AWS recommends federation through IAM Identity Center or another identity provider. The provider should assert both source identity and MFA status; do not replace them with caller-supplied strings.

Create the role with this trust policy, attach `rds-connect-policy`, and set a short `MaxSessionDuration` such as one hour.

### 4. Assume, mint, and connect

Alice assumes the role for 30 minutes, then signs an RDS authentication token with the returned credentials.

```bash assume-and-connect.sh
CREDS=$(aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/iam_jit_read \
  --role-session-name alice-incident-4471 \
  --source-identity alice@example.com \
  --serial-number arn:aws:iam::123456789012:mfa/alice \
  --token-code 123456 \
  --duration-seconds 1800)

export AWS_ACCESS_KEY_ID=$(echo "$CREDS" | jq -r .Credentials.AccessKeyId)
export AWS_SECRET_ACCESS_KEY=$(echo "$CREDS" | jq -r .Credentials.SecretAccessKey)
export AWS_SESSION_TOKEN=$(echo "$CREDS" | jq -r .Credentials.SessionToken)

TOKEN=$(aws rds generate-db-auth-token \
  --hostname mydb.abcdef123456.us-east-2.rds.amazonaws.com \
  --port 5432 \
  --username db_jit_read \
  --region us-east-2)

PGPASSWORD="$TOKEN" psql \
  "host=mydb.abcdef123456.us-east-2.rds.amazonaws.com port=5432 dbname=app user=db_jit_read sslmode=verify-full sslrootcert=/path/to/global-bundle.pem"
```

`generate-db-auth-token` signs locally; it is not an API call. The [database token is valid for 15 minutes](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html), while the STS session above lasts 30 minutes. The grant deadline controls how long Alice may start new STS sessions.

## Approval as a protected pull request

IAM has no request-and-approve primitive. Put the role trust policy in Terraform and treat a protected infrastructure pull request as the approval record.

```diff terraform.tfvars
 jit_read_grants = {
+  "alice@example.com" = "2026-08-10T18:00:00Z"
 }
```

Terraform renders each entry into a trust statement like the one above: a pinned principal, a pinned source identity, MFA, and a `DateLessThan` condition. Required independent review is the approval; merge and apply is the grant. CI should reject missing incident references and deadlines beyond the permitted maximum.

There are two clocks. The entry controls how long Alice may start a session. `MaxSessionDuration` controls how long that session may last. A session opened just before the grant deadline can therefore continue past it.

## What RDS IAM authentication does not solve

1. **Uniform engine coverage.** IAM database authentication supports [MySQL, MariaDB, and PostgreSQL](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html). RDS for SQL Server and Oracle need a different human authentication path, such as Kerberos and Active Directory.

1. **A joined audit trail.** CloudTrail records the STS role assumption, PostgreSQL `log_connections` records `db_jit_read` connecting from an address, and pgAudit can record its statements. AWS does not log the IAM database authentication itself, so tying the person to the queries still requires correlating separate logs.

1. **Query-level authorization.** IAM grants permission to authenticate as a database user. Tables, columns, row policies, and executable functions remain the database's responsibility; IAM cannot hold one SQL statement for approval.

1. **Termination of an open connection.** The token is checked only when PostgreSQL authenticates the connection. The connection can outlive the token, the STS session, and the grant deadline. Immediate revocation requires blocking new sessions and terminating existing database sessions.

## Where Bytebase fits

![Bytebase places one control plane between engineers and databases for identity, approval, access enforcement, and audit](/content/blog/_shared/middleware.svg)

[Bytebase](/) sits in front of the database and holds the database connection itself. Engineers sign in with their own identity and query through it. The four gaps move into one control plane:

1. The same request, approval, and expiry workflow covers PostgreSQL, MySQL, SQL Server, Oracle, and the rest of the fleet.
1. The audit record contains the person, SQL statement, database, and timestamp without joining STS and database logs.
1. Access can target databases or tables, sensitive columns can be masked, and a specific read-only statement can be approved.
1. Engineers receive neither database credentials nor a direct database session. When access expires, the next query is refused.

The tradeoff is another component in the human access path. Bytebase must be available for engineers to query through it, while application and break-glass connections remain separate. What it buys is one place where identity, approval, expiry, enforcement, and audit agree.

## Related reading

- [Just-in-Time Database Access](https://www.bytebase.com/blog/just-in-time-database-access/)
- [Database Access Control (DAC) Best Practices](https://www.bytebase.com/blog/database-access-control-best-practices/)