自宅内のDocker Registryコンテナにプライベートなまま正規証明書でアクセスする方法

January 02, 2024
tag icon
docker
tag icon
ipv6
tag icon
tls
tag icon
synology

開発したものをDockerコンテナで稼働させるようになると、プライベートなレジストリを立ててDockerイメージを管理したくなりますよね。

ただ最新のDockerはデフォルトのセキュリティが強化されており、自己証明書の許可やTLS無効化をするためには1つ1つのクライアントで設定が必要でちょっと面倒です。

IPv6環境+Synology NAS利用という条件がありますが、無料で、かつ、ポート開放などすることなく、正規証明書を使ってレジストリの構築ができる構成をご紹介します。

システム概要

システム概要イメージは下記です(下図のIPv6アドレスはドキュメント用のダミーです)。

正規証明書プライベートDocker Registry構成概要

システム要件

本項では以下のシステム要件が必要です。

動作概要

まずDockerレジストリを動かすSynology NAS(図中右下)が自身のグローバルIPv6アドレスを使って、DDNSサービス(図中右上)にDDNS登録をします(図中(1))。続いてDDNSサービスで登録したURL(図ではexample.synology.me)を使って、Let's EncriptでTLS用の証明書を発行してもらい、Synology NASに登録します(図中(2))。

Synology NAS上でDocker Registryコンテナを起動させるとともに、Synology NASに登録した証明書をコンテナ内のWEBサーバで使えるようにします(図中(3))。

Dockerイメージをpull/pushするクライアント(図中左下)がDockerレジストリのURL(図ではexample.synology.me)をDNSで名前解決すると、Synology NASのグローバルIPv6アドレスが返ってくるので、クライアントはNASとIPv6で通信します(図中(4))。

NAS側ではDocker Registryコンテナが公開しているポートにクライアントからリクエストが来ると、IPv4にNATして、Docker内部ネットワークに転送します(図中(5))。コンテナ内のWEBサーバが自分のURL(図ではexample.synology.me)用に発行された証明書を提示すると、Let's Encryptのルート証明書は信頼されているので、クライアントとの間でTLS通信が成立します。

なお、今回の構成では、Let's Encryptが90日ごとに証明書を更新するのに合わせて、コンテナ用証明書も都度更新する必要があります。Synology NASでリバースプロキシを利用するとTLS終端がリバースプロキシになるため回避できますが、今回は別途自動更新用のコードを書く予定です。

構成のポイント

1つ目のポイントは、DDNSサービスに登録しても問題ないグローバルIPv6アドレスがDockerレジストリ(を動かすサーバー)に付与されていて、DockerクライアントもIPv6を用いて当該レジストリにアクセス可能になっていることです。

IPv4だとグローバルアドレスを持っているのがルーターだけなので、通常このアドレスをDDNSに登録します。しかし、そうすると、そのURLを使ってLAN内からアクセスする場合には、ヘアピンNATと呼ばれる機能がルーターに必要です。

他方IPv6のグローバルアドレスであれば、Dockerレジストリ(を動かすサーバー)そのもののアドレスをDDNSに登録してもよいですし、そのIPv6アドレスでそのままLAN内デバイスが通信することができます。ルーターに特別な機能は不要です(フレッツ光IPv6 IPoE対応して、そのサービスを利用していればよいです)。

なお、今回Dockerコンテナを使ってDockerレジストリを立ち上げますが、Dockerはbridge接続であればIPv6で受け付けた通信もIPv4に変換してDocker内ネットワークと通信してくれるので、IPv6オプションを有効化する必要はないです。

2つ目のポイントはDDNSサービスがLet’s EncryptのDNS-01チャレンジに対応していることです。

TLSで使う証明書には「このURLは本物で別人がなりすましていない」という情報を書き込む必要があるため、Let’s Encryptは証明書の発行にあたって、そのチェックを行う必要があります。そのチェック方法にはHTTPを使う方法とDNSを使う方法があります。

HTTPを使う方法の場合は、DDNSで指定されたIPアドレスのサーバにLet’s Encryptのサーバがアクセスする必要があり、LAN側のルーターやファイアウォールでTCP80番のポート開放が必要です。他方DNSを使う方法だと、DNSサーバ側のチェックだけで済むため、LAN側のポート開放などは不要です。

SynologyのDDNSサービスはDNS-01チャレンジに対応しているため、LAN側のポート開放せずに証明書を発行してもらうことができます。

構築手順

