Skip to content

Commit ffe2d68

Browse files
committed
fix: propagate traceparent/tracestate headers from controller to agent pods
The A2A server deserializes incoming HTTP requests into JSON-RPC params, discarding the original HTTP headers. When the controller forwards requests to agent pods via the A2A client, trace context headers (traceparent, tracestate) are lost, breaking distributed tracing. Fix: capture W3C trace context headers from the incoming request into the Go context in the A2A auth middleware, then inject them into outgoing requests in the A2ARequestHandler. This closes the gap between the A2A server (which strips headers) and the A2A client (which constructs new HTTP requests). Also update the agent_with_passthrough golden test (added in #1327) to include the appProtocol field. Signed-off-by: Simon Zhu <simon.zhu@mongodb.com>
1 parent 256bbda commit ffe2d68

3 files changed

Lines changed: 41 additions & 2 deletions

File tree

go/internal/controller/translator/agent/testdata/outputs/agent_with_passthrough.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@
262262
"spec": {
263263
"ports": [
264264
{
265+
"appProtocol": "kgateway.dev/a2a",
265266
"name": "http",
266267
"port": 8080,
267268
"targetPort": 8080

go/internal/httpserver/auth/authn.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,21 @@ type A2AAuthenticator struct {
7070
}
7171

7272
func (p *A2AAuthenticator) Wrap(next http.Handler) http.Handler {
73-
return auth.AuthnMiddleware(p.provider)(next)
73+
authn := auth.AuthnMiddleware(p.provider)(next)
74+
// Capture W3C trace context headers from the incoming request into the
75+
// context before the A2A server strips the *http.Request. These headers
76+
// are later injected into outgoing requests by A2ARequestHandler.
77+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
78+
if tp := r.Header.Get("Traceparent"); tp != "" {
79+
traceHeaders := make(http.Header, 2)
80+
traceHeaders.Set("Traceparent", tp)
81+
if ts := r.Header.Get("Tracestate"); ts != "" {
82+
traceHeaders.Set("Tracestate", ts)
83+
}
84+
r = r.WithContext(auth.TraceHeadersTo(r.Context(), traceHeaders))
85+
}
86+
authn.ServeHTTP(w, r)
87+
})
7488
}
7589

7690
type handler func(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error)
@@ -107,6 +121,15 @@ func A2ARequestHandler(authProvider auth.AuthProvider, agentNns types.Namespaced
107121
}
108122
}
109123

124+
// Propagate W3C trace context headers captured from the incoming request.
125+
if traceHeaders, ok := auth.TraceHeadersFrom(ctx); ok {
126+
for key, values := range traceHeaders {
127+
for _, v := range values {
128+
req.Header.Set(key, v)
129+
}
130+
}
131+
}
132+
110133
resp, err = client.Do(req)
111134
if err != nil {
112135
return nil, fmt.Errorf("a2aClient.httpRequestHandler: http request failed: %w", err)

go/pkg/auth/auth.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,11 @@ type Authorizer interface {
5858
// context utils
5959

6060
type sessionKeyType struct{}
61+
type traceHeadersKeyType struct{}
6162

6263
var (
63-
sessionKey = sessionKeyType{}
64+
sessionKey = sessionKeyType{}
65+
traceHeadersKey = traceHeadersKeyType{}
6466
)
6567

6668
func AuthSessionFrom(ctx context.Context) (Session, bool) {
@@ -72,6 +74,19 @@ func AuthSessionTo(ctx context.Context, session Session) context.Context {
7274
return context.WithValue(ctx, sessionKey, session)
7375
}
7476

77+
// TraceHeadersFrom retrieves W3C trace context headers (traceparent, tracestate)
78+
// that were captured from an incoming request.
79+
func TraceHeadersFrom(ctx context.Context) (http.Header, bool) {
80+
v, ok := ctx.Value(traceHeadersKey).(http.Header)
81+
return v, ok && v != nil
82+
}
83+
84+
// TraceHeadersTo stores W3C trace context headers in the context so they can
85+
// be propagated to outgoing requests (e.g., from the controller to agent pods).
86+
func TraceHeadersTo(ctx context.Context, headers http.Header) context.Context {
87+
return context.WithValue(ctx, traceHeadersKey, headers)
88+
}
89+
7590
func AuthnMiddleware(authn AuthProvider) func(http.Handler) http.Handler {
7691
return func(next http.Handler) http.Handler {
7792
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

0 commit comments

Comments
 (0)