Harborのコンパイルとデバッグ with easy-rsa3

# はじめに

HarborをHelmから導入した際に不可解な挙動をしたので、git cloneしたコードをビルドしてみることにしました。その際のメモを残しておきます。

# コンパイル ＆ 再コンパイルのプロセス

初期のコンパイルは、公式のガイド https://github.com/goharbor/harbor/blob/master/docs/compile_guide.md に従って操作をします。

```bash
$ git clone https://github.com/goharbor/harbor.git
$ cd harbor
## git tag で最新版を確認し、checkoutする
$ git checkout refs/tags/v1.10.0 -b my_v1.10.0
## あらかじめTLS鍵ファイルを任意の場所に準備しておき、make/harbor.ymlに記述する
## 今回は、pki/ディレクトリを作成し、配置しておきます
$ mkdir pki
$ cp ..../pki/issued/harbor.example.com.crt pki/
$ cp ..../pki/private/harbor.example.com.key pki/
## keyファイルのパスワードを解除します
$ openssl rsa -in pki/harbor.example.com.key -out pki/harbor.example.com.nopass.key
$ vi make/harbor.yml
```

make/harbor.ymlファイルの差分は次のようになっています。

```diff:make/harbor.yml
diff --git a/make/harbor.yml b/make/harbor.yml
index 4589c352b..83a9ec31d 100644
--- a/make/harbor.yml
+++ b/make/harbor.yml
@@ -2,7 +2,7 @@

 # The IP address or hostname to access admin UI and registry service.
 # DO NOT use localhost or 127.0.0.1, because Harbor needs to be accessed by external clients.
-hostname: reg.mydomain.com
+hostname: harbor.example.com

 # http related config
 http:
@@ -14,22 +14,22 @@ https:
   # https port for harbor, default is 443
   port: 443
   # The path of cert and key files for nginx
-  certificate: /your/certificate/path
-  private_key: /your/private/key/path
+  certificate: ..../git/harbor/pki/harbor.example.com.crt
+  private_key: ..../git/harbor/pki/harbor.example.com.key
 
 # Uncomment external_url if you want to enable external proxy
 # And when it enabled the hostname will no longer used
@@ -24,12 +24,12 @@ https:
 # The initial password of Harbor admin
 # It only works in first time to install harbor
 # Remember Change the admin password from UI after launching Harbor.
-harbor_admin_password: Harbor12345
+harbor_admin_password: XXXXXXXXXXXXXX

 # Harbor DB configuration 
 database:
   # The password for the root user of Harbor DB. Change this before any production use.
-  password: root123
+  password: XXXXXXXXXXX
   # The maximum number of connections in the idle connection pool. If it <=0, no idle connections are retained.
   max_idle_conns: 50
   # The maximum number of open connections to the database. If it <= 0, then there is no limit on the number of open connections.
```

ファイルを編集したところでコンパイルを実行しますが、先に挙げた問題を回避するためにsudo makeを利用しています。コンパイル方法の詳細については、https://github.com/goharbor/harbor/blob/master/docs/compile_guide.md を参照してください。

```bash:コンパイルの実行
$ sudo docker pull golang:1.13.4
$ sudo make install GOBUILDIMAGE=golang:1.13.4 COMPILETAG=compile_golangimage
```

この操作をした後では、make/harbor.ymlファイルでDBやadminのパスワードを変更し、make cleanall; make install ... を再度実行しても反映されません。

## 再コンパイル手順

git cloneしたharborディレクトリに移動した後で、作業を行ないます。

```bash
$ sudo make down
$ sudo make cleanall
## 以下の作業は必要に応じて実施
$ sudo docker system prune -a  ## Dockerイメージを登録し直したい場合
$ sudo mv /data /data.old      ## DBのパスワード・データなどを初期化・削除した場合
```

ここまでしたら再度make installを実行します。

```bash
$ sudo make install GOBUILDIMAGE=golang:1.13.4 COMPILETAG=compile_golangimage
```

# Easy-RSAによる鍵ファイルの準備

Easy-RSA3を利用して、独自プライベートネットワーク用ドメイン(example.com)用のCA局を構築しておきます。

