2020/05/30に発生したDatadog AgentのCERTIFICATE_VERIFY_FAILED error について少し考察してみようかと思います。
Table of Contents
CERTIFICATE_VERIFY_FAILED は突然に
**「ギャー!めっちゃEC2の監視が落ちているんですけど!」**と連絡を受けた土曜日。
カンパリオレンジならぬカンパリ野菜生活をゴクゴク飲んでいた私は意識朦朧とする中、Slackを見る。
ギャー!!
めっちゃ落ちてますね....。
一通りサービスの状態を見て問題なかったのでこの日は持ち前の「大丈夫ですよ(大嘘)」をかまして眠ることにしました。
Datadogからのアナウンス
翌日、Datadogからアナウンスがありました。
CERTIFICATE_VERIFY_FAILED error
サイトを見ると何が起きていて、どんな対応がユーザーに必要かが書いてありました。
On Saturday May 30th, 2020, at 10:48 UTC, an SSL root certificate used to cross-sign some of the Datadog certificates expired, and caused some of your Agents to lose connectivity with Datadog endpoints. Because this root certificate is embedded in certain Agent versions, you will need to take action to restore connectivity.
ふむふむ... SSLのルート証明書(cross-sign)が失効してしまいましたわよ、という案件なのですね。
よくあるSSL証明書失効と何が違うのでしょうか...よくわかりませんね..。
とりあえずそこら辺の調査は後回しで直していきます。
いくつかサイトに直し方が書いてありましたが今回は、
sudo rm /opt/datadog-agent/agent/datadog-cert.pem && sudo service datadog-agent restart
の直し方にフォーカスしてみようかと思います。どうやら datadog-cert.pem を消してからdatadogのサービス再起動すればいいみたい。
ということで、各EC2に入ってごにょごにょしたら直りました。よかった...ホッと...。
そもそもSSLの仕組みをおさらい
直ったのはいいですが、何が起きていたかちゃんと把握したいですよね...。
ということで5年ぶりにSSLのお勉強をしました。
といっても私が知っているのは公開鍵暗号の話だけ...ですので実質初学習です。
公開鍵暗号について詳しく知りたい人は、
暗号技術入門 第3版 秘密の国のアリスがいいと思います。
私をこの業界に引き込んだ師が大学の頃に読め!と言われて買って読んだ記憶があります。
今でも家の本棚にありました。マジで良書です。わかりやすいです。
さて簡単にSSL通信のおさらいをします。
引用: https://ssl.sakura.ad.jp/column/ssl/
わかりやすいイラストがさくらのSSLにありましたので拝借します。
重要なポイントとして、ブラウザ(クライアント)はSSL通信をするときにSSLの通信リクエスト(ClientHello)を送信すると、サーバーからサーバー証明書と公開鍵が送られてきます。
暗号通信についてはこのうち公開鍵が利用され、クライアント側でランダムな値を共通鍵のシークレットとして利用することを決定し公開鍵を使って暗号化し、サーバーに送信します。
するとサーバーでは秘密鍵を使って共通鍵が取り出せるのでここから共通鍵暗号(例えばAESとか)での通信ができるわけです。これが鍵交換というやつです。
えっ!?普通に互いの公開鍵を交換して公開鍵暗号で通信すればいいだけじゃんアゼルバイジャンと思ったそこの君!!それは違いますね。
まず、その場合クライアントに公開鍵と対応する秘密鍵を用意する必要がありますね。あなたは田舎のおばあちゃんにOpenSSLコマンドを打たせるんですか...それはきついですよね。
もう1つの問題は一般的に公開鍵暗号は共通鍵暗号に比べて暗号コストが高いということです。それなりの桁数の乱数をぶつけていくので共通鍵の数十~数千倍遅いといわれてます。
ちなみにSSLの通信で、出てきた公開鍵暗号や共通鍵暗号のアルゴリズムパターンを定義するのが皆さん大好きおなじみの Cipher suiteです。
サイト管理者ならひと昔前、ATS対応とかでツライ思いをした人もいるかもしれませんね。
ECDHE-RSA-AES128-GCM-SHA256 こんなやつ...あぁ...SHA-1の悪夢がよみがえりますね。
脱線ついでにもう1つ。
上記で述べた鍵交換1つとってもたとえばRSAという方式ではほぼ上記で説明しているとおりの流れを組むのですが、前方秘匿性に弱い欠点(一言でいえば全暗号通信を記憶していた状態で秘密鍵を手に入れたらすべてがばれてしまう)があったりで最近ではDHE(DiffieHellman, Ephemeral)という鍵交換方式を楕円曲線暗号上で行なうECDHE(RFC4492)が主流だったりします。
ここまでのお話はぜひ暗号技術入門 第3版 秘密の国のアリスを読んでいただければ私なんかよりわかりやすく説明しているので興味がある人はぜひ読んでみましょう!!
SSL通信におけるサーバー認証とサーバー証明書
SSL通信でもう1つ重要な要素は なりすまし 盗聴 改ざんを防ぐことです。
サイトが本物のサイトか、途中に暗号を盗聴しているものはいないか、通信内容を不正に書き換えられていないかをチェックできないとネットショッピングとかで使える安全な暗号通信の意味がありません。
それを担保する仕組みがサーバー証明書となるわけです。
デジタル証明書も公開鍵暗号の仕組みを応用した話になります。
サーバー証明書が本物であるかどうかの話の前にサーバー証明書にはどんな情報が書いてあるか見ていきましょう。
サーバー証明書の中身
こちらはAWSのコンソールのSSL通信に使われているサーバー証明書です。
いろんな情報が書かれているかと思いますがこちらはX.509という規格に従って記載されています。
詳しく知りたい人はX.509を確認しましょう!
簡単にAmazonの証明書の要素列挙しますと、
- バージョン
: 3
- シリアル番号: 08 18 CC ....
- 署名アルゴリズム: RSA暗号化を使用するSHA-256
- 発行者
: Amazon CA 1B
- 有効期間
- 開始
: 2020/04/13 9:00:00 JST
- 満了
: 2021/03/15 21:00:00 JST
- 主体者
(サブジェクト)
- ap-northeast-1.console.aws.amazon.com
- 主体者の公開鍵情報
- 公開鍵アルゴリズム: RSA暗号化
- 主体者の公開鍵: 256バイト: 83 22 EB....
- 証明書の署名アルゴリズム: SHA-256 ECDSA
- 証明書の署名: 256バイト26 87 E2...
- フィンガープリント
- SHA-256: C0 75 7D...
- SHA-1: CD 63 42...
Amazonが発行し、ドメインがAmazon所有のap-northeast-1.console.aws.amazon.comがつらつらと書かれているわけです。
他に重要な要素としてサーバー証明書の有効期限があります。有効期限切れの証明書は証明書して無効となります。
基本的にサーバー証明書は有効期限が切れる前に更新しなければいけません。それをやり忘れるのがいわゆる**SSL証明書更新失敗!**というやつです。
証明書の有効期限はOpenSSLコマンドで簡単に取れますので、そういうことがないようにZabbixのExternalScriptとかできっちり管理することをおすすめしますー。
ちなみに下記コマンドで簡単に証明書期限は確認できます。
openssl s_client -connect hoge.com:443 -servername blog.tubone-project24.xyz < /dev/null 2> /dev/null | openssl x509 -noout -startdate -enddate
notBefore=Jul 1 00:00:00 2019 GMT
notAfter=Aug 29 12:00:00 2021 GMT
我が家のサーバーのSSL証明書はZabbixで管理してます。(余談)
認証局
しかし、サーバー証明書に書いてある内容は本当に改ざんなく正しいのでしょうか?
実はサーバー証明書自体はOpenSSLのコマンドでだれでも発行はできてしまいます。(そういうのを俗にオレオレ証明書とか言ったりします)なので証明書自体の確からしさは別の人にケツ持ちしてもらうことで担保しましょう!というのがデジタル証明書の認証局という仕組みとなります。
まずサーバー証明書の中身をハッシュ関数(SHA-256とかそういったやつ)にかけてサーバー証明書固有のハッシュ値を得ます。
そのハッシュ値に認証局というケツ持ちが秘密鍵を使って暗号化処理を行ないます。
すると、そのハッシュ値を複合できるのは認証局の公開鍵になるわけです。
(ちなみに秘密鍵所有者が自身の情報の出所の確からしさを証明するため、秘密鍵で暗号化させ、相手に公開鍵で複合させるという発想が公開鍵暗号を利用したデジタル署名の基本的な考え方です)
つまり..。
サーバー証明書の確からしさを確かめるには、
- サーバー証明書のハッシュを得る
- サーバー証明書についている認証局の秘密鍵で暗号化されたハッシュ値を認証局の公開鍵で複合する
- 1と2が一致しているか確認する
という手順を踏むわけです。
ちなみに認証局の証明書にもちゃんと有効期限が存在します。ココ重要。
手順のおさらい
サーバー証明書作成手順を理解すると実は仕組みがわかりやすいので蛇足ですがコマンド付きで解説します。
まず、サイト管理者などサーバー証明書を作りたい人はサーバー証明書のタマゴであるCertificate Signing Request、いわゆるCSRをOpenSSLのコマンドとかで作成します。
作成の際はセキュリティ上OpenSSLが使う疑似乱数をきっちり回しておくことをお勧めします。
# 疑似乱数作成
./openssl md5 * > rand.dat
# サーバ証明書の暗号化に使う秘密鍵
./openssl genrsa -rand rand.dat -des3 2048 > private.pem
# CSR作成
./openssl req -new -key private.pem -out newcsr.pem
# 証明書の内容について質問されるので答えていく
Country Name (2 letter code) [AU]:JP
State or Province Name (full name) [Some-State]:Tokyo
Locality Name (eg, city) []:Shinjuku-ku
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Tojo-kai
Organizational Unit Name (eg, section) []:Majima-gumi
Common Name (eg, YOUR name) []:yakuza-kusowaru-boryoku.com
CSRができたらこちらをお近くの認証局(DigiCertとか)に送ると認証局の秘密鍵で署名を暗号化したものをCSRにくっつけたいわゆるサーバー証明書が出来上がります。
パッとイメージしやすいのは893の代紋と親子の盃
893映画やゲームが好きなほうならピンとくるかもしれませんが、認証局って893の代紋みたいなもんなんですね。
晴れてあなたが組の長になったとき、その組の正当性や所属を明らかにするために後ろ盾の組の代紋飾るじゃないですか。
もちろんただでは代紋なんて飾れないので親子の盃を交わすことで許可がでるわけですが、これがCSRを認証局に提出すると思ってください。
晴れて、代紋をいただければあとは好きに暴れて結構。(ダメ)
まとめると代紋というのはいわゆる上位の組が下位の組を認証しているんですね!だからビジネスができるわけですね。
失効リストと破門状
余談ですが、証明書には失効リストというものがあります。
認証局が発行した証明書は基本的には認証局自体のもしくはサーバー証明書の有効期限まで効力を持ちます。
ただ、秘密鍵が流出したり、認証したサーバーが悪いことばかりやったりとで有効期限関係なく、 認証局の効力をなくしたい場合があります。
その時使えるのがCertificate Revocation List(CRL)です。失効リストでRevokeとしたサーバー証明書は認証局の署名効力を失います。これは認証局で行なうことができます。
これって893でいうところの破門状ですよね..。
破門状のいい画像がなかったし、本物の破門状を載せるのもどうかと思ったので勝手に小沢仁志オフィシャルブログから借りてきました。
破門状は893の死刑宣告といわれてますが、まさに失効リストはサーバー証明書の死刑宣告なのです。
証明書チェーンとルート証明書
今度はこのブログのサーバー証明書を確認してみましょう。
このブログのドメインであるblog.tubone-project24.xyzが確かに所有しているドメインとなっているわけですが認証局はLet's Encrypt Authority X3ですね。
ではLet's Encrypt Authority X3が本物の認証局かどうかはどうやって判断するのでしょうか?
この仕組みが証明書チェーンです。
実は先ほどのAWSの証明書もそうでしたがチェーンは簡単に追うことができます。
赤枠がチェーンです。
Let's Encrypt Authority X3の証明書を見てみましょう。
Let's Encrypt Authority X3のケツ持ちはどうやらDST Root CA X3がやっているみたいですね。
このように認証局を別の認証局にケツ持ちしてもらうことができます。
さらにチェーンを追ってみます。
DST Root CA X3のケツ持ちはDST Root CA X3ですね...????ん????
これはどういうことかというと言葉通り、DST Root CA X3のケツ持ちはDST Root CA X3がやっているということです。
証明書チェーンではそれ以上、上位の認証局がいない証明書が必ず出てきます。これがルート証明書というやつです。
ルート証明書はそれ以上認証してくれるケツ持ちがいないのでクライアントは盲目的に信頼する必要があります。
えっ!それでいいの?という声が聞こえてきますね...。
ルート証明書の確からしさ
ではサーバー証明書にチェーンされるルート証明書はどのように確からしさをチェックすればいいのでしょうか?
それは、PCを買ったときやブラウザを入れたときにルート証明書をクライアントそばに入れておいて共通のルート証明書を持ったサーバー証明書を検証する仕組みとしているわけです。
Macだとインストールされているルート証明書をキーチェーンから確認できます。
当然ルート証明書にも有効期限があります。
ルート証明書の有効期限が切れないようにOSの定期アップデートやブラウザのバージョンが更新タイミングで最新の証明書がインストールされる寸法になっています。
ルート証明書が更新できないときの代替手段とは?
そうはいってもデバイスがインターネットにつながっていなかったり、組み込み端末などたくさんのルート証明書をインストールする余裕がない場合などあるかと思います。
そういった場合たくさんのルート証明書を検証することはおろか、期限が迫っている証明書更新もできません。
そういった場合はどうやってルート証明書を使うのでしょうか?
クロスルート証明書について
そんなときにクロスルート証明書です。
ルート証明書を更新できなかったり、複数のルート証明書をまとめあげるために使います。
例えば下記の証明書チェーンがあったとします。
ラーメン食べている人がblog.tubone-project24.xyzにアクセスする際のチェーンは中間証明書Aからルート証明書Aをたどって自身の端末にあるルート証明書の検証します。
ではルート証明書が更新された場合はいかがでしょうか?
通常端末のアップデートなどでクライアント側のルート証明書も更新されますので、
と新しいルート証明書Bを使うことになりますね。
ではさらにこの端末がアップデートできない場合やインストールしてないルート証明書へアクセスする場合はどうなるでしょうか?
はい。当然ダメですね..。
(・ω・乂)
(乂・ω・)
(・ω・乂)
(乂・ω・)
blog.tubone-project24.xyzのサーバー証明書の中間証明書から先のルート証明書はルート証明書Bなので自端末に存在しないルート証明書になってしまいます。
これを解決するのがクロスルート証明書というわけです。
クロスルート証明書を使った場合、仮にあなたの端末にルート証明書Aしかない場合、blog.tubone-project24.xyzへのアクセスをするとサーバー証明書から中間証明書をまずチェーンします。
そのまま上のルート証明書を見てもこちら残念ながらさっきと同じなのですが中間証明書に紐づくクロスルート証明書というものがありこちらが内容はルート証明書Bと同じながらルート証明書Aで署名されている形を取ります。すると、クロスルート証明書からルート証明書Aをたどって検証が完了するわけです。
えっ?中間証明書を複数のルート証明書で署名するみたいなそんなことできるんですか..?という疑問が出たそこの君!優秀ですね。
クロスルート方式を利用する場合には、中間証明書とクロスルート設定証明書を連結させて1つにした中間証明書として利用することで上記の通りになります。
—–BEGIN CERTIFICATE—–
中間CA証明書の文字列
—–END CERTIFICATE—–
—–BEGIN CERTIFICATE—–
クロスルート設定用証明書の文字列
—–END CERTIFICATE—–
レガシー端末や組み込み系ではこのような方法を利用してルート証明書の管理をしているわけです。
話を戻してDatadog
さて話を戻してDatadogのコードに移ります。
さきほども申したとおり、通常ルート証明書はPC側のアップデートに追従して更新されていくのでそこまで気になりませんが、この手のエージェントやJDKなどは独自にルート証明書を抱えていて、それを使うようにする場合も多いです。
脱線しますが、たとえばJDKとかではJavaが使うルート証明書をcacertsに格納します。
例えばOpenJDK1.8.0の場合、Macなら、
lib/jvm/java-1.8.0-openjdk-1.8.0.201.b09-2.el7_6.x86_64/jre/lib/security/cacerts
にcacertsがいます。
$ keytool -keystore cacerts -list -v
というコマンドでJDKにインストールされているルート証明書が確認できます。
なんか回りくどいですがこうすることでJavaプロセス単体でSSL通信の検証ができるわけで30億のデバイスで走ることができるわけですね。
30億のデバイスで走るのに自分の環境では走らないJava
(とか揶揄されたことありましたね。余談です。)
DatadogもまさにJDKと同じような実装でした。
ちなみに今回有効期限の切れた証明書はDatadogのサイトCERTIFICATE_VERIFY_FAILED error や本事象のdatadog-agent修正PRのUpdate the Sectigo / USERTrust CA Certificate #3882に書いてある情報から判明しており、Sectigo AddTrust External CA Rootが2020/5/30に失効しました。
Datadog AgentはTornadoを使っている
そもそもDatadog AgentというのはEC2などの監視対象サーバーにサービスの形で常駐させるとCPUやディスクのメトリックを取得し、Datadogに送信してくれるやつです。
Zabbix AgentのDatadog版ですね。
Datadogへメトリックを送信しなければいけないので当然インターネット越しに通信をしなければいけません。もちろん通信はSSLで暗号化されてしかるべきです。
これは知らなかったのですがDatadog AgentってTornadoを使っています。
datadog-agent(version 5)をサービス起動するとlocalhostのport17123にTornadoのサーバーが立ち上がります。
この**Tornadoサーバー(Forwarder)**にmetricsのcollectorが収集した情報を投げ込むことでDatadogサーバーへmetricsをForwardingしてくれる仕組みのようです。
こうすることでDatadog謹製のcollectorだけでなくDogStatusD経由でcustom metricsも効率的に収集できます。
詳しくはdatadog-agentのGitHubhttps://github.com/DataDog/dd-agentや、
をご覧ください。
さてこのTornadoで実際にDatadogと通信しているところはシーケンス図上のTornado HttpClient、正確にはAsynchronous HTTP clientになるわけですが、datadog-agentの作りとして、Asynchronous HTTP clientに独自でca_certsを設定し、Datadogとの通信にdatadog-cert.pemというクロスルート証明書を使っております。
ではdatadog-cert.pemを見てみましょう。
確認にはOpenSSLコマンドが利用できます。
$ openssl x509 -text -noout -in datadog-cert.pem
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 1 (0x1)
Signature Algorithm: sha1WithRSAEncryption
Issuer: C = SE, O = AddTrust AB, OU = AddTrust External TTP Network, CN = AddTrust External CA Root
Validity
Not Before: May 30 10:48:38 2000 GMT
Not After : May 30 10:48:38 2020 GMT
Subject: C = SE, O = AddTrust AB, OU = AddTrust External TTP Network, CN = AddTrust External CA Root
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
00:b7:f7:1a:33:e6:f2:00:04:2d:39:e0:4e:5b:ed:
1f:bc:6c:0f:cd:b5:fa:23:b6:ce:de:9b:11:33:97:
a4:29:4c:7d:93:9f:bd:4a:bc:93:ed:03:1a:e3:8f:
cf:e5:6d:50:5a:d6:97:29:94:5a:80:b0:49:7a:db:
2e:95:fd:b8:ca:bf:37:38:2d:1e:3e:91:41:ad:70:
56:c7:f0:4f:3f:e8:32:9e:74:ca:c8:90:54:e9:c6:
5f:0f:78:9d:9a:40:3c:0e:ac:61:aa:5e:14:8f:9e:
87:a1:6a:50:dc:d7:9a:4e:af:05:b3:a6:71:94:9c:
71:b3:50:60:0a:c7:13:9d:38:07:86:02:a8:e9:a8:
69:26:18:90:ab:4c:b0:4f:23:ab:3a:4f:84:d8:df:
ce:9f:e1:69:6f:bb:d7:42:d7:6b:44:e4:c7:ad:ee:
6d:41:5f:72:5a:71:08:37:b3:79:65:a4:59:a0:94:
37:f7:00:2f:0d:c2:92:72:da:d0:38:72:db:14:a8:
45:c4:5d:2a:7d:b7:b4:d6:c4:ee:ac:cd:13:44:b7:
c9:2b:dd:43:00:25:fa:61:b9:69:6a:58:23:11:b7:
a7:33:8f:56:75:59:f5:cd:29:d7:46:b7:0a:2b:65:
b6:d3:42:6f:15:b2:b8:7b:fb:ef:e9:5d:53:d5:34:
5a:27
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Key Identifier:
AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
X509v3 Key Usage:
Certificate Sign, CRL Sign
X509v3 Basic Constraints: critical
CA:TRUE
X509v3 Authority Key Identifier:
keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
DirName:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root
serial:01
Signature Algorithm: sha1WithRSAEncryption
b0:9b:e0:85:25:c2:d6:23:e2:0f:96:06:92:9d:41:98:9c:d9:
84:79:81:d9:1e:5b:14:07:23:36:65:8f:b0:d8:77:bb:ac:41:
6c:47:60:83:51:b0:f9:32:3d:e7:fc:f6:26:13:c7:80:16:a5:
bf:5a:fc:87:cf:78:79:89:21:9a:e2:4c:07:0a:86:35:bc:f2:
de:51:c4:d2:96:b7:dc:7e:4e:ee:70:fd:1c:39:eb:0c:02:51:
14:2d:8e:bd:16:e0:c1:df:46:75:e7:24:ad:ec:f4:42:b4:85:
93:70:10:67:ba:9d:06:35:4a:18:d3:2b:7a:cc:51:42:a1:7a:
63:d1:e6:bb:a1:c5:2b:c2:36:be:13:0d:e6:bd:63:7e:79:7b:
a7:09:0d:40:ab:6a:dd:8f:8a:c3:f6:f6:8c:1a:42:05:51:d4:
45:f5:9f:a7:62:21:68:15:20:43:3c:99:e7:7c:bd:24:d8:a9:
91:17:73:88:3f:56:1b:31:38:18:b4:71:0f:9a:cd:c8:0e:9e:
8e:2e:1b:e1:8c:98:83:cb:1f:31:f1:44:4c:c6:04:73:49:76:
60:0f:c7:f8:bd:17:80:6b:2e:e9:cc:4c:0e:5a:9a:79:0f:20:
0a:2e:d5:9e:63:26:1e:55:92:94:d8:82:17:5a:7b:d0:bc:c7:
8f:4e:86:04
確かに有効期限は切れてますね。
sectigoのサイトによい図があったので拝借しますが、このようなチェーンをする証明書のようです。
Modern Browser側の動きでは最新のUSERTrust RSAのルート証明書から検証ができますが、Legacy(Datadogもこっち)BrowserはAddTrust External CA Rootまでチェーンしております。
上記のdatadog-cert.pemのCN(CommonName)を見ても確かにAddTrust External CA Rootですね。
よって2020/5/30~SSL通信できなくなってしまったというわけです。
結論
証明書周りでこれほど調べたのは久しぶりです...。
追記
JDKにインストールされているルート証明書も気になったので調べたら、OpenJDK9(JEP319)のルート証明書にAddTrust External CA Rootがいました。
https://openjdk.java.net/jeps/319
ルート証明書の罠に引っかからないようにみんなも気を付けよう!