306 lines
8.4 KiB
Markdown
306 lines
8.4 KiB
Markdown
## Generate PostgreSQL server certificate
|
||
|
||
PostgreSQL server generate **certificate private key** and a **certificate request** for the `CN = PostgreSQL server`
|
||
|
||
```bash
|
||
openssl genrsa -out postgres_server.key 4096
|
||
openssl req -new -key postgres_server.key -out postgres_server.csr
|
||
```
|
||
|
||
The CA root put the **certificate request** in a temporary location (ex. `generated` directory), generate a **signed certificate** and optionally create a **certificate full chain**.
|
||
|
||
Configuration file used by root CA:
|
||
|
||
```
|
||
[ req ]
|
||
default_bits = 4096
|
||
prompt = no
|
||
default_md = sha256
|
||
distinguished_name = dn
|
||
req_extensions = req_ext
|
||
|
||
[ dn ]
|
||
CN = PostgreSQL server
|
||
|
||
[ req_ext ]
|
||
subjectAltName = @alt_names
|
||
|
||
[ alt_names ]
|
||
DNS.1 = raxus.swgalaxy
|
||
DNS.2 = mobus.swgalaxy
|
||
DNS.3 = pgsql.swgalaxy
|
||
```
|
||
|
||
> **_NOTE:_** `CN` in CA configuration file can be diffrent than the `CN` of CSR. The CA does not replace it unless explicitly configured to do so.
|
||
|
||
```bash
|
||
openssl x509 -req \
|
||
-in generated/postgres_server.csr \
|
||
-CA rootCA.pem -CAkey rootCA.key -CAserial rootCA.srl \
|
||
-out generated/postgres_server.crt \
|
||
-days 3650 \
|
||
-sha256 \
|
||
-extensions req_ext -extfile generated/postgres_server.cnf
|
||
|
||
cat generated/postgres_server.crt rootCA.pem > generated/postgres_server.fullchain.crt
|
||
```
|
||
|
||
To inspect a certificate in Linux command line:
|
||
|
||
```bash
|
||
openssl x509 -in rootCA.pem -text -noout
|
||
openssl x509 -in generated/postgres_server.crt -text -noout
|
||
```
|
||
|
||
## Generate PostgreSQL client certificate(s)
|
||
|
||
We will generate 2 certificates for:
|
||
- `CN=PostgreSQL client1`
|
||
- `CN=PostgreSQL client2`
|
||
|
||
```bash
|
||
openssl genrsa -out postgres_client1.key 4096
|
||
openssl req -new -key postgres_client1.key -out postgres_client1.csr
|
||
|
||
openssl genrsa -out postgres_client2.key 4096
|
||
openssl req -new -key postgres_client2.key -out postgres_client2.csr
|
||
```
|
||
|
||
Configuration file used by root CA will be the same for both certificates:
|
||
|
||
```
|
||
[ req ]
|
||
default_bits = 4096
|
||
prompt = no
|
||
default_md = sha256
|
||
distinguished_name = dn
|
||
req_extensions = req_ext
|
||
|
||
[ dn ]
|
||
CN = Generic Client PostgreSQL
|
||
|
||
[ req_ext ]
|
||
subjectAltName = @alt_names
|
||
|
||
[ alt_names ]
|
||
DNS.1 = anyhost.anydomain
|
||
```
|
||
|
||
```bash
|
||
openssl x509 -req \
|
||
-in generated/postgres_client1.csr \
|
||
-CA rootCA.pem -CAkey rootCA.key -CAserial rootCA.srl \
|
||
-out generated/postgres_client1.crt \
|
||
-days 3650 \
|
||
-sha256 \
|
||
-extensions req_ext -extfile generated/postgres_client.cnf
|
||
|
||
cat generated/postgres_client1.crt rootCA.pem > generated/postgres_client1.fullchain.crt
|
||
|
||
openssl x509 -req \
|
||
-in generated/postgres_client2.csr \
|
||
-CA rootCA.pem -CAkey rootCA.key -CAserial rootCA.srl \
|
||
-out generated/postgres_client2.crt \
|
||
-days 3650 \
|
||
-sha256 \
|
||
-extensions req_ext -extfile generated/postgres_client.cnf
|
||
|
||
cat generated/postgres_client2.crt rootCA.pem > generated/postgres_client2.fullchain.crt
|
||
```
|
||
|
||
|
||
## On PostgreSQL server, add the root CA certificate as trusted certificate.
|
||
|
||
Put root CA certificate (`.crt` ou `.pem`)under `/etc/pki/ca-trust/source/anchors`, then update the system trust store.
|
||
|
||
```bash
|
||
cd /etc/pki/ca-trust/source/anchors
|
||
chmod 644 rootCA.pem
|
||
update-ca-trust extract
|
||
```
|
||
|
||
```bash
|
||
openssl verify -CAfile /etc/pki/tls/certs/ca-bundle.crt <path_to>/postgres_server.crt
|
||
# Inspect the root CA certificate to get the exact CN
|
||
grep -R "<CN_name>" /etc/pki/ca-trust/extracted/
|
||
```
|
||
|
||
## Set up SSL on PostgreSQL server
|
||
|
||
Place server certificate, server private key and root CA certificate (optional but recommended) in the PostgreSQL data directory.
|
||
Default paths:
|
||
- `$PGDATA/server.key`
|
||
- `$PGDATA/server.crt`
|
||
|
||
Or override with:
|
||
|
||
```
|
||
ssl_cert_file = '/path/to/server.crt'
|
||
ssl_key_file = '/path/to/server.key'
|
||
ssl_ca_file = '/path/to/root.crt'
|
||
```
|
||
|
||
Set file pemisions:
|
||
|
||
```bash
|
||
chmod 640 $PGDATA/postgres_server.crt
|
||
chmod 600 $PGDATA/postgres_server.key
|
||
chmod 640 $PGDATA/rootCA.pem
|
||
```
|
||
|
||
> root CA certificate is required **only for mTLS** mode when the server will validate the client certificate authenticity
|
||
> Add (concatebate) all CA intermediate (if any) in `ssl_cert_file`
|
||
|
||
Enable TLS in `$PGDATA/postgresql.conf`.
|
||
|
||
```
|
||
ssl_cert_file = 'postgres_server.crt'
|
||
ssl_key_file = 'postgres_server.key'
|
||
ssl_ca_file = 'rootCA.pem'
|
||
|
||
ssl = on
|
||
ssl_ciphers = 'HIGH:!aNULL:!MD5'
|
||
ssl_prefer_server_ciphers = on
|
||
ssl_min_protocol_version = 'TLSv1.2'
|
||
ssl_max_protocol_version = 'TLSv1.3'
|
||
```
|
||
PostgreSQL will now listen for both encrypted and unencrypted connections on the **same port**.
|
||
|
||
## Server side SSL modes
|
||
|
||
### Request TLS mode
|
||
|
||
Config in `$PGDATA/pg_hba.conf`:
|
||
|
||
```
|
||
host all all all md5
|
||
```
|
||
|
||
### Require TLS mode
|
||
|
||
Config in `$PGDATA/pg_hba.conf`:
|
||
```
|
||
hostssl all all 0.0.0.0/0 md5
|
||
```
|
||
|
||
### Require TLS mode + client certificate
|
||
|
||
Config in `$PGDATA/pg_hba.conf`:
|
||
```
|
||
hostssl all all 0.0.0.0/0 cert
|
||
```
|
||
|
||
## Client side SSL modes
|
||
|
||
| Mode | Encrypts | Validates CA | Validates Hostname | Typical Use |
|
||
| --- | --- | --- | --- | --- |
|
||
| `require` | Yes | No | No | Basic encryption |
|
||
| `verify-ca` | Yes | Yes | No | Internal/IP-based |
|
||
| `verify-full` | Yes | Yes | Yes | Production |
|
||
|
||
Examples:
|
||
|
||
```bash
|
||
# mode: require
|
||
psql "host=raxus.swgalaxy port=5501 user=vplesnila dbname=postgres sslmode=require"
|
||
|
||
# mode: verify-ca
|
||
psql "host=raxus.swgalaxy port=5501 user=vplesnila dbname=postgres sslmode=verify-ca sslrootcert=rootCA.pem"
|
||
```
|
||
|
||
For `verify-full` mode the client need client certificate, client private key and root CA certificate on client side.
|
||
In our example we will use previously generated certificats for:
|
||
- `CN=PostgreSQL client1`
|
||
- `CN=PostgreSQL client2`
|
||
|
||
Set file pemisions:
|
||
|
||
```bash
|
||
chmod 640 postgres_client1.crt
|
||
chmod 600 postgres_client1.key
|
||
|
||
chmod 640 postgres_client2.crt
|
||
chmod 600 postgres_client2.key
|
||
|
||
chmod 640 rootCA.pem
|
||
```
|
||
|
||
In `verify-full` mode we can get ride of client password by mapping the `CN` in the certificate to a **local PostgreSQL role** (aka **local user**).
|
||
Create local roles (with no password) in PostgreSQL instance:
|
||
```sql
|
||
CREATE ROLE app1 LOGIN;
|
||
CREATE ROLE app2 LOGIN;
|
||
```
|
||
|
||
Add in `$PGDATA/pg_ident.conf`:
|
||
|
||
```
|
||
# MAPNAME SYSTEM-IDENTITY PG-USERNAME
|
||
certmap_app1 "PostgreSQL client1" app1
|
||
certmap_app2 "PostgreSQL client2" app2
|
||
```
|
||
|
||
Add in `$PGDATA/pg_hba.conf`:
|
||
|
||
```
|
||
hostssl all app1 0.0.0.0/0 cert map=certmap_app1
|
||
hostssl all app2 0.0.0.0/0 cert map=certmap_app2
|
||
```
|
||
|
||
> Restart PostgreSQL instance after modifying `$PGDATA/pg_ident.conf` and `$PGDATA/pg_hba.conf`.
|
||
|
||
Connect in `verify-full` mode using certificate for authentification:
|
||
|
||
```bash
|
||
# mode: verify-full
|
||
psql "host=raxus.swgalaxy port=5501 user=app1 dbname=postgres sslmode=verify-full sslrootcert=rootCA.pem sslcert=postgres_client1.crt sslkey=postgres_client1.key"
|
||
|
||
psql "host=raxus.swgalaxy port=5501 user=app2 dbname=postgres sslmode=verify-full sslrootcert=rootCA.pem sslcert=postgres_client2.crt sslkey=postgres_client2.key"
|
||
```
|
||
|
||
As `SAN` **(Subject Alternative Name)** of the **server** certificate match to:
|
||
- `raxus.swgalaxy`
|
||
- `mobus.swgalaxy`
|
||
- `pgsql.swgalaxy`
|
||
|
||
it is possible to connect to all theses DNS enteries using the same **server** certificate.
|
||
Example:
|
||
|
||
```bash
|
||
psql "host=pgsql.swgalaxy port=5501 user=app1 dbname=postgres sslmode=verify-full sslrootcert=rootCA.pem sslcert=postgres_client1.crt sslkey=postgres_client1.key"
|
||
```
|
||
|
||
> ⭐ Server certificates → client checks SAN/CN for hostname
|
||
> ⭐ Client certificates → server checks only CN for user identity
|
||
|
||
> **IMPORTANT**: we can mix TLS authentification by certificate and password.
|
||
PostgreSQL processes pg_hba.conf top‑to‑bottom, and the first matching rule wins.
|
||
Once a connection matches a line (based on type, database, user, address, etc.), PostgreSQL stops and applies that rule’s authentication method.
|
||
|
||
The folowing `$PGDATA/pg_hba.conf` allows authentification using certificates for PostgreSQL users app1 and app2 and authentification using password for all other users.
|
||
|
||
```
|
||
hostssl all app1 0.0.0.0/0 cert map=certmap_app1
|
||
hostssl all app2 0.0.0.0/0 cert map=certmap_app2
|
||
hostssl all all 0.0.0.0/0 md5
|
||
```
|
||
|
||
Check if connections are using TLS:
|
||
|
||
```sql
|
||
SELECT * FROM pg_stat_ssl;
|
||
```
|
||
|
||
Check for connection username:
|
||
```sql
|
||
select pid as process_id,
|
||
usename as username,
|
||
datname as database_name,
|
||
client_addr as client_address,
|
||
application_name,
|
||
backend_start,
|
||
state,
|
||
state_change
|
||
from pg_stat_activity;
|
||
```
|