2014/12/12

インターネットの向こう側にあるDockerを使う

年末が近づいてきて仕事が燃えさかっているので記事を書いて現実逃避しています。

さて、(なんかいきなり一年を振り返ってるみたいで唐突ですが)今年はDockerをはじめとしたコンテナ技術がついに一般的な世界に降りてきてみんなドッカードッカーといろんなことを試したりした年でした。

Dockerは個人的に一つ面倒な点があって、基本的にLinuxじゃないと動かないというのがあります。ホントは手元のMacでDockerしたいのですが、さすがにDockerのコンテナはMacでは動きません。で、それに対する一般的なソリューションは、VirtualBoxをインストールしてLinux(CoreOSとかboot2docker)を動かしてそこにつなごう! というものでした。

まーそれでもいいんですが、出来ればMacの上でVMは動かしたくないんですよねー。ぼくの場合は自宅サーバにたくさんVM立ててあるからVMはそっちで動かせばいいし、そうでない人も会社でKVMなサーバがあったり、さくらVPSを使っていたりと、ちゃんと自由に使えるLinuxを手元のマシン以外のところに持っている人はそこそこいるはず。

DockerをTCP経由で使いたい

ということで、DockerにはデフォルトのUnix domain socketのほかにTCPで接続する機能もあるのでそれを使ってみようということで色々調べてみたのが7月頃。しかしながら、よくよく調べてみるともともとUnix domain socketを使う前提で作られてるということもあり、プロトコルに認証機構がないのでした。LAN上ならともかく、さすがにインターネット上に2375ポートをぼろーんと晒してしまうと知らないおじさんに夜な夜なドッカードッカーされてしまうという懸念があったので、その時はあきらめました。

で、一昨日そういえば夏頃そんなことを調べてたなぁと今年を振り返っていたのですが、そのとき閃いたんですよ。「あ、TCPポートをそのまま晒すのはまずいけど、前にstoneを置いてまずSSLで受けて、SSLのクライアント証明書がないと通れないようにすればいいのでは?」いやーマジオレ頭いいわー。

と思ったんですが、よく考えたら送信側もSSLに対応してないといけないので、送信側となる手元のMacにもstoneを置いてトンネルを張る必要があるのかー…と思ってちょっと二の足を踏みかけました。そこでさらに閃いて、「そうか! DockerクライアントがSSLに対応してたりするのでは?」とググってみると、なんと普通にRunning Docker with httpsというそのものずばりなオフィシャルドキュメントがありました。Docker自体がTLS認証のための機構をもってるのでstoneとか要らなかった。

ふむふむふむふむ…と読んでみると、非常に詳しく手順が書いてありました。まずプライベート認証局(CA)の証明書を作り、そこからサーバの証明書を作り、クライアントの証明書も作り、docker -dでサーバ証明書を有効にして2376ポートで待ち受けてそこにクライアント証明書を指定したdockerコマンドで接続してやればよいと。

これだよ! オレが求めてたのはこれだよ! ってことでいつからこの機能があったのかとChangelogを読んでみると、なんと0.10.0 (2014-04-08)からあったと。マジかー、全然しらんかったわー。半年くらい人生損してたわーみたいな気持ちになりましたが、気を取り直して設定してみました。

Docker over TLSを設定する

詳しくはオフィシャルのドキュメントを読むのが良いですが、コマンドを羅列しただけのメモを貼っておきます(オフィシャルのはコマンドの出力も含めて書いてあるのでちょっと読みづらい)。

以下のコマンドの羅列は、2014年12月12日時点のDockerドキュメントから引用しています。そのまま使ってもよいですが、ぼくが手元で実行するときは秘密鍵の鍵長を4096bitに変更して、証明書の有効期限をを3650日(10年)に伸ばしました。

# CAのシリアルナンバーファイルを作る
$ echo 01 > ca.srl

# CAの秘密鍵を作る
$ openssl genrsa -des3 -out ca-key.pem 2048

# CAの証明書を作る (CommonNameとか設定する必要ある)
$ openssl req -new -x509 -days 365 -key ca-key.pem -out ca.pem

# サーバー側の秘密鍵を作る
$ openssl genrsa -des3 -out server-key.pem 2048

# サーバー側の秘密鍵で証明書リクエスト(CSR)を作る (CommonName書く必要ある)
$ openssl req -subj '/CN=' -new -key server-key.pem -out server.csr

