53 lines
1.6 KiB
Go
53 lines
1.6 KiB
Go
package httpd
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/aoagents/agent-orchestrator/backend/internal/config"
|
|
)
|
|
|
|
// TestShutdownGuard verifies that POST /shutdown only fires for a trusted local
|
|
// caller: a loopback Host with no Origin header. A cross-site Origin or a
|
|
// non-loopback (DNS-rebinding) Host must be rejected without triggering the
|
|
// shutdown side effect.
|
|
func TestShutdownGuard(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
host string
|
|
origin string
|
|
wantStatus int
|
|
wantFired bool
|
|
}{
|
|
{name: "loopback no origin", host: "127.0.0.1:3001", wantStatus: http.StatusAccepted, wantFired: true},
|
|
{name: "localhost no origin", host: "localhost:3001", wantStatus: http.StatusAccepted, wantFired: true},
|
|
{name: "cross-site origin", host: "127.0.0.1:3001", origin: "https://evil.example", wantStatus: http.StatusForbidden, wantFired: false},
|
|
{name: "rebinding host", host: "evil.example", wantStatus: http.StatusForbidden, wantFired: false},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
fired := false
|
|
r := NewRouterWithControl(config.Config{}, discardLogger(), nil, APIDeps{}, ControlDeps{
|
|
RequestShutdown: func() { fired = true },
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "http://"+tc.host+"/shutdown", nil)
|
|
req.Host = tc.host
|
|
if tc.origin != "" {
|
|
req.Header.Set("Origin", tc.origin)
|
|
}
|
|
rec := httptest.NewRecorder()
|
|
r.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != tc.wantStatus {
|
|
t.Fatalf("status = %d, want %d", rec.Code, tc.wantStatus)
|
|
}
|
|
if fired != tc.wantFired {
|
|
t.Fatalf("shutdown fired = %v, want %v", fired, tc.wantFired)
|
|
}
|
|
})
|
|
}
|
|
}
|