この記事は541文字約30分で読めます

2020/05/30に発生したDatadog AgentのCERTIFICATEVERIFYFAILED error について少し考察してみようかと思います。

Table of Contents

CERTIFICATEVERIFYFAILED は突然に

「ギャー!めっちゃEC2の監視が落ちているんですけど!」と連絡を受けた土曜日。

カンパリオレンジならぬカンパリ野菜生活をゴクゴク飲んでいた私は意識朦朧とする中、Slackを見る

ギャー!!

img

めっちゃ落ちてますね....。

一通りサービスの状態を見て問題なかったのでこの日は持ち前の「大丈夫ですよ(大嘘)」をかまして眠ることにしました。

Datadogからのアナウンス

翌日、Datadogからアナウンスがありました。

CERTIFICATEVERIFYFAILED 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版 秘密の国のアリスがいいと思います。

私をこの業界に引き込んだ師が大学の頃に読め!と言われて買って読んだ記憶があります。

今でも家の本棚にありました。マジで良書です。わかりやすいです。

img

さて簡単にSSL通信のおさらいをします。

img 引用: https://ssl.sakura.ad.jp/column/ssl/

わかりやすいイラストがさくらのSSLにありましたので拝借します。

重要なポイントとして、ブラウザ(クライアント)はSSL通信をするときにSSLの通信リクエスト(ClientHello)を送信すると、サーバからサーバ証明書公開鍵が送られてきます。

暗号通信についてはこのうち公開鍵が利用され、クライアント側でランダムな値を共通鍵のシークレットとして利用することを決定し公開鍵を使って暗号化し、サーバに送信します。

するとサーバでは秘密鍵を使って共通鍵が取り出せるのでここから共通鍵暗号(例えばAESとか)での通信ができるわけです。これが鍵交換というやつです。

えっ!?普通に互いの公開鍵を交換して公開鍵暗号で通信すればいいだけじゃんアゼルバイジャンと思ったそこの君!!それは違いますね。

まず、その場合クライアントに公開鍵と対応する秘密鍵を用意する必要がありますね。あなたは田舎のおばあちゃんにOpenSSLコマンドを打たせるんですか...それはきついですよね。

もう一つの問題は一般的に公開鍵暗号は共通鍵暗号に比べて暗号コストが高いということです。それなりの桁数の乱数をぶつけていくので共通鍵の数十~数千倍遅いといわれてます。

ちなみにSSLの通信で、出てきた公開鍵暗号や共通鍵暗号のアルゴリズムパターンを定義するのが皆さん大好きおなじみの Cipher suiteです。

サイト管理者ならひと昔前、ATS対応とかでツライ思いをした人もいるかもしれませんね。

ECDHE-RSA-AES128-GCM-SHA256 こんなやつ...あぁ...SHA-1の悪夢がよみがえりますね。

脱線ついでにもう一つ。

上記で述べた鍵交換一つとってもたとえばRSAという方式ではほぼ上記で説明している通りの流れを組むのですが、前方秘匿性に弱い欠点(一言でいえば全暗号通信を記憶していた状態で秘密鍵を手に入れたらすべてがばれてしまう)があったりで最近ではDHE(DiffieHellman, Ephemeral)という鍵交換方式を楕円曲線暗号上で行うECDHE(RFC4492)が主流だったりします。

ここまでのお話はぜひ暗号技術入門 第3版 秘密の国のアリスを読んでいただければ私なんかよりわかりやすく説明しているので興味がある人はぜひ読んでみましょう!!

SSL通信におけるサーバ認証とサーバ証明書

SSL通信でもう一つ重要な要素は なりすまし 盗聴 改ざんを防ぐことです。

サイトが本物のサイトか、途中に暗号を盗聴しているものはいないか、通信内容を不正に書き換えられていないかをチェックできないとネットショッピングとかで使える安全な暗号通信の意味がありません。

