feat(api): add support for local management password validation and spoofed IP rejection

- Introduced `newTestServerWithOptions` to customize server initialization in tests.
- Added `TestManagementLocalPasswordRejectsSpoofedForwardedFor` to validate security against spoofed `X-Forwarded-For` headers.
- Enabled default WebSocket authentication (`ws-auth`) in `config.example.yaml`.
- Disabled trusted proxy headers in Gin engine with appropriate logging to enhance security.
This commit is contained in:
Luis Pater
2026-05-18 01:22:45 +08:00
parent 9ef99aa766
commit 605adaa3c2
4 changed files with 30 additions and 2 deletions

1
CLAUDE.md Normal file
View File

@@ -0,0 +1 @@
@AGENTS.md

View File

@@ -143,7 +143,7 @@ routing:
session-affinity-ttl: "1h"
# When true, enable authentication for the WebSocket API (/v1/ws).
ws-auth: false
ws-auth: true
# When true, enable Gemini CLI internal endpoints (/v1internal:*).
# Default is false for safety.

View File

@@ -217,6 +217,9 @@ func NewServer(cfg *config.Config, authManager *auth.Manager, accessManager *sdk
// Create gin engine
engine := gin.New()
if errSetTrustedProxies := engine.SetTrustedProxies(nil); errSetTrustedProxies != nil {
log.Warnf("failed to disable trusted proxy headers: %v", errSetTrustedProxies)
}
if optionState.engineConfigurator != nil {
optionState.engineConfigurator(engine)
}

View File

@@ -21,6 +21,10 @@ import (
)
func newTestServer(t *testing.T) *Server {
return newTestServerWithOptions(t)
}
func newTestServerWithOptions(t *testing.T, opts ...ServerOption) *Server {
t.Helper()
gin.SetMode(gin.TestMode)
@@ -46,7 +50,7 @@ func newTestServer(t *testing.T) *Server {
accessManager := sdkaccess.NewManager()
configPath := filepath.Join(tmpDir, "config.yaml")
return NewServer(cfg, authManager, accessManager, configPath)
return NewServer(cfg, authManager, accessManager, configPath, opts...)
}
func TestHealthz(t *testing.T) {
@@ -148,6 +152,26 @@ func TestManagementUsageRequiresManagementAuthAndPopsArray(t *testing.T) {
}
}
func TestManagementLocalPasswordRejectsSpoofedForwardedFor(t *testing.T) {
t.Setenv("MANAGEMENT_PASSWORD", "")
server := newTestServerWithOptions(t, WithLocalManagementPassword("test-local-key"))
req := httptest.NewRequest(http.MethodGet, "/v0/management/config", nil)
req.RemoteAddr = "203.0.113.10:45678"
req.Header.Set("X-Forwarded-For", "127.0.0.1")
req.Header.Set("Authorization", "Bearer test-local-key")
rr := httptest.NewRecorder()
server.engine.ServeHTTP(rr, req)
if rr.Code != http.StatusForbidden {
t.Fatalf("status = %d, want %d body=%s", rr.Code, http.StatusForbidden, rr.Body.String())
}
if body := rr.Body.String(); !strings.Contains(body, "remote management disabled") {
t.Fatalf("body = %q, want remote management disabled", body)
}
}
func TestHomeEnabledHidesManagementEndpointsAndControlPanel(t *testing.T) {
t.Setenv("MANAGEMENT_PASSWORD", "test-management-key")