構築の流れは下記の様になります。またSynology NASはDSM7.2での手順となります。

  • IPv6でDDNSへの登録とLet’s Encrypt証明書発行
  • Docker Registryコンテナ用の認証情報の準備
  • Docker Registryコンテナ用の証明書の準備
  • Docker Registryコンテナの起動

DDNS登録とLet's Encrypt証明書発行

Synology NASの管理画面でURLの設定と証明書発行ができます。

コントロールパネル > 外部アクセス > DDNS とメニューをたどり、追加 メニューを選択すると下記のようなメニューが表示されます(スクリーンショットは一部変更している個所があります)。

2024-01-02-man01-DDNS

下記のように入力を行います。

項目名 設定値 補足
サービスプロバイダー Synology
ホスト名 任意 入力後「テスト接続」を押すとホスト名の利用可否のチェックができる模様
Synologyアカウント ※利用中のものが自動で入ります
外部アドレス(IPv4) 無効
外部アドレス(IPv6) 自動 グローバルアドレスであることを確認してください
Let's Encryptからの証明書を~ チェックを入れる

2024-01-02-man02-DDNS

問題なければすぐにDDNSおよび、証明書がすぐに利用できるようになります。

Docker Registryコンテナ用の認証情報の準備

Registryには認証方法として、テスト用途とされている簡易的なsilly、OAuth2ベースのtoken、Basic認証ベースのhtpasswdが選べますが、今回はhtpasswdを使います。

何も設定せずに、コンテナを立ち上げると、自動的にユーザーとパスワードが作られて、標準出力に出されるのですが、今回は事前にユーザーIDとパスワードを作成します。案内されているコマンド例は下記です。出力されたファイルは、コンテナにボリュームとして読ませます。

# Dockerホスト(Synology NAS)で実行
docker run --entrypoint htpasswd registry:latest -Bbn testuser testpass > ./passfile

しかし、筆者のSynologyにSSHした環境では動かなかった(コンテナ内のPATHに htpasswd がないと言われてしまった)ので、今回はMacに入っていた htpasswd を使って作成しました。

# Macコンソールで実行
htpasswd -Bbn testuser testpass > ./passfile

Docker Registryコンテナ用の証明書の準備

コンテナ内のWEBサーバが提示できるように証明書をNASからダウンロードして変換します。

コントロールパネル > セキュリティ > 証明書 とメニューをたどると、前項で設定したドメイン名とその証明書が見つかりますので、操作 > 証明書のエクスポート で証明書が格納されたZIPファイルがダウンロードできます。

ZIPファイルを解凍した場所で下記コマンドを実行します。

# Macコンソールで実行
cp privkey.pem domain.key
awk '{print $0}' cert.pem chain.pem > domain.crt

cert.pemchain.pemcat でそのまま結合すると、ファイルの末尾に改行コードが入っていないため、ファイルの切れ目で改行されずに、不正な証明書となって動きません。ここでは awk を使って自動的に改行が入るようにしています。

またこの作業はLet's Encryptの証明書自動更新ごとに行う必要があります。Synology NAS上では /usr/syno/etc/certificate/_archive/にフォルダを作って格納されています。どのフォルダの証明書が該当かはフォルダ内の renew.jsonを見ると "domains" : "example.synology.me" という記載で確かめられます。

Docker Registryコンテナの起動

コンテナマネージャ を起動して、registryコンテナイメージをDocker Hubからダウンロードします。ダウンロードしたイメージを起動します。

コンテナ名は任意、自動再起動を有効にする はチェックを入れるとよいと思います。

2024-01-02-man03-Container

詳細設定では、ポート、ボリュームと環境変数を設定します。

ポートはレジストリデフォルトの5000番がSynology NASで利用されているため、ここでは5002番を指定してます。

ボリュームは前項で作成した証明書、認証情報をNASに保存して、それぞれ/certs/auth/htpasswd にマップしてます。PUSHされたイメージの保存場所も指定してます。

2024-01-02-man04-Container

環境変数は下記の通り、TLS証明書の情報と、利用する認証情報を指定してます。

2024-01-02-man05-Container

問題なければコンテナが起動します。失敗した場合は、ログ画面から原因を調査します。

動作確認

下記のような結果が返ってくれば成功です。

# Macのコンソールで実行
curl https://testuser:[email protected]:5002/v2/_catalog

# 下記のようなレスポンスが返る
{"repositories":[]}

お疲れさまでした。

参考文献


Profile picture

i氏 システムのデザインが好きな自称システムアーキテクト。データサイエンティスト見習い。Jamstackのアーキテクチャーに感動して、Gatsbyでブログを始めました。