Docker Registry v2設定(TLS)

# このメモについて

* [docker registry](https://hub.docker.com/_/registry) v2についての備忘録
* 既に数多くの記事があるので得るものがないかもしれないが、バージョンアップしたりして追記したくなったときのベースくらいには使えるだろう
* registryとdockerホスト間でHTTP通信を使う場合は、docker-engineにinsecure-registry指定をするが、今回は指定せずHTTPS通信とする。
 * なので、TLSで暗号化するための証明書を作成する必要がある。

# 環境

* OS : Ubuntu 18.04
* Docker-ce : 19.03.5
* OpenSSL : 1.1.1
 * 自己署名証明書を作成
 * 通信のサーバ側、クライアント側としての設定作業を実施。OSにより作業ディレクトリやコマンドは変わるので、他OSでは読み替える
* リバースプロキシ : 使わない構成（v1時のようにHTTPSのためのリバプロを用意したりしない）

```
$ dpkg -l docker-ce
$ openssl version
```

# 手順

1. SANの設定（OpenSSL）
1. 自己署名証明書の作成
 1. 証明書、鍵ファイルの作成 : TLS通信のサーバ側の作業
 1. 証明書の配置、更新 : TLS通信のクライアント側の作業
 1. dockerエンジン再起動
1. Basic認証設定
 1. htpasswd（を含むパッケージ）インストール
 1. パスワードファイル作成
1. registry起動
 1. 起動用スクリプト準備
 1. registryコンテナの起動
1. 設定ファイルについて

# SANの設定（OpenSSL）

証明書を作成する前に、OpenSSL設定ファイルのv3_caセクションでSAN(Subject Alternative Name)の設定をしておく。
これで別名を追加しておかないとブラウザによりうまく通信できないことがある。

追記：SAN含む自己署名証明書を作るのが面倒なのでテスト用に [スクリプト](https://gist.github.com/niiku-y/feaf7a2d4b4111641480dd4e9f737212) 書いた。
こっちのほうがいちいちopenssl.cnfの設定を変えないで済む。

```
$ sudo cp -p /etc/ssl/openssl.cnf /etc/ssl/openssl.cnf.org 
$ sudo vi /etc/ssl/openssl.cnf  
$ sudo diff /etc/ssl/openssl.cnf /etc/ssl/openssl.cnf.org 
254,257d253
< subjectAltName=@alt_names
< [alt_names]
< DNS.1 = ubu1804.local
< IP.1 = 192.168.137.2
$ 
$ hostname
ubu1804
$ grep 192.168.137.2 /etc/hosts
192.168.137.2	ubu1804.local
$ 
```

# 自己署名証明書の作成

## 証明書、鍵ファイルの作成

TLS通信のサーバ側の作業として、証明書、鍵を作成し、（後ほど）registryコンテナに配置する。
今回、CN以外は指定せず、すべてデフォルト値。

```
$ openssl req -newkey rsa:4096 -nodes -keyout certs/ubu1804.local.key -x509 -days 365 -out certs/ubu1804.local.crt
・・略・・
Common Name (e.g. server FQDN or YOUR name) []:ubu1804.local
・・略・・
$
$ ls -l certs/
$ openssl x509 -text -noout -in certs/ubu1804.local.crt
```

上記の一番下にあるopensslコマンドで、以下のように証明書にSANが設定されていることを確認する。

```
            X509v3 Subject Alternative Name: 
                DNS:ubu1804.local, IP Address:192.168.137.2
```

## 証明書の配置、更新

TLS通信のクライアント側の作業として、証明書を所定の場所（OSごとで違う）に配置し、証明書を更新するコマンド（これもOSごとで違う）を実行する。
以下は今回のubuntu 18.04の場合。

ca-certificates.confに並んでいるファイルパスは、/usr/share/ca-certificatesからの相対パスで書かれているので、crtファイルのファイル名のみ書いている。

```
$ sudo cp -p certs/ubu1804.local.crt /usr/share/ca-certificates/ubu1804.local.crt
$ sudo cp -p /etc/ca-certificates.conf /etc/ca-certificates.conf.org
$ sudo vi /etc/ca-certificates.conf
$ sudo diff /etc/ca-certificates.conf /etc/ca-certificates.conf.org 
145d144
< ubu1804.local.crt
$ 
$ sudo update-ca-certificates 
Updating certificates in /etc/ssl/certs...
1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d...
done.
$
```

## dockerエンジン再起動

証明書を更新したら、dockerエンジンを再起動する。

```
$ sudo systemctl restart docker 
```

# Basic認証設定

レジストリにアクセスする際、登録したアカウントでないとアクセスできないよう、Basic認証を設定しておく。

## htpasswd（を含むパッケージ）インストール

```
$ sudo apt install -y apache2-utils
$ sudo dpkg -l apache2-utils
```

## パスワードファイル作成

* -nで標準出力に出力
* -Bでbcryptによる暗号化
* -bでバッチモード（インタラクティブに対するバッチ）

```
$ mkdir auth
$ htpasswd -Bbn username password > auth/htpasswd
$ ls -l auth/htpasswd
```

# registry起動

## 起動用スクリプト準備

```
$ vi run_registry.sh
$ chmod +x run_registry.sh
$ cat run_registry.sh
#!/bin/bash
# run_registry.sh

#export PROXY_SERVER=proxy_server
#export PROXY_PORT=8080
#export NO_PROXY=127.0.0.1,localhost,192.168.0.1

export REGISTRY_PORT=8443

sudo docker run -d \
  -e REGISTRY_AUTH=htpasswd \
  -e REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" \
  -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
  -e REGISTRY_HTTP_ADDR=0.0.0.0:443 \
  -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/ubu1804.local.crt \
  -e REGISTRY_HTTP_TLS_KEY=/certs/ubu1804.local.key \
  -p ${REGISTRY_PORT}:443 \
  -v $(pwd)/certs:/certs \
  -v $(pwd)/auth:/auth \
  --restart=always \
  --name registry \
  registry:2.7

#  -e HTTP_PROXY=http://${PROXY_SERVER}:${PROXY_PORT} \
#  -e HTTPS_PROXY=http://${PROXY_SERVER}:${PROXY_PORT} \
#  -e NO_PROXY=${NO_PROXY} \

$
```

## registryコンテナの起動

```
$ ./run_registry.sh 
bfb7921c550fd3d693b2453d54f5c27ff06de45c2cdc62a7bc36bfbc1779dab9
$ sudo docker ps -a
　→　ポートが「5000/tcp, 0.0.0.0:8443->443/tcp」であることを確認
$ sudo docker login ubu1804.local:8443
Username: username
Password: 
WARNING! Your password will be stored unencrypted in /home/username/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded
$
```

# 設定ファイルについて

[設定リファレンス](http://docs.docker.jp/registry/configuration.html) によると、既に環境変数で設定変更するのは推奨されない手法となっているようだ。

ここまでで最低限、使える設定にはなっているので、次に、上記と同等の設定を含むYAMLを用意して起動してみよう。

```
$ cat config.yml
version: 0.1

log:
  level: debug
  formatter: text

storage:
  filesystem:
    rootdirectory: /var/lib/registry

auth:
  htpasswd:
    realm: basic-realm
    path: /auth/htpasswd

http:
  addr: 0.0.0.0:443
  host: https://ubu1804.local
  secret: mysecretstring
  tls:
    certificate: /certs/ubu1804.local.crt
    key: /certs/ubu1804.local.key

#proxy:
#  remoteurl: https://registry-1.docker.io
#  username: username
#  password: password

# end of file
$ 
```

起動スクリプトも少し修正。こちらのほうがスッキリする。

```
$ cat run_registry2.sh 
#!/bin/bash
# run_registry2.sh

export REGISTRY_PORT=8443

sudo docker run -d \
  -v $(pwd)/config.yml:/etc/docker/registry/config.yml \
  -p ${REGISTRY_PORT}:443 \
  -v $(pwd)/certs:/certs \
  -v $(pwd)/auth:/auth \
  --restart=always \
  --name registry \
  registry:2.7

$ 
```

起動スクリプトではホスト側のポート8443を指定していたので、レジストリへのログインのときもこの8443を指定する。

```
$ sudo docker login ubu1804.local:8443
Authenticating with existing credentials...
WARNING! Your password will be stored unencrypted in /home/niiku-y/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded
$
```

これでレジストリにdockerイメージを格納（push）できる。

# 参考：registryについて

以下、参考URL。

* [docker-hub](https://hub.docker.com/_/registry)
* [github](https://github.com/docker/distribution/tree/master/registry)
* [document](http://docs.docker.jp/registry/toc.html)
* [configuration options](https://github.com/docker/distribution/blob/master/docs/configuration.md#override-configuration-options)