mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-10 16:04:04 +00:00
fix: validate Agones assigned endpoints
This commit is contained in:
@@ -115,6 +115,8 @@ product policy are in [`docs/MATCHMAKING.md`](docs/MATCHMAKING.md).
|
||||
Valve-approved SDR POP/certificate/public-UDP overlays.
|
||||
- [ ] Add the local-safe Agones adapter and separate process-ready (listen then
|
||||
Ready) from assignment-ready (Allocated manifest verified and registered).
|
||||
The Go supervisor now validates dynamic address/port data and gates Ready on
|
||||
an explicit probe; Godot adapter and emulator integration remain.
|
||||
- [ ] **IN PROGRESS:** Allocate from Ready by region/build/protocol/transport; use separately
|
||||
verified ENet and SDR dynamic/passthrough port mappings. The Go allocator
|
||||
now owns assignment publication with idempotent replay/conflict handling;
|
||||
|
||||
+2
-2
@@ -1209,9 +1209,9 @@ the local/CI/community transport, not a silent production fallback.
|
||||
| # | Task | Acceptance |
|
||||
|---|---|---|
|
||||
| 8.26 `[D:8.1,8.6,8.12]` | Portable Helm/Kustomize Fleets per build/EU/NA region; isolate provider edge/network/DNS/secret and SDR POP/cert/public-UDP overlays | Two provider fixtures render; labels select region/build/protocol/transport; each fixture documents Valve approval and externally reachable UDP mapping |
|
||||
| 8.27 `[D:8.26]` | **IN PROGRESS.** Go supervisor package provides local-safe Agones REST discovery, dynamic `SDR_LISTEN_PORT`/`SDR_IP` injection, explicit process-ready probing and Ready transition; direct mode bypasses Agones | `server/supervisor/` covers allocated/direct startup and dynamic endpoint/Ready ordering; Godot Agones adapter, metadata watch, Health/annotation/Shutdown and emulator integration remain |
|
||||
| 8.27 `[D:8.26]` | **IN PROGRESS.** Go supervisor package provides local-safe Agones REST discovery, validates assigned address/port data, injects dynamic `SDR_LISTEN_PORT`/`SDR_IP`, performs explicit process-ready probing and Ready transition; direct mode bypasses Agones | `server/supervisor/` covers allocated/direct startup, invalid endpoint rejection, dynamic endpoint/Ready ordering and authenticated drain; Godot Agones adapter, metadata watch, Health/annotation/Shutdown and emulator integration remain |
|
||||
| 8.28 `[D:8.6,8.27]` | **IN PROGRESS.** Supervisor separates explicit process-ready from Agones Ready and never scrapes stdout; allocated mode refuses to mark Ready without a configured readiness probe | `server/supervisor/` tests prove Ready follows the probe and direct mode remains functional; Godot readiness endpoint, detached-container and Health-reclaim integration remain |
|
||||
| 8.29 `[D:8.26,8.27]` | **IN PROGRESS.** Supervisor discovers the Agones endpoint, propagates the actual dynamic `--port`, and exports `SDR_LISTEN_PORT`/`SDR_IP` only for Hosted-SDR while preserving an isolated ENet path | `server/supervisor/` tests cover dynamic port argument/env propagation and SDR-vs-ENet separation; real Agones dynamic/passthrough mapping, POP/cert/firewall/NAT and multi-match fixture remain |
|
||||
| 8.29 `[D:8.26,8.27]` | **IN PROGRESS.** Supervisor discovers and validates the Agones endpoint, propagates the actual dynamic `--port`, and exports `SDR_LISTEN_PORT`/`SDR_IP` only for Hosted-SDR while preserving an isolated ENet path | `server/supervisor/` tests cover invalid address/port rejection, dynamic port argument/env propagation and SDR-vs-ENet separation; real Agones dynamic/passthrough mapping, POP/cert/firewall/NAT and multi-match fixture remain |
|
||||
| 8.30 `[D:8.18,8.26,8.28,8.29]` | **IN PROGRESS.** Pure Go allocator filters Ready servers by region/build/protocol/transport, atomically claims one with idempotent allocation replay, and now owns the assignment-publication boundary | `server/domain/allocator.go` covers deterministic compatible selection, exhaustion, conflicting/identical allocation replay, unknown allocations, and assignment replay/conflict; Agones `GameServerAllocation`, signed roster metadata, bounded cross-replica retry and live integration remain |
|
||||
| 8.31 `[D:8.9,8.30]` | **IN PROGRESS.** Pure Go assignment gate requires Allocated state, exact allocation ID/match/server/region/build/protocol/transport compatibility, non-empty hosted endpoint and verified manifest signature before exposure; allocator publication cannot expose Ready state | `server/domain/assignment.go` and `allocator.go` plus adversarial fixtures cover early-connect, tampered signature/manifest, wrong compatibility, empty endpoint, unknown allocation and post-publication mutation rejection; Agones metadata watch, hosted-address registration, production signer and client-ticket publication remain |
|
||||
| 8.32 `[D:8.2,8.26,8.30]` | FleetAutoscaler with >=2 Ready processes across >=2 on-demand nodes/failure domains per queue-enabled region; pre-pull current/rollback; scale **Allocated** count to zero, never the Ready floor | Warm allocation meets p95 5 s/p99 10 s; disabled regions alone scale fully to zero; one-node loss retains certified Ready/headroom |
|
||||
|
||||
@@ -145,11 +145,11 @@ func (s *Supervisor) assignedEndpoint(ctx context.Context) (int, string, error)
|
||||
if err := s.sdkGet(ctx, "/gameserver", &server); err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
if len(server.Status.Ports) == 0 || server.Status.Address == "" {
|
||||
if len(server.Status.Ports) == 0 || strings.TrimSpace(server.Status.Address) == "" || strings.ContainsAny(server.Status.Address, " \t\r\n") {
|
||||
return 0, "", fmt.Errorf("Agones returned no assigned endpoint")
|
||||
}
|
||||
for _, port := range server.Status.Ports {
|
||||
if port.Port > 0 && (port.Name == "game" || len(server.Status.Ports) == 1) {
|
||||
if port.Port > 0 && port.Port <= 65535 && (port.Name == "game" || len(server.Status.Ports) == 1) {
|
||||
return port.Port, server.Status.Address, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,3 +142,28 @@ func TestDrainRequiresAndUsesAuthenticatedLocalEndpoint(t *testing.T) {
|
||||
t.Fatal("unauthenticated drain was allowed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssignedEndpointRejectsMalformedAddressAndPort(t *testing.T) {
|
||||
for _, response := range []string{
|
||||
`{"status":{"address":"203.0.113.9","ports":[{"name":"game","port":65536}]}}`,
|
||||
`{"status":{"address":" ","ports":[{"name":"game","port":31001}]}}`,
|
||||
`{"status":{"address":"203.0.113.9 bad","ports":[{"name":"game","port":31001}]}}`,
|
||||
} {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/gameserver" {
|
||||
_, _ = w.Write([]byte(response))
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
s, err := New(Config{Command: []string{"/bin/sh", "-c", "exit 0"}, SDKBaseURL: server.URL, ReadyURL: server.URL + "/probe", ReadyTimeout: time.Second})
|
||||
if err != nil {
|
||||
server.Close()
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := s.Start(context.Background()); err == nil {
|
||||
t.Errorf("malformed endpoint was accepted: %s", response)
|
||||
}
|
||||
server.Close()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user