Skip to content

Commit 360b289

Browse files
committed
Merge remote-tracking branch 'upstream/main'
# Conflicts: # cmd/atenet/internal/router/config.go # cmd/atenet/internal/router/dashboard.html # cmd/atenet/internal/router/extproc.go # cmd/atenet/internal/router/health.go # cmd/atenet/internal/router/health_test.go # cmd/atenet/internal/router/router.go # hack/install-ate.sh # manifests/ate-install/base/kustomization.yaml # manifests/ate-install/kind-agentgateway/kustomization.yaml # manifests/ate-install/token-client/kustomization.yaml
2 parents 978981e + 55f16fa commit 360b289

21 files changed

Lines changed: 400 additions & 97 deletions

File tree

charts/substrate/templates/atenet-router.yaml

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,8 @@ data:
8484
responseTrailerMode: skip
8585
backends:
8686
- dynamic: {}
87-
policies:
88-
transformations:
89-
request:
90-
set:
91-
":authority": 'request.headers["x-ate-actor-host"]'
92-
remove:
93-
- x-ate-actor-host
9487
{{- if eq .Values.auth.mode "mtls" }}
88+
policies:
9589
backendTLS:
9690
cert: "/run/podidentity.podcert.ate.dev/cert.pem"
9791
key: "/run/podidentity.podcert.ate.dev/key.pem"
@@ -128,14 +122,8 @@ data:
128122
responseTrailerMode: skip
129123
backends:
130124
- dynamic: {}
131-
policies:
132-
transformations:
133-
request:
134-
set:
135-
":authority": 'request.headers["x-ate-actor-host"]'
136-
remove:
137-
- x-ate-actor-host
138125
{{- if eq .Values.auth.mode "mtls" }}
126+
policies:
139127
backendTLS:
140128
cert: "/run/podidentity.podcert.ate.dev/cert.pem"
141129
key: "/run/podidentity.podcert.ate.dev/key.pem"

cmd/atenet/internal/router/README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
Router has several responsibilities:
44

5-
* (Optional) manages a Deployment of Envoy to function as a router for ATE requests.
6-
* This is optional to enable testing the router component in a standalone mode without managing the Kubernetes objects.
7-
* Envoy will be configured to send traffic to via xDS served by the Router.
8-
* ext_proc server for the Envoy. To make the deployment and debugging easier, we will run this component together
5+
* Serves Envoy xDS configuration when `--networking-mode=envoy`.
6+
Unless `--standalone` is set, it also manages the Envoy Deployment and
7+
Services in Kubernetes.
8+
With the default `--networking-mode=agentgateway`, the sidecar uses a static ConfigMap and
9+
atenet does not start an xDS server.
10+
* ext_proc server for the proxy. To make the deployment and debugging easier, we will run this component together
911
with the router, but this will be split later into its own component.
1012
* ext_proc will call into the ATE gRPC API to get the set of relevant backends (specific the worker IP) and
1113
route the traffic accordingly
@@ -27,4 +29,4 @@ Contents:
2729
* Global flags values
2830
* Command line args
2931
* Last 100 queries served
30-
* Build tag
32+
* Build tag

cmd/atenet/internal/router/agentgateway.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -140,16 +140,10 @@ func (p agentgatewayProvider) routeBlock(name string) string {
140140
}
141141