```bash:easy-rsa-v3によるCA局の準備
$ git clone https://github.com/OpenVPN/easy-rsa.git
$ cd easy-rsa
$ git checkout refs/tags/v3.0.6 -b my_v3.0.6
$ cd ..
$ easy-rsa/easyrsa3/easyrsa init-pki
$ easy-rsa/easyrsa3/easyrsa build-ca
## Passphraseには出来るだけ長い文字列を指定、CN(Common Name)には対象となるドメインが分かる名称を指定する - e.g. "EXAMPLE.COM Easy-RSA CA"
$ ls 
easy-rsa/  pki/
```

easy-rsa/README.quickstart.md を確認すると、reqファイルは別のマシンで作成するように書かれています。これはkeyファイルとcrtファイルを同一のeas-rsa/ディレクトリ配下に収めてしまう事での情報漏洩を懸念していると思われます。利用する状況に応じて適切にファイルを分離するようにしてください。ここでは禁忌とされている同一マシン上でのgen-req, sign-req clientを実施しています。

また **build-server-full**, **build-client-full** オプションの指定があります。今回はそれでもほぼ同じ結果になりますが、柔軟にファイル名と、Common Name(CN)を分離したファイルを作成するためには、gen-req, sign-reqを順番に呼び出す方法を覚えておいた方が便利だと思うので、ここではその方法を記述しています。

```bash:"harbor.localdomain"に対応するTLS鍵を生成
## easy-rsa/, pki/ が存在するディレクトリで作業を実施
$ easy-rsa/easyrsa3/easyrsa gen-req harbor.example.com
...
Keypair and certificate request completed. Your files are:
req: .../pki/reqs/harbor.example.com.req
key: .../pki/private/harbor.example.com.key

$ easy-rsa/easyrsa3/easyrsa sign-req client harbor.example.com
$ find pki -type f | grep harbor.example.com
pki/reqs/harbor.example.com.req
pki/issued/harbor.example.com.crt
pki/private/harbor.example.com.key
```

"harbor.example.com"の部分はプライベートネットワーク内部で名前解決できるホスト名を指定します。準備したcrtファイルとkeyファイルをmake/harbor.ymlに指定します。pki/ca.crtファイルは利用するシステムが認識できるように登録しておきます。

## Ubuntu 18.04上での自己署名TLSファイルの登録について

Ubuntu 18.04ホスト上ではシステムにCAファイルを登録する場合について説明します。ca-certificatesパッケージが導入されている場合には、次の方法が基本的なフローになります。

1. /usr/share/ca-certificates/ 以下に（通常はサブディレクトリを作成して）任意の名前(通常は"harbor.example.com.crt"のように、FQDN+".crt")でCAファイルを配置
2. /etc/ca-certificates.conf に /usr/share/ca-certificates/ からの相対パスで、配置したCAファイルを登録
3. ```$ sudo update-ca-certificates``` を実行

作業の結果は、/etc/ssl/certs/ ディレクトリに反映されますが、直接ここにファイルを配置する方法はお勧めしません。

```bash:実行例
$ sudo mkdir /usr/share/ca-certificates/local
$ sudo cp pki/ca.crt /usr/share/ca-certificates/local/harbor.example.com
$ sudo vi /etc/ca-certificates.conf
$ tail /etc/ca-certificates.conf
...
local/harbor.example.com.crt

$ sudo update-ca-certificates
```

## Firefoxへの自己署名CA情報の登録

この記事、https://askubuntu.com/questions/244582/add-certificate-authorities-system-wide-on-firefox にあるように、firefoxでは自前のcertificate DBを利用するため、ca-certificatesが準備する /etc/ssl/cets/ ファイルは利用できません。そのため certutils を利用して、sqlite DBを更新する方法が紹介されていますが、2020年1月時点では、プロファイルをリセットしても空になっているなど、変更できない挙動のように感じられます。

Web-UIにアクセスする際には、Advanced... →  Accept の流れで、一時的に接続を許可しています。

### Firefoxへの自己署名CA情報の登録

