Dockerコンテナ内でGCPの証明書エラーと戦ってみた

Docker for Macで作成したコンテナ内にgcloudコマンドの環境を構築したところ、社内プロキシ関連の証明書でエラーが出たので回避策をまとめておきます

# 結論

```
gcloud config set auth/disable_ssl_validation True
```

で証明書のチェックを無効化する。

あるいは、ホストマシンにインストールされた証明書をコンテナ内に持って行って、以下のコマンドで証明書を指定する

```
gcloud config set core/custom_ca_certs_file /path/to/ca_certs
```

# 動作環境

macOS Mojave  
version 10.14.5

```
$ docker --version
Docker version 18.09.2, build 6247962
$ code --version
1.35.1
c7d83e57cd18f18026a8162d042843bda1bcf21f
x64
```

# 事象

`gcloud auth login`コマンドでログイン処理を行なったところ、以下のエラーが発生しました

```
ERROR: gcloud crashed (SSLHandshakeError): [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:727)
```

`SSL: CERTIFICATE_VERIFY_FAILED`ということで証明書のエラーとのことです。

# 対策方法

調べた感じ対策は2種類かな、という感じでした。

1. gcloudコマンドのSSLのチェックを無効化する
1. ホスト(Mac)にインストールされている証明書をコンテナ側にインストールする

この２つの方法を試してみました。

## gcloudコマンドのSSLのチェックを無効化する

こちらは簡単で、コマンド1発で設定するだけです。

```
gcloud config set auth/disable_ssl_validation True
```

これでSSLのエラーが出ても無視するようになります。

## ホスト(Mac)にインストールされている証明書をコンテナ側にインストールする

完全無視の方法は楽なんですがちゃんとした証明書でやる方法も欲しいということでやってみました。  
プロキシ配下の場合にDockerコンテナ内でエラーが出るのは、プロキシのルート証明書がコンテナ内のOSにはインストールされていないからです。これによって、

* ホストマシン(Mac)には社内プロキシ用の証明書がインストールされているので、ブラウザでみた場合は問題なし
* コンテナや仮想マシンの場合は一般的な証明書しか入っていないのでエラーが出る

というような動きになります。

ということはホストマシンの証明書をコンテナ側にも設定してあげれば良い、ということになるのでやってみました
(逆に言うと、証明書が古いとかの原因でホスト側からもアクセスできないようなサイトの場合は無理なので、その場合は諦めます。)

手順はざっくり以下です。

1. `キーチェーン` アプリからルート証明書のファイルをエクスポートする
1. コンテナ内のLinuxに証明書のファイルをコピーする
1. gcloudコマンドで証明書を指定する

細かい手順は次から

### キーチェーンから証明書をエクスポートする

Macの`キーチェーン`アプリを開きます

以下のように、キーチェーンは`システム`、分類は`証明書`を選ぶとインストールされている証明書の一覧が確認できます。

![alt](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330514/1a4f3fb5-ecd1-a562-9df4-f52e87d769c5.png)

