ykore::tech_memo

技術的な事を適当に書きます

プロキシの内側から docker pull できない

Docker Hubから docker pull しようとしたところ、以下のエラーでイメージのダウンロードが失敗したところから始まる。

Error response from daemon: Get https://registry-1.docker.io/v2/:
remote error: tls: handshake failure

環境は社内プロキシの内側にあり、製品にはTLSの検閲を回避するオプションがある。 試しに検閲をOFFにすると pull に成功するが、設定変更なしに実現できないか色々探っていた。

docker pull したときのパケットをキャプチャし、 Wireshark で確認すると、以下の暗号スイートを Client Hello で送っていた。

Cipher Suites (8 suites)
    Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 (0xc02c)
    Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
    Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (0xc02b)
    Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)
    Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA (0xc00a)
    Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA (0xc009)
    Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (0xc014)
    Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (0xc013)

次に、このプロキシ製品でサポートされている暗号スイートを確認した。

openssl ciphers -v | awk '{print $1}' | xargs -n1 curl -ksv https://registry-1.docker.io/v2/ -x http://~<proxy>~/ --ciphers
AES256-SHA256
AES128-SHA
SRP-RSA-AES-256-CBC-SHA
SRP-AES-256-CBC-SHA
RSA-PSK-AES256-CBC-SHA384
DHE-PSK-AES256-CBC-SHA384
RSA-PSK-AES256-CBC-SHA
DHE-PSK-AES256-CBC-SHA
AES256-SHA

うーん、対応してないですねぇ...

Docker ソースコードの確認

念のため Docker のソースコードも確認した。Docker 製品群のネットワーク接続は go-connections というユーティリティにまとめられているらしいので、そちらのリポジトリから確認した。

GitHub - docker/go-connections: Utility package to work with network connections

Docker側が使っている go-connections のバージョンが不明だが、2018年あたりのコミットの tlsconfig/config_client_ciphers.gotlsconfig/config.go というファイルを確認すると、むちゃくちゃ絞られていた。

config_client_ciphers.go から抜粋
// Client TLS cipher suites (dropping CBC ciphers for client preferred suite set)
var clientCipherSuites = []uint16{
    tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
    tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
    tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
    tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
}
config.go から抜粋
// Extra (server-side) accepted CBC cipher suites - will phase out in the future
var acceptedCBCCiphers = []uint16{
    tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
    tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
    tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
    tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
}

後に気づいたのだが、 Issue にも普通に書かれていた。

github.com

多段プロキシ作戦

クライアントと社内プロキシの間にTLSプロキシを Squid で立てたら突破できるんじゃね?と考えたので試してみた。キャプチャを眺めたが、Squid はクライアントから送られた暗号スイートをそのまま使う動作をするようで、暗号スイートを変える(フォールバックというのか?)ようなことしてくれないので、この作戦は失敗に終わった。

Docker を古い暗号スイートに対応したい場合は、古いバージョンを利用するか、独自にビルドするしかなさそうだ。