142142
func (p agentgatewayProvider) backendBlock() string {
143-
backend := fmt.Sprintf(`- dynamic: {}
144-
policies:
145-
transformations:
146-
request:
147-
set:
148-
":authority": 'request.headers["%s"]'
149-
remove:
150-
- %s`, ActorHostHeader, ActorHostHeader)
143+
backend := `- dynamic: {}`
151144
if p.cfg.UpstreamCertPath != "" {
152145
backend += fmt.Sprintf(`
146+
policies:
153147
backendTLS:
154148
cert: %q
155149
key: %q

cmd/atenet/internal/router/cmd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ func NewRouterCmd() *cobra.Command {
4444
cmd.Flags().StringVar(&cfg.LogLevel, "log-level", "info", "Log level: debug, info, warn, error")
4545
cmd.Flags().StringVar(&cfg.MetricsAddr, "metrics-listen-addr", ":9090", "Address and port the prometheus metrics server should listen on.")
4646
cmd.Flags().BoolVar(&cfg.Standalone, "standalone", false, "Run in standalone mode, bypassing creation of managed deployment and services in Kubernetes cluster")
47+
cmd.Flags().StringVar(&cfg.NetworkingMode, "atenet-router", NetworkingModeAgentgateway, "Alias for --networking-mode")
4748
cmd.Flags().StringVar(&cfg.Namespace, "namespace", "default", "Target operations namespace")
4849
cmd.Flags().StringVar(&cfg.RouterServiceName, "router-service-name", installdefaults.RouterServiceName, "Service name of this atenet-router in the operations namespace. Override when the deployment renames the Service.")
4950
cmd.Flags().StringVar(&cfg.Kubeconfig, "kubeconfig", "", "Absolute path to the kubeconfig configuration file")

cmd/atenet/internal/router/config_test.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,6 @@ func TestRouterConfigValidate(t *testing.T) {
2525
cfg routerConfig
2626
wantErr string // substring; empty means valid
2727
}{
28-
{
29-
name: "defaults are valid (auto breaker)",
30-
cfg: routerConfig{ExtProcMaxRequests: 0, ParkedRequest: ParkedRequestConfig{Max: defaultParkedRequestMax}},
31-
},
3228
{
3329
name: "negative extproc-max-requests rejected",
3430
cfg: routerConfig{ExtProcMaxRequests: -1, ParkedRequest: ParkedRequestConfig{Max: 0}},

cmd/atenet/internal/router/extproc.go

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,19 @@ type ExtProcServer struct {
4848
networkingMode string
4949
}
5050

51-
func NewExtProcServer(port int, apiClient ateapipb.ControlClient, routeDuration metric.Float64Histogram, parkCfg ParkedRequestConfig, parkMetrics *parkingMetrics) *ExtProcServer {
52-
return &ExtProcServer{
51+
func NewExtProcServer(port int, apiClient ateapipb.ControlClient, routeDuration metric.Float64Histogram, parkCfg ParkedRequestConfig, parkMetrics *parkingMetrics, routeViaAuthority ...bool) *ExtProcServer {
52+
s := &ExtProcServer{
5353
port: port,
5454
apiClient: apiClient,
5555
recorder: NewQueryRecorder(100),
5656
resumer: NewActorResumer(apiClient, withParking(parkCfg)),
5757
routeDuration: routeDuration,
5858
parking: newParkingLot(parkCfg, parkMetrics),
5959
}
60+
if len(routeViaAuthority) > 0 && routeViaAuthority[0] {
61+
s.networkingMode = NetworkingModeAgentgateway
62+
}
63+
return s
6064
}
6165

6266
func (s *ExtProcServer) SetNetworkingMode(mode string) {
@@ -196,17 +200,7 @@ func (s *ExtProcServer) handleRequestHeaders(
196200
slog.InfoContext(ctx, "Route ok", slog.Any("actor", actorRef), slog.String("targetAddr", targetAddr))
197201

198202
mutation := &extprocv3.HeaderMutation{}
199-
if s.networkingMode == NetworkingModeAgentgateway {
200-
// Agentgateway's dynamic backend selects the upstream from :authority.
201-
// Its backend transformation restores the actor Host before the request
202-
// reaches atunnel.
203-
addAuthorityMutation(targetAddr, mutation)
204-
addActorHostMutation(metadata.host, mutation)
205-
} else {
206-
// Envoy's ORIGINAL_DST cluster reads the target from this header and
207-
// leaves :authority as the actor DNS name for atunnel authorization.
208-
addOriginalDstMutation(targetAddr, mutation)
209-
}
203+
addRoutingMutations(targetAddr, metadata.host, s.networkingMode == NetworkingModeAgentgateway, mutation)
210204

211205
return &extprocv3.HeadersResponse{
212206
Response: &extprocv3.CommonResponse{

cmd/atenet/internal/router/extproc_in.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import (
2222
corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
2323
)
2424

25+
const authorityHeader = ":authority"
26+
2527
type requestMetadata struct {
2628
headers map[string]string
2729
path string
@@ -44,7 +46,7 @@ func newRequestMetadata(headers []*corev3.HeaderValue) *requestMetadata {
4446
if k == ":path" {
4547
path = val
4648
}
47-
if k == ":authority" || k == "host" {
49+
if k == authorityHeader || k == "host" {
4850
host = val
4951
}
5052
}

cmd/atenet/internal/router/extproc_out.go

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,12 @@
1515
package router
1616

1717
import (
18+
"github.com/agent-substrate/substrate/internal/atunnel"
1819
corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
1920
extproc "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3"
2021
envoy_type "github.com/envoyproxy/go-control-plane/envoy/type/v3"
2122
)
2223

23-
const ActorHostHeader = "x-ate-actor-host"
24-
25-
func addAuthorityMutation(authority string, mut *extproc.HeaderMutation) {
26-
addHeaderMutation(":authority", authority, mut)
27-
}
28-
29-
func addActorHostMutation(host string, mut *extproc.HeaderMutation) {
30-
addHeaderMutation(ActorHostHeader, host, mut)
31-
}
32-
3324
func addHeaderMutation(name, value string, mut *extproc.HeaderMutation) {
3425
mut.SetHeaders = append(mut.SetHeaders, &corev3.HeaderValueOption{
3526
AppendAction: corev3.HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD,
@@ -65,6 +56,31 @@ func addOriginalDstMutation(dst string, mut *extproc.HeaderMutation) {
6556
addHeaderMutation(OriginalDstHeader, dst, mut)
6657
}
6758

59+
// addRoutingMutations overwrites all routing metadata derived from the
60+
// control-plane result. Envoy dials OriginalDstHeader while preserving
61+
// :authority. Agentgateway v1.4.1's static dynamic backend instead dials the
62+
// request :authority, so that mode rewrites it to the worker atunnel address.
63+
// OriginalHostHeader lets atunnel restore and authorize the actor authority.
64+
func addRoutingMutations(dst, actorHost string, routeViaAuthority bool, mut *extproc.HeaderMutation) {
65+
addOriginalDstMutation(dst, mut)
66+
mut.SetHeaders = append(mut.SetHeaders, &corev3.HeaderValueOption{
67+
AppendAction: corev3.HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD,
68+
Header: &corev3.HeaderValue{
69+
Key: atunnel.OriginalHostHeader,
70+
RawValue: []byte(actorHost),
71+
},
72+
})
73+
if routeViaAuthority {
74+
mut.SetHeaders = append(mut.SetHeaders, &corev3.HeaderValueOption{
75+
AppendAction: corev3.HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD,
76+
Header: &corev3.HeaderValue{
77+
Key: authorityHeader,
78+
RawValue: []byte(dst),
79+
},
80+
})
81+
}
82+
}
83+
6884
func immediateResponse(statusCode envoy_type.StatusCode, message string) *extproc.ProcessingResponse {
6985
return &extproc.ProcessingResponse{
7086
Response: &extproc.ProcessingResponse_ImmediateResponse{

cmd/atenet/internal/router/extproc_test.go

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"testing"
2525
"time"
2626

27+
"github.com/agent-substrate/substrate/internal/atunnel"
2728
"github.com/agent-substrate/substrate/pkg/proto/ateapipb"
2829
corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
2930
extprocv3 "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3"
@@ -58,7 +59,7 @@ func TestHandleRequestHeadersDoesNotLogSensitiveData(t *testing.T) {
5859
resumeFn: func(ctx context.Context, in *ateapipb.ResumeActorRequest, opts ...grpc.CallOption) (*ateapipb.ResumeActorResponse, error) {
5960
return &ateapipb.ResumeActorResponse{Actor: &ateapipb.Actor{AteomPodIp: "10.0.0.52"}}, nil
6061
},
61-
}, nil, ParkedRequestConfig{}, nil)
62+
}, nil, ParkedRequestConfig{}, nil, false)
6263

6364
reqHeaders := &extprocv3.HttpHeaders{
6465
Headers: &corev3.HeaderMap{
@@ -195,7 +196,7 @@ func TestExtProcHeadersEvaluation(t *testing.T) {
195196
// Parking disabled: these cases assert fail-fast mapping of resume
196197
// errors (e.g. FailedPrecondition -> immediate 503). Parking behavior
197198
// is covered separately in TestExtProc_ParkingLotFull and resumer_test.go.
198-
s := NewExtProcServer(50051, clientMock, nil, ParkedRequestConfig{}, nil)
199+
s := NewExtProcServer(50051, clientMock, nil, ParkedRequestConfig{}, nil, false)
199200

200201
reqHeaders := &extprocv3.HttpHeaders{
201202
Headers: &corev3.HeaderMap{
@@ -236,17 +237,19 @@ func TestExtProcHeadersEvaluation(t *testing.T) {
236237
}
237238

238239
mutation := res.Response.GetHeaderMutation()
239-
if len(mutation.GetSetHeaders()) != 1 {
240-
t.Fatalf("expected exactly one Header option set, found: %v", mutation.GetSetHeaders())
240+
if len(mutation.GetSetHeaders()) != 2 {
241+
t.Fatalf("expected exactly two header options, found: %v", mutation.GetSetHeaders())
241242
}
242243

243-
headerOption := mutation.GetSetHeaders()[0]
244-
if strings.ToLower(headerOption.Header.Key) != OriginalDstHeader {
245-
t.Errorf("invalid resulting dynamic parameter key: %s", headerOption.Header.Key)
244+
gotMutations := map[string]string{}
245+
for _, headerOption := range mutation.GetSetHeaders() {
246+
gotMutations[strings.ToLower(headerOption.Header.Key)] = string(headerOption.Header.RawValue)
246247
}
247-
248-
if string(headerOption.Header.RawValue) != tc.expectedTarget {
249-
t.Errorf("invalid destination mapping found: %s, expected: %s", headerOption.Header.RawValue, tc.expectedTarget)
248+
if got := gotMutations[OriginalDstHeader]; got != tc.expectedTarget {
249+
t.Errorf("destination mutation = %q, want %q", got, tc.expectedTarget)
250+
}
251+
if got := gotMutations[strings.ToLower(atunnel.OriginalHostHeader)]; got != tc.authority {
252+
t.Errorf("original host mutation = %q, want %q", got, tc.authority)
250253
}
251254

252255
// Confirm that query logs recorded metric trace details
@@ -271,7 +274,7 @@ func TestAgentgatewayRoutingHeaders(t *testing.T) {
271274
t.Fatal(err)
272275
}
273276
headers := res.Response.GetHeaderMutation().GetSetHeaders()
274-
if len(headers) != 2 || headers[0].GetHeader().GetKey() != ":authority" || string(headers[0].GetHeader().GetRawValue()) != "10.0.0.52:443" || headers[1].GetHeader().GetKey() != ActorHostHeader || string(headers[1].GetHeader().GetRawValue()) != host {
277+
if len(headers) != 3 || headers[2].GetHeader().GetKey() != authorityHeader || string(headers[2].GetHeader().GetRawValue()) != "10.0.0.52:443" {
275278
t.Fatalf("Agentgateway mutations = %v", headers)
276279
}
277280
}
@@ -291,7 +294,7 @@ func TestExtProc_ParkingLotFull(t *testing.T) {
291294

292295
// A 1-slot lot with the slot already occupied deterministically simulates a
293296
// full lot without needing a concurrent in-flight request.
294-
s := NewExtProcServer(50051, clientMock, nil, ParkedRequestConfig{Budget: time.Second, Max: 1}, nil)
297+
s := NewExtProcServer(50051, clientMock, nil, ParkedRequestConfig{Budget: time.Second, Max: 1}, nil, false)
295298
release, ok := s.parking.enter(context.Background())
296299
if !ok {
297300
t.Fatal("priming enter should be admitted")
@@ -410,7 +413,7 @@ func TestRecordRouteDuration_Attributes(t *testing.T) {
410413
t.Fatalf("failed to create histogram: %v", err)
411414
}
412415

413-
s := NewExtProcServer(50051, nil, h, ParkedRequestConfig{}, nil)
416+
s := NewExtProcServer(50051, nil, h, ParkedRequestConfig{}, nil, false)
414417
s.recordRouteDuration(context.Background(), 10*time.Millisecond, "team-a-ns", "tmpl-a", classifyOutcome(nil), string(ResumeOutcomeTriggered))
415418

416419
var rm metricdata.ResourceMetrics
@@ -435,3 +438,25 @@ func TestRecordRouteDuration_Attributes(t *testing.T) {
435438
}
436439
}
437440
}
441+
442+
func TestAddRoutingMutationsViaAuthority(t *testing.T) {
443+
mutation := &extprocv3.HeaderMutation{}
444+
addRoutingMutations("10.0.0.52:443", "actor-1.team-a.actors.resources.substrate.ate.dev", true, mutation)
445+
446+
got := map[string]string{}
447+
for _, option := range mutation.GetSetHeaders() {
448+
if option.GetAppendAction() != corev3.HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD {
449+
t.Errorf("mutation %q append action = %v, want overwrite", option.GetHeader().GetKey(), option.GetAppendAction())
450+
}
451+
got[strings.ToLower(option.GetHeader().GetKey())] = string(option.GetHeader().GetRawValue())
452+
}
453+
if got[OriginalDstHeader] != "10.0.0.52:443" {
454+
t.Errorf("%s = %q", OriginalDstHeader, got[OriginalDstHeader])
455+
}
456+
if got[strings.ToLower(atunnel.OriginalHostHeader)] != "actor-1.team-a.actors.resources.substrate.ate.dev" {
457+
t.Errorf("%s = %q", atunnel.OriginalHostHeader, got[strings.ToLower(atunnel.OriginalHostHeader)])
458+
}
459+
if got[authorityHeader] != "10.0.0.52:443" {
460+
t.Errorf("%s = %q", authorityHeader, got[authorityHeader])
461+
}
462+
}

cmd/atenet/internal/router/status.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ func (qr *QueryRecorder) AddRouterRequest(
119119
) {
120120
qr.Add(RecordedQuery{
121121
Timestamp: start,
122-
Client: m.headers[":authority"],
122+
Client: m.headers[authorityHeader],
123123
Host: m.host,
124124
Path: redactPath(m.path),
125125
Method: m.headers[":method"],

0 commit comments

Comments
 (0)