SSL証明書接続が必要な PostgresQL サーバーを docker-compose で立ち上げる

探してもあまり出てこなかったので、構築の記録を記します。
**今回紹介するのは docker 本来の使い方である immutable な使い方ではありませんので、ご注意を。**

# 参考

こちらの記事を参考にしました。

https://www.gab.lc/articles/postgresql_with_ssl/


# 環境

- Ubuntu 18.04.4 LTS
- Docker CE
- docker-compose 

``nano`` や ``cp`` を使いますが、特に記述がない場合は docker-compose が置いてあるフォルダからの相対パスだとお考え下さい。

# docker-compose

適当なフォルダを作成し、以下の2つのファイルを配置します。

```docker-compose.yaml
version: "3.5"
services:
  db:
    image: postgres:12.2
    container_name: db
    ports:
      - 5432:5432
    env_file:
      - postgres.env
    tty: true
    restart: always
    volumes:
      - ./pgdata:/var/lib/postgresql/data
```

起動すると、同じフォルダの中に pgdata というフォルダができ、その中に postgres のデータが入って来ます。

```postgres.env
POSTGRES_USER=postgres
POSTGRES_PASSWORD=password
```

postgres.env は、docker-compose.yaml の中にシークレットを保存しておきたくなかったので外持ちにしています。一度立ち上げて、 pgdata にデータが出来た後は消して問題ありません。

ファイルを用意したら、立ち上げます。

```shell
docker-compose up -d
```

# 証明書作成

まずはコンテナの中に入ります。

```sh
docker-compose exec db bash
```

``/var/lib/postgresql/data/`` にpostgresのデータが入っています。

```sh
cd /var/lib/postgresql/data/
```

あとは、基本的に愚直に参考記事の通りにコマンドを叩いていきます。

まずはサーバー関連の証明書。

```sh
mkdir cert && cd cert
openssl req -new -nodes -text -out ca.csr -keyout ca-key.pem -subj "/CN=certificate-authority"
openssl x509 -req -in ca.csr -text -extfile /etc/ssl/openssl.cnf -extensions v3_ca -signkey ca-key.pem -out ca-cert.pem
openssl req -new -nodes -text -out server.csr -keyout server-key.pem -subj "/CN=pg-server"
openssl x509 -req -in server.csr -text -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem
```

---
**追記**

100年有効にしたい場合はこちら

```sh
mkdir cert && cd cert
openssl req -new -nodes -text -out ca.csr -keyout ca-key.pem -subj "/CN=certificate-authority"
openssl x509 -req -days 36500 -in ca.csr -text -extfile /etc/ssl/openssl.cnf -extensions v3_ca -signkey ca-key.pem -out ca-cert.pem
openssl req -new -nodes -text -out server.csr -keyout server-key.pem -subj "/CN=pg-server"
openssl x509 -req -days 36500 -in server.csr -text -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem
```

---

次にクライアント用も作ります。
**DBに接続するユーザー名を変える場合は ``/CN=postgres`` の個所を変更してください。**

```sh
openssl req -new -nodes -text -out client.csr -keyout client-key.pem -subj "/CN=postgres"
openssl x509 -req -in client.csr -text -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem
```

100年有効にしたい場合はこちら

```sh
openssl req -new -nodes -text -out client.csr -keyout client-key.pem -subj "/CN=postgres"
openssl x509 -req -days 36500 -in client.csr -text -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem
```

``ls`` で確認すると、以下のファイルが出来ています。

```
ca-cert.pem  ca.csr      client-cert.pem  client-key.pem   server.csr
ca-cert.srl  ca-key.pem  client.csr       server-cert.pem  server-key.pem
```

所有者が root になっているので、postgres に変更します。

```sh
chown -R postgres:postgres ./
```

参考記事ではここから別の場所に移動していますが、この場所のまま設定していこうと思います。

# 通信の SSL 化

