[management,proxy] Add agentgateway integration (#7274)

* [management] Add agentgateway provider catalog entry

Allow Agent Network providers to target an operator-supplied agentgateway proxy while stamping trusted NetBird identity headers.

Signed-off-by: Daneyon Hansen <daneyon.hansen@solo.io>

* [proxy] Allow trusted Agent Network identity headers

Permit only the built-in identity injector to replace the two reserved agentgateway attribution headers while keeping them blocked for every other middleware.

Signed-off-by: Daneyon Hansen <daneyon.hansen@solo.io>

* [management,proxy] Add multi-vendor gateway routing

Let one Agent Network route declare multiple parser surfaces while preserving the existing singular vendor wire field.

Signed-off-by: Daneyon Hansen <daneyon.hansen@solo.io>

* [management] Update router test for model policies

Signed-off-by: Daneyon Hansen <daneyon.hansen@solo.io>

* [proxy] Cover reserved header policy

Signed-off-by: Daneyon Hansen <daneyon.hansen@solo.io>

* [management] Add agentgateway model discovery

Use agentgateway's OpenAI-compatible models endpoint and omit wildcard patterns until NetBird can authorize and price them consistently.

Signed-off-by: Daneyon Hansen <daneyon.hansen@solo.io>

---------

Signed-off-by: Daneyon Hansen <daneyon.hansen@solo.io>
This commit is contained in:
Daneyon Hansen
2026-09-01 13:03:16 +02:00
committed by GitHub
parent 4749005a50
commit 7a9582db16
13 changed files with 372 additions and 24 deletions
+59
View File
@@ -2,6 +2,7 @@ package middleware
import (
"context"
"net/http"
"strconv"
"testing"
@@ -278,6 +279,64 @@ func TestChain_ApplyMutations_RewriteGatedOnCanMutate(t *testing.T) {
assert.Nil(t, rewrite, "rewrite must be filtered when CanMutate=false")
}
func TestChain_IdentityInjectReplacesReservedNetBirdHeaders(t *testing.T) {
mw := &fakeMiddleware{
id: "llm_identity_inject",
slot: SlotOnRequest,
mutationsSupported: true,
canMutate: true,
mutations: &Mutations{
HeadersRemove: []string{"x-netbird-user-id", "x-netbird-groups"},
HeadersAdd: []KV{
{Key: "x-netbird-user-id", Value: "trusted-user"},
{Key: "x-netbird-groups", Value: "trusted-group"},
},
},
}
c := chainFor(t, mw)
req, err := http.NewRequest(http.MethodGet, "https://gateway.example.com/v1/models", nil)
require.NoError(t, err)
req.Header.Set("x-netbird-user-id", "spoofed-user")
req.Header.Set("x-netbird-groups", "spoofed-group")
denied, _, _, err := c.RunRequest(context.Background(), req, &Input{}, NewAccumulator(0))
require.NoError(t, err)
assert.Nil(t, denied, "identity injection must not deny the request")
assert.Equal(t, "trusted-user", req.Header.Get("x-netbird-user-id"),
"the built-in identity middleware must replace a spoofed user header")
assert.Equal(t, "trusted-group", req.Header.Get("x-netbird-groups"),
"the built-in identity middleware must replace spoofed groups")
}
func TestChain_OtherMiddlewareCannotReplaceReservedNetBirdHeaders(t *testing.T) {
mw := &fakeMiddleware{
id: "untrusted-middleware",
slot: SlotOnRequest,
mutationsSupported: true,
canMutate: true,
mutations: &Mutations{
HeadersRemove: []string{"x-netbird-user-id", "x-netbird-groups"},
HeadersAdd: []KV{
{Key: "x-netbird-user-id", Value: "replacement-user"},
{Key: "x-netbird-groups", Value: "replacement-group"},
},
},
}
c := chainFor(t, mw)
req, err := http.NewRequest(http.MethodGet, "https://gateway.example.com/v1/models", nil)
require.NoError(t, err)
req.Header.Set("x-netbird-user-id", "original-user")
req.Header.Set("x-netbird-groups", "original-group")
denied, _, _, err := c.RunRequest(context.Background(), req, &Input{}, NewAccumulator(0))
require.NoError(t, err)
assert.Nil(t, denied, "blocked mutations must not deny the request")
assert.Equal(t, "original-user", req.Header.Get("x-netbird-user-id"),
"other middleware must remain unable to mutate reserved identity headers")
assert.Equal(t, "original-group", req.Header.Get("x-netbird-groups"),
"other middleware must remain unable to mutate reserved identity headers")
}
// TestChain_RunRequest_PropagatesUserGroups asserts the chain forwards
// Input.UserGroups verbatim through cloneInputFor so policy-aware
// middlewares (e.g. llm_policy_check) can authorise without an extra