Dockerのpostgres公式イメージにはSSL/TLS設定を行う手段に公式には対応していないが、
独自のイメージを作らずに何とかできないかと調べた結果をメモする。
なお、できるにはできたが苦肉の策なのであまり推奨はしない。(Entrypointの可読性が酷い。。。)
前提条件
- 他のコンテナとかも同じ証明書や秘密鍵で済ませたいので、Dockerホスト側に事前に作成した証明書の格納ディレクトリをコンテナにマウントさせる。
※以下の記載におけるDockerホスト上の証明書と秘密鍵の場所は/etc/ssl/certs
配下にあるものとする。 - Dockerホスト側にはpostgresユーザはいないので、この証明書と秘密鍵のオーナーはpostresユーザではない。(今回はroot)
-rw-r--r--. 1 root root 1562 May 21 17:38 /etc/ssl/certs/docker/server.crt
-rw-------. 1 root root 1704 May 21 17:38 /etc/ssl/certs/docker/server.key
- 公式イメージベース(FROM:postgres)で独自イメージを作って、証明書をイメージの中に含めると更新できないので起動時のパラメータ設定で何とかできるようにする。
- 以下の試したことに記載する"postgres-data"は事前に作成したデータボリュームを意味する。
docker volume create --driver local postgres-data
前提知識
- postgres(デーモンの起動プログラム)のオプションに
-c name=value
といった形式で名前付きパラメータを指定することができ、 postgresql.confにSSL設定をしなくても以下のように起動オプションを付けることでSSL設定を行うことができる。
postgres -c ssl=on -c ssl_cert_file=${サーバー証明書のパス} -c ssl_key_file=${秘密鍵のパス}
試したことその1(失敗)
Dockerホストの証明書と秘密鍵をそのままマウントさせて起動オプションに↑の前提知識の記載内容のオプションを指定して起動してみる。
起動コマンド
docker run -d --name postgres \
-v postgres-data:/var/lib/postgresql/data \
-v /etc/ssl/certs/docker:/etc/ssl/certs/postgres \
-e POSTGRES_PASSWORD="postgres-passw0rd" \
postgres:11 \
-c ssl=on \
-c ssl_cert_file=/etc/ssl/certs/postgres/server.crt \
-c ssl_key_file=/etc/ssl/certs/postgres/server.key
結果
[test_user@docker-host ~]$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ff22f668ed21 postgres:11 "docker-entrypoint.s…" 7 seconds ago Exited (1) 4 seconds ago postgres
[test_user@docker-host ~]$ docker logs postgres
The files belonging to this database system will be owned by user "postgres".
This user must also own the server process.
・・・省略・・・
PostgreSQL init process complete; ready for start up.
2019-05-22 07:57:44.136 UTC [1] FATAL: could not load private key file "/etc/ssl/certs/postgres/server.key": Permission denied
2019-05-22 07:57:44.136 UTC [1] LOG: database system is shut down
[test_user@docker-host ~]$
could not load private key file "/etc/ssl/certs/postgres/server.key": Permission denied
にあるように秘密鍵のパーミッションが600でpostgresユーザのものではないため、起動できない。。。(当たり前だった)
この結果を踏まえて、ホストOSにpostges(uid=70)ユーザを作って証明書と秘密鍵のオーナーを合わせればうまくいったが、前提条件に記載した通り他のコンテナでも同じものを利用したいので不都合のため、これを成功とはしない。。。
試したことその2(失敗)
公式イメージのentrypointでは、"/docker-entrypoint-initdb.d"にシェル(.sh)やSQLスクリプト(.sql or *.sql.gz)を置くと実行してくれることを思い出して、ホストOS上の以下のシェルを置いた場所を"/docker-entrypoint-initdb.d"にマウントさせてみた。
#!/bin/bash
set -euo pipefail
cp -p /etc/ssl/certs/postgres/server.* /var/lib/postgresql/
chown postgres:postgres /var/lib/postgresql/server.*
# 念のためkeyを-rw-------にする
chmod 0600 /var/lib/postgresql/server.key
起動コマンド
docker run -d --name postgres \
-v postgres-data:/var/lib/postgresql/data \
-v /etc/ssl/certs/docker:/etc/ssl/certs/postgres \
-v $(pwd)/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d:ro \
-e POSTGRES_PASSWORD="postgres-passw0rd" \
postgres:11 \
-c ssl=on \
-c ssl_cert_file=/var/lib/postgresql/server.crt \
-c ssl_key_file=/var/lib/postgresql/server.key
結果
[test_user@docker-host postgres]$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e38f27d22a63 postgres:11 "docker-entrypoint.s…" 6 seconds ago Exited (1) 2 seconds ago postgres
[test_user@docker-host postgres]$ docker logs postgres
The files belonging to this database system will be owned by user "postgres".
This user must also own the server process.
・・・省略・・・
waiting for server to start....2019-05-22 08:05:13.718 UTC [40] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
2019-05-22 08:05:13.730 UTC [41] LOG: database system was shut down at 2019-05-22 08:05:13 UTC
2019-05-22 08:05:13.733 UTC [40] LOG: database system is ready to accept connections
done
server started
/usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/copy_cert.sh
cp: cannot open '/etc/ssl/certs/postgres/server.key' for reading: Permission denied
[test_user@docker-host postgres]$
これを試してわかったことは、DockerfileにUSER
指定がないので起動ユーザはrootだと勝手に思い込んでいたが、実際にはシェルのなかでsu-exec postgres $@
としているので、実質的な起動ユーザはpostgresであることがわかった。(シェルだけはrootで実行してほしかったなぁ。。。)
試したことその他3(成功=結論)
しょうがないので苦肉の策として、イメージのentrypointを以下のように上書きしてやることにした。(ようは↑のcopy_cert.shの内容を実行してからオリジナルのdocker-entrypoint.shを実行する)
起動コマンド
docker run -d --name postgres \
-v postgres-data:/var/lib/postgresql/data \
-v /etc/ssl/certs/docker:/etc/ssl/certs/postgres \
-e POSTGRES_PASSWORD="postgres-passw0rd" \
--entrypoint /bin/sh \
postgres:11 \
-c 'cp -f /etc/ssl/certs/postgres/server.* /var/lib/postgresql/ && chown postgres:postgres /var/lib/postgresql/server.* && chmod 0600 /var/lib/postgresql/server.key && docker-entrypoint.sh -c ssl=on -c ssl_cert_file=/var/lib/postgresql/server.crt -c ssl_key_file=/var/lib/postgresql/server.key'
余談になるが、どうやらコマンドラインから実行するときの"--entrypoint"オプションにスペースを含んだ文字列を指定するとうまくいかなかったので上のような記載になっている。。
結果
[vagrant@docker-host postgres]$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f1ba7135cd32 postgres:11 "/bin/sh -c 'cp -f /…" 8 seconds ago Up 6 seconds 5432/tcp postgres
[vagrant@docker-host postgres]$ docker logs postgres
The files belonging to this database system will be owned by user "postgres".
This user must also own the server process.
・・・省略・・・
PostgreSQL init process complete; ready for start up.
2019-05-22 08:06:46.826 UTC [9] LOG: listening on IPv4 address "0.0.0.0", port 5432
2019-05-22 08:06:46.827 UTC [9] LOG: listening on IPv6 address "::", port 5432
2019-05-22 08:06:46.828 UTC [9] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
2019-05-22 08:06:46.839 UTC [53] LOG: database system was shut down at 2019-05-22 08:06:46 UTC
2019-05-22 08:06:46.843 UTC [9] LOG: database system is ready to accept connections
[vagrant@docker-host postgres]$
SSL接続確認
[vagrant@docker-host postgres]$ docker exec -it postgres bash
root@f1ba7135cd32:/# psql 'postgres://postgres:postgres-passw0rd@localhost:5432/postgres?sslmode=require'
psql (11.3 (Debian 11.3-1.pgdg90+1))
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.
postgres=# quit
root@f1ba7135cd32:/# exit
localhostだが、ssl接続できていることが確認できる。
最終版(docker-composeによるまとめ)
version: '3.5'
services:
postgres-server:
image: postgres:11
container_name: postgres-server
hostname: postgres-server
restart: always
ports:
- "5432:5432"
networks:
- container-link
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
# 初期処理時にentrypoint.shからコールされる初期化シェル・スクリプト群
- ./services/postgres/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d:ro
# データ永続化はデータボリュームで。
- postgres-data:/var/lib/postgresql/data
# ホスト上の共通サーバ証明書/秘密鍵のディレクトリをマウント
- /etc/ssl/certs/docker:/etc/ssl/postgres:ro
# Timezoneをホストと一緒にする
- /etc/localtime:/etc/localtime:ro
# 1) マウントしたディレクトリの証明書と秘密鍵をコンテナ内にコピー
# 2) 証明書と秘密鍵のオーナーを変更
# 3) 念のため、秘密鍵のパーミッションを600にする(ホスト側がなっていれば不要)
# 4) オリジナルのdocker-entrypoint.shに名前付き引数にSSL設定して実行
entrypoint: >
/bin/sh -c "cp -f /etc/ssl/postgres/server.* /var/lib/postgresql/ &&
chown postgres:postgres /var/lib/postgresql/server.* &&
chmod 0600 /var/lib/postgresql/server.key &&
docker-entrypoint.sh -c ssl=on -c ssl_cert_file=/var/lib/postgresql/server.crt -c ssl_key_file=/var/lib/postgresql/server.key"
# 接続確認用コンテナ
postgres-client:
# alpineベースのpostgres-clientがあったので拝借
image: jbergknoff/postgresql-client
container_name: postgres-client
hostname: postgres-client
depends_on:
- postgres-server
networks:
- container-link
volumes:
# サーバー証明書を証明したルート証明書の場所
# (${HOME}/.postgresql/root.crtとしておくとCommonNameで検証ができる
- /etc/ssl/certs/docker:/root/.postgresql:ro
# このコンテナイメージのEntryPointは"psql"になっているので接続URLを渡してあげる
command:
- "postgres://postgres:postgres-passw0rd@postgres-server.docker.internal:5432/postgres?sslmode=verify-full"
# 落ちないように(後でdocker execで検証するため)
tty: true
networks:
# docker-composeで勝手にデフォルトネットワークが生成される予防。(自動で全コンテナが所属する)
default:
external:
name: bridge
# コンテナ間通信用のネットワークセグメント(各サービスの"${hostname}.docker.internal"でFQDNな感じの文字列でアクセスできる)
container-link:
name: docker.internal
driver: bridge
ipam:
driver: default
config:
- subnet: "172.20.0.0/16"
volumes:
# postgresコンテナのデータ永続化用のデータボリューム
postgres-data:
name: postgres-data
確認
↑の定義の接続クライアントはpostgres-serverがアクセスできるようになるまで待つ必要があるので、別々に起動する。
depends_onは起動順序は制御できるけど、Postgresサーバーが接続できるようになるまでは待ってくれるわけではない。
[test_user@docker-host test]$ docker-compose up -d postgres-server
Creating network "docker.internal" with driver "bridge"
Creating volume "postgres-data" with default driver
Creating postgres-server ... done
[test_user@docker-host test]$ docker logs -t postgres-server
The files belonging to this database system will be owned by user "postgres".
This user must also own the server process.
・・・省略・・・
2019-05-22 17:34:00.004 JST [9] LOG: listening on IPv4 address "0.0.0.0", port 5432
2019-05-22 17:34:00.004 JST [9] LOG: listening on IPv6 address "::", port 5432
2019-05-22 17:34:00.006 JST [9] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
2019-05-22 17:34:00.017 JST [53] LOG: database system was shut down at 2019-05-22 17:33:59 JST
2019-05-22 17:34:00.020 JST [9] LOG: database system is ready to accept connections
[test_user@docker-host test]$ docker-compose up -d postgres-client
postgres-server is up-to-date
Creating postgres-client ... done
[test_user@docker-host test]$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
88d604f8b9ac jbergknoff/postgresql-client "psql postgres://pos…" 4 seconds ago Up 3 seconds postgres-client
f89b5e18e50d postgres:11 "/bin/sh -c 'cp -f /…" About a minute ago Up About a minute 0.0.0.0:5432->5432/tcp postgres-server
[test_user@docker-host test]$ docker exec -it postgres-client psql
psql: could not connect to server: No such file or directory
Is the server running locally and accepting
connections on Unix domain socket "/tmp/.s.PGSQL.5432"?
[test_user@docker-host test]$ docker exec -it postgres-client sh
/ # exit
[test_user@docker-host test]$ docker logs -t postgres-client
2019-05-22T08:35:22.554877080Z psql (10.3, server 11.3 (Debian 11.3-1.pgdg90+1))
2019-05-22T08:35:22.554917194Z WARNING: psql major version 10, server major version 11.
2019-05-22T08:35:22.554921391Z Some psql features might not work.
2019-05-22T08:35:22.554924196Z SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
2019-05-22T08:35:22.554927753Z Type "help" for help.
2019-05-22T08:35:22.554930560Z
[test_user@docker-host test]$ docker exec -it postgres-client sh
/ # psql "postgres://postgres:postgres-passw0rd@postgres-server.docker.internal:5432/postgres?sslmode=verify-full"
psql (10.3, server 11.3 (Debian 11.3-1.pgdg90+1))
WARNING: psql major version 10, server major version 11.
Some psql features might not work.
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.
postgres=#
余談
サーバー証明書の検証について
psqlでサーバーのドメイン名などの検証する場合は、sslmode=verify-full
などを指定するが、このときにサーバー証明書を署名したルート証明書は"~/.postgresql/root.crt"に置くということを初めてしった。
SSL接続の強制などについて。
pg_hba.confにてSSLアクセスを強制させたい場合などについてだが、これについてはpg_hba.confのオーナーはpostgresユーザであるため、"/docker-entrypoint-initdb.d"配下にpg_hba.confをsedとかで編集するシェルを置けばよい。
ちなみにデフォルトのpg_hba.confはdocker-entrypoint.shの中で追記されているのでデフォルト設定とは多少違うみたい。。。