diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml
index aa78d289..01c4fdda 100644
--- a/.github/workflows/testing.yml
+++ b/.github/workflows/testing.yml
@@ -17,7 +17,7 @@ jobs:
strategy:
matrix:
- python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
+ python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v3
diff --git a/CHANGELOG b/CHANGELOG
index 7da94525..bfaecf60 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,16 @@
# Change Log
+## 3.0.0a5 27/10/2023
+
+* Bundle web-ui v3.0.0a5
+* Fix L2IOU "failed code signing checks" when IOU base file name is >= 63 characters
+* Python 3.12 support
+* Add igb Qemu adapter
+* Change "ip cef" to "no ip cef" in IOU default configs. Fixes #2298
+* Drop support for Python 3.7 and upgrade dependencies
+* Fix compute authentication for websocket endpoints
+* Add Qemu IGB network device
+
## 3.0.0a4 18/10/2023
* Bundle web-ui v3.0.0a4
diff --git a/dev-requirements.txt b/dev-requirements.txt
index 5e29ebc7..b923b844 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -1,6 +1,7 @@
-pytest==7.4.0
-flake8==5.0.4 # v5.0.4 is the last to support Python 3.7
-pytest-timeout==2.1.0
+pytest==7.4.2
+flake8==6.1.0
+pytest-timeout==2.2.0
pytest-asyncio==0.21.1
requests==2.31.0
-httpx==0.24.1
+httpx==0.24.1 # version 0.24.1 is required by httpx_ws
+httpx_ws==0.4.2
diff --git a/gns3server/api/routes/compute/__init__.py b/gns3server/api/routes/compute/__init__.py
index 4628dfd8..1922e31a 100644
--- a/gns3server/api/routes/compute/__init__.py
+++ b/gns3server/api/routes/compute/__init__.py
@@ -199,14 +199,12 @@ compute_api.include_router(
compute_api.include_router(
docker_nodes.router,
- dependencies=[Depends(compute_authentication)],
prefix="/projects/{project_id}/docker/nodes",
tags=["Docker nodes"]
)
compute_api.include_router(
dynamips_nodes.router,
- dependencies=[Depends(compute_authentication)],
prefix="/projects/{project_id}/dynamips/nodes",
tags=["Dynamips nodes"]
)
@@ -234,7 +232,6 @@ compute_api.include_router(
compute_api.include_router(
iou_nodes.router,
- dependencies=[Depends(compute_authentication)],
prefix="/projects/{project_id}/iou/nodes",
tags=["IOU nodes"])
@@ -247,28 +244,24 @@ compute_api.include_router(
compute_api.include_router(
qemu_nodes.router,
- dependencies=[Depends(compute_authentication)],
prefix="/projects/{project_id}/qemu/nodes",
tags=["Qemu nodes"]
)
compute_api.include_router(
virtualbox_nodes.router,
- dependencies=[Depends(compute_authentication)],
prefix="/projects/{project_id}/virtualbox/nodes",
tags=["VirtualBox nodes"]
)
compute_api.include_router(
vmware_nodes.router,
- dependencies=[Depends(compute_authentication)],
prefix="/projects/{project_id}/vmware/nodes",
tags=["VMware nodes"]
)
compute_api.include_router(
vpcs_nodes.router,
- dependencies=[Depends(compute_authentication)],
prefix="/projects/{project_id}/vpcs/nodes",
tags=["VPCS nodes"]
)
diff --git a/gns3server/api/routes/compute/dependencies/authentication.py b/gns3server/api/routes/compute/dependencies/authentication.py
index 5efb9927..377a89dc 100644
--- a/gns3server/api/routes/compute/dependencies/authentication.py
+++ b/gns3server/api/routes/compute/dependencies/authentication.py
@@ -15,12 +15,17 @@
# along with this program. If not, see .
import secrets
+import base64
+import binascii
+import logging
-from fastapi import Depends, HTTPException, status
+from fastapi import Depends, HTTPException, WebSocket, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
+from fastapi.security.utils import get_authorization_scheme_param
from gns3server.config import Config
-from typing import Optional
+from typing import Optional, Union
+log = logging.getLogger(__name__)
security = HTTPBasic()
@@ -35,3 +40,44 @@ def compute_authentication(credentials: Optional[HTTPBasicCredentials] = Depends
detail="Invalid compute username or password",
headers={"WWW-Authenticate": "Basic"},
)
+
+async def ws_compute_authentication(websocket: WebSocket) -> Union[None, WebSocket]:
+ """
+ """
+
+ await websocket.accept()
+
+ # handle basic HTTP authentication
+ invalid_user_credentials_exc = HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Could not validate credentials",
+ headers={"WWW-Authenticate": "Basic"},
+ )
+
+ try:
+ authorization = websocket.headers.get("Authorization")
+ scheme, param = get_authorization_scheme_param(authorization)
+ if not authorization or scheme.lower() != "basic":
+ raise invalid_user_credentials_exc
+ try:
+ data = base64.b64decode(param).decode("ascii")
+ except (ValueError, UnicodeDecodeError, binascii.Error):
+ raise invalid_user_credentials_exc
+
+ username, separator, password = data.partition(":")
+ 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):
+ raise invalid_user_credentials_exc
+
+ except HTTPException as e:
+ err_msg = f"Could not authenticate while connecting to compute WebSocket: {e.detail}"
+ websocket_error = {"action": "log.error", "event": {"message": err_msg}}
+ await websocket.send_json(websocket_error)
+ log.error(err_msg)
+ return await websocket.close(code=1008)
+ return websocket
diff --git a/gns3server/api/routes/compute/docker_nodes.py b/gns3server/api/routes/compute/docker_nodes.py
index e9a1e29f..11a2cf65 100644
--- a/gns3server/api/routes/compute/docker_nodes.py
+++ b/gns3server/api/routes/compute/docker_nodes.py
@@ -20,15 +20,18 @@ API routes for Docker nodes.
import os
-from fastapi import APIRouter, WebSocket, Depends, Body, Response, status
+from fastapi import APIRouter, WebSocket, Depends, Body, status
from fastapi.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse
from uuid import UUID
+from typing import Union
from gns3server import schemas
from gns3server.compute.docker import Docker
from gns3server.compute.docker.docker_vm import DockerVM
+from .dependencies.authentication import compute_authentication, ws_compute_authentication
+
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Docker node"}}
router = APIRouter(responses=responses)
@@ -49,6 +52,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> DockerVM:
response_model=schemas.Docker,
status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Docker node"}},
+ dependencies=[Depends(compute_authentication)]
)
async def create_docker_node(project_id: UUID, node_data: schemas.DockerCreate) -> schemas.Docker:
"""
@@ -85,7 +89,11 @@ async def create_docker_node(project_id: UUID, node_data: schemas.DockerCreate)
return container.asdict()
-@router.get("/{node_id}", response_model=schemas.Docker)
+@router.get(
+ "/{node_id}",
+ response_model=schemas.Docker,
+ dependencies=[Depends(compute_authentication)]
+)
def get_docker_node(node: DockerVM = Depends(dep_node)) -> schemas.Docker:
"""
Return a Docker node.
@@ -94,7 +102,11 @@ def get_docker_node(node: DockerVM = Depends(dep_node)) -> schemas.Docker:
return node.asdict()
-@router.put("/{node_id}", response_model=schemas.Docker)
+@router.put(
+ "/{node_id}",
+ response_model=schemas.Docker,
+ dependencies=[Depends(compute_authentication)]
+)
async def update_docker_node(node_data: schemas.DockerUpdate, node: DockerVM = Depends(dep_node)) -> schemas.Docker:
"""
Update a Docker node.
@@ -131,7 +143,11 @@ async def update_docker_node(node_data: schemas.DockerUpdate, node: DockerVM = D
return node.asdict()
-@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/start",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def start_docker_node(node: DockerVM = Depends(dep_node)) -> None:
"""
Start a Docker node.
@@ -140,7 +156,11 @@ async def start_docker_node(node: DockerVM = Depends(dep_node)) -> None:
await node.start()
-@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/stop",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def stop_docker_node(node: DockerVM = Depends(dep_node)) -> None:
"""
Stop a Docker node.
@@ -149,7 +169,11 @@ async def stop_docker_node(node: DockerVM = Depends(dep_node)) -> None:
await node.stop()
-@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/suspend",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def suspend_docker_node(node: DockerVM = Depends(dep_node)) -> None:
"""
Suspend a Docker node.
@@ -158,7 +182,11 @@ async def suspend_docker_node(node: DockerVM = Depends(dep_node)) -> None:
await node.pause()
-@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/reload",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def reload_docker_node(node: DockerVM = Depends(dep_node)) -> None:
"""
Reload a Docker node.
@@ -167,7 +195,11 @@ async def reload_docker_node(node: DockerVM = Depends(dep_node)) -> None:
await node.restart()
-@router.post("/{node_id}/pause", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/pause",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def pause_docker_node(node: DockerVM = Depends(dep_node)) -> None:
"""
Pause a Docker node.
@@ -176,7 +208,11 @@ async def pause_docker_node(node: DockerVM = Depends(dep_node)) -> None:
await node.pause()
-@router.post("/{node_id}/unpause", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/unpause",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def unpause_docker_node(node: DockerVM = Depends(dep_node)) -> None:
"""
Unpause a Docker node.
@@ -185,7 +221,11 @@ async def unpause_docker_node(node: DockerVM = Depends(dep_node)) -> None:
await node.unpause()
-@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
+@router.delete(
+ "/{node_id}",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def delete_docker_node(node: DockerVM = Depends(dep_node)) -> None:
"""
Delete a Docker node.
@@ -194,7 +234,12 @@ async def delete_docker_node(node: DockerVM = Depends(dep_node)) -> None:
await node.delete()
-@router.post("/{node_id}/duplicate", response_model=schemas.Docker, status_code=status.HTTP_201_CREATED)
+@router.post(
+ "/{node_id}/duplicate",
+ response_model=schemas.Docker,
+ status_code=status.HTTP_201_CREATED,
+ dependencies=[Depends(compute_authentication)]
+)
async def duplicate_docker_node(
destination_node_id: UUID = Body(..., embed=True),
node: DockerVM = Depends(dep_node)
@@ -211,6 +256,7 @@ async def duplicate_docker_node(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO,
+ dependencies=[Depends(compute_authentication)]
)
async def create_docker_node_nio(
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: DockerVM = Depends(dep_node)
@@ -229,6 +275,7 @@ async def create_docker_node_nio(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO,
+ dependencies=[Depends(compute_authentication)]
)
async def update_docker_node_nio(
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: DockerVM = Depends(dep_node)
@@ -245,7 +292,11 @@ async def update_docker_node_nio(
return nio.asdict()
-@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
+@router.delete(
+ "/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def delete_docker_node_nio(
adapter_number: int,
port_number: int,
@@ -259,7 +310,10 @@ async def delete_docker_node_nio(
await node.adapter_remove_nio_binding(adapter_number)
-@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
+@router.post(
+ "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
+ dependencies=[Depends(compute_authentication)]
+)
async def start_docker_node_capture(
adapter_number: int,
port_number: int,
@@ -278,7 +332,8 @@ async def start_docker_node_capture(
@router.post(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
- status_code=status.HTTP_204_NO_CONTENT
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
)
async def stop_docker_node_capture(
adapter_number: int,
@@ -293,7 +348,10 @@ async def stop_docker_node_capture(
await node.stop_capture(adapter_number)
-@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
+@router.get(
+ "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream",
+ dependencies=[Depends(compute_authentication)]
+)
async def stream_pcap_file(
adapter_number: int,
port_number: int,
@@ -310,15 +368,23 @@ async def stream_pcap_file(
@router.websocket("/{node_id}/console/ws")
-async def console_ws(websocket: WebSocket, node: DockerVM = Depends(dep_node)) -> None:
+async def console_ws(
+ websocket: Union[None, WebSocket] = Depends(ws_compute_authentication),
+ node: DockerVM = Depends(dep_node)
+) -> None:
"""
Console WebSocket.
"""
- await node.start_websocket_console(websocket)
+ if websocket:
+ await node.start_websocket_console(websocket)
-@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/console/reset",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def reset_console(node: DockerVM = Depends(dep_node)) -> None:
await node.reset_console()
diff --git a/gns3server/api/routes/compute/dynamips_nodes.py b/gns3server/api/routes/compute/dynamips_nodes.py
index 5f34f066..89be7f69 100644
--- a/gns3server/api/routes/compute/dynamips_nodes.py
+++ b/gns3server/api/routes/compute/dynamips_nodes.py
@@ -20,16 +20,18 @@ API routes for Dynamips nodes.
import os
-from fastapi import APIRouter, WebSocket, Depends, Response, status
+from fastapi import APIRouter, WebSocket, Depends, status
from fastapi.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse
-from typing import List
+from typing import List, Union
from uuid import UUID
from gns3server.compute.dynamips import Dynamips
from gns3server.compute.dynamips.nodes.router import Router
from gns3server import schemas
+from .dependencies.authentication import compute_authentication, ws_compute_authentication
+
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Dynamips node"}}
router = APIRouter(responses=responses)
@@ -53,6 +55,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> Router:
response_model=schemas.Dynamips,
status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Dynamips node"}},
+ dependencies=[Depends(compute_authentication)]
)
async def create_router(project_id: UUID, node_data: schemas.DynamipsCreate) -> schemas.Dynamips:
"""
@@ -84,7 +87,11 @@ async def create_router(project_id: UUID, node_data: schemas.DynamipsCreate) ->
return vm.asdict()
-@router.get("/{node_id}", response_model=schemas.Dynamips)
+@router.get(
+ "/{node_id}",
+ response_model=schemas.Dynamips,
+ dependencies=[Depends(compute_authentication)]
+)
def get_router(node: Router = Depends(dep_node)) -> schemas.Dynamips:
"""
Return Dynamips router.
@@ -93,7 +100,11 @@ def get_router(node: Router = Depends(dep_node)) -> schemas.Dynamips:
return node.asdict()
-@router.put("/{node_id}", response_model=schemas.Dynamips)
+@router.put(
+ "/{node_id}",
+ response_model=schemas.Dynamips,
+ dependencies=[Depends(compute_authentication)]
+)
async def update_router(node_data: schemas.DynamipsUpdate, node: Router = Depends(dep_node)) -> schemas.Dynamips:
"""
Update a Dynamips router.
@@ -104,7 +115,11 @@ async def update_router(node_data: schemas.DynamipsUpdate, node: Router = Depend
return node.asdict()
-@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
+@router.delete(
+ "/{node_id}",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def delete_router(node: Router = Depends(dep_node)) -> None:
"""
Delete a Dynamips router.
@@ -113,7 +128,11 @@ async def delete_router(node: Router = Depends(dep_node)) -> None:
await Dynamips.instance().delete_node(node.id)
-@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/start",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def start_router(node: Router = Depends(dep_node)) -> None:
"""
Start a Dynamips router.
@@ -126,7 +145,11 @@ async def start_router(node: Router = Depends(dep_node)) -> None:
await node.start()
-@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/stop",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def stop_router(node: Router = Depends(dep_node)) -> None:
"""
Stop a Dynamips router.
@@ -135,13 +158,21 @@ async def stop_router(node: Router = Depends(dep_node)) -> None:
await node.stop()
-@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/suspend",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def suspend_router(node: Router = Depends(dep_node)) -> None:
await node.suspend()
-@router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/resume",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def resume_router(node: Router = Depends(dep_node)) -> None:
"""
Resume a suspended Dynamips router.
@@ -150,7 +181,11 @@ async def resume_router(node: Router = Depends(dep_node)) -> None:
await node.resume()
-@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/reload",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def reload_router(node: Router = Depends(dep_node)) -> None:
"""
Reload a suspended Dynamips router.
@@ -163,6 +198,7 @@ async def reload_router(node: Router = Depends(dep_node)) -> None:
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO,
+ dependencies=[Depends(compute_authentication)]
)
async def create_nio(
adapter_number: int,
@@ -183,6 +219,7 @@ async def create_nio(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO,
+ dependencies=[Depends(compute_authentication)]
)
async def update_nio(
adapter_number: int,
@@ -201,7 +238,11 @@ async def update_nio(
return nio.asdict()
-@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
+@router.delete(
+ "/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def delete_nio(adapter_number: int, port_number: int, node: Router = Depends(dep_node)) -> None:
"""
Delete a NIO (Network Input/Output) from the node.
@@ -211,7 +252,10 @@ async def delete_nio(adapter_number: int, port_number: int, node: Router = Depen
await nio.delete()
-@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
+@router.post(
+ "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
+ dependencies=[Depends(compute_authentication)]
+)
async def start_capture(
adapter_number: int,
port_number: int,
@@ -228,7 +272,9 @@ async def start_capture(
@router.post(
- "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
+ "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
)
async def stop_capture(adapter_number: int, port_number: int, node: Router = Depends(dep_node)) -> None:
"""
@@ -238,7 +284,10 @@ async def stop_capture(adapter_number: int, port_number: int, node: Router = Dep
await node.stop_capture(adapter_number, port_number)
-@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
+@router.get(
+ "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream",
+ dependencies=[Depends(compute_authentication)]
+)
async def stream_pcap_file(
adapter_number: int,
port_number: int,
@@ -253,7 +302,10 @@ async def stream_pcap_file(
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
-@router.get("/{node_id}/idlepc_proposals")
+@router.get(
+ "/{node_id}/idlepc_proposals",
+ dependencies=[Depends(compute_authentication)]
+)
async def get_idlepcs(node: Router = Depends(dep_node)) -> List[str]:
"""
Retrieve Dynamips idle-pc proposals
@@ -263,7 +315,10 @@ async def get_idlepcs(node: Router = Depends(dep_node)) -> List[str]:
return await node.get_idle_pc_prop()
-@router.get("/{node_id}/auto_idlepc")
+@router.get(
+ "/{node_id}/auto_idlepc",
+ dependencies=[Depends(compute_authentication)]
+)
async def get_auto_idlepc(node: Router = Depends(dep_node)) -> dict:
"""
Get an automatically guessed best idle-pc value.
@@ -273,7 +328,12 @@ async def get_auto_idlepc(node: Router = Depends(dep_node)) -> dict:
return {"idlepc": idlepc}
-@router.post("/{node_id}/duplicate", response_model=schemas.Dynamips, status_code=status.HTTP_201_CREATED)
+@router.post(
+ "/{node_id}/duplicate",
+ response_model=schemas.Dynamips,
+ status_code=status.HTTP_201_CREATED,
+ dependencies=[Depends(compute_authentication)]
+)
async def duplicate_router(destination_node_id: UUID, node: Router = Depends(dep_node)) -> schemas.Dynamips:
"""
Duplicate a router.
@@ -284,15 +344,24 @@ async def duplicate_router(destination_node_id: UUID, node: Router = Depends(dep
@router.websocket("/{node_id}/console/ws")
-async def console_ws(websocket: WebSocket, node: Router = Depends(dep_node)) -> None:
+async def console_ws(
+ websocket: Union[None, WebSocket] = Depends(ws_compute_authentication),
+ node: Router = Depends(dep_node)
+
+) -> None:
"""
Console WebSocket.
"""
- await node.start_websocket_console(websocket)
+ if websocket:
+ await node.start_websocket_console(websocket)
-@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/console/reset",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def reset_console(node: Router = Depends(dep_node)) -> None:
await node.reset_console()
diff --git a/gns3server/api/routes/compute/iou_nodes.py b/gns3server/api/routes/compute/iou_nodes.py
index 5b74acc9..1b456a97 100644
--- a/gns3server/api/routes/compute/iou_nodes.py
+++ b/gns3server/api/routes/compute/iou_nodes.py
@@ -30,6 +30,8 @@ from gns3server import schemas
from gns3server.compute.iou import IOU
from gns3server.compute.iou.iou_vm import IOUVM
+from .dependencies.authentication import compute_authentication, ws_compute_authentication
+
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or IOU node"}}
router = APIRouter(responses=responses)
@@ -50,6 +52,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> IOUVM:
response_model=schemas.IOU,
status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create IOU node"}},
+ dependencies=[Depends(compute_authentication)]
)
async def create_iou_node(project_id: UUID, node_data: schemas.IOUCreate) -> schemas.IOU:
"""
@@ -82,7 +85,11 @@ async def create_iou_node(project_id: UUID, node_data: schemas.IOUCreate) -> sch
return vm.asdict()
-@router.get("/{node_id}", response_model=schemas.IOU)
+@router.get(
+ "/{node_id}",
+ response_model=schemas.IOU,
+ dependencies=[Depends(compute_authentication)]
+)
def get_iou_node(node: IOUVM = Depends(dep_node)) -> schemas.IOU:
"""
Return an IOU node.
@@ -91,7 +98,11 @@ def get_iou_node(node: IOUVM = Depends(dep_node)) -> schemas.IOU:
return node.asdict()
-@router.put("/{node_id}", response_model=schemas.IOU)
+@router.put(
+ "/{node_id}",
+ response_model=schemas.IOU,
+ dependencies=[Depends(compute_authentication)]
+)
async def update_iou_node(node_data: schemas.IOUUpdate, node: IOUVM = Depends(dep_node)) -> schemas.IOU:
"""
Update an IOU node.
@@ -112,7 +123,11 @@ async def update_iou_node(node_data: schemas.IOUUpdate, node: IOUVM = Depends(de
return node.asdict()
-@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
+@router.delete(
+ "/{node_id}",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def delete_iou_node(node: IOUVM = Depends(dep_node)) -> None:
"""
Delete an IOU node.
@@ -121,7 +136,12 @@ async def delete_iou_node(node: IOUVM = Depends(dep_node)) -> None:
await IOU.instance().delete_node(node.id)
-@router.post("/{node_id}/duplicate", response_model=schemas.IOU, status_code=status.HTTP_201_CREATED)
+@router.post(
+ "/{node_id}/duplicate",
+ response_model=schemas.IOU,
+ status_code=status.HTTP_201_CREATED,
+ dependencies=[Depends(compute_authentication)]
+)
async def duplicate_iou_node(
destination_node_id: UUID = Body(..., embed=True),
node: IOUVM = Depends(dep_node)
@@ -134,7 +154,11 @@ async def duplicate_iou_node(
return new_node.asdict()
-@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/start",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def start_iou_node(start_data: schemas.IOUStart, node: IOUVM = Depends(dep_node)) -> None:
"""
Start an IOU node.
@@ -148,7 +172,11 @@ async def start_iou_node(start_data: schemas.IOUStart, node: IOUVM = Depends(dep
await node.start()
-@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/stop",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def stop_iou_node(node: IOUVM = Depends(dep_node)) -> None:
"""
Stop an IOU node.
@@ -157,7 +185,11 @@ async def stop_iou_node(node: IOUVM = Depends(dep_node)) -> None:
await node.stop()
-@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/stop",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
def suspend_iou_node(node: IOUVM = Depends(dep_node)) -> None:
"""
Suspend an IOU node.
@@ -167,7 +199,11 @@ def suspend_iou_node(node: IOUVM = Depends(dep_node)) -> None:
pass
-@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/reload",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def reload_iou_node(node: IOUVM = Depends(dep_node)) -> None:
"""
Reload an IOU node.
@@ -180,6 +216,7 @@ async def reload_iou_node(node: IOUVM = Depends(dep_node)) -> None:
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_201_CREATED,
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
+ dependencies=[Depends(compute_authentication)]
)
async def create_iou_node_nio(
adapter_number: int,
@@ -200,6 +237,7 @@ async def create_iou_node_nio(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_201_CREATED,
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
+ dependencies=[Depends(compute_authentication)]
)
async def update_iou_node_nio(
adapter_number: int,
@@ -218,7 +256,11 @@ async def update_iou_node_nio(
return nio.asdict()
-@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
+@router.delete(
+ "/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def delete_iou_node_nio(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)) -> None:
"""
Delete a NIO (Network Input/Output) from the node.
@@ -227,7 +269,10 @@ async def delete_iou_node_nio(adapter_number: int, port_number: int, node: IOUVM
await node.adapter_remove_nio_binding(adapter_number, port_number)
-@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
+@router.post(
+ "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
+ dependencies=[Depends(compute_authentication)]
+)
async def start_iou_node_capture(
adapter_number: int,
port_number: int,
@@ -244,7 +289,9 @@ async def start_iou_node_capture(
@router.post(
- "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
+ "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
)
async def stop_iou_node_capture(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)) -> None:
"""
@@ -254,7 +301,10 @@ async def stop_iou_node_capture(adapter_number: int, port_number: int, node: IOU
await node.stop_capture(adapter_number, port_number)
-@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
+@router.get(
+ "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream",
+ dependencies=[Depends(compute_authentication)]
+)
async def stream_pcap_file(
adapter_number: int,
port_number: int,
@@ -269,16 +319,26 @@ async def stream_pcap_file(
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
-@router.websocket("/{node_id}/console/ws")
-async def console_ws(websocket: WebSocket, node: IOUVM = Depends(dep_node)) -> None:
+@router.websocket(
+ "/{node_id}/console/ws",
+)
+async def console_ws(
+ websocket: Union[None, WebSocket] = Depends(ws_compute_authentication),
+ node: IOUVM = Depends(dep_node)
+) -> None:
"""
Console WebSocket.
"""
- await node.start_websocket_console(websocket)
+ if websocket:
+ await node.start_websocket_console(websocket)
-@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/console/reset",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def reset_console(node: IOUVM = Depends(dep_node)) -> None:
await node.reset_console()
diff --git a/gns3server/api/routes/compute/notifications.py b/gns3server/api/routes/compute/notifications.py
index 26a04b61..47b30f00 100644
--- a/gns3server/api/routes/compute/notifications.py
+++ b/gns3server/api/routes/compute/notifications.py
@@ -18,14 +18,13 @@
API routes for compute notifications.
"""
-import base64
-import binascii
-from fastapi import APIRouter, WebSocket, WebSocketDisconnect, status, HTTPException
-from fastapi.security.utils import get_authorization_scheme_param
+from fastapi import APIRouter, Depends, WebSocket, WebSocketDisconnect
+from typing import Union
from websockets.exceptions import ConnectionClosed, WebSocketException
from gns3server.compute.notification_manager import NotificationManager
+from .dependencies.authentication import ws_compute_authentication
import logging
@@ -35,53 +34,27 @@ router = APIRouter()
@router.websocket("/notifications/ws")
-async def project_ws_notifications(websocket: WebSocket) -> None:
+async def project_ws_notifications(websocket: Union[None, WebSocket] = Depends(ws_compute_authentication)) -> None:
"""
Receive project notifications about the project from WebSocket.
"""
- await websocket.accept()
-
- # handle basic HTTP authentication
- invalid_user_credentials_exc = HTTPException(
- status_code=status.HTTP_401_UNAUTHORIZED,
- detail="Invalid authentication credentials",
- headers={"WWW-Authenticate": "Basic"},
- )
-
- try:
- authorization = websocket.headers.get("Authorization")
- scheme, param = get_authorization_scheme_param(authorization)
- if not authorization or scheme.lower() != "basic":
- raise invalid_user_credentials_exc
+ if websocket:
+ log.info(f"New client {websocket.client.host}:{websocket.client.port} has connected to compute WebSocket")
try:
- data = base64.b64decode(param).decode("ascii")
- except (ValueError, UnicodeDecodeError, binascii.Error):
- raise invalid_user_credentials_exc
- username, separator, password = data.partition(":")
- if not separator:
- raise invalid_user_credentials_exc
- except invalid_user_credentials_exc as e:
- websocket_error = {"action": "log.error", "event": {"message": f"Could not authenticate while connecting to "
- f"compute WebSocket: {e.detail}"}}
- await websocket.send_json(websocket_error)
- return await websocket.close(code=1008)
-
- log.info(f"New client {websocket.client.host}:{websocket.client.port} has connected to compute WebSocket")
- try:
- with NotificationManager.instance().queue() as queue:
- while True:
- notification = await queue.get_json(5)
- await websocket.send_text(notification)
- except (ConnectionClosed, WebSocketDisconnect):
- log.info(f"Client {websocket.client.host}:{websocket.client.port} has disconnected from compute WebSocket")
- except WebSocketException as e:
- log.warning(f"Error while sending to controller event to WebSocket client: {e}")
- finally:
- try:
- await websocket.close()
- except OSError:
- pass # ignore OSError: [Errno 107] Transport endpoint is not connected
+ with NotificationManager.instance().queue() as queue:
+ while True:
+ notification = await queue.get_json(5)
+ await websocket.send_text(notification)
+ except (ConnectionClosed, WebSocketDisconnect):
+ log.info(f"Client {websocket.client.host}:{websocket.client.port} has disconnected from compute WebSocket")
+ except WebSocketException as e:
+ log.warning(f"Error while sending to controller event to WebSocket client: {e}")
+ finally:
+ try:
+ await websocket.close()
+ except OSError:
+ pass # ignore OSError: [Errno 107] Transport endpoint is not connected
if __name__ == "__main__":
diff --git a/gns3server/api/routes/compute/qemu_nodes.py b/gns3server/api/routes/compute/qemu_nodes.py
index b1b95417..1689fabe 100644
--- a/gns3server/api/routes/compute/qemu_nodes.py
+++ b/gns3server/api/routes/compute/qemu_nodes.py
@@ -20,15 +20,17 @@ API routes for Qemu nodes.
import os
-from fastapi import APIRouter, WebSocket, Depends, Body, Path, Response, status
+from fastapi import APIRouter, WebSocket, Depends, Body, Path, status
from fastapi.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse
+from typing import Union
from uuid import UUID
from gns3server import schemas
from gns3server.compute.qemu import Qemu
from gns3server.compute.qemu.qemu_vm import QemuVM
+from .dependencies.authentication import compute_authentication, ws_compute_authentication
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Qemu node"}}
@@ -50,6 +52,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> QemuVM:
response_model=schemas.Qemu,
status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Qemu node"}},
+ dependencies=[Depends(compute_authentication)]
)
async def create_qemu_node(project_id: UUID, node_data: schemas.QemuCreate) -> schemas.Qemu:
"""
@@ -78,7 +81,11 @@ async def create_qemu_node(project_id: UUID, node_data: schemas.QemuCreate) -> s
return vm.asdict()
-@router.get("/{node_id}", response_model=schemas.Qemu)
+@router.get(
+ "/{node_id}",
+ response_model=schemas.Qemu,
+ dependencies=[Depends(compute_authentication)]
+)
def get_qemu_node(node: QemuVM = Depends(dep_node)) -> schemas.Qemu:
"""
Return a Qemu node.
@@ -87,7 +94,11 @@ def get_qemu_node(node: QemuVM = Depends(dep_node)) -> schemas.Qemu:
return node.asdict()
-@router.put("/{node_id}", response_model=schemas.Qemu)
+@router.put(
+ "/{node_id}",
+ response_model=schemas.Qemu,
+ dependencies=[Depends(compute_authentication)]
+)
async def update_qemu_node(node_data: schemas.QemuUpdate, node: QemuVM = Depends(dep_node)) -> schemas.Qemu:
"""
Update a Qemu node.
@@ -103,7 +114,11 @@ async def update_qemu_node(node_data: schemas.QemuUpdate, node: QemuVM = Depends
return node.asdict()
-@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
+@router.delete(
+ "/{node_id}",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def delete_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
"""
Delete a Qemu node.
@@ -112,7 +127,12 @@ async def delete_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
await Qemu.instance().delete_node(node.id)
-@router.post("/{node_id}/duplicate", response_model=schemas.Qemu, status_code=status.HTTP_201_CREATED)
+@router.post(
+ "/{node_id}/duplicate",
+ response_model=schemas.Qemu,
+ status_code=status.HTTP_201_CREATED,
+ dependencies=[Depends(compute_authentication)]
+)
async def duplicate_qemu_node(
destination_node_id: UUID = Body(..., embed=True),
node: QemuVM = Depends(dep_node)
@@ -127,7 +147,8 @@ async def duplicate_qemu_node(
@router.post(
"/{node_id}/disk_image/{disk_name}",
- status_code=status.HTTP_204_NO_CONTENT
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
)
async def create_qemu_disk_image(
disk_name: str,
@@ -144,7 +165,8 @@ async def create_qemu_disk_image(
@router.put(
"/{node_id}/disk_image/{disk_name}",
- status_code=status.HTTP_204_NO_CONTENT
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
)
async def update_qemu_disk_image(
disk_name: str,
@@ -161,7 +183,8 @@ async def update_qemu_disk_image(
@router.delete(
"/{node_id}/disk_image/{disk_name}",
- status_code=status.HTTP_204_NO_CONTENT
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
)
async def delete_qemu_disk_image(
disk_name: str,
@@ -174,7 +197,11 @@ async def delete_qemu_disk_image(
node.delete_disk_image(disk_name)
-@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/start",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def start_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
"""
Start a Qemu node.
@@ -183,7 +210,11 @@ async def start_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
await node.start()
-@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/stop",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def stop_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
"""
Stop a Qemu node.
@@ -192,7 +223,11 @@ async def stop_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
await node.stop()
-@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/reload",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def reload_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
"""
Reload a Qemu node.
@@ -201,7 +236,11 @@ async def reload_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
await node.reload()
-@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/suspend",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def suspend_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
"""
Suspend a Qemu node.
@@ -210,7 +249,11 @@ async def suspend_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
await node.suspend()
-@router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/resume",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def resume_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
"""
Resume a Qemu node.
@@ -223,6 +266,7 @@ async def resume_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO,
+ dependencies=[Depends(compute_authentication)]
)
async def create_qemu_node_nio(
*,
@@ -245,6 +289,7 @@ async def create_qemu_node_nio(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO,
+ dependencies=[Depends(compute_authentication)]
)
async def update_qemu_node_nio(
*,
@@ -267,7 +312,11 @@ async def update_qemu_node_nio(
return nio.asdict()
-@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
+@router.delete(
+ "/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def delete_qemu_node_nio(
adapter_number: int,
port_number: int = Path(..., ge=0, le=0),
@@ -281,7 +330,10 @@ async def delete_qemu_node_nio(
await node.adapter_remove_nio_binding(adapter_number)
-@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
+@router.post(
+ "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
+ dependencies=[Depends(compute_authentication)]
+)
async def start_qemu_node_capture(
*,
adapter_number: int,
@@ -300,7 +352,9 @@ async def start_qemu_node_capture(
@router.post(
- "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
+ "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
)
async def stop_qemu_node_capture(
adapter_number: int,
@@ -315,7 +369,10 @@ async def stop_qemu_node_capture(
await node.stop_capture(adapter_number)
-@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
+@router.get(
+ "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream",
+ dependencies=[Depends(compute_authentication)]
+)
async def stream_pcap_file(
adapter_number: int,
port_number: int = Path(..., ge=0, le=0),
@@ -330,16 +387,26 @@ async def stream_pcap_file(
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
-@router.websocket("/{node_id}/console/ws")
-async def console_ws(websocket: WebSocket, node: QemuVM = Depends(dep_node)) -> None:
+@router.websocket(
+ "/{node_id}/console/ws"
+)
+async def console_ws(
+ websocket: Union[None, WebSocket] = Depends(ws_compute_authentication),
+ node: QemuVM = Depends(dep_node)
+) -> None:
"""
Console WebSocket.
"""
- await node.start_websocket_console(websocket)
+ if websocket:
+ await node.start_websocket_console(websocket)
-@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/console/reset",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def reset_console(node: QemuVM = Depends(dep_node)) -> None:
await node.reset_console()
diff --git a/gns3server/api/routes/compute/virtualbox_nodes.py b/gns3server/api/routes/compute/virtualbox_nodes.py
index f457cbff..f676025e 100644
--- a/gns3server/api/routes/compute/virtualbox_nodes.py
+++ b/gns3server/api/routes/compute/virtualbox_nodes.py
@@ -20,16 +20,19 @@ API routes for VirtualBox nodes.
import os
-from fastapi import APIRouter, WebSocket, Depends, Path, Response, status
+from fastapi import APIRouter, WebSocket, Depends, Path, status
from fastapi.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse
from uuid import UUID
+from typing import Union
from gns3server import schemas
from gns3server.compute.virtualbox import VirtualBox
from gns3server.compute.virtualbox.virtualbox_error import VirtualBoxError
from gns3server.compute.virtualbox.virtualbox_vm import VirtualBoxVM
+from .dependencies.authentication import compute_authentication, ws_compute_authentication
+
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or VirtualBox node"}}
router = APIRouter(responses=responses, deprecated=True)
@@ -50,6 +53,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> VirtualBoxVM:
response_model=schemas.VirtualBox,
status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VirtualBox node"}},
+ dependencies=[Depends(compute_authentication)]
)
async def create_virtualbox_node(project_id: UUID, node_data: schemas.VirtualBoxCreate) -> schemas.VirtualBox:
"""
@@ -82,7 +86,11 @@ async def create_virtualbox_node(project_id: UUID, node_data: schemas.VirtualBox
return vm.asdict()
-@router.get("/{node_id}", response_model=schemas.VirtualBox)
+@router.get(
+ "/{node_id}",
+ response_model=schemas.VirtualBox,
+ dependencies=[Depends(compute_authentication)]
+)
def get_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> schemas.VirtualBox:
"""
Return a VirtualBox node.
@@ -91,7 +99,11 @@ def get_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> schemas.Virtu
return node.asdict()
-@router.put("/{node_id}", response_model=schemas.VirtualBox)
+@router.put(
+ "/{node_id}",
+ response_model=schemas.VirtualBox,
+ dependencies=[Depends(compute_authentication)]
+)
async def update_virtualbox_node(
node_data: schemas.VirtualBoxUpdate,
node: VirtualBoxVM = Depends(dep_node)
@@ -136,7 +148,11 @@ async def update_virtualbox_node(
return node.asdict()
-@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
+@router.delete(
+ "/{node_id}",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def delete_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
"""
Delete a VirtualBox node.
@@ -145,7 +161,11 @@ async def delete_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None
await VirtualBox.instance().delete_node(node.id)
-@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/start",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def start_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
"""
Start a VirtualBox node.
@@ -154,7 +174,11 @@ async def start_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
await node.start()
-@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/stop",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def stop_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
"""
Stop a VirtualBox node.
@@ -163,7 +187,11 @@ async def stop_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
await node.stop()
-@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/suspend",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def suspend_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
"""
Suspend a VirtualBox node.
@@ -172,7 +200,11 @@ async def suspend_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> Non
await node.suspend()
-@router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/resume",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def resume_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
"""
Resume a VirtualBox node.
@@ -181,7 +213,11 @@ async def resume_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None
await node.resume()
-@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/reload",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def reload_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
"""
Reload a VirtualBox node.
@@ -194,6 +230,7 @@ async def reload_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO,
+ dependencies=[Depends(compute_authentication)]
)
async def create_virtualbox_node_nio(
*,
@@ -216,6 +253,7 @@ async def create_virtualbox_node_nio(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO,
+ dependencies=[Depends(compute_authentication)]
)
async def update_virtualbox_node_nio(
*,
@@ -238,7 +276,11 @@ async def update_virtualbox_node_nio(
return nio.asdict()
-@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
+@router.delete(
+ "/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def delete_virtualbox_node_nio(
adapter_number: int,
port_number: int = Path(..., ge=0, le=0),
@@ -252,7 +294,10 @@ async def delete_virtualbox_node_nio(
await node.adapter_remove_nio_binding(adapter_number)
-@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
+@router.post(
+ "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
+ dependencies=[Depends(compute_authentication)]
+)
async def start_virtualbox_node_capture(
*,
adapter_number: int,
@@ -271,7 +316,9 @@ async def start_virtualbox_node_capture(
@router.post(
- "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
+ "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
)
async def stop_virtualbox_node_capture(
adapter_number: int,
@@ -286,7 +333,10 @@ async def stop_virtualbox_node_capture(
await node.stop_capture(adapter_number)
-@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
+@router.get(
+ "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream",
+ dependencies=[Depends(compute_authentication)]
+)
async def stream_pcap_file(
adapter_number: int,
port_number: int = Path(..., ge=0, le=0),
@@ -302,8 +352,13 @@ async def stream_pcap_file(
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
-@router.websocket("/{node_id}/console/ws")
-async def console_ws(websocket: WebSocket, node: VirtualBoxVM = Depends(dep_node)) -> None:
+@router.websocket(
+ "/{node_id}/console/ws"
+)
+async def console_ws(
+ websocket: Union[None, WebSocket] = Depends(ws_compute_authentication),
+ node: VirtualBoxVM = Depends(dep_node)
+) -> None:
"""
Console WebSocket.
"""
@@ -311,7 +366,11 @@ async def console_ws(websocket: WebSocket, node: VirtualBoxVM = Depends(dep_node
await node.start_websocket_console(websocket)
-@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/console/reset",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def reset_console(node: VirtualBoxVM = Depends(dep_node)) -> None:
await node.reset_console()
diff --git a/gns3server/api/routes/compute/vmware_nodes.py b/gns3server/api/routes/compute/vmware_nodes.py
index d7c38844..f1e3752c 100644
--- a/gns3server/api/routes/compute/vmware_nodes.py
+++ b/gns3server/api/routes/compute/vmware_nodes.py
@@ -20,16 +20,18 @@ API routes for VMware nodes.
import os
-from fastapi import APIRouter, WebSocket, Depends, Path, Response, status
+from fastapi import APIRouter, WebSocket, Depends, Path, status
from fastapi.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse
from uuid import UUID
+from typing import Union
from gns3server import schemas
from gns3server.compute.vmware import VMware
-from gns3server.compute.project_manager import ProjectManager
from gns3server.compute.vmware.vmware_vm import VMwareVM
+from .dependencies.authentication import compute_authentication, ws_compute_authentication
+
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or VMware node"}}
router = APIRouter(responses=responses, deprecated=True)
@@ -50,6 +52,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> VMwareVM:
response_model=schemas.VMware,
status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VMware node"}},
+ dependencies=[Depends(compute_authentication)]
)
async def create_vmware_node(project_id: UUID, node_data: schemas.VMwareCreate) -> schemas.VMware:
"""
@@ -76,7 +79,11 @@ async def create_vmware_node(project_id: UUID, node_data: schemas.VMwareCreate)
return vm.asdict()
-@router.get("/{node_id}", response_model=schemas.VMware)
+@router.get(
+ "/{node_id}",
+ response_model=schemas.VMware,
+ dependencies=[Depends(compute_authentication)]
+)
def get_vmware_node(node: VMwareVM = Depends(dep_node)) -> schemas.VMware:
"""
Return a VMware node.
@@ -85,7 +92,11 @@ def get_vmware_node(node: VMwareVM = Depends(dep_node)) -> schemas.VMware:
return node.asdict()
-@router.put("/{node_id}", response_model=schemas.VMware)
+@router.put(
+ "/{node_id}",
+ response_model=schemas.VMware,
+ dependencies=[Depends(compute_authentication)]
+)
def update_vmware_node(node_data: schemas.VMwareUpdate, node: VMwareVM = Depends(dep_node)) -> schemas.VMware:
"""
Update a VMware node.
@@ -102,7 +113,11 @@ def update_vmware_node(node_data: schemas.VMwareUpdate, node: VMwareVM = Depends
return node.asdict()
-@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
+@router.delete(
+ "/{node_id}",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def delete_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
"""
Delete a VMware node.
@@ -111,7 +126,11 @@ async def delete_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
await VMware.instance().delete_node(node.id)
-@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/start",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def start_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
"""
Start a VMware node.
@@ -120,7 +139,11 @@ async def start_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
await node.start()
-@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/stop",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def stop_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
"""
Stop a VMware node.
@@ -129,7 +152,11 @@ async def stop_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
await node.stop()
-@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/suspend",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def suspend_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
"""
Suspend a VMware node.
@@ -138,7 +165,11 @@ async def suspend_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
await node.suspend()
-@router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/resume",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def resume_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
"""
Resume a VMware node.
@@ -147,7 +178,11 @@ async def resume_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
await node.resume()
-@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/reload",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def reload_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
"""
Reload a VMware node.
@@ -160,6 +195,7 @@ async def reload_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO,
+ dependencies=[Depends(compute_authentication)]
)
async def create_vmware_node_nio(
*,
@@ -182,6 +218,7 @@ async def create_vmware_node_nio(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO,
+ dependencies=[Depends(compute_authentication)]
)
async def update_vmware_node_nio(
*,
@@ -202,7 +239,11 @@ async def update_vmware_node_nio(
return nio.asdict()
-@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
+@router.delete(
+ "/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def delete_vmware_node_nio(
adapter_number: int,
port_number: int = Path(..., ge=0, le=0),
@@ -216,7 +257,10 @@ async def delete_vmware_node_nio(
await node.adapter_remove_nio_binding(adapter_number)
-@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
+@router.post(
+ "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
+ dependencies=[Depends(compute_authentication)]
+)
async def start_vmware_node_capture(
*,
adapter_number: int,
@@ -235,7 +279,9 @@ async def start_vmware_node_capture(
@router.post(
- "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
+ "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
)
async def stop_vmware_node_capture(
adapter_number: int,
@@ -250,7 +296,10 @@ async def stop_vmware_node_capture(
await node.stop_capture(adapter_number)
-@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
+@router.get(
+ "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream",
+ dependencies=[Depends(compute_authentication)]
+)
async def stream_pcap_file(
adapter_number: int,
port_number: int = Path(..., ge=0, le=0),
@@ -266,7 +315,11 @@ async def stream_pcap_file(
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
-@router.post("/{node_id}/interfaces/vmnet", status_code=status.HTTP_201_CREATED)
+@router.post(
+ "/{node_id}/interfaces/vmnet",
+ status_code=status.HTTP_201_CREATED,
+ dependencies=[Depends(compute_authentication)]
+)
def allocate_vmnet(node: VMwareVM = Depends(dep_node)) -> dict:
"""
Allocate a VMware VMnet interface on the server.
@@ -280,16 +333,23 @@ def allocate_vmnet(node: VMwareVM = Depends(dep_node)) -> dict:
@router.websocket("/{node_id}/console/ws")
-async def console_ws(websocket: WebSocket, node: VMwareVM = Depends(dep_node)) -> None:
+async def console_ws(
+ websocket: Union[None, WebSocket] = Depends(ws_compute_authentication),
+ node: VMwareVM = Depends(dep_node)
+) -> None:
"""
Console WebSocket.
"""
- await node.start_websocket_console(websocket)
+ if websocket:
+ await node.start_websocket_console(websocket)
-@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/console/reset",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def reset_console(node: VMwareVM = Depends(dep_node)) -> None:
await node.reset_console()
-
diff --git a/gns3server/api/routes/compute/vpcs_nodes.py b/gns3server/api/routes/compute/vpcs_nodes.py
index df4f82c3..143ceef8 100644
--- a/gns3server/api/routes/compute/vpcs_nodes.py
+++ b/gns3server/api/routes/compute/vpcs_nodes.py
@@ -20,15 +20,18 @@ API routes for VPCS nodes.
import os
-from fastapi import APIRouter, WebSocket, Depends, Body, Path, Response, status
+from fastapi import APIRouter, WebSocket, Depends, Body, Path, status
from fastapi.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse
+from typing import Union
from uuid import UUID
from gns3server import schemas
from gns3server.compute.vpcs import VPCS
from gns3server.compute.vpcs.vpcs_vm import VPCSVM
+from .dependencies.authentication import compute_authentication, ws_compute_authentication
+
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or VMware node"}}
router = APIRouter(responses=responses)
@@ -49,6 +52,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> VPCSVM:
response_model=schemas.VPCS,
status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VMware node"}},
+ dependencies=[Depends(compute_authentication)]
)
async def create_vpcs_node(project_id: UUID, node_data: schemas.VPCSCreate) -> schemas.VPCS:
"""
@@ -69,7 +73,11 @@ async def create_vpcs_node(project_id: UUID, node_data: schemas.VPCSCreate) -> s
return vm.asdict()
-@router.get("/{node_id}", response_model=schemas.VPCS)
+@router.get(
+ "/{node_id}",
+ response_model=schemas.VPCS,
+ dependencies=[Depends(compute_authentication)]
+)
def get_vpcs_node(node: VPCSVM = Depends(dep_node)) -> schemas.VPCS:
"""
Return a VPCS node.
@@ -78,7 +86,11 @@ def get_vpcs_node(node: VPCSVM = Depends(dep_node)) -> schemas.VPCS:
return node.asdict()
-@router.put("/{node_id}", response_model=schemas.VPCS)
+@router.put(
+ "/{node_id}",
+ response_model=schemas.VPCS,
+ dependencies=[Depends(compute_authentication)]
+)
def update_vpcs_node(node_data: schemas.VPCSUpdate, node: VPCSVM = Depends(dep_node)) -> schemas.VPCS:
"""
Update a VPCS node.
@@ -92,7 +104,11 @@ def update_vpcs_node(node_data: schemas.VPCSUpdate, node: VPCSVM = Depends(dep_n
return node.asdict()
-@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
+@router.delete(
+ "/{node_id}",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def delete_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
"""
Delete a VPCS node.
@@ -101,7 +117,12 @@ async def delete_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
await VPCS.instance().delete_node(node.id)
-@router.post("/{node_id}/duplicate", response_model=schemas.VPCS, status_code=status.HTTP_201_CREATED)
+@router.post(
+ "/{node_id}/duplicate",
+ response_model=schemas.VPCS,
+ status_code=status.HTTP_201_CREATED,
+ dependencies=[Depends(compute_authentication)]
+)
async def duplicate_vpcs_node(
destination_node_id: UUID = Body(..., embed=True),
node: VPCSVM = Depends(dep_node)) -> None:
@@ -113,7 +134,11 @@ async def duplicate_vpcs_node(
return new_node.asdict()
-@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/start",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def start_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
"""
Start a VPCS node.
@@ -122,7 +147,11 @@ async def start_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
await node.start()
-@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/stop",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def stop_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
"""
Stop a VPCS node.
@@ -131,7 +160,11 @@ async def stop_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
await node.stop()
-@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/suspend",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def suspend_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
"""
Suspend a VPCS node.
@@ -141,7 +174,11 @@ async def suspend_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
pass
-@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
+@router.post(
+ "/{node_id}/reload",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def reload_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
"""
Reload a VPCS node.
@@ -154,6 +191,7 @@ async def reload_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO,
+ dependencies=[Depends(compute_authentication)]
)
async def create_vpcs_node_nio(
*,
@@ -176,6 +214,7 @@ async def create_vpcs_node_nio(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO,
+ dependencies=[Depends(compute_authentication)]
)
async def update_vpcs_node_nio(
*,
@@ -196,7 +235,11 @@ async def update_vpcs_node_nio(
return nio.asdict()
-@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
+@router.delete(
+ "/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
async def delete_vpcs_node_nio(
*,
adapter_number: int = Path(..., ge=0, le=0),
@@ -211,7 +254,10 @@ async def delete_vpcs_node_nio(
await node.port_remove_nio_binding(port_number)
-@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
+@router.post(
+ "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
+ dependencies=[Depends(compute_authentication)]
+)
async def start_vpcs_node_capture(
*,
adapter_number: int = Path(..., ge=0, le=0),
@@ -230,7 +276,9 @@ async def start_vpcs_node_capture(
@router.post(
- "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
+ "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
)
async def stop_vpcs_node_capture(
*,
@@ -246,13 +294,10 @@ async def stop_vpcs_node_capture(
await node.stop_capture(port_number)
-@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
-async def reset_console(node: VPCSVM = Depends(dep_node)) -> None:
-
- await node.reset_console()
-
-
-@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
+@router.get(
+ "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream",
+ dependencies=[Depends(compute_authentication)]
+)
async def stream_pcap_file(
*,
adapter_number: int = Path(..., ge=0, le=0),
@@ -269,10 +314,24 @@ async def stream_pcap_file(
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
-@router.websocket("/{node_id}/console/ws")
-async def console_ws(websocket: WebSocket, node: VPCSVM = Depends(dep_node)) -> None:
+@router.websocket(
+ "/{node_id}/console/ws"
+)
+async def console_ws(
+ websocket: Union[None, WebSocket] = Depends(ws_compute_authentication),
+ node: VPCSVM = Depends(dep_node)) -> None:
"""
Console WebSocket.
"""
await node.start_websocket_console(websocket)
+
+
+@router.post(
+ "/{node_id}/console/reset",
+ status_code=status.HTTP_204_NO_CONTENT,
+ dependencies=[Depends(compute_authentication)]
+)
+async def reset_console(node: VPCSVM = Depends(dep_node)) -> None:
+
+ await node.reset_console()
diff --git a/gns3server/api/routes/controller/dependencies/authentication.py b/gns3server/api/routes/controller/dependencies/authentication.py
index ce49ab0a..05e4d4ae 100644
--- a/gns3server/api/routes/controller/dependencies/authentication.py
+++ b/gns3server/api/routes/controller/dependencies/authentication.py
@@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-import re
+import logging
from fastapi import Request, Query, Depends, HTTPException, WebSocket, status
from fastapi.security import OAuth2PasswordBearer
@@ -26,6 +26,7 @@ from gns3server.db.repositories.rbac import RbacRepository
from gns3server.services import auth_service
from .database import get_repository
+log = logging.getLogger(__name__)
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/v3/access/users/login", auto_error=False)
@@ -108,7 +109,9 @@ async def get_current_active_user_from_websocket(
return user
except HTTPException as e:
- websocket_error = {"action": "log.error", "event": {"message": f"Could not authenticate while connecting to "
- f"WebSocket: {e.detail}"}}
+ err_msg = f"Could not authenticate while connecting to controller WebSocket: {e.detail}"
+ websocket_error = {"action": "log.error", "event": {"message": err_msg}}
await websocket.send_json(websocket_error)
- await websocket.close(code=1008)
+ log.error(err_msg)
+ return await websocket.close(code=1008)
+
diff --git a/gns3server/api/routes/controller/nodes.py b/gns3server/api/routes/controller/nodes.py
index 865ad683..13e84450 100644
--- a/gns3server/api/routes/controller/nodes.py
+++ b/gns3server/api/routes/controller/nodes.py
@@ -29,6 +29,7 @@ from typing import List, Callable
from uuid import UUID
from gns3server.controller import Controller
+from gns3server.config import Config
from gns3server.controller.node import Node
from gns3server.controller.project import Project
from gns3server.utils import force_unix_path
@@ -510,16 +511,22 @@ async def post_file(file_path: str, request: Request, node: Node = Depends(dep_n
# FIXME: response with correct status code (from compute)
-@router.websocket("/{node_id}/console/ws", dependencies=[Depends(has_privilege_on_websocket("Node.Console"))])
-async def ws_console(websocket: WebSocket, node: Node = Depends(dep_node)) -> None:
+@router.websocket("/{node_id}/console/ws")
+async def ws_console(
+ websocket: WebSocket,
+ current_user: schemas.User = Depends(has_privilege_on_websocket("Node.Console")),
+ node: Node = Depends(dep_node)
+) -> None:
"""
WebSocket console.
Required privilege: Node.Console
"""
+ if current_user is None:
+ return
+
compute = node.compute
- await websocket.accept()
log.info(
f"New client {websocket.client.host}:{websocket.client.port} has connected to controller console WebSocket"
)
@@ -557,9 +564,20 @@ async def ws_console(websocket: WebSocket, node: Node = Depends(dep_node)) -> No
try:
# receive WebSocket data from compute console WebSocket and forward to client.
- async with HTTPClient.get_client().ws_connect(ws_console_compute_url) as ws_console_compute:
- asyncio.ensure_future(ws_receive(ws_console_compute))
- async for msg in ws_console_compute:
+ log.info(f"Forwarding console WebSocket to '{ws_console_compute_url}'")
+ server_config = Config.instance().settings.Server
+ user = server_config.compute_username
+ password = server_config.compute_password
+ if not user:
+ raise ControllerForbiddenError("Compute username is not set")
+ user = user.strip()
+ if user and password:
+ auth = aiohttp.BasicAuth(user, password.get_secret_value(), "utf-8")
+ else:
+ auth = aiohttp.BasicAuth(user, "")
+ async with HTTPClient.get_client().ws_connect(ws_console_compute_url, auth=auth) as ws:
+ asyncio.ensure_future(ws_receive(ws))
+ async for msg in ws:
if msg.type == aiohttp.WSMsgType.TEXT:
await websocket.send_text(msg.data)
elif msg.type == aiohttp.WSMsgType.BINARY:
diff --git a/gns3server/appliances/fortianalyzer.gns3a b/gns3server/appliances/fortianalyzer.gns3a
index 58486e57..fb0aed44 100644
--- a/gns3server/appliances/fortianalyzer.gns3a
+++ b/gns3server/appliances/fortianalyzer.gns3a
@@ -11,15 +11,16 @@
"product_url": "https://www.fortinet.com/products-services/products/management-reporting/fortianalyzer.html",
"registry_version": 4,
"status": "stable",
- "maintainer": "GNS3 Team",
- "maintainer_email": "developers@gns3.net",
- "usage": "Default username is admin, no password is set.\n\n- Versions 7.0 and higher require:\n--RAM: 8192 MB\n--CPU:4",
+ "maintainer": "Ean Towne",
+ "maintainer_email": "ean.fortinet@gmail.com",
+ "usage": "Default username is admin, no password is set.\n\n- Versions lower than 7.0.x can reduce CPU/RAM",
"symbol": "fortinet.svg",
"port_name_format": "Port{port1}",
"qemu": {
"adapter_type": "e1000",
"adapters": 4,
- "ram": 4096,
+ "ram": 16384,
+ "cpus": 4,
"hda_disk_interface": "virtio",
"hdb_disk_interface": "virtio",
"arch": "x86_64",
@@ -28,6 +29,20 @@
"kvm": "allow"
},
"images": [
+ {
+ "filename": "FAZ_VM64_KVM-v7.4.1-build2308-FORTINET.out.kvm.qcow2",
+ "version": "7.4.1",
+ "md5sum": "f30caac36854c2a0cc1e35c4ab5f310d",
+ "filesize": 435310592,
+ "download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
+ },
+ {
+ "filename": "FAZ_VM64_KVM-v7.2.4-build1460-FORTINET.out.kvm.qcow2",
+ "version": "7.2.4",
+ "md5sum": "d53bd5c61cc3f5e387557dfcfe9bc530",
+ "filesize": 363327488,
+ "download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
+ },
{
"filename": "FAZ_VM64_KVM-v7.2.2-build1334-FORTINET.out.kvm.qcow2",
"version": "7.2.2",
@@ -42,6 +57,13 @@
"filesize": 340631552,
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
},
+ {
+ "filename": "FAZ_VM64_KVM-v7.0.9-build0489-FORTINET.out.kvm.qcow2",
+ "version": "7.0.9",
+ "md5sum": "3f69c9bc4fa7776476edf0ce9728ebd7",
+ "filesize": 347889664,
+ "download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
+ },
{
"filename": "FAZ_VM64_KVM-v7.0.6-build0372-FORTINET.out.kvm.qcow2",
"version": "7.0.6",
@@ -56,6 +78,13 @@
"filesize": 334184448,
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
},
+ {
+ "filename": "FAZ_VM64_KVM-v6.4.12-build2610-FORTINET.out.kvm.qcow2",
+ "version": "6.4.12",
+ "md5sum": "b9e164c2d4e778348a6a7107d375abf3",
+ "filesize": 300691456,
+ "download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
+ },
{
"filename": "FAZ_VM64_KVM-v6-build2288-FORTINET.out.kvm.qcow2",
"version": "6.4.5",
@@ -206,6 +235,20 @@
}
],
"versions": [
+ {
+ "name": "7.4.1",
+ "images": {
+ "hda_disk_image": "FAZ_VM64_KVM-v7.4.1-build2308-FORTINET.out.kvm.qcow2",
+ "hdb_disk_image": "empty30G.qcow2"
+ }
+ },
+ {
+ "name": "7.2.4",
+ "images": {
+ "hda_disk_image": "FAZ_VM64_KVM-v7.2.4-build1460-FORTINET.out.kvm.qcow2",
+ "hdb_disk_image": "empty30G.qcow2"
+ }
+ },
{
"name": "7.2.2",
"images": {
@@ -220,6 +263,13 @@
"hdb_disk_image": "empty30G.qcow2"
}
},
+ {
+ "name": "7.0.9",
+ "images": {
+ "hda_disk_image": "FAZ_VM64_KVM-v7.0.9-build0489-FORTINET.out.kvm.qcow2",
+ "hdb_disk_image": "empty30G.qcow2"
+ }
+ },
{
"name": "7.0.6",
"images": {
@@ -234,6 +284,13 @@
"hdb_disk_image": "empty30G.qcow2"
}
},
+ {
+ "name": "6.4.12",
+ "images": {
+ "hda_disk_image": "FAZ_VM64_KVM-v6.4.12-build2610-FORTINET.out.kvm.qcow2",
+ "hdb_disk_image": "empty30G.qcow2"
+ }
+ },
{
"name": "6.4.5",
"images": {
diff --git a/gns3server/appliances/fortigate.gns3a b/gns3server/appliances/fortigate.gns3a
index 9f85fd5e..f3efb72c 100644
--- a/gns3server/appliances/fortigate.gns3a
+++ b/gns3server/appliances/fortigate.gns3a
@@ -11,15 +11,15 @@
"product_url": "http://www.fortinet.com/products/fortigate/virtual-appliances.html",
"registry_version": 4,
"status": "stable",
- "maintainer": "GNS3 Team",
- "maintainer_email": "developers@gns3.net",
+ "maintainer": "Ean Towne",
+ "maintainer_email": "ean.fortinet@gmail.com",
"usage": "Default username is admin, no password is set.\n\n- FortiGate version 7.0.0 and above require 2GB RAM.\n\n- FortiGate versions higher than 7.2.0 trial license is VERY restrictive, not recommended for use.",
"symbol": "fortinet.svg",
"port_name_format": "Port{port1}",
"qemu": {
"adapter_type": "e1000",
"adapters": 10,
- "ram": 1024,
+ "ram": 2048,
"hda_disk_interface": "virtio",
"hdb_disk_interface": "virtio",
"arch": "x86_64",
@@ -28,6 +28,20 @@
"kvm": "allow"
},
"images": [
+ {
+ "filename": "FGT_VM64_KVM-v7.4.1.F-build2463-FORTINET.out.kvm.qcow2",
+ "version": "7.4.1",
+ "md5sum": "362a2f3d4ca842aaabd87191d4446584",
+ "filesize": 116064256,
+ "download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
+ },
+ {
+ "filename": "FGT_VM64_KVM-v7.2.6.F-build1575-FORTINET.out.kvm.qcow2",
+ "version": "7.2.6",
+ "md5sum": "b5ef3c844abb4947f98b88ae0048660b",
+ "filesize": 103022592,
+ "download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
+ },
{
"filename": "FGT_VM64_KVM-v7.2.4.F-build1396-FORTINET.out.kvm.qcow2",
"version": "7.2.4",
@@ -49,6 +63,13 @@
"filesize": 86704128,
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
},
+ {
+ "filename": "FGT_VM64_KVM-v7.0.12.M-build0523-FORTINET.out.kvm.qcow2",
+ "version": "7.0.12",
+ "md5sum": "7cd2452dde489c80f48c40a7f8a48c8e",
+ "filesize": 88997888,
+ "download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
+ },
{
"filename": "FGT_VM64_KVM-v7.0.10.M-build0450-FORTINET.out.kvm.qcow2",
"version": "7.0.10",
@@ -63,6 +84,13 @@
"filesize": 77135872,
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
},
+ {
+ "filename": "FGT_VM64_KVM-v6.4.14.M-build2093-FORTINET.out.kvm.qcow2",
+ "version": "6.4.14",
+ "md5sum": "5758340f9d3e1a03139176ab46e63e8d",
+ "filesize": 81461248,
+ "download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
+ },
{
"filename": "FGT_VM64_KVM-v6.4.12.M-build2060-FORTINET.out.kvm.qcow2",
"version": "6.4.12",
@@ -304,6 +332,20 @@
}
],
"versions": [
+ {
+ "name": "7.4.1",
+ "images": {
+ "hda_disk_image": "FGT_VM64_KVM-v7.4.1.F-build2463-FORTINET.out.kvm.qcow2",
+ "hdb_disk_image": "empty30G.qcow2"
+ }
+ },
+ {
+ "name": "7.2.6",
+ "images": {
+ "hda_disk_image": "FGT_VM64_KVM-v7.2.6.F-build1575-FORTINET.out.kvm.qcow2",
+ "hdb_disk_image": "empty30G.qcow2"
+ }
+ },
{
"name": "7.2.4",
"images": {
@@ -325,6 +367,13 @@
"hdb_disk_image": "empty30G.qcow2"
}
},
+ {
+ "name": "7.0.12",
+ "images": {
+ "hda_disk_image": "FGT_VM64_KVM-v7.0.12.M-build0523-FORTINET.out.kvm.qcow2",
+ "hdb_disk_image": "empty30G.qcow2"
+ }
+ },
{
"name": "7.0.10",
"images": {
@@ -339,6 +388,13 @@
"hdb_disk_image": "empty30G.qcow2"
}
},
+ {
+ "name": "6.4.14",
+ "images": {
+ "hda_disk_image": "FGT_VM64_KVM-v6.4.14.M-build2093-FORTINET.out.kvm.qcow2",
+ "hdb_disk_image": "empty30G.qcow2"
+ }
+ },
{
"name": "6.4.12",
"images": {
diff --git a/gns3server/appliances/fortimanager.gns3a b/gns3server/appliances/fortimanager.gns3a
index 636bb14d..cc35e900 100644
--- a/gns3server/appliances/fortimanager.gns3a
+++ b/gns3server/appliances/fortimanager.gns3a
@@ -11,15 +11,16 @@
"product_url": "http://www.fortinet.com/products/fortimanager/virtual-security-management.html",
"registry_version": 4,
"status": "stable",
- "maintainer": "GNS3 Team",
- "maintainer_email": "developers@gns3.net",
- "usage": "Default username is admin, no password is set.\n\n- Versions 7.0 and higher require:\n--RAM: 8192 MB\n--CPU:4",
+ "maintainer": "Ean Towne",
+ "maintainer_email": "ean.fortinet@gmail.com",
+ "usage": "Default username is admin, no password is set.\n\n- Versions lower than 7.0.x require less CPU/RAM",
"symbol": "fortinet.svg",
"port_name_format": "Port{port1}",
"qemu": {
"adapter_type": "virtio-net-pci",
"adapters": 4,
- "ram": 2048,
+ "ram": 8192,
+ "cpus": 4,
"hda_disk_interface": "virtio",
"hdb_disk_interface": "virtio",
"arch": "x86_64",
@@ -28,6 +29,20 @@
"kvm": "allow"
},
"images": [
+ {
+ "filename": "FMG_VM64_KVM-v7.4.1-build2308-FORTINET.out.kvm.qcow2",
+ "version": "7.4.1",
+ "md5sum": "e542cc8f2d8f46e9c32b783bf31bef39",
+ "filesize": 309387264,
+ "download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
+ },
+ {
+ "filename": "FMG_VM64_KVM-v7.2.4-build1460-FORTINET.out.kvm.qcow2",
+ "version": "7.2.4",
+ "md5sum": "98fa9830d9ecb5911a703d03b80026b6",
+ "filesize": 261992448,
+ "download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
+ },
{
"filename": "FMG_VM64_KVM-v7.2.2-build1334-FORTINET.out.kvm.qcow2",
"version": "7.2.2",
@@ -42,6 +57,13 @@
"filesize": 242814976,
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
},
+ {
+ "filename": "FMG_VM64_KVM-v7.0.9-build0489-FORTINET.out.kvm.qcow2",
+ "version": "7.0.9",
+ "md5sum": "dbeb6a79b6e421000573dbbbdb50b8b5",
+ "filesize": 247955456,
+ "download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
+ },
{
"filename": "FMG_VM64_KVM-v7.0.6-build0372-FORTINET.out.kvm.qcow2",
"version": "7.0.6",
@@ -56,6 +78,13 @@
"filesize": 237535232,
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
},
+ {
+ "filename": "FMG_VM64_KVM-v6.4.12-build2610-FORTINET.out.kvm.qcow2",
+ "version": "6.4.12",
+ "md5sum": "36c0dc531d921e5f1e1e09b030f7c813",
+ "filesize": 219455488,
+ "download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
+ },
{
"filename": "FMG_VM64_KVM-v6-build2288-FORTINET.out.kvm.qcow2",
"version": "6.4.5",
@@ -206,6 +235,20 @@
}
],
"versions": [
+ {
+ "name": "7.4.1",
+ "images": {
+ "hda_disk_image": "FMG_VM64_KVM-v7.4.1-build2308-FORTINET.out.kvm.qcow2",
+ "hdb_disk_image": "empty30G.qcow2"
+ }
+ },
+ {
+ "name": "7.2.4",
+ "images": {
+ "hda_disk_image": "FMG_VM64_KVM-v7.2.4-build1460-FORTINET.out.kvm.qcow2",
+ "hdb_disk_image": "empty30G.qcow2"
+ }
+ },
{
"name": "7.2.2",
"images": {
@@ -220,6 +263,13 @@
"hdb_disk_image": "empty30G.qcow2"
}
},
+ {
+ "name": "7.0.9",
+ "images": {
+ "hda_disk_image": "FMG_VM64_KVM-v7.0.9-build0489-FORTINET.out.kvm.qcow2",
+ "hdb_disk_image": "empty30G.qcow2"
+ }
+ },
{
"name": "7.0.6",
"images": {
@@ -234,6 +284,13 @@
"hdb_disk_image": "empty30G.qcow2"
}
},
+ {
+ "name": "6.4.12",
+ "images": {
+ "hda_disk_image": "FMG_VM64_KVM-v6.4.12-build2610-FORTINET.out.kvm.qcow2",
+ "hdb_disk_image": "empty30G.qcow2"
+ }
+ },
{
"name": "6.4.5",
"images": {
diff --git a/gns3server/compute/base_node.py b/gns3server/compute/base_node.py
index c877f5ff..20f437be 100644
--- a/gns3server/compute/base_node.py
+++ b/gns3server/compute/base_node.py
@@ -485,6 +485,11 @@ class BaseNode:
:param ws: Websocket object
"""
+ log.info(
+ f"New client {websocket.client.host}:{websocket.client.port} has connected to compute"
+ f" console WebSocket"
+ )
+
if self.status != "started":
raise NodeError(f"Node {self.name} is not started")
@@ -492,20 +497,13 @@ class BaseNode:
raise NodeError(f"Node {self.name} console type is not telnet")
try:
- (telnet_reader, telnet_writer) = await asyncio.open_connection(
- self._manager.port_manager.console_host, self.console
- )
+ host = self._manager.port_manager.console_host
+ port = self.console
+ (telnet_reader, telnet_writer) = await asyncio.open_connection(host, port)
+ log.info(f"Connected to local Telnet server {host}:{port}")
except ConnectionError as e:
raise NodeError(f"Cannot connect to node {self.name} telnet server: {e}")
- log.info("Connected to Telnet server")
-
- await websocket.accept()
- log.info(
- f"New client {websocket.client.host}:{websocket.client.port} has connected to compute"
- f" console WebSocket"
- )
-
async def ws_forward(telnet_writer):
try:
diff --git a/gns3server/compute/iou/iou_vm.py b/gns3server/compute/iou/iou_vm.py
index 50e132c1..f3f572db 100644
--- a/gns3server/compute/iou/iou_vm.py
+++ b/gns3server/compute/iou/iou_vm.py
@@ -587,7 +587,12 @@ class IOUVM(BaseNode):
# create a symbolic link to the image to avoid IOU error "failed code signing checks"
# on newer images, see https://github.com/GNS3/gns3-server/issues/1484
try:
- symlink = os.path.join(self.working_dir, os.path.basename(self.path))
+ iou_image_path = os.path.basename(self.path)
+ if len(iou_image_path) > 63:
+ # IOU file basename length must be <= 63 chars
+ iou_file_name, iou_file_ext = os.path.splitext(iou_image_path)
+ iou_image_path = iou_file_name[:63 - len(iou_file_ext)] + iou_file_ext
+ symlink = os.path.join(self.working_dir, iou_image_path)
if os.path.islink(symlink):
os.unlink(symlink)
os.symlink(self.path, symlink)
diff --git a/gns3server/configs/iou_l2_base_startup-config.txt b/gns3server/configs/iou_l2_base_startup-config.txt
index 501355f6..4a09db82 100644
--- a/gns3server/configs/iou_l2_base_startup-config.txt
+++ b/gns3server/configs/iou_l2_base_startup-config.txt
@@ -13,7 +13,8 @@ logging console discriminator EXCESS
!
no ip icmp rate-limit unreachable
!
-ip cef
+! due to some bugs with IOU, try to change the following line to 'ip cef' if your routing does not work
+no ip cef
no ip domain-lookup
!
!
diff --git a/gns3server/configs/iou_l3_base_startup-config.txt b/gns3server/configs/iou_l3_base_startup-config.txt
index 81d574ff..67628f77 100644
--- a/gns3server/configs/iou_l3_base_startup-config.txt
+++ b/gns3server/configs/iou_l3_base_startup-config.txt
@@ -12,7 +12,8 @@ no ip icmp rate-limit unreachable
!
!
!
-ip cef
+! due to some bugs with IOU, try to change the following line to 'ip cef' if your routing does not work
+no ip cef
no ip domain-lookup
!
!
diff --git a/gns3server/crash_report.py b/gns3server/crash_report.py
index 3ccd6cc1..7b2c7c15 100644
--- a/gns3server/crash_report.py
+++ b/gns3server/crash_report.py
@@ -58,7 +58,7 @@ class CrashReport:
Report crash to a third party service
"""
- DSN = "https://c6696321127aaa1b5bfd332536eb3676@o19455.ingest.sentry.io/38482"
+ DSN = "https://803d7abaf0e865096421affb70ee9368@o19455.ingest.sentry.io/38482"
_instance = None
def __init__(self):
diff --git a/gns3server/schemas/compute/qemu_nodes.py b/gns3server/schemas/compute/qemu_nodes.py
index ec007edf..8d7e23dd 100644
--- a/gns3server/schemas/compute/qemu_nodes.py
+++ b/gns3server/schemas/compute/qemu_nodes.py
@@ -124,6 +124,7 @@ class QemuAdapterType(str, Enum):
i82559er = "i82559er"
i82562 = "i82562"
i82801 = "i82801"
+ igb = "igb"
ne2k_pci = "ne2k_pci"
pcnet = "pcnet"
rocker = "rocker"
diff --git a/gns3server/schemas/controller/appliances.py b/gns3server/schemas/controller/appliances.py
index cfece410..e88a7339 100644
--- a/gns3server/schemas/controller/appliances.py
+++ b/gns3server/schemas/controller/appliances.py
@@ -170,6 +170,7 @@ class AdapterType(str, Enum):
i82559er = 'i82559er'
i82562 = 'i82562'
i82801 = 'i82801'
+ igb = 'igb'
ne2k_pci = 'ne2k_pci'
pcnet = 'pcnet'
rocker = 'rocker'
diff --git a/gns3server/server.py b/gns3server/server.py
index 6673d81c..f7f1d89e 100644
--- a/gns3server/server.py
+++ b/gns3server/server.py
@@ -267,9 +267,9 @@ class Server:
else:
log.info(f"Compute authentication is enabled with username '{config.Server.compute_username}'")
- # we only support Python 3 version >= 3.7
- if sys.version_info < (3, 7, 0):
- raise SystemExit("Python 3.7 or higher is required")
+ # we only support Python 3 version >= 3.8
+ if sys.version_info < (3, 8, 0):
+ raise SystemExit("Python 3.8 or higher is required")
log.info(
"Running with Python {major}.{minor}.{micro} and has PID {pid}".format(
diff --git a/gns3server/static/web-ui/index.html b/gns3server/static/web-ui/index.html
index 8cfd0ee7..eb373481 100644
--- a/gns3server/static/web-ui/index.html
+++ b/gns3server/static/web-ui/index.html
@@ -46,6 +46,6 @@
gtag('config', 'G-5D6FZL9923');
-
+