Skip to content

Keep the router status dashboard private by default - #2297

Closed
aiqubits wants to merge 1 commit into
agent-substrate:mainfrom
aiqubits:fix/atenet-router-status-exposure
Closed

aiqubits wants to merge 1 commit into
agent-substrate:mainfrom
aiqubits:fix/atenet-router-status-exposure

Conversation

@aiqubits

@aiqubits aiqubits commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

The router's unauthenticated /statusz dashboard exposes recent cross-tenant requests, worker destinations, and process configuration to other pods. Bind it to 127.0.0.1 by default through the new --status-address flag, and remove its port from the router Service. Administrators can still access the HTML and JSON dashboard through a pod port-forward.

Fixes #2275

Access the dashboard with kubectl -n ate-system port-forward deployment/atenet-router 4040:4040 instead of the Service or Pod IP. Migrate GKE HealthCheckPolicy configurations using port 4040 and /statusz to the existing port 9090 and /readyz. GKE probes Pod IPs directly, so the metrics port does not need to be published by the Service. Please apply the breaking-change label.

Validation:

  • Regression tests fail before the fix and pass afterward under the race detector, covering local HTML/JSON access, listener isolation, and Service exposure.

  • All 14 repository verification scripts pass. Related race tests pass with the known IPv6 admin baseline failure excluded.

  • Additional review checks pass for IPv4/IPv6 address overrides, health-listener isolation, and the rendered Envoy and AgentGateway Services.

  • Tests pass (targeted tests pass; full-suite baseline failures are listed above)

  • Appropriate changes to documentation are included in the PR

Bind the unauthenticated status dashboard to loopback and remove its port
from the router Service to prevent other pods from reading recent requests
and process configuration.

Retain diagnostic access through pod port-forwarding and document health
checks through the existing metrics listener.
@maxsmythe Max Smythe (maxsmythe) added the area/security Security related issue/pr label Oct 8, 2026
@destijl

Copy link
Copy Markdown
Collaborator

ping aiqubits I'd like to get this merged.

@aiqubits

Copy link
Copy Markdown
Contributor Author

Greg Castle (@destijl) I'm very happy that you can do this.

@JeffLuoo Jeff Luo (JeffLuoo) left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In manifests/ate-install/atenet-router.yaml (lines 214–215), the Deployment still declares - name: status / containerPort: 4040. Nothing listens on the pod IP at that port now. A PodMonitor, a NetworkPolicy, or a health check that uses the named port gets a refused connection. Please remove the entry (matching atenet-egress.yaml), or add a comment that the port is loopback-only.

port: 8444
targetPort: 8444
protocol: TCP
- name: status

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This removal breaks the e2e StatuszClient. internal/e2e/statusz.go calls portforward.ServicePortForward(..., ResourceName("atenet-router"), routerStatusPort) with port 4040, and resolveTargetPort looks up that port in the Service spec. With no 4040 port on the Service, NewStatuszClient returns an error, and every parking e2e test that reads the parked-request gauge fails.

Please port-forward to the pod directly. One option is to export podPortForward and use it with firstReadyPodForService. Please also update the comment on routerStatusPort, which calls 4040 "the atenet-router Service port".

listener, err := net.Listen("tcp", statusListenAddr)
if err != nil {
return fmt.Errorf("failed binding Router HTTP status server port: %w", err)
return fmt.Errorf("failed binding Router HTTP status server address %q: %w", statusListenAddr, err)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the status listener fails to bind (for example, a typo or unbracketed IPv6 literal in --status-address), Run returns here before drainOnShutdown and g.Wait(). Because startDataplane, s.health.Start, and extprocGRPC have already been launched above, those goroutines keep running and holding their ports (18000 and 50051).

Please bind the status listener (with defer listener.Close()) before startDataplane and the g.Go calls so a bad bind fails before starting the background servers.

// Run() dials ateapi in mtls mode, which requires real TLS material; generate it.
caPath, clientCertPath := writeTestTLSMaterial(t)

statusAddr, err := NewRouterCmd().Flags().GetString("status-address")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reads the default value of the flag, but nothing checks the args in the manifest. If someone adds --status-address=0.0.0.0 to atenet-router.yaml or to an overlay, all tests stay green and the dashboard is on the pod IP again.

Can you parse the container args from the manifest with NewRouterCmd().ParseFlags, as routerShutdownBudget does, and assert that the status address is loopback?

}

// A wildcard listener would also accept connections on this address.
conn, err := net.DialTimeout("tcp", fmt.Sprintf("127.0.0.2:%d", httpPort), time.Second)

@JeffLuoo Jeff Luo (JeffLuoo) Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: The 127.0.0.2 dial catches a bind to all interfaces only on Linux. On macOS, lo0 has only 127.0.0.1, so the dial always fails. CI runs on Linux, so this is not a blocker. An assertion on the bind address of the listener works on every OS.

}
}

func TestRouterServiceDoesNotExposeStatus(t *testing.T) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: This loop copies the split, kind check, and decode logic of routerShutdownBudget in config_test.go. Can you move it into one shared helper, for example routerManifestObject(t, kind, name, into)? Then a change to the manifest path or format needs only one fix.

continue
}
for _, port := range service.Spec.Ports {
if port.Name == "status" || port.Port == 4040 || port.TargetPort.IntValue() == 4040 || port.TargetPort.StrVal == "status" {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hard-codes 4040. If --status-port changes in the Deployment, this guard no longer checks the correct port. Can you read the port from the --status-port arg in the manifest?

@LiorLieberman

Copy link
Copy Markdown
Collaborator

Closed in favor #2573

Vicente Ferrara (vicentefb) pushed a commit to vicentefb/substrate that referenced this pull request Oct 13, 2026
This picks up agent-substrate#2297 from @aiqubits, rebased on main, with the e2e
failure fixed.

The router's unauthenticated /statusz dashboard shows recent
cross-tenant requests, worker destinations, and process configuration to
any pod that can reach it. This binds it to 127.0.0.1 by default through
a new --status-address flag and removes its port from the router
Service. Admins can still get to it with kubectl -n ate-system
port-forward deployment/atenet-router 4040:4040.

On top of the original change, the e2e parking suite now port-forwards
to the status port on a router pod rather than the Service, since the
Service no longer publishes it.

#### Breaking change

GKE HealthCheckPolicy configs using port 4040 and /statusz should move
to port 9091 and /readyz. The router Deployment no longer declares the
status container port either.

Fixes agent-substrate#2275

- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR

---------

Co-authored-by: aiqubits <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/security Security related issue/pr

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Router /statusz exposes cross-tenant request log and worker pod IPs on ClusterIP Service

6 participants