設定メニューのセキュリティ関連の設定項目からServer Certificatesを追加できます。
ただし、pkcs12形式である必要があるためopensslからca.crt, ca.keyファイルを元に作成します。

```bash:opensslコマンドでpkcs12形式のファイルを生成する
$ openssl pkcs12 -export -in pki/ca.crt -inkey pki/private/ca.key -out ca.p12
Enter pass phrase for pki/private/ca.key:
Enter Export Password:
Verifying - Enter Export Password:
```

"Enter Export Password"には、Firefoxにインポートする際に利用するパスワードを利用します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78296/a9e7b972-dbf4-87a3-9927-9d9475f08077.png)


## Kubernetes上へのCAファイルの配置

既に説明したように、/etc/docker/certs.d/以下に配置します。ansibleでは該当ディレクトリをあらかじめ作成する必要があるので、ちゃんとtasks/main.ymlなどにTASKを追加するか、```$ ansible all -m command -b -a "mkdir -p /etc/docker/certs.d/＿hostname__"```のようにディレクトリを準備します。("__hostname__"の部分はdocker loginの引数に指定するホスト名に変更)

# Harborのコンパイル時に遭遇した問題

1. 一般ユーザーでmakeコマンドを実行すると、harbor/make/common/config/log/logrotate.conf の書き込み権限がないためmakeに失敗する (golangにDockerイメージを利用する際に作成されるファイルのUID,GIDが1000:1000となる。もしUID,GIDが一致していればこのエラーには遭遇しないかもしれません)
2. TLS用のcert,keyファイルを準備・指定する必要がある
3. make installによってdocker runまで実行されるため、（何も設定していなければ）root権限が必要になる

1. についてはsudo makeで回避します。2.については今後のためにもプライベートネットワークで利用するCA局をeasy-rsaを利用して構築しておきます。

# Helmでtls:falseの際に、遭遇した unknown blob エラー について

tls: falseのまま```$ helm install```した場合、TLSを有効にしたReverse-Proxyを配置することで、docker loginまでは成功します。しかし、私の環境ではdocker pushの最後にunknown blobと表示されて失敗するエラーに遭遇しました。フロントエンドでTLSを有効にしたReverse-Proxy Serverを設置して、HTTPでHarborに通信させようとすると、エラーが発生します。

```text:nginxのノードはNAPT-gatewayとしても機能している
<------ global ip space --------><------------ private address space ---------->
  +----------+              +-------+                 +--------------+   +-----+
  | Intranet |---TLS(443)---| nginx |---non-TLS(80)---| LoadBalancer |---| Pod |
  +----------+    ↑         +-------+                 +--------------+   +-----+
                  ↑             ↑                     （※ tls: false)
                  ↑            (proxy_pass: http://kubeweb.example.com;)
                (https://harbor.example.com/)
```

この前段まででは、ここで不具合が発生しているのと、docker/kubernetesはregistryとの通信にTLSを前提としているので、原因が100%はっきりしているわけではないですが、前段まではtls: trueを前提とした構成を説明しています。


```bash:エラーが発生した状況
$ sudo docker login example.com
$ sudo docker build . --tag proxy.example.com/library/mynginx:1.0 --no-cache
$ sudo docker push example.com/library/mynginx:1.0
4fc1aa8003a3: Pushing  3.584kB
5fb987d2e54d: Preparing 
831c5620387f: Pushing  69.21MB/69.21MB
unknown blob
```

proxy.example.comは正式なTLS鍵を利用していて、通信自体は問題なく行なえています。しかし、この時のReverse-Proxy nginxのログを確認すると、次のようになっています。

```text:エラーと関連するaccess_log
...
192.168.1.240 - - [09/Jan/2020:22:03:30 +0900] "HEAD /v2/library/mynginx/blobs/sha256:eb22865337de3edb54ec8b52f6c06de320f415e7ec43f01426fdafb8df6d6eb7 HTTP/1.1" 404 0 "-" "docker/19.03.5 go/go1.12.12 git-commit/633a0ea838 kernel/4.15.0-74-generic os/linux arch/amd64 UpstreamClient(Docker-Client/19.03.5 \x5C(linux\x5C))"
192.168.1.240 - - [09/Jan/2020:22:03:30 +0900] "HEAD /v2/library/mynginx/blobs/sha256:bee5d581ef8bfee2b5a54685813ba6ad9bbe922115d7aef84a21a9dbfcc2d979 HTTP/1.1" 404 0 "-" "docker/19.03.5 go/go1.12.12 git-commit/633a0ea838 kernel/4.15.0-74-generic os/linux arch/amd64 UpstreamClient(Docker-Client/19.03.5 \x5C(linux\x5C))"
...
```

