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;
|
|||
|
|
```
|