🍞

OTel Collector 間にて認証を行う

に公開
2025/12/14

はじめに

本記事は OpenTelemetry Advent Calendar 2025 の 9 日目の記事です。

https://qiita.com/advent-calendar/2025/otel

こんにちは、児玉です。
今回は表題の通り、OTel Collector 間の認証 (OIDC) を行うパターンを紹介できればと思います。

マルチクラウド環境や外部から、何らかの理由でテレメトリを受け取る Gateway を公開する必要がある場合などで、(まずは)認証をかけたいというようなケースが場合によってはあるのかなと思います。

そういった場合の一つの実装例として、oauth2clientauthextension (テレメトリを送る側にて利用) と oidcauthextension (テレメトリを受け取る側にて利用) を用いて OTel Collector として認証を設け、テレメトリを送ることができるクライアントを制限する、という形を紹介できればと思います。

構成

構成は以下の通り、マルチクラウド環境のクライアントより、 OIDC による認証した上で、そこで発行されたトークンを持ってテレメトリ Gateway にテレメトリを送信していく、というシンプルな構成にしています。

実際に利用するサービスとしては、それぞれ以下のような形で進めたいと思います。
Google Cloud と AWS のクライアント (telemetrygen という、テレメトリを生成できるテスト用途のクライアント) から Keycloak に対して認証リクエストを送り、Keycloak によって認証された後、テレメトリ (Trace) をテレメトリ Gateway に、そしてそこから Grafana Tempo に送るようにしています。

また、本記事では簡単のため、Keycloak は Cloud Run にて構築しておりますが、本来 Keycloak はステートフルアプリケーションのため、ホスト先として Compute Engine や DB にて永続化を行うなどが必要になってきますので、その点ご承知おきください。

設定例

長くなってしまうので、Cloud Run や ECS、Keycloak、Grafana Tempo などの構築手順自体は省略とさせていただきますが、それぞれの設定例などを紹介できればと思います。

また、所々設定についても省略しておりますが、最後に連携する GitHub リポジトリにて実際のファイルをご覧いただければと思います。

telemetrygen

テレメトリ生成クライアントである telemetrygen については、以下のようなコマンドでデバッグしやすいよう、 Trace を流し続けるようにしました。

telemetrygen traces \              # 'traces' or 'logs' or 'metrics'
  --otlp-insecure \
  --otlp-endpoint=localhost:4317 \ # Cloud Run, ECS にてサイドカー(OTel Col)への通信のため 'localhost'
  --service=test-service \
  --rate=1 \                       # 1秒あたりの生成数
  --duration=inf                   # ずっと実行し続ける

OTel Col (Agent)

以下は OTel Col (Agent) の OCB (OpenTelemetry Collector Builder) マニフェストになります。 extensions として oauth2clientauthextension を追加しており、こちらの拡張機能によって OAuth2 Client Credentials Grant (client_id/secret と access_token を交換する、M2M のためのブラウザを介さないシンプルな認証) が可能になります。

otelcol-agent-ocb.yaml
dist:
  name: otelcol-agent
  output_path: /otelcol-dist

receivers:
  - gomod: go.opentelemetry.io/collector/receiver/otlpreceiver v0.115.0

...

extensions: # 認証のために追加
  - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/oauth2clientauthextension v0.115.0

以下は OTel Col (Agent) の設定ファイルになります。 Keycloak で設定した cliet_id/secret やトークン発行エンドポイント、テレメトリ送信先や認証方式の設定などを行なっています。

otelcol-agent-config.yaml
extensions:
  oauth2client:
    client_id: otel-collector            # Keycloak にて設定した client_id
    client_secret: otel-collector-secret # Keycloak にて設定した client_secret
    token_url: https://keycloak.../realms/opentelemetry/protocol/openid-connect/token # Keycloak トークン発行エンドポイント
    scopes: ["openid"] # 'openid' とすることで JWT 形式とする。Receiver (Gateway) 側での検証に必要。

...

exporters:
  otlphttp/auth:
    endpoint: https://gateway...:4318 # テレメトリ送信先エンドポイント (テレメトリ Gateway)
    auth:
      authenticator: oauth2client # どの認証方式を使うか。extentions にて指定した名前。

service:
  extensions: [oauth2client]
  pipelines:
    traces:
      ...
      exporters: [otlphttp/auth, debug]

Keycloak

続いて、Keycloak の設定をしていきます。特に、Mapper の設定にて otel-collector という値をアクセストークン内の aud (Audience) クレーム (本トークンが誰向けに発行されたかを示す項目) に含めるようにしています。この項目を後述する、テレメトリ Gateway における認証で検証することになります。