それを担保する仕組みがサーバ証明書となるわけです。

デジタル証明書も公開鍵暗号の仕組みを応用した話になります。

サーバ証明書が本物であるかどうかの話の前にサーバ証明書にはどんな情報が書いてあるか見ていきましょう。

サーバ証明書の中身

img

こちらは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証明書更新失敗!というやつです。

img

証明書の有効期限は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で管理してます。(余談)

img

認証局

しかし、サーバ証明書に書いてある内容は本当に改ざんなく正しいのでしょうか?

実はサーバ証明書自体はOpenSSLのコマンドでだれでも発行はできてしまいます。(そういうのを俗にオレオレ証明書とか言ったりします)なので証明書自体の確からしさは別の人にケツ持ちしてもらうことで担保しましょう!というのがデジタル証明書の認証局という仕組みとなります。

まずサーバ証明書の中身をハッシュ関数(SHA-256とかそういったやつ)にかけてサーバ証明書固有のハッシュ値を得ます。

そのハッシュ値に認証局というケツ持ちが秘密鍵を使って暗号化処理を行います。

すると、そのハッシュ値を複合できるのは認証局の公開鍵になるわけです。

(ちなみに秘密鍵所有者が自身の情報の出所の確からしさを証明するため、秘密鍵で暗号化させ、相手に公開鍵で複合させるという発想が公開鍵暗号を利用したデジタル署名の基本的な考え方です)

つまり...

サーバ証明書の確からしさを確かめるには

  1. サーバ証明書のハッシュを得る
  2. サーバ証明書についている認証局の秘密鍵で暗号化されたハッシュ値を認証局の公開鍵で複合する
  3. 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の代紋と親子の盃

img

893映画やゲームが好きな方ならピンとくるかもしれませんが、認証局って893の代紋みたいなもんなんですね。

晴れてあなたが組の長になったとき、その組の正当性や所属を明らかにするために後ろ盾の組の代紋飾るじゃないですか。

もちろんただでは代紋なんて飾れないので親子の盃を交わすことで許可がでるわけですが、これがCSRを認証局に提出すると思ってください。

晴れて、代紋をいただければあとは好きに暴れて結構。(ダメ)

まとめると代紋というのはいわゆる上位の組が下位の組を認証しているんですね!だからビジネスができるわけですね。

img

失効リストと破門状

余談ですが、証明書には失効リストというものがあります。

認証局が発行した証明書は基本的には認証局自体のもしくはサーバ証明書の有効期限まで効力を持ちます。

ただ、秘密鍵が流出したり、認証したサーバが悪いことばかりやったりとで有効期限関係なく、  認証局の効力をなくしたい場合があります。

その時使えるのがCertificate Revocation List(CRL)です。失効リストでRevokeとしたサーバ証明書は認証局の署名効力を失います。これは認証局で行うことができます。

これって893でいうところの破門状ですよね...

img

破門状のいい画像がなかったし、本物の破門状を載せるのもどうかと思ったので勝手に小沢仁志オフィシャルブログから借りてきました。

破門状は893の死刑宣告といわれてますが、まさに失効リストはサーバ証明書の死刑宣告なのです。

証明書チェーンとルート証明書

今度はこのブログのサーバ証明書を確認してみましょう。

img

このブログのドメインであるblog.tubone-project24.xyzが確かに所有しているドメインとなっているわけですが認証局はLet's Encrypt Authority X3ですね。

ではLet's Encrypt Authority X3が本物の認証局かどうかはどうやって判断するのでしょうか?

この仕組みが証明書チェーンです。

実は先ほどのAWSの証明書もそうでしたがチェーンは簡単に追うことができます。

img

赤枠がチェーンです。

Let's Encrypt Authority X3の証明書を見てみましょう。

img

Let's Encrypt Authority X3のケツ持ちはどうやらDST Root CA X3がやっているみたいですね。

このように認証局を別の認証局にケツ持ちしてもらうことができます。

