Connecting to PostgreSQL
CardNexus dev guide — IAM auth via SSO, DataGrip + psql.
CardNexus’ Aurora PostgreSQL clusters use AWS IAM Database Authentication for humans. You connect with your existing SSO identity — there are no shared passwords to leak, rotate, or paste into a chat. Tokens last 15 minutes and are refreshed automatically by your client.
This guide covers DataGrip, psql, and the underlying mechanics. If your tool isn’t here but speaks the Postgres protocol, the AWS-side steps still apply.
Overview
| Concept | Value |
|---|---|
| Auth model | AWS IAM Database Authentication (your SSO identity → a short-lived token) |
| Region | eu-west-1 |
| Port | 5432 |
| Database | cardnexus |
| Writer host | postgres.<env>.internal.cardnexus.com |
| PG roles | db_admin / db_readwrite / db_readonly (you connect as the role, not your email) |
| Token validity | 15 minutes, refreshed automatically by the client |
| SSL | Required (no-verify mode) |
| Network | Private subnets — VPN required |
tl;dr
- Set up an AWS SSO profile pointed at the account + permission set you want.
aws sso login --profile <name>.- Connect VPN.
- In your client: host =
postgres.<env>.internal.cardnexus.com, user = the PG role name (db_admin/db_readwrite/db_readonly) — not your email. AWS-IAM auth, regioneu-west-1, SSL on (no-verifymode).
Prerequisites
- AWS CLI v2 —
brew install awsclior equivalent. - VPN connected — postgres lives in private subnets; the hostname only resolves and routes from inside the workload VPC or via Client VPN. See the VPN guide.
- A SSO permission set — granted to you by ops. Determines which PG role(s) you can assume.
- A Postgres client — DataGrip (2024.x+ has native AWS IAM auth) or psql.
SSO profile setup (one-time)
Run aws configure sso
aws configure sso --profile dev-admin
# SSO start URL : https://cardnexus.awsapps.com/start
# SSO region : eu-west-1
# Pick the account: 583323753955 (dev) or 848474831621 (prod)
# Pick the permission set you've been granted (e.g. InfraAdmin, LimitedReadWrite, Readonly)
# Default region : eu-west-1
# Default output : jsonCreate one profile per (env × permission set) combination you regularly use. Naming convention: <env>-<role> (dev-admin, dev-readonly, prod-readonly, …).
Confirm the profile block
Profiles live in ~/.aws/config:
[profile dev-admin]
sso_start_url = https://cardnexus.awsapps.com/start
sso_region = eu-west-1
sso_account_id = 583323753955
sso_role_name = InfraAdmin
region = eu-west-1
output = jsonLogging in
aws sso login --profile dev-adminOpens a browser, you complete SSO once, the CLI caches the session in ~/.aws/sso/cache/. Session length depends on the permission set:
| Permission set | Session duration |
|---|---|
| InfraAdmin | 4 h |
| Readonly | 4 h |
| LimitedReadWrite | 4 h |
| InfraContributor | 8 h |
Sanity check:
aws sts get-caller-identity --profile dev-admin
# expect: an ARN like arn:aws:sts::583323753955:assumed-role/AWSReservedSSO_InfraAdmin_xxxx/you@cardnexus.comWhen the session expires you’ll start seeing ExpiredToken errors. Just re-run aws sso login.
Which PG role do I use?
The username you put in your client is the Postgres role name, not your email. AWS IAM controls which roles you can assume; once you’ve assumed one, Postgres sees you as that role.
Use the PG role name, not your email — the User field takes db_admin / db_readwrite / db_readonly. AWS IAM does the identity mapping; Postgres just sees the role.
| Your SSO permission set | PG role(s) you can connect as | Where (envs) |
|---|---|---|
InfraAdmin | db_admin, db_readwrite, db_readonly | all envs (AdministratorAccess covers everything) |
InfraContributor | db_admin, db_readwrite, db_readonly | dev only (PowerUserAccess) |
LimitedReadWrite | db_readwrite, db_readonly | dev / staging only |
Readonly | db_readonly | all envs |
What each role can do at the Postgres level:
| PG role | Privileges |
|---|---|
db_admin | rds_superuser — full DDL/DML, role management, everything bar the real PG superuser bit (Aurora reserves it). |
db_readwrite | Schema USAGE + SELECT/INSERT/UPDATE/DELETE on tables, USAGE on sequences. No DDL. |
db_readonly | Schema USAGE + SELECT on tables, USAGE+SELECT on sequences. Read everything, change nothing. |
Audit trail: every rds:GenerateDbAuthToken call is logged in CloudTrail with your SSO identity, even though the PG-wire identity is the shared role name. So select current_user; returns db_readonly for everyone in the readonly group, but CloudTrail can answer “who got a token for db_readonly at 14:32?”.
DataGrip
Tested on DataGrip 2024.x+. Older versions may not have the native AWS auth dropdown — fall back to the psql one-shot token method below.
Create the data source
| Field | Value |
|---|---|
| Driver | PostgreSQL |
| Host | postgres.dev.internal.cardnexus.com (or postgres.prod... for prod) |
| Port | 5432 |
| Database | cardnexus |
| User | the PG role name (e.g. db_admin) — NOT your email |
| Authentication | AWS (dropdown — pick this, not “User & Password”) |
| AWS Region | eu-west-1 |
| Profile | the AWS profile you created (e.g. dev-admin) |
| Credentials | ”From profile” — DataGrip uses the AWS SDK to call rds:GenerateDbAuthToken and refreshes the token automatically while the connection’s open |
Configure the SSL tab
| Field | Value |
|---|---|
| Use SSL | âś“ (Aurora rejects IAM-auth attempts without SSL) |
| Mode | require if you’ve imported the AWS RDS CA bundle into DataGrip’s truststore; otherwise no-verify. |
Aurora rejects IAM-auth attempts without SSL. Leave Use SSL checked.
Test the connection
Hit “Test Connection”. DataGrip:
- Reads SSO cache from
~/.aws/sso/cache/ - Calls
rds:GenerateDbAuthTokenfor your username + host + region - Opens the Postgres connection with the token as the password
- Performs the SSL handshake
Once connected:
select current_user, current_database(), version();
-- db_admin | cardnexus | PostgreSQL 17.7 ... aarch64psql
One-shot connection
PGPASSWORD=$(aws rds generate-db-auth-token \
--hostname postgres.dev.internal.cardnexus.com \
--port 5432 \
--username db_admin \
--region eu-west-1 \
--profile dev-admin) \
psql "host=postgres.dev.internal.cardnexus.com \
port=5432 \
dbname=cardnexus \
user=db_admin \
sslmode=no-verify"The PGPASSWORD=$(...) expansion grabs a fresh 15-minute token and feeds it to libpq.
Token’s good for 15 min — if you idle longer than that and the connection drops, reconnect.
Service accounts (app_user)
Backend-api, public-api, and Trigger.dev connect as app_user via a static password (TD can’t IAM-auth from outside AWS). You usually shouldn’t use this — IAM auth is cleaner — but if you need it for a migration script or one-off ETL:
aws secretsmanager get-secret-value \
--secret-id dev/postgres/app-user-credentials \
--region eu-west-1 \
--profile dev-admin \
--query SecretString --output text | jq -r .url
# prints the full POSTGRES_URI: postgresql://app_user:...@postgres.dev.internal.cardnexus.com:5432/cardnexus?sslmode=no-verifyDon’t paste that URI anywhere persistent. The password rotates via terragrunt apply on the postgres-roles unit.
SSL: why no-verify?
sslmode=require in libpq just means “encrypt the connection”. Most node-postgres setups, however, treat require as “encrypt + verify the cert” — and the Amazon RDS CA bundle isn’t in the default Node trust store. The connection fails the verification step.
sslmode=no-verify (a node-postgres extension that libpq also accepts in many wrappers) keeps the channel encrypted but skips cert validation. Acceptable here because:
- The cluster has
publicly_accessible = falseand lives in a private subnet. - The Aurora SG only admits VPC CIDR + the shared VPC (which the VPN SNATs to).
- IAM auth is happening over a network you control — we trust the routing, not the cert chain.
If you want full verify: download the global RDS CA bundle , import it into your client’s trust store, and switch to sslmode=verify-full.
Connection details cheat sheet
| Env | Writer hostname | Reader hostname | Account ID |
|---|---|---|---|
| dev | postgres.dev.internal.cardnexus.com | postgres-ro.dev.internal.cardnexus.com | 583323753955 |
| prod | postgres.prod.internal.cardnexus.com | postgres-ro.prod.internal.cardnexus.com | 848474831621 |
Always port = 5432, database = cardnexus. The reader hostname is for queries that don’t need write capability — useful for analytics, but in dev it resolves to the same instance as the writer (single-instance cluster).
Troubleshooting
password authentication failed for user "<your-email>"
You put your SSO email in the User field. Use the PG role name (db_admin / db_readwrite / db_readonly) instead. AWS IAM does the identity mapping; Postgres just sees the role.
password authentication failed for user "db_admin" (or another role)
One of:
- SSO session expired →
aws sso login --profile <name>. - Token expired (15 min) → reconnect; DataGrip refreshes automatically, psql needs a new token.
- Wrong AWS region in the token request — must be
eu-west-1. - SSL not enabled in the client — Aurora rejects IAM auth without SSL.
rds_iamgrant missing on the role — should never happen, but flag in #infra if so.
FATAL: no pg_hba.conf entry for host "..." user "..." SSL off
SSL not configured properly. Verify the SSL tab in DataGrip / the sslmode= param in psql.
connection refused / timeout
Not on VPN, or the VPN doesn’t route to the workload VPC. Check dig +short postgres.dev.internal.cardnexus.com — should return a 10.50.x.x (dev) or 10.52.x.x (prod) address.
AccessDenied: ... is not authorized to perform: rds-db:connect
Your SSO permission set’s IAM policy doesn’t allow connecting as that PG user. Check the table at the top of this guide — e.g. Readonly can only connect as db_readonly, not db_admin. If you’re sure your group should have access and it doesn’t, ping #infra.
ExpiredToken: The security token included in the request is expired
Your SSO session expired. aws sso login --profile <name> and try again.
What’s happening under the hood
Token validity: 15 minutes from generation. DataGrip refreshes transparently while the connection is open. The SSO session above (4-8 h) is what’s actually backing all of this — when it expires, no more tokens.