Securing Database Traffic with PgBouncer and Amazon RDS

Securing database traffic inside your network can be a great step for defense in depth. It’s also a necessity for Zero Trust Networks.

Both Amazon RDS and PgBouncer have built-in support for TLS, but it’s a little bit of work to get it set up. This tutorial will show you how.

Direct Connections

The first step is to make sure all direct connections are secure. Luckily, Amazon RDS has a parameter named rds.force_ssl for this. Once it’s applied, you’ll see an error if you try to connect without TLS. You can test this out with:

psql "postgresql://user:secret@dbhost:5432/ssltest?sslmode=disable

You’ll see an error like FATAL: no pg_hba.conf entry ... SSL off if everything is configured correctly.

There are a number of possible values for sslmode, which you can read about here. The most secure (and one we want) is verify-full, as it provides protection against both eavesdropping and man-in-the-middle attacks. This mode requires you to provide a root certificate to verify against. AWS makes this certificate available on their website.

wget https://s3.amazonaws.com/rds-downloads/rds-combined-ca-bundle.pem

To use it with psql, run:

psql "postgresql://user:secret@dbhost:5432/ssltest?sslmode=verify-full&sslrootcert=rds-combined-ca-bundle.pem"

Once connected, you should see an SSL connection line before the first prompt.

There’s also an extension you can use (useful for non-psql connections).

CREATE EXTENSION IF NOT EXISTS sslinfo;
SELECT ssl_is_used();

Now direct connections are good, so let’s secure connections from PgBouncer to the database.

PgBouncer to the Database

Follow this guide to set up PgBouncer. Once that’s completed, there are two settings to add to /etc/pgbouncer/pgbouncer.ini:

server_tls_sslmode = verify-full
server_tls_ca_file = /path/to/rds-combined-ca-bundle.pem

Restart the service

sudo service pgbouncer restart

And test it

psql "postgresql://user:secret@bouncerhost:6432/ssltest"

The connection should succeed and the server should report SSL is used.

SELECT ssl_is_used();

We’ve now successfully encrypted traffic between the bouncer and the database!

However, you’ll notice the psql prompt does not have an SSL connection line as it did before. You can also use sslmode=disable to successfully connect, and programs like tcpdump or tshark will show unencrypted traffic between the client and the bouncer. You can test this out with:

sudo tcpdump -i lo -X -s 0 'port 6432'

Run commands in psql and you’ll see plaintext statements printed.

Clients to PgBouncer

This last flow is the trickiest. PgBouncer 1.7+ supports TLS, but we need to create keys and certificates for it. For this, we’ll create a private PKI. Minica and Vault are two ways to do this.

We’ll use Minica (here are instructions for Vault). Install the latest version:

sudo apt-get install minica

And run:

minica --domains bouncerhost

We now have the files we need to connect. Add the key and certificate to /etc/pgbouncer/pgbouncer.ini:

client_tls_sslmode = require # not verify-full
client_tls_key_file = /path/to/bouncerhost/key.pem
client_tls_cert_file = /path/to/bouncerhost/cert.pem

And restart the service. To connect, we once again use verify-full but this time with the root certificate we generated above:

psql "postgresql://user:secret@bouncerhost:6432/ssltest?sslmode=verify-full&sslrootcert=minica.pem"

Confirm the SSL connection line is printed and sslmode=disable no longer works.

We’ve now successfully encrypted traffic end-to-end!

Published November 27, 2017


You might also enjoy

PgBouncer Setup

Strong Encryption Keys for Rails

Active Storage S3 Client-Side Encryption


All code examples are public domain.
Use them however you’d like (licensed under CC0).