We use PgBouncer to listen with TLS and pass through to a cluster Postgres server listening without TLS. This ensures that connections from the outside world into the cluster are encrypted.
(We don’t worry about internal cluster connections for now. In the future, we should use cluster networking that does e2ee for us.)
No SNI #
Traefik IngressRoute
understands HTTPS well enough to do “Server Name Indication” (SNI).
It can route the request to different apps depending on the domain name.
E.g. it can tell the difference between radarr.example.com
and sonarr.example.com
,
and route requests to those two domains to different apps.
TCP connections do not have an SNI feature, so for SSH ports for git or Postgres ports for database connections, you cannot differentiate based on the hostname like this.
To deal with this, you can either assign a dedicated LoadBalancer IP address to each PgBouncer, or just use separate ports.
Future work #
TODO: can PgBouncer be configured as a kind of generic Postgres frontend to any database?
I think this is possible via the PGBOUNCER_DSN_*
environment variables in the
bitnami container.
If this really works:
- Use a generic DNS name like
pgbouncer.micahrl.me
- Move the bouncer to the
ingress
namespace - Show examples for connecting to other databases
PgBouncer and superuser access #
You cannot connect through PgBouncer as a Postgres superuser. If you need this, spin up a pod to run the psql client instead, like:
PGPASSWORD="$(k get secret -n datadump datadumppg-superuser -ojson | jq -r '.data.password | @base64d')"
k run -n datadump psql-client --rm --env=PGPASSWORD="$PGPASSWORD" -it --image=postgres -- psql -h datadumppg-rw -U postgres datadump
PgBouncer requirements #
See the datadump cluster for a full example.
In short:
Run these statements against the db
(here they are run during initdb
in the CloudNative-PG Cluster resource).
They set up a pgbouncer_auth role,
and allow it to authenticate other users.
# Create a role for pgbouncer that allows it to authenticate users
- CREATE ROLE pgbouncer_auth;
- CREATE SCHEMA pgbouncer AUTHORIZATION pgbouncer_auth;
- GRANT CONNECT ON DATABASE datadump TO pgbouncer_auth;
- GRANT SELECT ON pg_shadow TO pgbouncer_auth;
- |+
CREATE FUNCTION pgbouncer.get_auth(username TEXT)
RETURNS TABLE(username TEXT, password TEXT) AS
$$
SELECT usename::TEXT, passwd::TEXT FROM pg_shadow WHERE usename = username;
$$
LANGUAGE sql SECURITY DEFINER;
# - CREATE VIEW pgbouncer.auth AS SELECT * FROM pgbouncer.get_auth('');
- GRANT SELECT ON pgbouncer.auth TO pgbouncer_auth;
Add a pgbouncer
managed role.
This allows setting a password which automatically creates a kubernetes Secret,
and gives it inherited access to the role created above with the permissions.
(Postgres roles often work like user accounts and can be assigned a password directly. Idk.)
- name: pgbouncer
login: true
passwordSecret:
name: pg-user-pgbouncer
inherit: true
inRoles:
- pgbouncer_auth
Then deploy PgBouncer configured to authenticate as pgbouncer
,
see the related Deployment and Service.