From 8c3aaa78f5fe842a2374ad32c839ab1bca8a9bcf Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Tue, 3 Mar 2026 00:28:57 +0300 Subject: [PATCH] feat(compute): option to disable compute authentication --- .../compute/dependencies/authentication.py | 26 +++++++++++++++++-- gns3server/schemas/config.py | 1 + gns3server/server.py | 5 +++- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/gns3server/api/routes/compute/dependencies/authentication.py b/gns3server/api/routes/compute/dependencies/authentication.py index 377a89dc6..5e725b3d8 100644 --- a/gns3server/api/routes/compute/dependencies/authentication.py +++ b/gns3server/api/routes/compute/dependencies/authentication.py @@ -26,12 +26,29 @@ from gns3server.config import Config from typing import Optional, Union log = logging.getLogger(__name__) -security = HTTPBasic() +security = HTTPBasic(auto_error=False) def compute_authentication(credentials: Optional[HTTPBasicCredentials] = Depends(security)) -> None: + """ + Authenticate compute requests. + + Returns None if authentication is disabled or if authentication succeeds + Raises HTTPException if authentication is required but credentials are invalid + """ server_settings = Config.instance().settings.Server + + if not server_settings.enable_http_auth: + return None + + if credentials is None: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid compute username or password", + headers={"WWW-Authenticate": "Basic"}, + ) + username = secrets.compare_digest(credentials.username, server_settings.compute_username) password = secrets.compare_digest(credentials.password, server_settings.compute_password.get_secret_value()) if not (username and password): @@ -44,6 +61,12 @@ def compute_authentication(credentials: Optional[HTTPBasicCredentials] = Depends async def ws_compute_authentication(websocket: WebSocket) -> Union[None, WebSocket]: """ """ + + server_settings = Config.instance().settings.Server + + if not server_settings.enable_http_auth: + await websocket.accept() + return websocket await websocket.accept() @@ -68,7 +91,6 @@ async def ws_compute_authentication(websocket: WebSocket) -> Union[None, WebSock if not separator: raise invalid_user_credentials_exc - server_settings = Config.instance().settings.Server username = secrets.compare_digest(username, server_settings.compute_username) password = secrets.compare_digest(password, server_settings.compute_password.get_secret_value()) if not (username and password): diff --git a/gns3server/schemas/config.py b/gns3server/schemas/config.py index 34ea90690..4d0426b0e 100644 --- a/gns3server/schemas/config.py +++ b/gns3server/schemas/config.py @@ -114,6 +114,7 @@ class BuiltinSymbolTheme(str, Enum): class ServerSettings(BaseModel): local: bool = False + enable_http_auth: bool = True name: str = f"{socket.gethostname()} (controller)" protocol: ServerProtocol = ServerProtocol.http host: str = "0.0.0.0" diff --git a/gns3server/server.py b/gns3server/server.py index 6ae18fdfb..be2b62564 100644 --- a/gns3server/server.py +++ b/gns3server/server.py @@ -240,7 +240,10 @@ class Server: self._set_config_defaults_from_command_line(args) config = Config.instance().settings - if not config.Server.compute_password.get_secret_value(): + + if not config.Server.enable_http_auth: + log.info("Compute authentication is disabled") + elif not config.Server.compute_password.get_secret_value(): alphabet = string.ascii_letters + string.digits + string.punctuation generated_password = ''.join(secrets.choice(alphabet) for _ in range(16)) config.Server.compute_password = SecretStr(generated_password)