kmuto’s blog

はてな社でMackerel CREをやっています。料理と旅行といろんなIT技術

OBI 0.7.0で実装されたHTTPヘッダエンリッチメントの機能を使ってみた

OBI(OpenTelemetry eBPF Instrumentation)の新しいマイナーバージョン0.7.0が4月4日にリリースされた。その後9日にバグフィクスの0.7.1がリリースされている。

github.com

v0.7.0の大きめのトピックとしては、HTTPヘッダエンリッチメントがあり、OpenTelemetryのブログ記事が出ている。

opentelemetry.io

HTTP header enrichment for spans」にも短くまとまっている。

「エンリッチメント」はどうにも訳しづらいが、増やす・拡張・拡充といったニュアンスになるだろうか。機能として要はHTTPのリクエストやレスポンスに含まれている任意のヘッダフィールドの情報をスパンの属性に含めることができるようになった。

ということで、このHTTPヘッダエンリッチメントを試してみた。

HTTPヘッダの内容はデフォルトではスパンに表れない

たとえば次のようなHTTPリクエストを送ったとする。

curl -H "Authorization: secret" -H "X-Tenant-Id: tenant-2" -H 'X-User-Segment: free' http://localhost:8000

通常のOBIではhttp.routehttp.response.status_codeなどはわかるが、リクエストに付けていたヘッダフィールド(上記ではAuthorizationX-Tenant-IdX-User-Segment)はどれも失われている。

HTTPヘッダエンリッチメントを有効にするための設定

v0.7.0から加わったHTTPヘッダエンリッチメントを有効にすると、ヘッダフィールドとその値が、スパンの属性として含まれるようになる。

とはいえ、上記のAuthorizationフィールドのように「送られていることは知りたいが値は機微なので見せたくない」というものもある。そのようなときにはobfuscate(不明瞭化)アクションで特定文字列に置換できるようになっている。

上記のリクエストに対応してHTTPヘッダエンリッチメントを使うようにしたOBI設定ファイルの例を示す。

ebpf:
  buffer_sizes:
    http: 8192
  payload_extraction:
    http:
      enrichment:
        enabled: true
        policy:
          default_action: exclude
          obfuscation_string: '*****'
        rules:
          - action: include
            type: headers
            scope: all
            match:
              patterns:
                - x-tenant-id
                - x-user-*
              case_sensitive: false
          - action: obfuscate
            type: headers
            scope: all
            match:
              patterns:
                - authorization
              case_sensitive: false

まず、リクエストヘッダの解析のためにはbuffer_sizes.httpでキャプチャバッファサイズを広くとることが必須だ。ここでは8192(バイト)としているが、やりとりのペイロードによってはもっと大きくする必要があるかもしれない(最大65536)。

payload_extraction.http.enrichment.enabledtrueにすることで、HTTPヘッダエンリッチメントが有効になる。環境変数OTEL_EBPF_HTTP_ENRICHMENT_ENABLEDでも指定できる。

policydefault_actionでは、ヘッダフィールドのスパン属性化の基本の挙動をinclude(除外するものをルールで指定。ブラックリスト方式)かexclude(取り込むものをルールで指定。ホワイトリスト方式)かで決定する。実際の用途で全部属性化したいというシーンはそうないだろうから、excludeでよいだろう(default_actionのデフォルト値なので、実はこの行は省略できる)。対応する環境変数はOTEL_EBPF_HTTP_ENRICHMENT_DEFAULT_ACTION

obfuscation_stringでは、不明瞭化対象のフィールドの値に対し、置き換える文字列を指定している。環境変数ではOTEL_EBPF_HTTP_ENRICHMENT_OBFUSCATION_STRING

あとはrulesに配列形式で具体的にアクションを記述していく。

  • action:デフォルトのアクションがexcludeなので、取り込みたいときにはincludeを指定する。obfuscateは取り込みつつ値はobfuscation_stringで指定した文字列で置き換える(policyで定義しているため、ルールによって文字列を使い分けることはできない)
  • type:探す対象。ここではHTTPヘッダとしてheadersを指定する
  • scope:適用対象としてリクエスト(request)/レスポンス(response)/両方(all)から選ぶ
  • matchpatternsには配列形式で文字列パターンを指定する。グロブ(ワイルドカード)が使えるので、この例ではx-tenant-idという完全一致のフィールドのほかに、x-user-から始まる任意の名前のフィールドが対象になる。case_sensitiveは名前のとおり英字の大文字・小文字を区別するかどうかの設定で、falseの場合は区別しない(これはデフォルト値なのでこの設定行は省略可能)

リクエストを実行し、スパン属性を見てみよう。

期待どおりhttp.request.header.authorizationhttp.request.header.x-tenant-idhttp.request.header.x-user-segmentというスパン属性が含まれており、対応する値も入っている。現時点ではHTTPヘッダ向けのsemconv(Semantic Conventions、属性名の命名規則)はまだ決まっていないようだ。

obfuscateアクション対象のAuthorizationフィールド、つまりhttp.request.header.authorizationは、obfuscation_stringの文字列*****に置き換えられた。

確認はOBIのログでも可能だ。OBI設定ファイルでtrace_printer: json_indentとしておき、リクエストを実行すると、出力されるJSONから見つけられる。

[
  {
   ...
   "attributes": {
    ...
    "http.request.header.authorization": "*****",
    "http.request.header.x-tenant-id": "tenant-2",
    "http.request.header.x-user-segment": "free",
    ...

同じヘッダを複数指定するとどうなるだろうか。

良い感じの配列になってくれた。

特殊な文字を含むケースも試してみよう。

curl -H "Authorization: secret" -H "X-Tenant-Id: tenant-2" -H "X-User-Segment: <&>\":'😎" -H 'X-User-Segment: campaign' http://localhost:8000

"http.request.header.x-user-segment": "\u003c\u0026\u003e\":'😎, campaign",

このあたりはもともとjson.Marshalでエスケープされているようだ。

まとめ

HTTPヘッダエンリッチメントの機能を使い、HTTPヘッダをスパン属性化できた。ここから調査に活用できるシーンはいろいろ思いつきそうだ。

  • User-Agentから人間とボットを分類する
  • X-Forwarded-ForViaからネットワーク経路のボトルネックを探る
  • X-Cacheからコンテンツキャッシュの効果を見る

OBIではハッシュ化などの丁寧なことはしてくれないため、誤って機微情報をオブザーバビリティプラットフォームに投稿してしまわないように注意したい。ハッシュ化をしたいときにはたとえばOpenTelemetryコレクターを挟めば実現できるだろう。

In the future...次のバージョンではHTTPボディも見える

ところでpayload_extraction.http.enrichment.policy.default_actionについては、Add HTTP full body extraction by NimrodAvni78 · Pull Request #1750 · open-telemetry/opentelemetry-ebpf-instrumentationがmainブランチにマージされたことで破壊的変更となることが確定した。

JSON形式限定ではあるものの、HTTPボディも対象になることで、より詳細な解析ができるだろう。

新しい書き方ではHTTPヘッダとHTTPボディをそれぞれ分けて設定するために、default_actionを以下のように記述することになっている(ルールではtype: bodyが使えるようになる。環境変数OTEL_EBPF_HTTP_ENRICHMENT_DEFAULT_ACTIONにはおそらく何か改修が入ると思われる)。ペイロードが大きくなるので、buffer_sizes.httpをさらに大きくとる必要も出てきそうだ。

ebpf:
  ...
  payload_extraction:
    http:
      enrichment:
        enabled: true
        policy:
          default_action:
            headers: exclude
            body: include
  .     ..