Back to blog
AWSBest PracticesCloud SecurityDatabasesIdentity & Access

RDS IAM Authentication Not Enabled: Kill Static Database Passwords

Learn why RDS IAM database authentication matters, how to enable it on PostgreSQL and MySQL instances, and how to enforce it in CI with policy-as-code.

TL;DR

This check flags RDS PostgreSQL and MySQL instances that rely only on static database passwords instead of IAM database authentication. Enabling IAM auth lets you issue short-lived tokens tied to IAM identities, which kills long-lived credential sprawl. Fix it by setting --enable-iam-database-authentication on the instance and granting the right IAM policy.

Database passwords have a habit of outliving the people who created them. They end up baked into application config, copied into Slack threads, shared between services, and forgotten in .env files that get committed by accident. RDS IAM database authentication offers a way out: instead of a password that sits unchanged for months, your application requests a token that expires in 15 minutes and is tied directly to an IAM principal.

The rds_noiamauth check looks for RDS PostgreSQL or MySQL instances where IAM database authentication is switched off. If it fires, that instance is depending entirely on traditional username and password authentication for at least some of its connections.


What this check detects

Lensix inspects each RDS instance and reads the IAMDatabaseAuthenticationEnabled property. When it is false on a MySQL or PostgreSQL engine, the check is marked as failing.

You can confirm the same thing yourself with a quick describe call:

aws rds describe-db-instances \
  --query "DBInstances[?Engine=='postgres' || Engine=='mysql'].[DBInstanceIdentifier,IAMDatabaseAuthenticationEnabled]" \
  --output table

Any row showing False in the second column is what this check is pointing at.

Note: IAM database authentication is supported on RDS for MySQL, MariaDB, and PostgreSQL, plus Aurora MySQL and Aurora PostgreSQL. It is not available for Oracle or SQL Server engines, so this check only applies to the supported engines.


Why it matters

Static database credentials are one of the most common footholds in real breaches. Here is what makes them risky in practice:

  • They rarely rotate. A database password set during initial provisioning often stays in place for the life of the instance because rotating it means coordinating a change across every service that connects.
  • They spread. One password gets shared by multiple services, batch jobs, and the occasional engineer running a manual query. When it leaks, you cannot tell who or what was using it.
  • They sit in plaintext. Config files, CI variables, container images, and application logs are all places a static password ends up where it should not.

IAM database authentication changes the model. Instead of a shared secret, the database trusts AWS IAM. Your application calls rds generate-db-auth-token, receives a token that is valid for 15 minutes, and uses that token as the password. The token is signed using the caller's IAM credentials, so access is governed by IAM policies you already manage.

The security wins are concrete:

  1. No long-lived passwords to leak. A token that expires in 15 minutes is far less useful to an attacker who scrapes a log file or a leaked config.
  2. Centralized access control. You revoke access by changing an IAM policy, not by rotating a password and redeploying every consumer.
  3. Auditability. Token generation goes through AWS APIs, so you can tie connection attempts back to IAM identities and roles.
  4. It pairs naturally with instance roles. An EC2 instance or ECS task with an attached IAM role can authenticate to the database with no secret stored anywhere.

Warning: IAM authentication is throttled. RDS supports a limited number of new IAM-authenticated connections per second (around 200 for most instance classes, fewer on smaller ones). For high-churn connection patterns, use connection pooling so you are not opening a fresh authenticated connection on every request.


How to fix it

There are three steps: enable the feature on the instance, create a database user mapped to IAM, and grant an IAM policy that allows token generation.

1. Enable IAM authentication on the instance

Warning: Applying this without --apply-immediately defers the change to your next maintenance window. With --apply-immediately, the parameter takes effect right away but may briefly affect the instance. Test on a non-production instance first.

aws rds modify-db-instance \
  --db-instance-identifier my-app-db \
  --enable-iam-database-authentication \
  --apply-immediately

For Aurora, you enable it on the cluster instead:

aws rds modify-db-cluster \
  --db-cluster-identifier my-app-cluster \
  --enable-iam-database-authentication \
  --apply-immediately

2. Create a database user that uses IAM auth

The feature being on at the AWS level does not change anything until you create a database user that authenticates through IAM. The exact syntax depends on the engine.

For PostgreSQL, grant the predefined role:

CREATE USER app_service WITH LOGIN;
GRANT rds_iam TO app_service;

For MySQL, point the user at the IAM authentication plugin:

CREATE USER 'app_service' IDENTIFIED WITH AWSAuthenticationPlugin AS 'RDS';
GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO 'app_service';