さらにチェーンを追ってみます。

img

DST Root CA X3のケツ持ちはDST Root CA X3ですね...????ん????

これはどういうことかというと言葉通り、DST Root CA X3のケツ持ちはDST Root CA X3がやっているということです。

証明書チェーンではそれ以上、上位の認証局がいない証明書が必ず出てきます。これがルート証明書というやつです。

ルート証明書はそれ以上認証してくれるケツ持ちがいないのでクライアントは盲目的に信頼する必要があります。

えっ!それでいいの?という声が聞こえてきますね...。

ルート証明書の確からしさ

ではサーバ証明書にチェーンされるルート証明書はどのように確からしさをチェックすればいいのでしょうか?

それは、PCを買ったときやブラウザを入れたときにルート証明書をクライアント側に入れておいて共通のルート証明書を持ったサーバ証明書を検証する仕組みとしているわけです。

Macだとインストールされているルート証明書をキーチェーンから確認することができます。

img

当然ルート証明書にも有効期限があります。

ルート証明書の有効期限が切れないようにOSの定期アップデートやブラウザのバージョンが更新タイミングで最新の証明書がインストールされる寸法になっています。

ルート証明書が更新できない時の代替手段とは?

そうはいってもデバイスがインターネットにつながっていなかったり、組み込み端末などたくさんのルート証明書をインストールする余裕がない場合などあるかと思います。

そういった場合たくさんのルート証明書を検証することも、期限が迫っている証明書を更新することもできません。

そういった場合はどうやってルート証明書を使うのでしょうか?

クロスルート証明書について

そんなときにクロスルート証明書です。

ルート証明書を更新できなかったり、複数のルート証明書をまとめあげるために使います。

例えば下記の証明書チェーンがあったとします。

img

ラーメン食べている人がblog.tubone-project24.xyzにアクセスする際のチェーンは中間証明書Aからルート証明書Aをたどって自身の端末にあるルート証明書の検証を行います。

ではルート証明書が更新された場合はいかがでしょうか?

通常端末のアップデートなどでクライアント側のルート証明書も更新されますので

img

と新しいルート証明書Bを使うことになりますね。

ではさらにこの端末がアップデートできない場合やインストールしてないルート証明書へアクセスする場合はどうなるでしょうか?

img

はい。当然ダメですね...

(・ω・乂) (乂・ω・) (・ω・乂) (乂・ω・)

blog.tubone-project24.xyzのサーバ証明書の中間証明書から先のルート証明書はルート証明書Bなので自端末に存在しないルート証明書になってしまいます。

これを解決するのがクロスルート証明書というわけです。

img

クロスルート証明書を使った場合、仮にあなたの端末にルート証明書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億のデバイスで走ることができるわけですね。

img

30億のデバイスで走るのに自分の環境では走らないJava

(とか揶揄されたことありましたね。余談です。)

DatadogもまさにJDKと同じような実装でした。

ちなみに今回有効期限の切れた証明書はDatadogのサイトCERTIFICATEVERIFYFAILED error や本事象のdatadog-agent修正PRのUpdate the Sectigo / USERTrust CA Certificate #3882に書いてある情報から判明しており、Sectigo AddTrust External CA Root2020/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のサーバが立ち上がります。

img

このTornadoサーバ(Forwarder)にmetricsのcollectorが収集した情報を投げ込むことでDatadogサーバへmetricsをForwardingしてくれる仕組みのようです。

こうすることでDatadog謹製のcollectorだけでなくDogStatusD経由でcustom metricsも効率的に収集することができます。

img

詳しくはdatadog-agentのGitHubhttps://github.com/DataDog/dd-agent

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のサイトによい図があったので拝借しますが、このようなチェーンをする証明書のようです。

img

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がいました。

img

https://openjdk.java.net/jeps/319

ルート証明書の罠に引っかからないようにみんなも気を付けよう!

˚