KubernetesのDockerコンテナ内で自己署名証明書を信頼する

... 自己署名証明書を使うほうが最早難しい時代だよねメモその2。

# "自己署名証明書を信頼する" とは

HTTPにおけるSSL/TLSは基本的に "**有名な会社(CA)から信頼されている証明書は信頼できる**" というモデルで成立している。有名な会社から発行されていない自己署名証明書(歴史的事情でオレオレ証明書とも呼ばれる)は何らかの方法で信頼させる必要がある。

証明書が信頼されていない場合、`unable to get local issuer certificate` とか `unable to verify the first certificate` のようなエラーが出て接続に失敗してしまうことが多い。

この信頼させる方法がOSディストリビューションごとにかなりマチマチで、かつ、覚えづらい。例えば、以前書いた [CygwinでHTTPSの自己署名証明書を信頼する](https://qiita.com/okuoku/items/4bc3a62b27ba40f2d2fc) はCygwinや、その他Red Hat系OSで使える。

今回は、Debian系OS(DebianやUbuntu)のコンテナイメージで自己署名証明書を信頼するようにしてみる。

# コンテナ起動時に証明書をインストールする

... 色々試してはみたけど、これが一番安定するということで。。流れとしては、

1. `/usr/local/share/ca-certificates` 以下に **拡張子`crt`で** 公開鍵ファイルを置く
2. `update-ca-certificates` を実行する
3. (通常のエントリーポイントを実行する)

KubernetesのPod内にファイルを導入するには `ConfigMap` が使える:

- https://github.com/okuoku/nextstripe/blob/10a709178cf368f0fd9bf0b4dbbc3ad296e90964/localcacerts/config.yaml#L7

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
    name: localcacerts
    namespace: default
data:
    local.cer: |
        -----BEGIN CERTIFICATE-----
        MIIFMTCCAxmgAwIBAgIQcD70x1cge65BwtMjAd3ljDANBgkqhkiG9w0BAQsFADAV
        MRMwEQYDVQQDDApmbG93LmxvY2FsMCAXDTE5MTIzMDE3MjExMVoYDzIwNjkxMjMw
(後略)
```

で、 `ConfigMap` をファイルシステムとしてマウントした上で、適当な位置への配置と `update-ca-certificates` を一緒に実行するように、 `command` を書いてしまえば良い:

- https://github.com/okuoku/nextstripe/blob/10a709178cf368f0fd9bf0b4dbbc3ad296e90964/nc/nc.yaml#L58

```sh
cp /cer/local.cer /usr/local/share/ca-certificates/local.crt && update-ca-certificates && /entrypoint.sh apache2-foreground
```

ファイルが正常に認識されている場合は、 `update-ca-certificates` の実行時に `1 added` のように表示される。

```
Updating certificates in /etc/ssl/certs...
1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d...
done.
Initializing nextcloud 17.0.2.1 ...
```

# うまく行かない場合

コピーされている筈なのにうまくSSL接続できない場合は、 `kubectl exec` 等でシェルに入り、 `curl` なりなんなりでリクエストを手動で発行してみる。これで上手くいく場合は、そもそもアプリがOS側の設定を見ていない可能性がある。

## ホスト名が合っていないケース

常識的なSSL/TLS証明書にはホスト名が含まれているため、接続に使用するホスト名も揃える必要がある。幸い、最近のKubernetesには `hosts` を設定する機能(`hostAliases`)が付いているので、それで設定してしまう手がある。

- https://github.com/okuoku/nextstripe/blob/10a709178cf368f0fd9bf0b4dbbc3ad296e90964/gitea/gitea.yaml#L27

```yaml
        spec:
            hostAliases:
                - ip: "192.168.1.209"
                  hostnames:
                      - "flow.local"
```

## CA証明書として認識されないケース

環境変数 `SSL_CERT_FILE` で直接指定すると上手くいくのに `update-ca-certificates` で上手くいかない場合、鍵ペアの用途として"証明書の署名"が含まれていない等といった理由でCA証明書として認識されていないケースがある。

`openssl` コマンドで `openssl x509 -text -noout -in key.pem` のようにして中身を確認すると:

```
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment, Certificate Sign ★
            X509v3 Extended Key Usage:
                TLS Web Client Authentication, TLS Web Server Authentication
            X509v3 Subject Alternative Name:
                DNS:flow.local
            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:0 ★
            X509v3 Subject Key Identifier:
                55:A2:3A:79:F1:72:73:91:4B:FB:D7:5E:65:BF:D7:F5:10:4E:08:4E
```

`Certificate Sign` のKey Usageと、 `CA:TRUE` のBasic Constraintsが揃っていないと(少くとも手元の検証では)ダメだった。一個前のエントリ([KubernetesのIngressで使うSSL自己署名証明書をPowerShellで作る](https://qiita.com/okuoku/items/fd6301df0ad520cef656))で引いたように、これは [OpenSSL側のバグ](https://github.com/openssl/openssl/issues/1418) っぽい挙動で、ひとまず `CA:TRUE` を立ててワークアラウンドするしか無いようだ。