Files
grafana/pkg/clientauth/roundtripper.go
Gabriel MABILLE 93566ce4ef Chore: Unify token exchange round trippers (#115609)
* Chore: Unify token exchange rount trippers

* Remove the conditional provider for now

* Remove unecessary strategy

* test cleanup

* Lint
2026-01-05 11:23:35 +01:00

91 lines
2.9 KiB
Go

package clientauth
import (
"fmt"
"net/http"
authnlib "github.com/grafana/authlib/authn"
utilnet "k8s.io/apimachinery/pkg/util/net"
"k8s.io/client-go/transport"
)
// tokenExchangeRoundTripper wraps an http.RoundTripper and injects an exchanged
// access token into outgoing requests via the X-Access-Token header.
type tokenExchangeRoundTripper struct {
exchanger authnlib.TokenExchanger
transport http.RoundTripper
namespaceProvider NamespaceProvider
audienceProvider AudienceProvider
}
var _ http.RoundTripper = (*tokenExchangeRoundTripper)(nil)
// newTokenExchangeRoundTripperWithStrategies creates a new RoundTripper with custom
// namespace and audience strategies, allowing for flexible configuration.
func newTokenExchangeRoundTripperWithStrategies(
exchanger authnlib.TokenExchanger,
transport http.RoundTripper,
namespaceProvider NamespaceProvider,
audienceProvider AudienceProvider,
) *tokenExchangeRoundTripper {
return &tokenExchangeRoundTripper{
exchanger: exchanger,
transport: transport,
namespaceProvider: namespaceProvider,
audienceProvider: audienceProvider,
}
}
// RoundTrip implements http.RoundTripper by exchanging a token and setting it
// in the X-Access-Token header before forwarding the request.
func (t *tokenExchangeRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
ctx := req.Context()
tokenResponse, err := t.exchanger.Exchange(ctx, authnlib.TokenExchangeRequest{
Audiences: t.audienceProvider.GetAudiences(ctx),
Namespace: t.namespaceProvider.GetNamespace(ctx),
})
if err != nil {
return nil, fmt.Errorf("failed to exchange token: %w", err)
}
// Clone the request as RoundTrippers are not expected to mutate the passed request
req = utilnet.CloneRequest(req)
req.Header.Set("X-Access-Token", "Bearer "+tokenResponse.Token)
return t.transport.RoundTrip(req)
}
// NewStaticTokenExchangeTransportWrapper creates a transport.WrapperFunc that wraps
// an http.RoundTripper with token exchange authentication for use with k8s
// rest.Config.WrapTransport.
func NewStaticTokenExchangeTransportWrapper(
exchanger authnlib.TokenExchanger,
audience string,
namespace string,
) transport.WrapperFunc {
return func(rt http.RoundTripper) http.RoundTripper {
return newTokenExchangeRoundTripperWithStrategies(exchanger, rt, NewStaticNamespaceProvider(namespace), NewStaticAudienceProvider(audience))
}
}
// NewTokenExchangeTransportWrapperWithStrategies creates a transport.WrapperFunc with custom strategies.
func NewTokenExchangeTransportWrapper(
exchanger authnlib.TokenExchanger,
namespaceProvider NamespaceProvider,
audienceProvider AudienceProvider,
) transport.WrapperFunc {
return func(rt http.RoundTripper) http.RoundTripper {
return newTokenExchangeRoundTripperWithStrategies(
exchanger,
rt,
namespaceProvider,
audienceProvider,
)
}
}
// WildcardNamespace is a convenience constant for the wildcard namespace.
const WildcardNamespace = "*"