PostgreSQLのオフィシャルDockerイメージをSSL化したい

# What's？

DockerHubにあるPostgreSQLのオフィシャルのDockerイメージですが、こちらはSSLが有効になっていません。

[DockerHub / postgres](https://hub.docker.com/_/postgres)

これをなんとかしたいな、ということで。

# 環境

今回の環境は、こちら。

```shell
$ docker version
Client: Docker Engine - Community
 Version:           23.0.3
 API version:       1.42
 Go version:        go1.19.7
 Git commit:        3e7cbfd
 Built:             Tue Apr  4 22:05:48 2023
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          23.0.3
  API version:      1.42 (minimum version 1.12)
  Go version:       go1.19.7
  Git commit:       59118bf
  Built:            Tue Apr  4 22:05:48 2023
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.20
  GitCommit:        2806fc1057397dbaeefbea0e4e17bddfbd388f38
 runc:
  Version:          1.1.5
  GitCommit:        v1.1.5-0-gf19387a
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0
```

Docker Composeも。

```shell
$ docker compose version
Docker Compose version v2.17.2
```

PostgreSQLのイメージは、15.2のものを使うことにします。

# SSL証明書を作成する

SSL接続に関するPostgreSQLのドキュメントはこちら。

https://www.postgresql.org/docs/15/ssl-tcp.html

PostgreSQLのイメージをSSL化するにあたり、SSL証明書を作成することになるわけですが、コマンドは先に載せておきましょう。

```shell
$ openssl req -new -x509 -days 365 -nodes -text -out /path/to/server.crt -keyout /path/to/server.key -subj '/CN=example.com'
```

`/path/to`の部分は、利用する箇所に応じて変更して使うことにします。

SubjectはCommmon Nameだけ指定することにしました。

# Dockerfileを書く

身も蓋もないですが、この方法が1番まとまりがいい気がします…。

```dockerfile:Dockerfile
FROM postgres:15.2-bullseye

RUN openssl req -new -x509 -days 365 -nodes -text -out /etc/postgresql/server.crt -keyout /etc/postgresql/server.key -subj '/CN=example.com' \
    && chown postgres:postgres /etc/postgresql/server.key \
    && chmod 600 /etc/postgresql/server.key

RUN chown postgres:postgres /etc/postgresql/server.key \
    && chmod 600 /etc/postgresql/server.key

CMD ["-c", "ssl=on", "-c", "ssl_cert_file=/etc/postgresql/server.crt", "-c", "ssl_key_file=/etc/postgresql/server.key"]
```

ビルド。

```shell
$ docker image build -t charon/postgres:15.2-bullseye .
```

確認。

```shell
$ docker container run -it --rm --name postgres \
   -e POSTGRES_PASSWORD=password \
   -e POSTGRES_USER=myuser \
   -e POSTGRES_DB=example \
   charon/postgres:15.2-bullseye
```

```shell
$ docker container exec -it postgres psql -U myuser -h localhost example
psql (15.2 (Debian 15.2-1.pgdg110+1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, compression: off)
Type "help" for help.

example=#
```

OKですね。

あらかじめSSL証明書を作成しておいて

```shell
$ openssl req -new -x509 -days 365 -nodes -text -out server.crt -keyout server.key -subj '/CN=example.com'
```

コンテナに取り込むという方法でもいいと思います。

```dockerfile:Dockerfile
FROM postgres:15.2-bullseye

COPY server.key /etc/postgresql/server.key
COPY server.crt /etc/postgresql/server.crt

RUN chown postgres:postgres /etc/postgresql/server.key \
    && chmod 600 /etc/postgresql/server.key

CMD ["-c", "ssl=on", "-c", "ssl_cert_file=/etc/postgresql/server.crt", "-c", "ssl_key_file=/etc/postgresql/server.key"]
```

# オフィシャルイメージのままでなんとかしたい

とはいえ、オフィシャルイメージのままでもなんとかしたいという話はあると思うので…。

## SSL証明書の所有者を変更する

こんな感じで、SSL証明書を作成してuidを`999`にします。

```shell
$ openssl req -new -x509 -days 365 -nodes -text -out server.crt -keyout server.key -subj '/CN=example.com'
$ chmod 600 server.key
$ sudo chown 999 server.key
```

`999`は、オフィシャルイメージ内での`postgres`ユーザーのuidですね。

https://github.com/docker-library/postgres/blob/25b3034e9b0155c3e71acaf650243e7d12a571c1/15/bullseye/Dockerfile#L20-L23

前は`70`だったようなので、あまりこの方法は取りたくないものですが…。

確認。

```shell
$ docker container run -it --rm --name postgres \
   -e POSTGRES_PASSWORD=password \
   -e POSTGRES_USER=myuser \
   -e POSTGRES_DB=example \
   -v $(pwd)/server.crt:/etc/postgresql/server.crt \
   -v $(pwd)/server.key:/etc/postgresql/server.key \
   postgres:15.2-bullseye \
   -c ssl=on \
   -c ssl_cert_file=/etc/postgresql/server.crt \
   -c ssl_key_file=/etc/postgresql/server.key
```

OKですね。

```shell
$ docker container exec -it postgres psql -U myuser -h localhost example
psql (15.2 (Debian 15.2-1.pgdg110+1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, compression: off)
Type "help" for help.

example=#
```

当然ながら、uidが決め打ちになるのが難点です。

## ENTRYPOINTを調整する

あまりやりたくないですが、`ENTRYPOINT`を調整する方法もあるでしょう。

```shell
$ docker container run -it --rm --name postgres \
   -e POSTGRES_PASSWORD=password \
   -e POSTGRES_USER=myuser \
   -e POSTGRES_DB=example \
   --entrypoint bash \
   postgres:15.2-bullseye \
   -c 'openssl req -new -x509 -days 365 -nodes -text -out /etc/postgresql/server.crt -keyout /etc/postgresql/server.key -subj "/CN=example.com" && chown postgres:postgres /etc/postgresql/server.{crt,key} && chmod 600 /etc/postgresql/server.key && docker-entrypoint.sh postgres -c ssl=on -c ssl_cert_file=/etc/postgresql/server.crt -c ssl_key_file=/etc/postgresql/server.key'
```

これでできなくもないですが、ここまでするならもう`Dockerfile`を書いた方がいい気がしますね…。

Docker Composeで定義すると、多少は見やすくなるかもしれません。

```yaml:compose.yaml
services:
  postgres:
    image: postgres:15.2-bullseye
    environment:
      POSTGRES_PASSWORD: password
      POSTGRES_USER: myuser
      POSTGRES_DB: example
    entrypoint: >-
      bash -c '
        openssl req -new -x509 -days 365 -nodes -text -out /etc/postgresql/server.crt -keyout /etc/postgresql/server.key -subj '/CN=example.com' \
        && chown postgres:postgres /etc/postgresql/server.{crt,key} \
        && chmod 600 /etc/postgresql/server.key \
        && docker-entrypoint.sh postgres \
        -c ssl=on -c ssl_cert_file=/etc/postgresql/server.crt -c ssl_key_file=/etc/postgresql/server.key
        '
```