OBI 0.7.0で実装されたHTTPヘッダエンリッチメントの機能を使ってみた
OBI(OpenTelemetry eBPF Instrumentation)の新しいマイナーバージョン0.7.0が4月4日にリリースされた。その後9日にバグフィクスの0.7.1がリリースされている。
v0.7.0の大きめのトピックとしては、HTTPヘッダエンリッチメントがあり、OpenTelemetryのブログ記事が出ている。
「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.routeやhttp.response.status_codeなどはわかるが、リクエストに付けていたヘッダフィールド(上記ではAuthorization、X-Tenant-Id、X-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.enabledをtrueにすることで、HTTPヘッダエンリッチメントが有効になる。環境変数OTEL_EBPF_HTTP_ENRICHMENT_ENABLEDでも指定できる。
policyのdefault_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)から選ぶmatch:patternsには配列形式で文字列パターンを指定する。グロブ(ワイルドカード)が使えるので、この例ではx-tenant-idという完全一致のフィールドのほかに、x-user-から始まる任意の名前のフィールドが対象になる。case_sensitiveは名前のとおり英字の大文字・小文字を区別するかどうかの設定で、falseの場合は区別しない(これはデフォルト値なのでこの設定行は省略可能)
リクエストを実行し、スパン属性を見てみよう。

期待どおりhttp.request.header.authorization、http.request.header.x-tenant-id、http.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-ForやViaからネットワーク経路のボトルネックを探る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 . ..