fix(security): enforce mandatory authentication and update deployment
Enforce strict Bearer token validation even when the bridge auth token is not explicitly configured in the environment. This ensures unauthenticated requests are rejected with a 401 status code by default. Updated deployment scripts to pass the required auth token and adjusted the test suite to align with the new security requirements.
This commit is contained in:
parent
75c940fba5
commit
f30c8d4816
@ -568,6 +568,7 @@ func TestHandleRPCRequiresExplicitBearerForExternalProvider(t *testing.T) {
|
||||
defer externalServer.Close()
|
||||
|
||||
t.Setenv("INTERNAL_SERVICE_TOKEN", "synced-provider-token")
|
||||
t.Setenv("BRIDGE_AUTH_TOKEN", "")
|
||||
server := NewServer()
|
||||
setTestBridgeProvider(server, syncedProvider{
|
||||
ProviderID: "codex",
|
||||
|
||||
@ -305,10 +305,6 @@ func (s *Server) authorized(r *http.Request) bool {
|
||||
if s == nil {
|
||||
return false
|
||||
}
|
||||
expected := strings.TrimSpace(shared.EnvOrDefault("BRIDGE_AUTH_TOKEN", ""))
|
||||
if expected == "" {
|
||||
return true
|
||||
}
|
||||
if s.authService == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ import (
|
||||
|
||||
func TestHTTPHandlerRootAndPingExposeRuntimeVersionInfo(t *testing.T) {
|
||||
t.Setenv("IMAGE", "ghcr.io/x-evor/xworkmate-bridge:0123456789abcdef0123456789abcdef01234567")
|
||||
t.Setenv("BRIDGE_AUTH_TOKEN", "")
|
||||
|
||||
server := NewServer()
|
||||
handler := server.Handler()
|
||||
@ -75,6 +76,7 @@ func TestParseImageVersionInfoHandlesTaggedImageRef(t *testing.T) {
|
||||
|
||||
func TestHandleWebSocketRejectsUnknownOrigin(t *testing.T) {
|
||||
t.Setenv("ACP_ALLOWED_ORIGINS", "https://xworkmate.svc.plus")
|
||||
t.Setenv("BRIDGE_AUTH_TOKEN", "")
|
||||
|
||||
server := NewServer()
|
||||
recorder := httptest.NewRecorder()
|
||||
@ -93,6 +95,7 @@ func TestHandleWebSocketRejectsUnknownOrigin(t *testing.T) {
|
||||
|
||||
func TestHandleRPCAllowsPreflightForConfiguredOrigin(t *testing.T) {
|
||||
t.Setenv("ACP_ALLOWED_ORIGINS", "https://xworkmate.svc.plus,http://localhost:*")
|
||||
t.Setenv("BRIDGE_AUTH_TOKEN", "")
|
||||
|
||||
server := NewServer()
|
||||
recorder := httptest.NewRecorder()
|
||||
@ -110,7 +113,8 @@ func TestHandleRPCAllowsPreflightForConfiguredOrigin(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleRPCAllowsRequestsWhenBridgeAuthTokenUnset(t *testing.T) {
|
||||
func TestHandleRPCRequiresAuthorizationEvenWhenBridgeAuthTokenUnset(t *testing.T) {
|
||||
t.Setenv("BRIDGE_AUTH_TOKEN", "")
|
||||
server := NewServer()
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(
|
||||
@ -122,8 +126,8 @@ func TestHandleRPCAllowsRequestsWhenBridgeAuthTokenUnset(t *testing.T) {
|
||||
|
||||
server.HandleRPC(recorder, request)
|
||||
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200 when BRIDGE_AUTH_TOKEN is unset, got %d", recorder.Code)
|
||||
if recorder.Code != http.StatusUnauthorized {
|
||||
t.Fatalf("expected 401 when BRIDGE_AUTH_TOKEN is unset but no header provided, got %d", recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,6 +152,7 @@ func TestHandleRPCRequiresBearerAuthorizationWhenBridgeAuthTokenConfigured(t *te
|
||||
|
||||
func TestHandleRPCRejectsUnknownOrigin(t *testing.T) {
|
||||
t.Setenv("ACP_ALLOWED_ORIGINS", "https://xworkmate.svc.plus")
|
||||
t.Setenv("BRIDGE_AUTH_TOKEN", "")
|
||||
|
||||
server := NewServer()
|
||||
recorder := httptest.NewRecorder()
|
||||
@ -175,6 +180,7 @@ func TestHandleRPCRejectsUnknownOrigin(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestHandleRPCMethodErrorUsesJSONEnvelope(t *testing.T) {
|
||||
t.Setenv("BRIDGE_AUTH_TOKEN", "")
|
||||
server := NewServer()
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(http.MethodGet, "http://127.0.0.1/acp/rpc", nil)
|
||||
@ -191,6 +197,7 @@ func TestHandleRPCMethodErrorUsesJSONEnvelope(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestHandleRPCCapabilitiesStillReturnsJSONResult(t *testing.T) {
|
||||
t.Setenv("BRIDGE_AUTH_TOKEN", "")
|
||||
server := NewServer()
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(
|
||||
@ -214,15 +221,17 @@ func TestHandleRPCCapabilitiesStillReturnsJSONResult(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthorizedAllowsRequestsWhenBridgeAuthTokenUnset(t *testing.T) {
|
||||
func TestAuthorizedRejectsUnauthenticatedRequestsWhenBridgeAuthTokenUnset(t *testing.T) {
|
||||
t.Setenv("BRIDGE_AUTH_TOKEN", "")
|
||||
server := NewServer()
|
||||
request := httptest.NewRequest(http.MethodGet, "http://127.0.0.1/acp", nil)
|
||||
if !server.authorized(request) {
|
||||
t.Fatal("expected requests to be authorized when BRIDGE_AUTH_TOKEN is unset")
|
||||
if server.authorized(request) {
|
||||
t.Fatal("expected unauthenticated request to be rejected even if BRIDGE_AUTH_TOKEN is unset")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleRPCCapabilitiesReturnsCanonicalProviderContract(t *testing.T) {
|
||||
t.Setenv("BRIDGE_AUTH_TOKEN", "")
|
||||
server := NewServer()
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(
|
||||
@ -301,6 +310,7 @@ func TestHandleRPCSessionStartSucceedsWithExplicitProvider(t *testing.T) {
|
||||
defer externalServer.Close()
|
||||
|
||||
t.Setenv("INTERNAL_SERVICE_TOKEN", "internal-test-token")
|
||||
t.Setenv("BRIDGE_AUTH_TOKEN", "")
|
||||
|
||||
server := NewServer()
|
||||
setTestBridgeProvider(server, syncedProvider{
|
||||
|
||||
@ -523,12 +523,11 @@ func (s *Server) applyCORS(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (s *Server) authorized(r *http.Request) bool {
|
||||
if s == nil || s.authService == nil {
|
||||
return true
|
||||
if s == nil {
|
||||
return false
|
||||
}
|
||||
expected := strings.TrimSpace(shared.EnvOrDefault("GEMINI_ADAPTER_AUTH_TOKEN", ""))
|
||||
if expected == "" {
|
||||
return true
|
||||
if s.authService == nil {
|
||||
return false
|
||||
}
|
||||
return s.authService.ValidateAuthorizationHeader(r.Header.Get("Authorization"))
|
||||
}
|
||||
|
||||
@ -85,6 +85,7 @@ func TestHandleRPCSessionStartReturnsUpstreamResult(t *testing.T) {
|
||||
},
|
||||
})
|
||||
request := httptest.NewRequest(http.MethodPost, "http://127.0.0.1/acp/rpc", bytes.NewReader(body))
|
||||
request.Header.Set("Authorization", "Bearer test-token")
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
server.HandleRPC(recorder, request)
|
||||
@ -223,7 +224,9 @@ func TestHandleWebSocketCapabilities(t *testing.T) {
|
||||
defer httpServer.Close()
|
||||
|
||||
wsURL := "ws" + httpServer.URL[len("http"):]
|
||||
conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
|
||||
header := http.Header{}
|
||||
header.Set("Authorization", "Bearer test-token")
|
||||
conn, _, err := websocket.DefaultDialer.Dial(wsURL, header)
|
||||
if err != nil {
|
||||
t.Fatalf("dial websocket: %v", err)
|
||||
}
|
||||
|
||||
@ -41,4 +41,5 @@ ANSIBLE_CONFIG="${PWD}/ansible.cfg" \
|
||||
SERVICE_COMPOSE_IMAGE="${SERVICE_COMPOSE_IMAGE}" \
|
||||
GHCR_USERNAME="${GHCR_USERNAME:-}" \
|
||||
GHCR_PASSWORD="${GHCR_PASSWORD:-}" \
|
||||
BRIDGE_AUTH_TOKEN="${INTERNAL_SERVICE_TOKEN:-}" \
|
||||
"${args[@]}"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user