reaml.json
{
  "realm": "opentelemetry",
  "clients": [
    {
      "clientId": "otel-collector",
      "clientAuthenticatorType": "client-secret",
      "secret": "otel-collector-secret",
      "serviceAccountsEnabled": true, // Client Credentials Grant(M2M 認証)を有効にする。
      "standardFlowEnabled": false, // 通常のブラウザ経由のログインフローを無効にする。
      "protocol": "openid-connect",
      "protocolMappers": [
        {
          ...
          "config": {
            "included.client.audience": "otel-collector", // 'aud' クレームに設定される値。
            "id.token.claim": "true",
            "access.token.claim": "true"
          }
        }
      ]
    }
  ]
}

以下は上記を設定した後実施いただける、トークンを発行するリクエストの例とトークンペイロード (中身) の例になります。

token-request
curl -X POST "https://keycloak.../realms/opentelemetry/protocol/openid-connect/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=otel-collector" \ # Keycloak にて設定した client_id
  -d "client_secret=otel-collector-secret" \ # Keycloak にて設定した client_secret
  -d "scope=openid"

aud クレームに先ほど設定した otel-collector という値が入っていることがわかりますね。
また、iss クレーム (トークンの発行者) の値も後述するテレメトリ Gateway にて検証されることとなります。

token-decoded
{
  "exp": 1765476759,
  "iat": 1765476459,
  "iss": "https://keycloak.../realms/opentelemetry",
  "aud": ["otel-collector", "account"], # 'otel-collector' が含まれている。
  "typ": "Bearer",
  "scope": "openid email profile",
  "client_id": "otel-collector"
  ...
}

OTel Col (Gateway)

最後にテレメトリ Gateway における設定を見ていきたいと思います。

まずは、OCB マニフェストを見ていきましょう。extensions として oidcauthextension を追加しており、これによって、リクエストに含まれる JWT (アクセストークン) を OIDC プロバイダー (今回の場合は Keycloak) の公開鍵によって検証することができるようになります。

otelcol-gateway-ocb.yaml
dist:
  name: otelcol-gateway
  output_path: /otelcol-dist

receivers:
  - gomod: go.opentelemetry.io/collector/receiver/otlpreceiver v0.115.0

...

extensions: # 認証のために追加
  - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/oidcauthextension v0.115.0

次に、OTel Col (Gateway) の設定ファイルになります。extensions.oidc.providers にて先ほど Keycloak にて設定し、発行したトークン内にも含まれていた issuer_url と audience の値が指定されています。これらいずれかの値が、受け取ったリクエスト内のトークンでの値と一致しない場合、認証としては失敗し、401 が返ることとなります。

otelcol-gateway-config.yaml
extensions:
  oidc:
    providers:
      - issuer_url: https://keycloak.../realms/opentelemetry # トークン発行者 (Keycloak) の URL
        audience: otel-collector # `aud` クレームにて指定した値

receivers:
  otlp:
    protocols:
      http:
        endpoint: 0.0.0.0:4318
        auth:
          authenticator: oidc # どの認証方式を使うか。extentions にて指定した名前。

...

service:
  extensions: [oidc]
...

動作確認

結果のみを見せる形にはなってしまいますが、上記の構成・設定にて実際に Trace を送った場合、Grafana Tempo からは以下のように見えます。それぞれのクライアントから Trace が送られ、その都度きちんと認証が実行されているよう (トークンエンドポイントが呼ばれているよう) ですね。

Cloud Run (OTel Agent A) からの Trace

ECS (OTel Agent B) からの Trace

それぞれの Trace が到達しています。

Keycloak のログで、Cloud Run・ECS より Trace 送信時に認証が走るため、その都度トークンエンドポイントが叩かれています。

ここで、 Keycloak 側の設定にて、トークンに含まれる aud クレームの値を、テレメトリ Gateway より期待されている otel-collector から wrong-audience に変更してみます。

以下は Cloud Run (OTel Agent A) のログですが、期待されているものとは違う aud クレームとなっていると、テレメトリ Gateway より 401 で弾かれるようになりました。

また、テレメトリ Gateway の方でもたくさん 401 が出ていますね。

aud クレーム変更後、どちらからも Trace が届かなくなりました。想定通りの動作になっていそうです。

おまけ

おまけとして、認証にて発行したトークン内のカスタムクレームを用いて、サービスごとに異なるテレメトリパイプラインを実行する、という例についても紹介したいと思います。

ユースケースとしては、「マルチテナント環境にて、テナント(サービス)ごとに異なるバックエンドへテレメトリをルーティングする」や「サービスの重要度に応じてサンプリング率を変える(決済や認証などクリティカルなサービスは 100% 保持、バッチ処理は 10% のみ保持してコスト削減)」など、様々考えられると思いますが、ここでは「PII (個人情報) を含むサービスのみ、マスキング処理を適用する」というケースを想定してみたいと思います。

PII を含む User Service と PII を含まない Batch Service を想定し、User Service からのテレメトリ (Log) に対してのみ、メールアドレスや電話番号などの PII をマスキングする処理を適用します。

これを実現するために、Keycloak で発行するトークンに contains_pii (任意の名前で問題ありません。) というカスタムクレームを追加し、テレメトリ Gateway 側でそのクレームの値に基づいてパイプラインを分岐させます。

