Files
netbird/proxy/internal/middleware/headerpolicy.go
Daneyon Hansen 7a9582db16 [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>
2026-09-01 13:03:16 +02:00

114 lines
2.8 KiB
Go

package middleware
import "strings"
const trustedIdentityMiddlewareID = "llm_identity_inject"
var denyHeaders = []string{
"Authorization",
"Connection",
"Cookie",
"Set-Cookie",
"Forwarded",
"Keep-Alive",
"Proxy-Authorization",
"Proxy-Authenticate",
"Proxy-Connection",
"TE",
"Upgrade",
"Via",
"X-Real-IP",
"X-Request-ID",
"Host",
"Content-Length",
"Transfer-Encoding",
"Trailer",
}
var denyHeaderPrefixes = []string{
"X-Authenticated-",
"X-Forwarded-",
"X-Remote-",
"X-NetBird-",
}
// IsHeaderMutable reports whether a middleware is allowed to mutate
// the named header. The check is case-insensitive and honours both
// exact matches and the compiled-in prefix denylist.
func IsHeaderMutable(name string) bool {
if name == "" {
return false
}
if !isHeaderFieldName(name) {
return false
}
for _, d := range denyHeaders {
if strings.EqualFold(d, name) {
return false
}
}
for _, p := range denyHeaderPrefixes {
if len(name) >= len(p) && strings.EqualFold(name[:len(p)], p) {
return false
}
}
return true
}
// isHeaderFieldName reports whether name is a valid RFC 7230 header
// field-name (a non-empty token of tchar octets). Rejects names with
// spaces, control characters, or separators that could enable header
// injection or smuggling when applied to the outbound request.
func isHeaderFieldName(name string) bool {
for i := 0; i < len(name); i++ {
c := name[i]
if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') {
continue
}
switch c {
case '!', '#', '$', '%', '&', '\'', '*', '+', '-', '.', '^', '_', '`', '|', '~':
continue
default:
return false
}
}
return true
}
// FilterHeaderMutations returns the subsets of HeadersAdd and
// HeadersRemove that are safe to apply, plus the list of blocked
// header names so the dispatcher can increment the blocked-header
// metric.
func FilterHeaderMutations(m *Mutations) (filteredAdd []KV, filteredRemove []string, blocked []string) {
return filterHeaderMutations(m, "")
}
func filterHeaderMutations(m *Mutations, middlewareID string) (filteredAdd []KV, filteredRemove []string, blocked []string) {
if m == nil {
return nil, nil, nil
}
for _, kv := range m.HeadersAdd {
if IsHeaderMutable(kv.Key) || isTrustedIdentityHeader(middlewareID, kv.Key) {
filteredAdd = append(filteredAdd, kv)
continue
}
blocked = append(blocked, kv.Key)
}
for _, name := range m.HeadersRemove {
if IsHeaderMutable(name) || isTrustedIdentityHeader(middlewareID, name) {
filteredRemove = append(filteredRemove, name)
continue
}
blocked = append(blocked, name)
}
return filteredAdd, filteredRemove, blocked
}
func isTrustedIdentityHeader(middlewareID, name string) bool {
if middlewareID != trustedIdentityMiddlewareID {
return false
}
return strings.EqualFold(name, "x-netbird-user-id") ||
strings.EqualFold(name, "x-netbird-groups")
}