# CAでサーバー側の証明書を発行
$ openssl x509 -req -days 365 -in server.csr -CA ca.pem -CAkey ca-key.pem -out server-cert.pem

# クライアント側の秘密鍵作る
$ openssl genrsa -des3 -out key.pem 2048

# クライアント側の秘密鍵で証明書リクエストを作る
$ openssl req -subj '/CN=client' -new -key key.pem -out client.csr

# CAで証明書発行するときのオプションを指定する設定ファイルを用意
$ echo extendedKeyUsage = clientAuth > extfile.cnf

# CAでクライアント側の証明書を発行
$ openssl x509 -req -days 365 -in client.csr -CA ca.pem -CAkey ca-key.pem -out cert.pem -extfile extfile.cnf

# ここまでで作業は完了だが、サーバー側、クライアント側ともに秘密鍵にパスワードがかかっていて不便なので、それを外す
$ openssl rsa -in server-key.pem -out server-key.pem
$ openssl rsa -in key.pem -out key.pem

最後の手順で秘密鍵のパスワードを外してますが、クライアント側はパスワードかけたままにして、Macのキーチェインに秘密鍵のパスワードを入れておくという手もあるのかなと思います(未確認)。

これで一通り秘密鍵と証明書ができたので、Dockerを上げなおします。たぶんyumなりaptなりで入れていると思いますので、dockerサービスを一度止めて、コマンドラインからforeground実行してみます。

# 作ったCA証明書、サーバ秘密鍵、サーバ証明書を使ってDocker daemonを起動
$ sudo docker -d --tlsverify --tlscacert=ca.pem --tlscert=server-cert.pem --tlskey=server-key.pem -H=0.0.0.0:2376

ここに、別のホストからTLSで接続します。

# CA証明書、クライアント証明書、クライアント秘密鍵と接続先を指定してdocker version実行
$ sudo docker --tlsverify --tlscacert=ca.pem --tlscert=cert.pem --tlskey=key.pem -H=docker-host:2376 version

…と、ここまでオフィシャルのマニュアルに書いてあります。

Daemonizeとかクライアント側の設定とか

rpmとかaptで入れた人は、serviceで動くようにする必要があるので起動スクリプトの環境変数DOCKER_OPTSにこれを書く必要があります。ぼくのdockerはdebianなので/etc/default/dockerを開いて以下の記述を入れました。

DOCKER_OPTS="--tlsverify --tlscacert=/path/to/ca.pem --tlscert=/path/to/server-cert.pem --tlskey=/path/to/server-key.pem -H=0.0.0.0:2376"

と書いてservice docker startし、無事動きました。ちゃんと調べてないですが、Debian, Amazon Linux, CentOS6は/etc/default/dockerで、CentOS7だと/usr/lib/systemd/system/docker.serviceに書く必要があるのかな…?

さて次はクライアント側です。

Macに入れたdockerコマンドは、環境変数DOCKER_HOSTtcp://example.com:2376の形式で接続先を指定し、DOCKER_TLS_VERIFYに1を指定しておくと勝手に~/.docker/の下においたca.pem(認証局証明書)、cert.pem(クライアント証明書)、key.pem(クライアント秘密鍵)を使ってくれるので、ここまで設定すればもうLinux上でdockerコマンドを叩いてるのと同じ気持ちでMacでdockerを使えるようになりました。

ちなみに、なんでこれクライアント側にも認証局証明書を指定してんの? って疑問に思った人は鋭いです。認証は双方向に行われており、サーバは接続してきた人のクライアント証明書を認証します。その一方で、クライアント側も接続したサーバの証明書を認証局証明書でチェックします。これにより、サーバが知らないおじさんからドッカーされてしまうのを防ぐ一方で、万が一DNSにいたずらされて知らないサーバーにクライアントが接続しても、クライアントが認証局証明書でサーバが正しいかどうかの検証し、それが失敗するのでなりすましサーバーに接続してしまうのを防いでいます。

というわけで、TLSベースの認証であればインターネットを通してDockerを使うというのも現実的になってきます。手元のMac/WindowsでDockerのために仮想マシンを動かすのはなぁ…という方はお手持ちのVPSやサーバなどにこの設定をしてみてはいかがでしょうかー。

うされもん @acidlemonについて

|'-')/ acidlemonです。鎌倉市在住で、鎌倉で働く普通のITエンジニアです。

30年弱住んだ北海道を離れ、鎌倉でまったりぽわぽわしています。

外部サイト情報

  • twitter
  • github
  • facebook
  • instagram
  • work on kayac