Guobin Yue df28036a68
fix(deps): resolve 3 security vulnerabilities (CVE-2025-71176, CVE-2026-40347, CVE-2018-25031) (#2671)
* fix(deps): upgrade pytest to 9.0.3 to fix CVE-2025-71176

CVE-2025-71176: pytest 9.0.2 and earlier versions have a local
security vulnerability due to predictable temporary directory naming.

- pytest: 8.4.2 → 9.0.3
- Python 3.10+ is now required

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(deps): upgrade python-multipart to 0.0.26 to fix CVE-2026-40347

CVE-2026-40347: python-multipart < 0.0.26 has a denial of
service vulnerability when parsing multipart data.

- python-multipart: 0.0.22 → 0.0.26

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(deps): upgrade swagger-ui to 4.1.3 to fix CVE-2018-25031

CVE-2018-25031: swagger-ui < 4.1.3 has a spoofing vulnerability
where remote attackers can display remote OpenAPI definitions
via crafted URLs.

- swagger-ui: 3.30.0 → 4.1.3
- Updated swagger-ui-bundle.js and swagger-ui.css

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(api): change FastAPI version from "v3" to "3.0.0" for Swagger UI 4.1.3 compatibility

Swagger UI 4.x enforces stricter version format validation.
The version "v3" is not accepted by the new validator.

Changed from:
- version="v3"

To:
- version="3.0.0"

This affects both controller and compute API definitions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(deps): upgrade swagger-ui to 5.32.4

Fixes "Unable to render this definition" error when loading OpenAPI 3.0
docs. Swagger UI 3.x had incomplete OpenAPI 3.0 support.

- swagger-ui: 3.19.1 → 5.32.4 (latest)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 00:35:15 +08:00

268 lines
7.5 KiB
Python

#
# Copyright (C) 2020 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from fastapi import FastAPI, Request, Depends
from fastapi.responses import JSONResponse
from starlette.exceptions import HTTPException as StarletteHTTPException
from gns3server.controller.gns3vm.gns3_vm_error import GNS3VMError
from gns3server.compute.error import ImageMissingError, NodeError
from gns3server.compute.ubridge.ubridge_error import UbridgeError
from .dependencies.authentication import compute_authentication
from gns3server.compute.compute_error import (
ComputeError,
ComputeNotFoundError,
ComputeTimeoutError,
ComputeForbiddenError,
ComputeUnauthorizedError,
)
from . import capabilities
from . import compute
from . import projects
from . import notifications
from . import images
from . import atm_switch_nodes
from . import cloud_nodes
from . import docker_nodes
from . import dynamips_nodes
from . import ethernet_hub_nodes
from . import ethernet_switch_nodes
from . import frame_relay_switch_nodes
from . import iou_nodes
from . import nat_nodes
from . import qemu_nodes
from . import virtualbox_nodes
from . import vmware_nodes
from . import vpcs_nodes
import logging
log = logging.getLogger(__name__)
compute_api = FastAPI(
title="GNS3 compute API",
description="This page describes the private compute API for GNS3. PLEASE DO NOT USE DIRECTLY!",
version="3.0.0",
)
compute_api.state.controller_host = None
@compute_api.exception_handler(ComputeError)
async def compute_error_handler(request: Request, exc: ComputeError):
log.error(f"Compute error: {exc}")
return JSONResponse(
status_code=409,
content={"message": str(exc)},
)
@compute_api.exception_handler(ComputeTimeoutError)
async def compute_timeout_error_handler(request: Request, exc: ComputeTimeoutError):
log.error(f"Compute timeout error: {exc}")
return JSONResponse(
status_code=408,
content={"message": str(exc)},
)
@compute_api.exception_handler(ComputeUnauthorizedError)
async def compute_unauthorized_error_handler(request: Request, exc: ComputeUnauthorizedError):
log.error(f"Compute unauthorized error: {exc}")
return JSONResponse(
status_code=401,
content={"message": str(exc)},
)
@compute_api.exception_handler(ComputeForbiddenError)
async def compute_forbidden_error_handler(request: Request, exc: ComputeForbiddenError):
log.error(f"Compute forbidden error: {exc}")
return JSONResponse(
status_code=403,
content={"message": str(exc)},
)
@compute_api.exception_handler(ComputeNotFoundError)
async def compute_not_found_error_handler(request: Request, exc: ComputeNotFoundError):
log.error(f"Compute not found error: {exc}")
return JSONResponse(
status_code=404,
content={"message": str(exc)},
)
@compute_api.exception_handler(GNS3VMError)
async def compute_gns3vm_error_handler(request: Request, exc: GNS3VMError):
log.error(f"Compute GNS3 VM error: {exc}")
return JSONResponse(
status_code=409,
content={"message": str(exc)},
)
@compute_api.exception_handler(ImageMissingError)
async def image_missing_error_handler(request: Request, exc: ImageMissingError):
log.error(f"Compute image missing error: {exc}")
return JSONResponse(
status_code=409,
content={"message": str(exc), "image": exc.image, "exception": exc.__class__.__name__},
)
@compute_api.exception_handler(NodeError)
async def node_error_handler(request: Request, exc: NodeError):
log.error(f"Compute node error: {exc}")
return JSONResponse(
status_code=409,
content={"message": str(exc), "exception": exc.__class__.__name__},
)
@compute_api.exception_handler(UbridgeError)
async def ubridge_error_handler(request: Request, exc: UbridgeError):
log.error(f"Compute uBridge error: {exc}")
return JSONResponse(
status_code=409,
content={"message": str(exc), "exception": exc.__class__.__name__},
)
# make sure the content key is "message", not "detail" per default
@compute_api.exception_handler(StarletteHTTPException)
async def http_exception_handler(request: Request, exc: StarletteHTTPException):
return JSONResponse(
status_code=exc.status_code,
content={"message": exc.detail},
)
compute_api.include_router(
capabilities.router,
dependencies=[Depends(compute_authentication)],
tags=["Capabilities"]
)
compute_api.include_router(
compute.router,
dependencies=[Depends(compute_authentication)],
tags=["Compute"]
)
compute_api.include_router(
notifications.router,
tags=["Notifications"]
)
compute_api.include_router(
projects.router,
dependencies=[Depends(compute_authentication)],
tags=["Projects"]
)
compute_api.include_router(
images.router,
dependencies=[Depends(compute_authentication)],
tags=["Images"]
)
compute_api.include_router(
atm_switch_nodes.router,
dependencies=[Depends(compute_authentication)],
prefix="/projects/{project_id}/atm_switch/nodes",
tags=["ATM switch"]
)
compute_api.include_router(
cloud_nodes.router,
dependencies=[Depends(compute_authentication)],
prefix="/projects/{project_id}/cloud/nodes",
tags=["Cloud nodes"]
)
compute_api.include_router(
docker_nodes.router,
prefix="/projects/{project_id}/docker/nodes",
tags=["Docker nodes"]
)
compute_api.include_router(
dynamips_nodes.router,
prefix="/projects/{project_id}/dynamips/nodes",
tags=["Dynamips nodes"]
)
compute_api.include_router(
ethernet_hub_nodes.router,
dependencies=[Depends(compute_authentication)],
prefix="/projects/{project_id}/ethernet_hub/nodes",
tags=["Ethernet hub nodes"]
)
compute_api.include_router(
ethernet_switch_nodes.router,
dependencies=[Depends(compute_authentication)],
prefix="/projects/{project_id}/ethernet_switch/nodes",
tags=["Ethernet switch nodes"]
)
compute_api.include_router(
frame_relay_switch_nodes.router,
dependencies=[Depends(compute_authentication)],
prefix="/projects/{project_id}/frame_relay_switch/nodes",
tags=["Frame Relay switch nodes"]
)
compute_api.include_router(
iou_nodes.router,
prefix="/projects/{project_id}/iou/nodes",
tags=["IOU nodes"])
compute_api.include_router(
nat_nodes.router,
dependencies=[Depends(compute_authentication)],
prefix="/projects/{project_id}/nat/nodes",
tags=["NAT nodes"]
)
compute_api.include_router(
qemu_nodes.router,
prefix="/projects/{project_id}/qemu/nodes",
tags=["Qemu nodes"]
)
compute_api.include_router(
virtualbox_nodes.router,
prefix="/projects/{project_id}/virtualbox/nodes",
tags=["VirtualBox nodes"]
)
compute_api.include_router(
vmware_nodes.router,
prefix="/projects/{project_id}/vmware/nodes",
tags=["VMware nodes"]
)
compute_api.include_router(
vpcs_nodes.router,
prefix="/projects/{project_id}/vpcs/nodes",
tags=["VPCS nodes"]
)