意思決定のためにOpenTelemetry Metricsを利用する

Mackerelチームでアプリケーションエンジニアをやっているid:lufiabbです。

この記事はMackerel Advent Calendar 2024の20日目の記事です。昨日はid:kmutoさんの「今年もMackerel CREの仕事に絡めて技術素振りしていた、という話」でした*1

Mackerelでは、ラベル付きメトリックとしてOpenTelemetry対応を進めていますが、このメトリックを意思決定の参考にする事例をひとつ共有したいなと思います。

背景

Mackerelの外形監視において、お客さまのサイトへ毎分アクセスするクローラはGoで実装しています。GoではTLS接続を行うパッケージとして標準ライブラリに crypto/tls を持っていて、よほど特別な事情がなければこれが使われることが多いでしょう。Mackerelの外形監視でも crypto/tls を使っています。

ところで、Goは1.xの間は互換性を壊さないことを約束しています。この約束はGo 1 and the Future of Go Programsで公開されているのですが、実はいくつか例外があり、そのうちのひとつに「セキュリティの理由によって互換性を破壊することでしか安全性を確保できない場合」が含まれています。

以下はセキュリティ都合による破壊的変更の例です。

  • Go 1.18でクライアント側のTLS 1.0、1.1がデフォルトで無効になった(オプションを設定すれば使える)
  • Go 1.22でRSAを利用した鍵交換がデフォルトに含まれなくなった(オプションを設定すれば使える)

外形監視ではどうしているのか

上記の例でいえば、TLS 1.0と1.1は2023年8月の告知「外形監視におけるTLSサポートバージョンの変更について」をもって正式にサポート外としましたが、RSAによる鍵交換は現在でもまだサポートしています。

しかしながら、脆弱な暗号スイートを維持し続けてしまうと、本来は対応が必要であるものを正常だとして安心感を与えてしまうことになって望ましくないため、いつかは廃止が必要になります。このとき、過去の事例では、世の中の温度感や緊急度を考慮して廃止時期の判断をしていました*2が、実はあまりデータに基いた判断は実現できていませんでした。

OpenTelemetry対応によって、従来よりも簡単にデータを取れるようになったので、暗号スイートの利用状況を計装しようと思ったところが発端となります。

具体的な実装

OpenTelemetryのSDKでは、カウンタを事前に用意しておいて、必要なときにカウンタへ加算する設計となっていますので、まずはカウンタを用意しました。今回は暗号スイートの利用状況を知りたいので active_cipher_suite と名前を付けました。

import (
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/metric"
)

var meter = otel.Meter("(実際にはここにGoのパッケージ名が入ります)")

var activeCipherSuiteCounter metric.Int64Counter = Must(
    meter.Int64Counter("active_cipher_suite",
        metric.WithUnit("1"),
        metric.WithDescription("監視対象URLへの接続で利用されている暗号スイート")),
)

この他にもExporterを設定する等の手続きが必要ですが、だいたいいつも同じコードになりますし、この記事の主題ではないため省略します。

次にカウントする部分です。

var resp *http.Response = ...

// resp.Request.URL.Scheme == "http" の場合はTLSがnilとなる
if resp.TLS != nil {
    activeCipherSuiteCounter.Add(ctx, 1,
        metric.WithAttributes(semconv.TLSCipher(tls.CipherSuiteName(resp.TLS.CipherSuite))))
}

少し複雑にみえるので分解すると、

resp.TLS.CipherSuite

これで、HTTPクライアントがサーバとの接続で実際に利用した暗号スイートのIDを得ることができます。この値は以下の左側に書かれた数値です。

0x0005 // TLS_RSA_WITH_RC4_128_SHA
0xc007 // TLS_ECDHE_ECDSA_WITH_RC4_128_SHA
0xc02c // TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
...

次に、OpenTelemetryではSemantic Conventionsとして「よく使う属性名」を事前に定義してくれていますので、そこにあるなら利用したほうが望ましいでしょう。暗号スイートとしてはtls.cipherが定義されているのですが、この値は TLS_ECDHE_ECDSA_WITH_RC4_128_SHA 形式の文字列を期待しているため、resp.TLS.CipherSuite の値をそのまま使うことができません。

そのため、tls.CipherSuiteName 関数で数値から文字列に変換して、それをカウントしているというコードとなります。

計装してみての感想

計装の完了後、メトリックエクスプローラーで active_cipher_suite を選択すると利用状況のグラフが見えるようになります。実際の利用状況は以下のようなグラフになりました。

tls.cipherの利用状況をグラフに描画した様子です。暗号スイートごとに変化を描画しています。

ボイラープレート的な部分を除けば、実質的なコードは3行程度で「いつまでサポートするか」の判断に使えるデータを得ることができました。

こういった、アプリケーションの中では精度の高いデータがあるけれど、これまでは手軽に見られなかったようなものを観測するためにも使えるので、お手元のアプリケーションで知りたいものがあれば選択肢として持っておいて頂けると開発者としては嬉しいです。

Mackerel Advent Calendar 2024、明日は@yohfeeさんです。

*1:素振りとは

*2:廃止の場合は、必ず1ヶ月前に告知をしてから廃止するという段階を踏みます