opentelemetry collector oidc auth

OpenID Connect authentication with the OpenTelemetry Collector

SEAN K.H. LIAO

opentelemetry collector oidc auth

OpenID Connect authentication with the OpenTelemetry Collector

otelcol oidc auth

The OpenTelemetry Collector (otelcol) has an oidcauth extension. This makes for an interesting alternative to using:

oidc token

First, we're going to need an OIDC identity token. I'm going to use GCP because... that's what I have (and where my workloads are running). gcloud has a command to print out an identity token for a service account, note this requires roles/iam.serviceAccountTokenCreator on the account itself, and maybe switching over to it for default auth?

gcloud auth print-identity-token \
  hwaryun@com-seankhliao.iam.gserviceaccount.com \
  --include-email \
  --audiences otelcol \
  --impersonate-service-account hwaryun@com-seankhliao.iam.gserviceaccount.com

This gives you a long token like (------ parts will be actual chars):

eyJhbGci------------J0eXAiOiJKV1QifQ.eyJhdWQi---------xODYifQ.FfpWwTlS24zl------v3pRd33tZmQgq1gLabQ

collector

On the collector side we'll nned the oidcauth extension configured for Google's accounts endpoint, and with the right header extractor.

The verified data is stored in the context's authdata which can be extracted with the processors into proper attributes, something you'll want to do before reshaping/regrouping data with batch/groupbyX. Also, as an attribute, it's where you can implement richer filtering (eg checking project via email).

extensions:
  # due to header canonicaliztion weirdness
  # http1 uses "Authorization" (leading capital)
  # while grpc uses lowercase
  oidc/grpc:
    issuer_url: https://accounts.google.com
    audience: otelcol
    username_claim: email

  oidc/http:
    issuer_url: https://accounts.google.com
    audience: otelcol
    username_claim: email
    attribute: Authorization

receivers:
  otlp:
    protocols:
      http:
        auth:
          authenticator: oidc/http
      grpc:
        auth:
          authenticator: oidc/grpc

processors:
  # using th resource processor, we can pull out auth data from the auth. * context
  # also available for the attribute processor
  # if we want to do any stricter validation, it can be done in a processor
  # eg, only allowing users with our @project.iam.gserviceaccount.com emails
  resource:
    attributes:
      - key: oidc.subject
        action: upsert
        from_context: auth.subject
      # google service accounts don't set this field
      - key: oidc.membership
        action: upsert
        from_context: auth.membership

exporters:
  logging:
    verbosity: detailed

service:
  extensions: [oidc/http, oidc/grpc]
  pipelines:
    traces:
      receivers: [otlp]
      processors: [resource]
      exporters: [logging]
  telemetry:
    logs:
      level: debug

client

Now we need a client to use the token

curl

If we only care about testing if the token is accepted, curl works.

note: this uses the otlp http endpoint.

curl -v http://localhost:4318/ -H "authorization: Bearer ${ID_TOKEN}"
tracegen

tracegen is a tool that is part of the opentelemetry-collector-contrib project. It's an easy way to generate valid data.

note: the quoting rules as tracegen requires the value be quoted

tracegen -otlp-insecure -otlp-header 'authorization="Bearer '${ID_TOKEN}'"'
go

For Go, this just mean pulling in GCP's idtoken generator and hooking that in to gRPC. It should be easy to swap out to any other token provider.

package main

import (
        "context"
        "crypto/tls"
        "log"

        "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
        "google.golang.org/api/idtoken"
        "google.golang.org/grpc"
        "google.golang.org/grpc/credentials"
        "google.golang.org/grpc/credentials/oauth"
)

func main() {
        ctx := context.Background()
        gcpTS, err := idtoken.NewTokenSource(ctx, "otelcol")
        if err != nil {
                log.Fatal(err)
        }

        exporter, err := otlptracegrpc.New(ctx,
                otlptracegrpc.WithDialOption(
                        // TLS required for credentials
                        grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: true})),
                        // id token per request in headers
                        grpc.WithPerRPCCredentials(&oauth.TokenSource{TokenSource: gcpTS}),
                ),
        )
        if err != nil {
                log.Fatalln(err)
        }
}