この状態のregistry podの/storage以下は次のようになっていて、pushされたイメージがそのまま保存されたまま、処理されていない様子が分かります。

```bash:エラーが発生する場合_uploads/以下のみ存在
$ kc exec -it my-harbor-harbor-registry-79767646d6-rbv6j find /storage
...
/storage/docker/registry/v2/repositories/library/mynginx
/storage/docker/registry/v2/repositories/library/mynginx/_uploads
/storage/docker/registry/v2/repositories/library/mynginx/_uploads/bbe0e1fa-2d97-4b55-8d4d-a41ed2ca7c3a
/storage/docker/registry/v2/repositories/library/mynginx/_uploads/bbe0e1fa-2d97-4b55-8d4d-a41ed2ca7c3a/hashstates
...
```

正常に動作しているHarborでは、.../_uploads/ディレクトリは空になって,library以下にイメージのメタ情報、blobs以下にイメージファイルが展開されています。

```bash:正常に稼動している場合:blobs/以下に展開
$ kubectl exec -it my-harbor-harbor-registry-57dc985f45-mql55 find /storage
/storage/docker/registry/v2/repositories/library
/storage/docker/registry/v2/repositories/library/mynginx
/storage/docker/registry/v2/repositories/library/mynginx/_uploads
/storage/docker/registry/v2/repositories/library/mynginx/_layers
/storage/docker/registry/v2/repositories/library/mynginx/_manifests/tags/1.0/current/link
...
/storage/docker/registry/v2/blobs/sha256
/storage/docker/registry/v2/blobs/sha256/be
/storage/docker/registry/v2/blobs/sha256/be/bee5d581ef...
...
```

_uploads/に配置されたファイルを処理して、library/, blobs/に展開する処理をするコードを探してみます。

```bash
$ git clone https://github.com/goharbor/harbor.git
$ cd harbor
$ find . -type f -exec grep _uploads {} \; -print
## 結果が表示されなかった
```

registory上の/storageに存在するので、何かしらそこに配置された理由があるはずですが、harborのコード中には原因がなさそうです。コードを詳細に読む前に、再現させてnginxのログファイルをもう少し詳しくみていこうと思います。