3. Attach an IAM policy that allows connecting

The IAM principal (a role for an application, ideally) needs permission to generate a token for that specific database user. Scope it tightly:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "rds-db:connect",
      "Resource": "arn:aws:rds-db:us-east-1:123456789012:dbuser:db-ABCDEFGHIJKL01234/app_service"
    }
  ]
}

The resource ARN uses the DbiResourceId of the instance (the value starting with db-), not the instance identifier. Look it up with:

aws rds describe-db-instances \
  --db-instance-identifier my-app-db \
  --query "DBInstances[0].DbiResourceId" \
  --output text

4. Connect using a token

Your application now generates a token and uses it as the password. From the CLI it looks like this:

TOKEN=$(aws rds generate-db-auth-token \
  --hostname my-app-db.abcdefg.us-east-1.rds.amazonaws.com \
  --port 5432 \
  --region us-east-1 \
  --username app_service)

psql "host=my-app-db.abcdefg.us-east-1.rds.amazonaws.com \
  port=5432 \
  dbname=appdb \
  user=app_service \
  password=$TOKEN \
  sslmode=require"

Note: SSL is mandatory for IAM-authenticated connections. The token is only valid over an encrypted connection, so download the RDS CA bundle and set sslmode=require (or stricter) in your client.

In application code, the AWS SDKs do the token generation for you. Most database drivers let you supply a callback that fetches a fresh token before each connection, so you never hardcode anything.

Terraform example

If you manage RDS as code, this is a one-line attribute:

resource "aws_db_instance" "app" {
  identifier                          = "my-app-db"
  engine                              = "postgres"
  instance_class                      = "db.t3.medium"
  allocated_storage                   = 50
  iam_database_authentication_enabled = true
  storage_encrypted                   = true
  # ... other settings
}

Tip: Keep a master password user around even after moving to IAM auth. You will need it for break-glass access if IAM is misconfigured, and for some administrative tasks that IAM users may not be granted. Store that master password in Secrets Manager with automatic rotation rather than in plaintext.


How to prevent it from happening again

Enabling it once does not stop the next instance from being created without it. Bake the requirement into the places where infrastructure gets defined.

Policy-as-code in CI

If you use Terraform, an OPA/Conftest or tfsec-style policy can reject any RDS resource missing the flag. A simple Rego rule:

package terraform.rds

deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "aws_db_instance"
  engine := resource.change.after.engine
  engine == "postgres"
  not resource.change.after.iam_database_authentication_enabled
  msg := sprintf("RDS instance '%s' must enable IAM database authentication", [resource.address])
}

Wire that into the pull request pipeline so the check runs against the Terraform plan before anything reaches AWS.

Service Control Policies and Config rules

AWS Config does not ship a managed rule for this specific setting, but you can write a custom Config rule backed by a Lambda that inspects IAMDatabaseAuthenticationEnabled and marks instances noncompliant. That gives you continuous detection across every account in the organization, not just at deploy time.

Tip: Continuous scanning is exactly where a platform like Lensix earns its keep. Rather than maintaining custom Config Lambdas per account, the rds_noiamauth check runs across all your accounts and surfaces every instance that drifts away from IAM auth, so you catch the ones created outside your Terraform pipeline.


Best practices

  • Use roles, not users. Grant rds-db:connect to the IAM role your application assumes (an EC2 instance profile, ECS task role, or EKS IRSA role) rather than to long-lived IAM users.
  • Scope the connect permission to a single dbuser. Avoid wildcards in the resource ARN. One policy per database user keeps blast radius small.
  • Combine IAM auth with least-privilege grants inside the database. IAM controls who can connect; SQL grants control what they can do once connected. You need both.
  • Always require SSL. Set a parameter group with rds.force_ssl = 1 (PostgreSQL) so connections cannot fall back to unencrypted.
  • Pool connections. Because of the per-second connection rate limit, route traffic through RDS Proxy or an application-level pool to avoid throttling under load.
  • Keep encryption at rest on. IAM auth protects credentials in transit and at the access layer, but it does not encrypt your storage. Pair it with storage_encrypted = true.

IAM database authentication does not replace good password hygiene on your admin accounts, and it does not encrypt your data at rest. Treat it as one layer that removes a whole class of credential-leak risk, then keep the other layers in place.

Once IAM auth is on, in use, and enforced in your pipeline, the rds_noiamauth finding should stay green, and your applications will stop carrying around passwords that nobody remembers setting.