Skip to content

feat(sc-dbuser) add policy for sc-dbuser #3

Merged
merged 4 commits into from
Jun 10, 2026
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions .tflint.hcl
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
config {
module = true
force = false
module = true
force = false
disabled_by_default = false

# ignore_module = {
# "terraform-aws-modules/vpc/aws" = true
# "terraform-aws-modules/security-group/aws" = true
# }
# ignore_module = {
# "terraform-aws-modules/vpc/aws" = true
# "terraform-aws-modules/security-group/aws" = true
# }

# varfile = ["example1.tfvars", "example2.tfvars"]
# variables = ["foo=bar", "bar=[\"baz\"]"]
# varfile = ["example1.tfvars", "example2.tfvars"]
# variables = ["foo=bar", "bar=[\"baz\"]"]
}

rule "aws_instance_invalid_type" {
Expand Down
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,8 @@

* 1.8.1 -- 2026-05-28
- updated policies/sc-servicecatalog-t1,-t2
- deny update to provisioned products and properties of provisioned products
- deny update to provisioned products and properties of provisioned products

* 1.9.0 -- 2026-06-04
- created policies
- policies/sc-dbuser
271 changes: 271 additions & 0 deletions examples/rds-mfa-test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
# IAM DB Authentication for Aurora PostgreSQL / Amazon RDS PostgreSQL

This guide shows how to enable IAM database authentication and connect using:
- `psql`
- `pgAdmin`
- `DBeaver`

It includes CLI commands and sample CLI output you can compare against your environment.

## References

- AWS Docs (RDS IAM DB Authentication): https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html
- AWS Docs (Connecting with IAM token): https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.Connecting.html
- AWS Docs (Aurora IAM DB Authentication): https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/UsingWithRDS.IAMDBAuth.html
- AWS Blog: Using IAM authentication to connect with pgAdmin Amazon Aurora PostgreSQL or Amazon RDS for PostgreSQL: https://aws.amazon.com/blogs/database/using-iam-authentication-to-connect-with-pgadmin-amazon-aurora-postgresql-or-amazon-rds-for-postgresql/

## Prerequisites

1. PostgreSQL engine on Amazon RDS or Aurora PostgreSQL.
2. TLS/SSL enabled from client to database.
3. AWS CLI v2 installed and authenticated.
4. Identity Center (SSO) login completed with MFA.
5. PostgreSQL user exists for your login name (or mapped username).

Optional but recommended:
- Download the AWS RDS CA bundle for strict certificate validation:
https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem

## 1. Enable IAM authentication on the database

Choose one path depending on your deployment type.

### RDS PostgreSQL instance
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would like terraform null resource example of making this change via Terraform (will not hold up merge for it though)


```bash
aws rds modify-db-instance \
--db-instance-identifier my-postgres-instance \
--enable-iam-database-authentication \
--apply-immediately
```

Example output:

```json
{
"DBInstance": {
"DBInstanceIdentifier": "my-postgres-instance",
"Engine": "postgres",
"IAMDatabaseAuthenticationEnabled": true,
"DBInstanceStatus": "modifying"
}
}
```

### Aurora PostgreSQL cluster
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would like terraform null resource example of making this change via Terraform (will not hold up merge for it though)


```bash
aws rds modify-db-cluster \
--db-cluster-identifier my-aurora-pg-cluster \
--enable-iam-database-authentication \
--apply-immediately
```

Example output:

```json
{
"DBCluster": {
"DBClusterIdentifier": "my-aurora-pg-cluster",
"Engine": "aurora-postgresql",
"IAMDatabaseAuthenticationEnabled": true,
"Status": "modifying"
}
}
```

## 2. Create or update IAM policy for connect permission
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is dbuser? Is it the JBID? The email address?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the policy, it is the email address (all lowercase). See https://github.e.it.census.gov/terraform-modules/aws-sso/pull/3/files#diff-fb90d613136f0d47a7d3fd49d287d6fcb5a7dc7005dd0704ffc05ee272f5cef8R27-R37.

Please indicate that it's an email address.


Grant `rds-db:connect` to the exact DB user ARN.

### Policy example for RDS PostgreSQL

```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["rds-db:connect"],
"Resource": ["arn:aws:rds-db:us-east-1:123456789012:dbuser:db-ABCDEFGHIJKL01234/mydbuser"]
}
]
}
```
Comment on lines +83 to +94
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where does this value come from? I'd expect some details on how to construct the specific ARN here. We should also have a terraform data resource for the policy statement and not hardcode stuff. Also, we won't be able to do this with an SSO role.


### Policy example for Aurora PostgreSQL

```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["rds-db:connect"],
"Resource": ["arn:aws:rds-db:us-east-1:123456789012:dbuser:cluster-ABCDEFGHIJKL01234/mydbuser"]
}
]
}
```
Comment on lines +98 to +109
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where does this value come from? I'd expect some details on how to construct the specific ARN here. We should also have a terraform data resource for the policy statement and not hardcode stuff. Also, we won't be able to do this with an SSO role.


Attach the policy to the IAM role/user you assume for database access.

## 3. Grant database user membership to `rds_iam`

Connect as an admin user and run:

```sql
CREATE USER mydbuser WITH LOGIN;
GRANT rds_iam TO mydbuser;
```

Validation query:

```sql
\du mydbuser
```

Example output:

```text
Role name | Attributes | Member of
----------+------------+-----------------
mydbuser | | {rds_iam}
```

## 4. Authenticate to AWS with MFA (Identity Center / SSO)

If using AWS CLI SSO profile:

```bash
aws sso login --profile prod-db
```

Example output:

```text
Attempting to automatically open the SSO authorization page in your default browser.
Successfully logged into Start URL: https://start.us-gov-east-1.us-gov-home.awsapps.com/directory/d-c2672d0b4e#/
```

## 5. Generate an IAM auth token

Set variables:

```bash
export AWS_PROFILE=prod-db
export AWS_REGION=us-east-1
export DBHOST=my-postgres-instance.abcdefghijkl.us-east-1.rds.amazonaws.com
export DBPORT=5432
export DBUSER=mydbuser
```

Generate token:

```bash
export PGPASSWORD="$(aws rds generate-db-auth-token \
--hostname "$DBHOST" \
--port "$DBPORT" \
--region "$AWS_REGION" \
--username "$DBUSER")"
```

Example output:

```text
No stdout output. Token stored in PGPASSWORD.
```

Token notes:
- Token lifetime is about 15 minutes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this adjustable?

- Generate a new token before each new connection if needed.

## 6. Connect with psql

```bash
psql "host=$DBHOST port=$DBPORT dbname=postgres user=$DBUSER sslmode=require"
```

Example output:

```text
psql (16.4, server 15.6)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384)
Type "help" for help.
postgres=> select current_user, inet_client_addr();
current_user | inet_client_addr
--------------+-----------------
mydbuser | 10.100.42.17
(1 row)
```

If you use CA verification, replace `sslmode=require` with:

```bash
sslmode=verify-full sslrootcert=/path/to/global-bundle.pem
```

## 7. Connect with pgAdmin

1. Open pgAdmin and create a new server registration.
2. In Connection tab:
- Host name/address: your RDS or Aurora endpoint
- Port: `5432`
- Maintenance database: `postgres` (or target DB)
- Username: `mydbuser`
- Password: paste the generated IAM token
3. In SSL tab:
- SSL mode: `require` (or `verify-full` with CA cert)
- Root certificate: path to `global-bundle.pem` if using `verify-full`
4. Save and connect before token expiration.

Expected behavior:
- Connection succeeds.
- If token expires, pgAdmin prompts again; generate a new token and reconnect.

## 8. Connect with DBeaver

1. Create a new PostgreSQL connection.
2. Main settings:
- Host: your RDS or Aurora endpoint
- Port: `5432`
- Database: `postgres` (or target DB)
- Username: `mydbuser`
- Password: paste generated IAM token
3. SSL settings:
- Enable SSL
- Mode: `require` or `verify-full`
- CA certificate: `global-bundle.pem` when using verification
4. Click Test Connection, then Finish.

Expected behavior:
- Test connection succeeds while token is valid.
- Regenerate token when prompted after expiration.

## Troubleshooting

### `FATAL: PAM authentication failed`

Check:
- IAM auth is enabled on instance/cluster.
- IAM principal has correct `rds-db:connect` ARN.
- Database user exists and has `rds_iam` membership.
- Username in token command exactly matches DB user.

### `The security token included in the request is invalid`

Check:
- AWS profile/session is active.
- `aws sso login --profile ...` was completed.
- Region and endpoint are correct.

### SSL or certificate errors

Check:
- Client uses SSL.
- Root CA bundle path is valid when using `verify-ca` or `verify-full`.

## Optional screenshots from this example

Screenshots in [images/](images/) can be used to supplement the SSO and token workflow in your runbook.
Binary file added examples/rds-mfa-test/images/image_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/rds-mfa-test/images/image_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/rds-mfa-test/images/image_3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/rds-mfa-test/images/image_4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/rds-mfa-test/images/image_5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/rds-mfa-test/images/image_6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
44 changes: 44 additions & 0 deletions policies/sc-dbuser/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
## Requirements

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.12 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 6.0 |

## Providers

| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 6.0 |

## Modules

No modules.

## Resources

| Name | Type |
|------|------|
| [aws_arn.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/arn) | data source |
| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
| [aws_iam_policy_document.inline](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_account_alias"></a> [account\_alias](#input\_account\_alias) | AWS Account Alias | `string` | `""` | no |
| <a name="input_account_id"></a> [account\_id](#input\_account\_id) | AWS Account ID (default will pull from current user) | `string` | `""` | no |
| <a name="input_override_prefixes"></a> [override\_prefixes](#input\_override\_prefixes) | Override built-in prefixes by component. This should be used primarily for common infrastructure things | `map(string)` | `{}` | no |
| <a name="input_tags"></a> [tags](#input\_tags) | AWS Tags to apply to appropriate resources | `map(string)` | `{}` | no |

## Outputs

| Name | Description |
|------|-------------|
| <a name="output_customer_managed_policy_names"></a> [customer\_managed\_policy\_names](#output\_customer\_managed\_policy\_names) | Map of policy name to permission boundary of Customer Managed Policy to attach to the permissionset |
| <a name="output_inline_policy"></a> [inline\_policy](#output\_inline\_policy) | AWS Policy document for the single allowed inline policy (use .json to get policy) |
| <a name="output_managed_policy_names"></a> [managed\_policy\_names](#output\_managed\_policy\_names) | Names of AWS Managed Policy to attach to the permissionset |
| <a name="output_name"></a> [name](#output\_name) | Permission Set Name for which all settings apply |
| <a name="output_relay_state"></a> [relay\_state](#output\_relay\_state) | Relay State to pass along to permissionset |
3 changes: 3 additions & 0 deletions policies/sc-dbuser/base_arn.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
locals {
all_account_arn_iam = format("arn:%v:%v::%v:%%v", data.aws_arn.current.partition, "iam", "*")
}
1 change: 1 addition & 0 deletions policies/sc-dbuser/data.tf
1 change: 1 addition & 0 deletions policies/sc-dbuser/defaults.tf
12 changes: 12 additions & 0 deletions policies/sc-dbuser/locals.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
locals {
account_id = var.account_id != "" ? var.account_id : data.aws_caller_identity.current.account_id
account_environment = data.aws_arn.current.partition == "aws-us-gov" ? "gov" : "ew"
region = data.aws_region.current.region
region_short = join("", [for c in split("-", local.region) : substr(c, 0, 1)])

base_tags = {
"boc:tf_module_version" = local._module_version
"boc:tf_module_name" = local._module_name
"boc:created_by" = "terraform"
}
}
2 changes: 2 additions & 0 deletions policies/sc-dbuser/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/*
*/
3 changes: 3 additions & 0 deletions policies/sc-dbuser/module_name.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
locals {
_module_name = "aws-sso/policies/sc-dbuser"
}
Loading