open-telemetry / opentelemetry-go

OpenTelemetry Go API and SDK
https://opentelemetry.io/docs/languages/go
Apache License 2.0
5.3k stars 1.08k forks source link

OAuth2 Authentication for OTLP-HTTP Exporter/Client #4536

Open lprakashv opened 1 year ago

lprakashv commented 1 year ago

Problem Statement

No option to set dynamic OAuth2 headers using otlphttp.Client, is seems to be a standard approach to create a custom round-tripper (oauth2.Transport) with a token-source.

There is currently no-way to either override the http.Client or underlying http.Transport / RoundTripper.

Proposed Solution

I think a better way would be to just expose another option to set OAuth2 credentials.

This is how we can generate a final http.Transport or RoundTripper to be used in httpClient which will do all the necessary oauth2 token injection:


oauth2ClientCredentials := clientcredentials.Config{
    ClientID:               clientID,
    ClientSecret:        clientSecret,
    TokenURL:            tokenURL,
    Scopes:                scopes,
    EndpointParams: endpointParams,
}

tokenClientTransport := http.DefaultTransport.(*http.Transport).Clone()
// ...

tokenSourceCtx := context.WithValue(context.Background(), oauth2.HTTPClient, http.Client{
    Transport: tokenClientTransport,
    Timeout:   timeout,
})

// this will be the final transport/round-tripper to be set inside `httpClient`
transport := &oauth2.Transport{
    Source: oauth2ClientCredentials.TokenSource(tokenSourceCtx),
    Base:   tokenClientTransport,
}

Alternatives

Currently, it is not supported by the otlpmetric.Client (and other clients) to have an OAuth2 token injector or any dynamic header injector. We would need to have an opentelemetry-collector in-between just for the authentication using oauth2clientauthextension to the backend.

Prior Art

Custom Transport with oauth2 transport with token source:

https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/extension/oauth2clientauthextension/extension.go#L91

Additional Context

NA

dmathieu commented 1 year ago

While we definitely could make it easier to setup specific and dynamic HTTP headers (static headers can already be set with WithHeaders), I don't think we should handle oauth2 specifically. The exporter should remain generic, and allow any protocol, not only some that we thought of implementing.

https://github.com/open-telemetry/opentelemetry-go/issues/2632 would then possibly be a solution for you. However, the problem of infinite traces loops in case the custom client includes otelhttp is risen.

lprakashv commented 1 year ago

Okay, I partially get it that allowing a custom RoundTripper could potentially start an infinite loop of traces being generated unless we restrict it somehow.

But, what do you suggest the solution for this? I believe it is a trivial problem and could become a blocker for some use-cases.

dmathieu commented 1 year ago

Do you know how other SDKs are handling this? (the prior art you mention above is for the collector, with an oauth2-specific extension)

lprakashv commented 1 year ago

Not sure about other SDKs but the current way of handling this in our use-case is having an otel-collector as a backend with oauth2client extension enabled (which adds the dynamic oauth2 token header).

Challenge with this approach is that of having another application (otel-collector as a sidecar) running just for authentication and could be an overkill for some containerized environments like ECS etc. and we want to reduce this overhead.

psx95 commented 7 months ago

Do you know how other SDKs are handling this? (the prior art you mention above is for the collector, with an oauth2-specific extension)

I can talk a little about the OTel Java SDK.

Broadly speaking, the OtlpHttpExporters for Java allow the user to set a headerSupplier (thereby allowing a function to provide the headers) in the Builder for the Exporter, instead of a constant Map of header key-values .

OTel Java SDK has abstractions built for managing HTTP requests - HttpSender. The implementations of this abstraction use the supplier to resolve the value of the headers at the time of building the request.

The user can refresh credentials in the supplier if required. This effectively makes the header based authentication at a per http call basis.

@dmathieu Do you think this can be replicated in the Go SDK for http based otlp exporters?