この中から該当する`ルート証明書`を選びます。
複数あってどれかわからないと言う場合は[備考：ルート証明書の確認](#備考ルート証明書の確認)の方法で確認できます。

必要となる証明書を右クリックして`***を書き出す`というメニューからファイルに書き出します。  

<img width="276" alt="g_export.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330514/5f64910b-06ee-2849-af1c-c77477f74929.png">


ファイル形式は`pem`として、説明の為にファイル名は`root.pem`とします。  
この`root.pem`ができればホスト側の作業は終わりです。

### 試してみる

本当にちゃんと通信できるか試してみました。

まず、以下のDockerfileで`gcloud`コマンドがインストールされたUbuntuイメージを作成します。

こんな構成でファイルを配置して、docker-compose.ymlで立ち上げます。
root.pemは上記で書き出したファイルです。

```console
./Dockerfile
./certificates
./certificates/root.pem
./docker-compose.yml
```

```Dockerfile
FROM ubuntu:18.04

RUN apt update && apt install -y lsb-release
RUN apt install -y curl gnupg
RUN export CLOUD_SDK_REPO="cloud-sdk-$(lsb_release -c -s)" && \
    echo "deb http://packages.cloud.google.com/apt $CLOUD_SDK_REPO main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list && \
    curl -k https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - && \
    apt-get update -y && apt-get install google-cloud-sdk -y
```

```docker-compose.yml
version: '3.3'

services:
  main:
    build: .
    tty: true
    volumes:
      - ./certificates:/certificates
```

docker-compose立ち上げて、コンテナに接続します

```
docker-compose up -d
docker-compose exec main bash
```

コンテナ内から`gcloud auth login`で認証をしてみますが、まだ何も設定していないので当然ながらエラーが出ました。

```console
$ gcloud auth login
Go to the following link in your browser:

    https://accounts.google.com/o/oauth2/auth?***********************

Enter verification code: 
ERROR: gcloud crashed (SSLHandshakeError): [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:727)

gcloud's default CA certificates failed to verify your connection, which can happen if you are behind a proxy or firewall.
To use a custom CA certificates file, please run the following command:
  gcloud config set core/custom_ca_certs_file /path/to/ca_certs

If you would like to report this issue, please run the following command:
  gcloud feedback

To check gcloud for common problems, please run the following command:
  gcloud info --run-diagnostics
```

# `gcloud set`コマンドで証明書を指定する

エラーメッセージにもあるように、`gcloud config set core/custom_ca_certs_file /path/to/ca_certs`で証明書のファイル指定すれば良いということなのでやってみます。

証明書のマウント先は`/certificates/root.pem`になります

```console
$ gcloud config set core/custom_ca_certs_file /certificates/root.pem 
Updated property [core/custom_ca_certs_file].

To take a quick anonymous survey, run:
  $ gcloud alpha survey
```

`gcloud alpha survey`は何？と思いましたが、ただのアンケートのようなので無視します  
この状態でもう一度ログイン処理を走らせます

```console
$ gcloud auth login
Go to the following link in your browser:
    https://accounts.google.com/o/oauth2/auth?***********************

Enter verification code: 
WARNING: `gcloud auth login` no longer writes application default credentials.
If you need to use ADC, see:
  gcloud auth application-default --help

You are now logged in as [********].
Your current project is [None].  You can change this setting by running:
```

無事にログインできました。  
この状態からプロジェクトを設定して、GCPのAPIなども叩けたので問題ないかなと思います。

# 備考：ルート証明書の確認

Webブラウザでサイトにアクセスすると、どの証明書で認証したかが確認できます。
Chromeで[Visual Studio Marketplace](https://marketplace.visualstudio.com/)を開いた場合は以下のような感じです。

URLの左側の鍵をクリックするとこんなダイアログが出るので

<img width="276" alt="g_export.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330514/b45ffba1-b3da-5577-c2bf-9ce83dfcf35f.png">

`証明書`を選択すれば使われているルート証明書が確認できます。この中の一番上のものを選んでおけば大丈夫かと思います

<img width="500" alt="g_export.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330514/20188cee-46ee-a7ab-80c5-fad2e0f667b2.png">


# おわりに

gcloudの証明書エラーと戦ってみました。
社内プロキシがあるとどうしても出てくる問題なので仕方ないような、無駄な時間のような...

ちなみに[独自のルートCA証明書を追加する方法（Ubuntu, CentOS 7） | cloudpack.media](https://cloudpack.media/14148) の方法を使えばOS自体の証明書にも追加できるので、その他のツール周りでエラーが出る場合でもいけるはずです。

# 参考

[Adding a custom CA Root certificate to GCloud utility (or Python generally) on Windows - Stack Overflow](https://stackoverflow.com/questions/49310591/adding-a-custom-ca-root-certificate-to-gcloud-utility-or-python-generally-on-w?noredirect=1&lq=1)