```text:docker-push時のreverse-proxyログの抜粋
92.168.1.240 - - [10/Jan/2020:15:01:36 +0900] "GET /v2/ HTTP/1.1" 401 87 "-" "docker/19.03.5 go/go1.12.12 git-commit/633a0ea838 kernel/4.15.0-74-generic os/linux arch/amd64 UpstreamClient(Docker-Client/19.03.5 \x5C(linux\x5C))" "-"
192.168.1.240 - user01 [10/Jan/2020:15:01:36 +0900] "GET /service/token?account=user01&scope=repository%3Alibrary%2Fmynginx%3Apush%2Cpull&service=harbor-registry HTTP/1.1" 200 984 "-" "docker/19.03.5 go/go1.12.12 git-commit/633a0ea838 kernel/4.15.0-74-generic os/linux arch/amd64 UpstreamClient(Docker-Client/19.03.5 \x5C(linux\x5C))" "-"
192.168.1.240 - - [10/Jan/2020:15:01:36 +0900] "HEAD /v2/library/mynginx/blobs/sha256:8...d HTTP/1.1" 404 0 "-" "docker/19.03.5 go/go1.12.12 git-commit/633a0ea838 kernel/4.15.0-74-generic os/linux arch/amd64 UpstreamClient(Docker-Client/19.03.5 \x5C(linux\x5C))" "-"
192.168.1.240 - - [10/Jan/2020:15:01:36 +0900] "HEAD /v2/library/mynginx/blobs/sha256:d...7 HTTP/1.1" 404 0 "-" "docker/19.03.5 go/go1.12.12 git-commit/633a0ea838 kernel/4.15.0-74-generic os/linux arch/amd64 UpstreamClient(Docker-Client/19.03.5 \x5C(linux\x5C))" "-"
192.168.1.240 - - [10/Jan/2020:15:01:36 +0900] "HEAD /v2/library/mynginx/blobs/sha256:b...6 HTTP/1.1" 404 0 "-" "docker/19.03.5 go/go1.12.12 git-commit/633a0ea838 kernel/4.15.0-74-generic os/linux arch/amd64 UpstreamClient(Docker-Client/19.03.5 \x5C(linux\x5C))" "-"
192.168.1.240 - - [10/Jan/2020:15:01:37 +0900] "POST /v2/library/mynginx/blobs/uploads/ HTTP/1.1" 202 0 "-" "docker/19.03.5 go/go1.12.12 git-commit/633a0ea838 kernel/4.15.0-74-generic os/linux arch/amd64 UpstreamClient(Docker-Client/19.03.5 \x5C(linux\x5C))" "-"
192.168.1.240 - - [10/Jan/2020:15:01:37 +0900] "POST /v2/library/mynginx/blobs/uploads/ HTTP/1.1" 202 0 "-" "docker/19.03.5 go/go1.12.12 git-commit/633a0ea838 kernel/4.15.0-74-generic os/linux arch/amd64 UpstreamClient(Docker-Client/19.03.5 \x5C(linux\x5C))" "-"
192.168.1.240 - - [10/Jan/2020:15:01:37 +0900] "POST /v2/library/mynginx/blobs/uploads/ HTTP/1.1" 202 0 "-" "docker/19.03.5 go/go1.12.12 git-commit/633a0ea838 kernel/4.15.0-74-generic os/linux arch/amd64 UpstreamClient(Docker-Client/19.03.5 \x5C(linux\x5C))" "-"
192.168.1.240 - - [10/Jan/2020:15:01:37 +0900] "GET /v2/library/mynginx/blobs/uploads/8...d?_state=m...Q%3D%3D HTTP/1.1" 204 0 "http://example.com/v2/library/mynginx/blobs/uploads/8...d?_state=m...Q%3D%3D" "docker/19.03.5 go/go1.12.12 git-commit/633a0ea838 kernel/4.15.0-74-generic os/linux arch/amd64 UpstreamClient(Docker-Client/19.03.5 \x5C(linux\x5C))" "-"
192.168.1.240 - - [10/Jan/2020:15:01:37 +0900] "GET /v2/library/mynginx/blobs/uploads/5...5?_state=-8...Q%3D%3D HTTP/1.1" 204 0 "http://example.com/v2/library/mynginx/blobs/uploads/5...5?_state=-8...Q%3D%3D" "docker/19.03.5 go/go1.12.12 git-commit/633a0ea838 kernel/4.15.0-74-generic os/linux arch/amd64 UpstreamClient(Docker-Client/19.03.5 \x5C(linux\x5C))" "-"
192.168.1.240 - - [10/Jan/2020:15:01:37 +0900] "GET /v2/library/mynginx/blobs/uploads/9...c?_state=E...Q%3D%3D HTTP/1.1" 204 0 "http://example.com/v2/library/mynginx/blobs/uploads/9...c?_state=E...Q%3D%3D" "docker/19.03.5 go/go1.12.12 git-commit/633a0ea838 kernel/4.15.0-74-generic os/linux arch/amd64 UpstreamClient(Docker-Client/19.03.5 \x5C(linux\x5C))" "-"
192.168.1.240 - - [10/Jan/2020:15:01:38 +0900] "GET /v2/library/mynginx/blobs/uploads/9...c?_state=o...9&digest=sha256%3A1...9 HTTP/1.1" 204 0 "http://example.com/v2/library/mynginx/blobs/uploads/9...c?_state=o...9&digest=sha256%3A1...9" "docker/19.03.5 go/go1.12.12 git-commit/633a0ea838 kernel/4.15.0-74-generic os/linux arch/amd64 UpstreamClient(Docker-Client/19.03.5 \x5C(linux\x5C))" "-"
192.168.1.240 - - [10/Jan/2020:15:01:38 +0900] "GET /v2/library/mynginx/blobs/uploads/5...5?_state=W...6 HTTP/1.1" 204 0 "http://example.com/v2/library/mynginx/blobs/uploads/5...5?_state=W...6" "docker/19.03.5 go/go1.12.12 git-commit/633a0ea838 kernel/4.15.0-74-generic os/linux arch/amd64 UpstreamClient(Docker-Client/19.03.5 \x5C(linux\x5C))" "-"
192.168.1.240 - - [10/Jan/2020:15:01:38 +0900] "GET /v2/library/mynginx/blobs/uploads/8...d?_state=...9&digest=sha256%3A7...0 HTTP/1.1" 204 0 "http://example.com/v2/library/mynginx/blobs/uploads/8...d?_state=i...9&digest=sha256%3A7...0" "docker/19.03.5 go/go1.12.12 git-commit/633a0ea838 kernel/4.15.0-74-generic os/linux arch/amd64 UpstreamClient(Docker-Client/19.03.5 \x5C(linux\x5C))" "-"
192.168.1.240 - - [10/Jan/2020:15:01:38 +0900] "HEAD /v2/library/mynginx/blobs/sha256:7...0 HTTP/1.1" 404 0 "-" "docker/19.03.5 go/go1.12.12 git-commit/633a0ea838 kernel/4.15.0-74-generic os/linux arch/amd64 UpstreamClient(Docker-Client/19.03.5 \x5C(linux\x5C))" "-"
192.168.1.240 - - [10/Jan/2020:15:01:38 +0900] "HEAD /v2/library/mynginx/blobs/sha256:1...9 HTTP/1.1" 404 0 "-" "docker/19.03.5 go/go1.12.12 git-commit/633a0ea838 kernel/4.15.0-74-generic os/linux arch/amd64 UpstreamClient(Docker-Client/19.03.5 \x5C(linux\x5C))" "-"
192.168.1.240 - - [10/Jan/2020:15:01:38 +0900] "HEAD /v2/library/mynginx/blobs/sha256:b...6 HTTP/1.1" 404 0 "-" "docker/19.03.5 go/go1.12.12 git-commit/633a0ea838 kernel/4.15.0-74-generic os/linux arch/amd64 UpstreamClient(Docker-Client/19.03.5 \x5C(linux\x5C))" "-"
```

