Docker Private Registry + TLS認証 + Basic認証 を作る時のハマりどころを解説【１０分でできる】

## 概要

今回は、会社であったり、個人開発であったりいろいろとDocker のイメージを管理する
registory を使用することがあると思いますが、
その際のprivate registry を自分で作る時のハマりどころ（自分が実際にハマったこと）を
備忘録的にまとめておこうと思います。


## 達成したいこと

タイトルに書いている通り、
TLS認証（公開鍵認証）と、Basic認証（パスワード認証）を持った
Docker Private Registry を作ることを目標にします。
また、Registryはクラウド上のインスタンスにおくことを想定し、
Ubuntu環境でRegistry の設置、
実際にRegistryにイメージをとりにいくのもUbuntu環境から取得する、という構成にします。

今回はこのような流れで執り行います。

- クラウドのインスタンス上にdocker image registry を立ち上げる（TLS無し、Basic認証無し）
- Opensslを用いて、自己証明のTLSを準備する
- 鍵などを所定の場所に置き、サービスを再起動する
- 鍵をクライアントの環境にコピーする
- クライアントからRegistryに接続を試みる

この時に慣れていないと色々とハマりどころがあると思うので、そこを中心に書きたいと思います。

## 手順１　Ubuntu環境にDockerをサクッとインストール

https://qiita.com/kenmaro/items/92811e3a4f99c6ab265a

まっさらなUbuntu１８とか２０の環境をクラウド上に作ったら、
上のリンクのコマンドを実行し、Docker, Docker-compose をワンライナーで
インストールしましょう。


## 手順２　Docker image registry 認証無しバージョンを を立ち上げてみる

私は基本的に

https://qiita.com/shakiyam/items/8f23ae73a0acd6ae493e#%E6%89%8B%E9%A0%862-%E8%87%AA%E5%B7%B1%E8%A8%BC%E6%98%8E%E6%9B%B8%E3%81%AB%E3%82%88%E3%82%8Btls

この記事を参考にさせていただきました。
ここからはこの記事の内容に沿ってレジストリを立ち上げます。

まずはTLS無し、Basic認証無しでいいのでレジストリを立ち上げてみましょう。


## 手順３　Basic認証のためのユーザ名とパスワードをhtpasswdを使って登録する

```
htpasswd -cB auth/htpasswd username
```

とすると、auth/htpasswd
に、username というユーザネームの認証情報が登録されます。
パスワードはコンソールで聞かれますので、設定します。

このauthフォルダは、レジストリを作る時にコンテナにマウントされ、
クライアントからのログイン時のBasic認証に使用されます。

このコマンドは初回に使用するコマンドであり、
２回目以降は

```
htpasswd -B auth/htpasswd user2
```
などとします。

下を参考にしました。
https://qiita.com/masakuni-ito/items/3d951035884184632b99
https://qiita.com/kimihito_/items/3fb0788f28e5b51bcaa9

## 手順４　OpenSSLを用いて鍵を準備する

このコマンドを実行し、必要な鍵 ca.key, ca.crtを作成します。
この時、上の記事とは異なる場所が少しあり、それは

-addext 

の部分です。

openssl コマンドを実行した時、

![Screen Shot 2022-07-14 at 17.36.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/519792/11cac968-8542-15ad-2f95-1856e4847714.png)

上のような画面が出ると思いますが、Common Name にはドメイン名
つまりRegistry を作るサーバのグローバルIPを書きます。

しかし、-addext 無しだと、

後にクライアントサイドから、Registry にDocker login しようとした際、

```
Error response from daemon: Get "https://xxx.xxx.xxx/v2/": x509: certificate relies on legacy Common Name field, use SANs instead
```
となります。
SANについては調べれば

https://ken-ohwada.hatenadiary.org/entry/2021/02/27/122841

この記事など色々と解説されています。

結論としては、Common Name にドメイン名を指定するのではなく、
下のコマンドのように　-addext を用いてSANを指定するようにしましょう。


```bash
sudo openssl req -newkey rsa:2048 -nodes -sha256 -keyout certs/ca.key -x509 -days 3650 -out certs/ca.crt -addext "subjectAltName = IP:xxx.xxx.xxx"
```

鍵の生成が済んだら、certs の下にca.key, ca.crt が生成されています。
それらを以下のコマンドで所定の場所に移動させ、
ca-certificates、docker の再起動を行います。