改めて、それぞれの設定例を紹介できればと思います。
まず Keycloak では、contains_pii という名前のカスタムクレームを設定しています。

realm.json
{
  "clients": [
    {
      "clientId": "user-service-collector", // ユーザサービス (個人情報あり)
      "secret": "user-service-secret",
      "serviceAccountsEnabled": true,
      "protocolMappers": [
        {
          "name": "pii-flag-mapper",
          "config": {
            "claim.name": "contains_pii",
            "claim.value": "true", // PII あり
          }
        }
      ]
    },
    {
      "clientId": "batch-service-collector", // バッチサービス (個人情報なし)
      "secret": "batch-service-secret",
      "protocolMappers": [
        {
          "name": "pii-flag-mapper",
          "protocolMapper": "oidc-hardcoded-claim-mapper",
          "config": {
            "claim.name": "contains_pii",
            "claim.value": "false", // PII なし
          }
        }
      ]
    }
  ]
}

上記設定により、発行されるトークンには以下のようなカスタムクレームが含まれるようになります。

token-decoded
{
  "exp": 1765498091,
  "iat": 1765497791,
  "iss": "https://keycloak.../realms/opentelemetry",
  "contains_pii": "true", # ユーザサービスなので 'true' となっている。
  "service_name": "user-service",
}

そして、テレメトリ Gateway の OTel Col 設定ファイルを設定します。認証にかかる情報 (カスタムクレームなど) は、以下、マスキングを行っている Transform Processor より直接参照することができないため、Resource Processor を用いて resource.attributes へコピーするなどしています。また、マスキングの処理も記述しています。

otelcol-gateway-config.yaml
processors:
  # カスタムクレームを Resource Attributes に抽出
  # transform processor は resource.attributes のみ参照可能なため、
  # auth context から橋渡しが必要
  resource/extract_pii_flag:
    attributes:
      - key: pii.contains
        from_context: auth.claims.contains_pii
        action: upsert
      - key: service.name.from_claim
        from_context: auth.claims.service_name
        action: upsert

  # PII マスキング (email, phone, name)
  transform/mask_pii:
    error_mode: ignore
    log_statements:
      - context: log
        statements:
          # email: example@test.com -> ex***@test.com
          - replace_pattern(attributes["user.email"], "^(.{2}).*(@.*)$$", "$$1***$$2")
          # phone: 000-0000-0000 -> 000-****-0000
          - replace_pattern(attributes["user.phone"], "^(.{3}).*(.{4})$$", "$$1-****-$$2")
          # name: 田中太郎 -> 田***
          - replace_pattern(attributes["user.name"], "^(.).*$$", "$$1***")

service:
  extensions: [oidc]
  pipelines:
    # PII あり: マスキング適用
    logs/pii:
      receivers: [routing/logs]
      processors: [transform/mask_pii]
      exporters: [otlp/tempo, debug]

    # PII なし: そのまま転送
    logs/normal:
      receivers: [routing/logs]
      processors: []
      exporters: [otlp/tempo, debug]

準備ができたので、以下のような、個人情報を含んだ User Service からのテレメトリ (Log) を生成してみます。

# user-service (PII あり)
telemetrygen logs \
  --logs=3 \
  --telemetry-attributes='user.email="example@test.com"' \
  --telemetry-attributes='user.phone="000-0000-0000"' \
  --telemetry-attributes='user.name="田中太郎"'

以下テレメトリ Gateway のログにて、contains_pii が true となった上で、name などがマスキングされているのがわかります。

同様に、Batch Service からのテレメトリ (Log) を生成してみます。(ややこしくなり申し訳ないですが、わかりやすさのため先ほどの個人情報と似た形式の情報を送っています。)

# batch-service (PII なし)
telemetrygen logs \
  --logs=3 \
  --telemetry-attributes='server.app="batch@example.com"' \
  --telemetry-attributes='user.phone="111-2222-3333"' \
  --telemetry-attributes='user.name="山田花子"'

contains_pii が false となり、マスキングが行われていないことが確認できます。

このように、サービスごとに異なるテレメトリパイプラインを実行する方法の一つとして、カスタムクレームによる分岐を紹介させていただきましが、上記で紹介したような分岐やロジックが Gateway に集中することになり、運用しづらくなるケースもあるかと思います。その場合は、処理を Agent 側で行う、あるいは PII を扱うサービス専用の Gateway を分離するなどの構成も考えられるのかなと思います。

認証で得られたクレーム情報を、テレメトリパイプラインの制御に活用する一例として、参考にしていただければ幸いです。

おわりに

長くなってしまいましたが、最後まで読んでいただきありがとうございました。
今回利用した設定ファイルなどは、以下リポジトリにありますので、興味のある方は参考にしていただければと思います。
https://github.com/ppluuums-jp/otel-2-otel-auth

Discussion