この時にregistory podの/storageは次のようになっていて、library/ubuntu/_uploads以外にはファイルが存在しない状態です。

```bash
$ /storage
/storage/docker
/storage/docker/registry
/storage/docker/registry/v2
/storage/docker/registry/v2/repositories
/storage/docker/registry/v2/repositories/library
/storage/docker/registry/v2/repositories/library/ubuntu
/storage/docker/registry/v2/repositories/library/ubuntu/_uploads
/storage/docker/registry/v2/repositories/library/ubuntu/_uploads/fc83002e-b800-4ef2-bbab-16d2014fbc82
/storage/docker/registry/v2/repositories/library/ubuntu/_uploads/fc83002e-b800-4ef2-bbab-16d2014fbc82/hashstates
/storage/docker/registry/v2/repositories/library/ubuntu/_uploads/fc83002e-b800-4ef2-bbab-16d2014fbc82/hashstates/sha256
/storage/docker/registry/v2/repositories/library/ubuntu/_uploads/fc83002e-b800-4ef2-bbab-16d2014fbc82/hashstates/sha256/0
/storage/docker/registry/v2/repositories/library/ubuntu/_uploads/fc83002e-b800-4ef2-bbab-16d2014fbc82/data   
/storage/docker/registry/v2/repositories/library/ubuntu/_uploads/fc83002e-b800-4ef2-bbab-16d2014fbc82/startedat
....
```

ひとまずHarborを自分でコンパイルしてみて、_uploadsに配置されたファイルがblobsに移動されるまでの処理のどこに問題があるのか調べていきます。issuesを眺める限りは再現性がなく、いつのまにか解決しているようです。


以上