Skip to content

Commit 9a1ff04

Browse files
Add a simple tool to turn custom RDS IAM DSN into a connection string (#19)
1 parent f523edd commit 9a1ff04

2 files changed

Lines changed: 124 additions & 0 deletions

File tree

cmd/rds-iam-to-dsn/README.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# rds-iam-to-dsn
2+
3+
A CLI that resolves a `postgres+rds-iam://...` URL into a usable tokenized PostgreSQL DSN and prints it to stdout.
4+
5+
Use this when you want to script `psql`, `pg_dump`, or other Postgres tools without manual IAM token generation.
6+
7+
## Installation
8+
9+
```bash
10+
go install github.com/corbaltcode/go-libraries/cmd/rds-iam-to-dsn@latest
11+
```
12+
13+
Or build from source:
14+
15+
```bash
16+
cd ./cmd/rds-iam-to-dsn
17+
go build
18+
```
19+
20+
## Prerequisites
21+
22+
- **AWS credentials** configured (env vars, `~/.aws/credentials`, IAM role, etc.)
23+
- **AWS region** configured for SDK resolution (for example: `AWS_REGION`, shared config profile, or runtime role config)
24+
- **RDS IAM authentication enabled** on your database instance
25+
- A DB user configured for IAM auth (`CREATE USER myuser WITH LOGIN; GRANT rds_iam TO myuser;`)
26+
27+
## Usage
28+
29+
```bash
30+
rds-iam-to-dsn '<postgres+rds-iam-url>'
31+
```
32+
33+
- Database path is optional. If omitted, `pgutils` defaults DB name to the username.
34+
- The command prints the resolved DSN to **stdout**.
35+
36+
## Examples
37+
38+
Resolve DSN only:
39+
40+
```bash
41+
rds-iam-to-dsn 'postgres+rds-iam://app_user@mydb.abc123.us-east-1.rds.amazonaws.com:5432/myapp'
42+
```
43+
44+
Use with `psql` in a script:
45+
46+
```bash
47+
DSN="$(rds-iam-to-dsn 'postgres+rds-iam://app_user@mydb.abc123.us-east-1.rds.amazonaws.com:5432/myapp')"
48+
psql "$DSN"
49+
```
50+
51+
Or directly:
52+
53+
```bash
54+
psql "$(rds-iam-to-dsn 'postgres+rds-iam://app_user@mydb.abc123.us-east-1.rds.amazonaws.com:5432/myapp')"
55+
```
56+
57+
Use with `pg_dump`:
58+
59+
```bash
60+
DSN="$(rds-iam-to-dsn 'postgres+rds-iam://app_user@mydb.abc123.us-east-1.rds.amazonaws.com:5432/myapp')"
61+
pg_dump "$DSN" > myapp.sql
62+
```
63+
64+
Cross-account role assumption:
65+
66+
```bash
67+
rds-iam-to-dsn 'postgres+rds-iam://app_user@mydb.abc123.us-east-1.rds.amazonaws.com:5432/myapp?assume_role_arn=arn:aws:iam::123456789012:role/db-connect&assume_role_session_name=foo'
68+
```
69+
70+
## Troubleshooting
71+
72+
`PAM authentication failed for user "<user>"`
73+
74+
- This indicates IAM database authentication failed, but the message itself is not specific.
75+
- Check RDS IAM auth error logs in CloudWatch:
76+
`/aws/rds/instance/<db-instance-identifier>/iam-db-auth-error`
77+
78+
`pg_hba.conf rejects connection for host "...", user "...", database "...", no encryption`
79+
80+
- This usually means the connection attempt was not encrypted.
81+
- In MAC-FC, RDS parameter groups should enforce SSL. If this appears, verify the endpoint, user, and DSN being used.
82+
83+
## Notes
84+
85+
- IAM auth tokens are short-lived (typically 15 minutes). Generate DSNs close to use time.
86+
- Treat emitted DSNs as secrets while valid.

cmd/rds-iam-to-dsn/main.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
"os"
8+
"path/filepath"
9+
10+
"github.com/corbaltcode/go-libraries/pgutils"
11+
)
12+
13+
func main() {
14+
log.SetFlags(0) // no timestamps — keep output clean for CLI use
15+
16+
if len(os.Args) != 2 || os.Args[1] == "-help" || os.Args[1] == "--help" || os.Args[1] == "-h" {
17+
fmt.Fprintf(os.Stderr,
18+
"Usage: %s 'postgres+rds-iam://user@host:5432/db'\n",
19+
filepath.Base(os.Args[0]),
20+
)
21+
os.Exit(2)
22+
}
23+
24+
rawURL := os.Args[1]
25+
ctx := context.Background()
26+
27+
connectionStringProvider, err := pgutils.NewConnectionStringProviderFromURLString(ctx, rawURL)
28+
if err != nil {
29+
log.Fatalf("failed to create connection string provider: %v", err)
30+
}
31+
32+
dsnWithToken, err := connectionStringProvider.ConnectionString(ctx)
33+
if err != nil {
34+
log.Fatalf("failed to get connection string from provider: %v", err)
35+
}
36+
37+
fmt.Println(dsnWithToken)
38+
}

0 commit comments

Comments
 (0)