```bash
sudo cp certs/ca.* /usr/share/ca-certificates/
sudo update-ca-certificates
sudo vim /etc/ca-certificates.conf
sudo systemctl restart docker
```

ここで、３行目で sudo vim /etc/ca-certificates.conf
としていますが、最後の行に

/usr/share/ca-certificates/
からみた相対的なca.crtへのパスを表記します。

上のコマンドに従ったのであれば、ca.crt は/usr/share/ca-certificates/
直下に格納されていますから、
/etc/ca-certificates.conf
の最終行に

```/etc/ca-certificates.conf
ca.crt
```
と追加すれば大丈夫です。

## 手順５　レジストリを立ち上げる

以上が準備できれば、

```docker-compose.yml

version: '3'
services:
  registry:
    image: registry:2
    volumes:
      - ./auth:/auth
      - ./certs:/certs
      - registry:/var/lib/registry
    ports:
      - "443:443"
    environment:
      - REGISTRY_AUTH=htpasswd
      - REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm"
      - REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd
      - REGISTRY_HTTP_ADDR=0.0.0.0:443
      - REGISTRY_HTTP_TLS_CERTIFICATE=/certs/ca.crt
      - REGISTRY_HTTP_TLS_KEY=/certs/ca.key
      - REGISTRY_STORAGE_DELETE_ENABLED=true
    restart: always
  web:
    depends_on:
      - registry
    image: parabuzzle/craneoperator:latest
    ports:
      - "8000:80"
    environment:
      - REGISTRY_HOST=registry
      - REGISTRY_PORT=443
      - REGISTRY_PROTOCOL=https
      - SSL_VERIFY=false
      - REGISTRY_PUBLIC_URL=20.210.192.115
      - REGISTRY_USERNAME=username
      - REGISTRY_PASSWORD=password
      - USERNAME=username
      - PASSWORD=password
      - REGISTRY_ALLOW_DELETE=true
    restart: always
volumes:
  registry:

```

上のcompose に従い、docker-compose up -d
でレジストリを立ち上げることが可能です。


## 手順６　鍵をクライアントの環境にコピーする

鍵（ca.crt, ca.key)をクライアントサイドにコピーします。

これはscp などを用いて行います。

私の場合、クライアントサイドのホームディレクトリの
certs フォルダ内にca.crt, ca.keyを配置しました。


## 手順７　クライアントサイドの鍵を所定の位置に起き、Dockerなどを再起動する

これは手順３と同じ手順なのですが、下のコマンドをクライアントサイドで実行します。

```bash
sudo cp certs/ca.* /usr/share/ca-certificates/
```

```bash
sudo vim /etc/ca-certificates.conf
```
vim コマンドにより追記する内容は、
手順４と同じで、
/usr/share/ca-certificates/
からみた相対的なca.crtへのパスを表記します。

上のコマンドに従ったのであれば、ca.crt は/usr/share/ca-certificates/
直下に格納されていますから、
/etc/ca-certificates.conf
の最終行に

```/etc/ca-certificates.conf
ca.crt
```
と追加すれば大丈夫です。




```bash
sudo update-ca-certificates
sudo systemctl restart docker
```


## 手順８　クライアントサイドからレジストリにアクセスしてみる

以上が整ったら、クライアントサイドから

```
docker login xxx.xxx.xxx
```

xxx.xxx.xxxはレジストリが置いてあるインスタンスのIP
として、ログインできるか確かめましょう。


### 注意

ここで、
```
Error response from daemon: Get https://20.210.192.115/v2/: x509: certificate signed by unknown authority (possibly because of "crypto/rsa: verification error" while trying to verify candidate authority certificate "Internet Widgits Pty Ltd")
```

と言うようなエラーとなったときは、以下を試しましょう。

```
sudo vim /etc/docker/daemon.json
```

```
{ "insecure-registries":["xxx.xxx.xxx:yyyy"] }
```
として、xxx.xxx.xxx:yyy のところには、レジストリのIPを書きます。


ログインできたのであれば、docker push, docker pull 
などが行えるはずです。お疲れ様でした。




## まとめ

今回はdocker private registry 
を、SSL認証、Basic認証込みで構築してみました。
また、いくつかの記事に沿って実行しようとした結果うまくいかずに詰まったところ、
特にSANSの設定のところ、について言及しました。


もし同じエラーで悩んでいる方がいたら助けになれば幸いです。



今回はこの辺で。


@kenmaro