``nano`` を使いたいのですが、そんな高等なものはこのイメージにはインストールされていないので、一旦コンテナを抜けます。``vi`` は入っているので、使える方は ``vi`` で直接編集した方が楽です。

```sh
exit
```

pgdata フォルダに入っているデータは root じゃないと編集できないので、root になります。

```sh
sudo su
```

まずは postgres.conf から。

```sh
nano pgdata/postgresql.conf
```

``# - SSL -`` から始まるセクションを、以下のように変更します。

```conf
# - SSL -
ssl = on
ssl_ca_file = '/var/lib/postgresql/data/cert/ca-cert.pem'
ssl_cert_file = '/var/lib/postgresql/data/cert/server-cert.pem'
#ssl_crl_file = ''
ssl_key_file = '/var/lib/postgresql/data/cert/server-key.pem'
#ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers
#ssl_prefer_server_ciphers = on
#ssl_ecdh_curve = 'prime256v1'
#ssl_min_protocol_version = 'TLSv1'
#ssl_max_protocol_version = ''
#ssl_dh_params_file = ''
#ssl_passphrase_command = ''
#ssl_passphrase_command_supports_reload = off
```

次に、pg_hba.conf を編集します。

```sh
nano pgdata/pg_hba.conf
```

最後の行をコメントアウトし、新たに一行追加します。

```conf
# host all all all md5 
hostssl all             all             0.0.0.0/0               md5
```

元のユーザーに戻り、コンテナを再起動します。

```sh
exit
```
```sh
docker-compose down
docker-compose up -d
```

``psql`` がインストールされている適当な別マシンから、接続を試みます。

```sh
psql "host=<host address> user=postgres dbname=postgres"
```

次のように、``SSL connection ~~~`` と出てくれば成功です。

```psql
psql (12.2 (Ubuntu 12.2-2.pgdg18.04+1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.

postgres=#
```

# 証明書接続の強制

ここから、クライアント側に証明書による接続を強制していきます。

まず、 pg_hba.conf を編集し、最後の行のmd5をcertに変更します。

```conf
# host all all all md5 
hostssl all             all             0.0.0.0/0               cert
```

試しにこの状態で、別クライアントから先程と同じコマンドで接続すると…

```
psql: error: could not connect to server: FATAL:  connection requires a valid client certificate
FATAL:  no pg_hba.conf entry for host "<host address>", user "postgres", database "postgres", SSL off
```

このようにエラーが出ます。
サーバーの方に戻って、クライアント用の証明書を用意します。

…と言っても、証明書自体は先程一緒に作ったので、分かりやすい位置にコピーしてきます。
相変わらず root 権限が必要です。

```sh
sudo su
```

```sh
mkdir client
cp pgdata/cert/ca-cert.pem client/root.crt
cp pgdata/cert/client-cert.pem client/postgresql.crt
cp pgdata/cert/client-key.pem client/postgresql.key
```

```sh
exit
```

コピーしたものは root 権限にになっているので、現在のユーザーとグループに権限を与えます。

```sh
sudo chown -R $(id -u -n):$(id -g -n)  client
```

この client フォルダを、クライアントに配布します。
方法はUSBメモリでも ``scp`` でも問題ありませんが、**コピー先はクライアントの``~/.postgresql`` である必要があります。**

```sh
scp -r <user>@<server>:/home/<user>/<docker-compose dir>/client ~/.postgresql
```

証明書を配置したら接続します。
``sslmode=require`` あるいは ``sslmode=verify-ca`` で接続するのですが、 ~/.postgresql が存在していれば自動的に見に行ってくれるようで、指定なしでも接続出来ました。

```sh
psql "host=<host address> user=postgres dbname=postgres"
psql "sslmode=require host=<host address> user=postgres dbname=postgres"
psql "sslmode=verify-ca host=<host address> user=postgres dbname=postgres"
```

先程と同じようにログインできれば、成功です。

```psql
psql (12.2 (Ubuntu 12.2-2.pgdg18.04+1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.

postgres=#
```