diff --git a/.github/workflows/publish-api-documentation.yml b/.github/workflows/publish-api-documentation.yml
index 782e76fe..30beb62f 100644
--- a/.github/workflows/publish-api-documentation.yml
+++ b/.github/workflows/publish-api-documentation.yml
@@ -12,11 +12,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
with:
fetch-depth: 0
ref: "gh-pages"
- - uses: actions/setup-python@v2
+ - uses: actions/setup-python@v3
with:
python-version: 3.7
- name: Merge changes from 3.0 branch
@@ -24,13 +24,10 @@ jobs:
git config user.name github-actions
git config user.email github-actions@github.com
git merge origin/3.0 -X theirs
- - name: Install dependencies
+ - name: Install GNS3 server and dependencies
run: |
python -m pip install --upgrade pip
- if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- - name: Install GNS3 server
- run: |
- python setup.py install
+ python -m pip install .
- name: Generate the API documentation
run: |
cd scripts
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 571c5931..7da94525 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,76 @@
# Change Log
+## 3.0.0a4 18/10/2023
+
+* Bundle web-ui v3.0.0a4
+* Do not enforce Compute.Audit and Template.Audit privileges due to current web-ui limitations
+* Support to create empty disk images on the controller
+* Fix issue with importlib.resources.files() and Python 3.9
+* New RBAC system with resource pools support.
+* Use controller vars file to store version and appliance etag
+* Pydantic v2 migration
+* Allow connection to ws console over IPv6
+* Allow computes to be dynamically or manually allocated
+* Add UEFI boot mode option for Qemu VMs
+* Mark VMware and VirtualBox support as deprecated
+* Make port name for custom adapters optional. Fixes https://github.com/GNS3/gns3-web-ui/issues/1430
+* Support for database schema migrations using alembic
+* Add config option to change the server name. Ref #2149
+* Option to disable image discovery and do not scan parent directory
+* Allow raw images by default. Fixes https://github.com/GNS3/gns3-server/issues/2097
+* Fix bug when creating Dynamips router with chassis setting
+* Stricter checks to create/update an Ethernet switch and add tests
+* Fix schema for removing WICs from Cisco routers. Fixes #3392
+* Fix some issues with HTTP notification streams
+* API endpoint to get the locked status of a project
+* Global project lock and unlock
+* Require name for custom adapters. Fixes #2098
+* Allow empty adapter slots for Dynamips templates. Ref https://github.com/GNS3/gns3-gui/issues/3373
+* Custom adapters should not be in node (compute) properties returned to clients. Fixes https://github.com/GNS3/gns3-gui/issues/3366
+* Optionally allow Qemu raw images
+* Ignore image detection for IOU user libraries in image directory
+* Checks for valid hostname on server side for Dynamips, IOU, Qemu and Docker nodes
+* Only check files (not directories) when looking for new images on file system.
+* Support user defined loader/libraries to run IOU
+* Remove explicit Response for VPCS endpoints returning HTTP 204 status code
+* Remove explicit Response for endpoints returning HTTP 204 status code
+* Make 'vendor_url' and 'maintainer_email' optional for template validation.
+* Allow auth token to be passed as a URL param
+* Add controller endpoints to get VirtualBox VMs, VMware VMs and Docker images
+* Detect new images added to the default image directory. * Images can be present before the server starts or while it is running * Images are recorded in the database
+* Support delete Qemu disk image from API Return the real disk image name in the 'hdx_disk_image_backed' property for Qemu VMs
+* Fix ComputeConflictError import
+* Handle creating Qemu disk images and resizing
+* Finish to clean up local setting usage. Ref #1460
+* "Local" command line parameter is only for stopping a server that has been started by the desktop GUI
+* Fix AsyncSession handling after breaking changes in FastAPI 0.74.0 See https://github.com/tiangolo/fastapi/releases/tag/0.74.0 for details.
+* Detect image type instead of requesting it from user
+* Add connect endpoint for computes Param to connect to compute after creation Report compute unauthorized HTTP errors to client
+* Replace CORS origins by origin regex
+* Allow empty compute_id. Ref #1657
+* Secure controller to compute communication using HTTP basic authentication
+* Secure websocket endpoints
+* Allocate compute when compute_id is unset
+* Return the current controller hostname/IP from any compute
+* Remove Qemu legacy networking support
+* Appliance management refactoring: * Install an appliance based on selected version * Each template have unique name and version * Allow to download an appliance file
+* Add isolate and unisolate endpoints. Ref https://github.com/GNS3/gns3-gui/issues/3190
+* Allow images to be stored in subdirs and used by templates.
+* Use uuid5 to create new compute_id. Fixes #1641 #1887
+* Migrate PCAP streaming code to work with FastAPI.
+* Refactor WebSocket console code to work with FastAPI. Fix endpoint routes.
+
+
+## 2.2.43 19/09/2023
+
+* Force English output for VBoxManage. Fixes #2266
+* Automatically add vboxnet and DHCP server if not present for VirtualBox GNS3 VM. Ref #2266
+* Fix issue with controller config saved before checking current version with previous one
+* Prevent X11 socket file to be modified by Docker container
+* Use the user data dir to store built-in appliances
+* Catch ConnectionResetError exception when client disconnects
+* Upgrade to PyQt 5.15.9 and pywin32
+
## 2.2.42 09/08/2023
* Bundle web-ui v2.2.42
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/acl.py b/gns3server/api/routes/controller/acl.py
index cef43bf2..ea33d46e 100644
--- a/gns3server/api/routes/controller/acl.py
+++ b/gns3server/api/routes/controller/acl.py
@@ -78,9 +78,16 @@ async def endpoints(
for project in projects:
add_to_endpoints(f"/projects/{project.id}", f'Project "{project.name}"', "project")
+ if project.status == "closed":
+ nodes = project.nodes.values()
+ links = project.links.values()
+ else:
+ nodes = [v.asdict() for v in project.nodes.values()]
+ links = [v.asdict() for v in project.links.values()]
+
# nodes
add_to_endpoints(f"/projects/{project.id}/nodes", f'All nodes in project "{project.name}"', "node")
- for node in project.nodes.values():
+ for node in nodes:
add_to_endpoints(
f"/projects/{project.id}/nodes/{node['node_id']}",
f'Node "{node["name"]}" in project "{project.name}"',
@@ -89,7 +96,7 @@ async def endpoints(
# links
add_to_endpoints(f"/projects/{project.id}/links", f'All links in project "{project.name}"', "link")
- for link in project.links.values():
+ for link in links:
node_id_1 = link["nodes"][0]["node_id"]
node_id_2 = link["nodes"][1]["node_id"]
node_name_1 = project.nodes[node_id_1]["name"]
@@ -183,7 +190,7 @@ async def create_ace(
route_path = re.sub(r"/{[\w:]+}", r"/\\w+", route_path)
if re.fullmatch(route_path, ace_create.path):
- log.info("Creating ACE for route path", ace_create.path, route_path)
+ log.info(f"Creating ACE for route path {route_path}")
return await rbac_repo.create_ace(ace_create)
raise ControllerBadRequestError(f"Path '{ace_create.path}' doesn't match any existing endpoint")
diff --git a/gns3server/api/routes/controller/computes.py b/gns3server/api/routes/controller/computes.py
index e9619d0b..ca86e912 100644
--- a/gns3server/api/routes/controller/computes.py
+++ b/gns3server/api/routes/controller/computes.py
@@ -64,7 +64,7 @@ async def create_compute(
@router.post(
"/{compute_id}/connect",
status_code=status.HTTP_204_NO_CONTENT,
- dependencies=[Depends(has_privilege("Compute.Audit"))]
+ #dependencies=[Depends(has_privilege("Compute.Audit"))] # FIXME: this is a temporary workaround due to a bug in the web-ui
)
async def connect_compute(compute_id: Union[str, UUID]) -> None:
"""
@@ -82,7 +82,7 @@ async def connect_compute(compute_id: Union[str, UUID]) -> None:
"/{compute_id}",
response_model=schemas.Compute,
response_model_exclude_unset=True,
- dependencies=[Depends(has_privilege("Compute.Audit"))]
+ #dependencies=[Depends(has_privilege("Compute.Audit"))] # FIXME: this is a temporary workaround due to a bug in the web-ui
)
async def get_compute(
compute_id: Union[str, UUID], computes_repo: ComputesRepository = Depends(get_repository(ComputesRepository))
@@ -100,7 +100,7 @@ async def get_compute(
"",
response_model=List[schemas.Compute],
response_model_exclude_unset=True,
- dependencies=[Depends(has_privilege("Compute.Audit"))]
+ #dependencies=[Depends(has_privilege("Compute.Audit"))] # FIXME: this is a temporary workaround due to a bug in the web-ui
)
async def get_computes(
computes_repo: ComputesRepository = Depends(get_repository(ComputesRepository)),
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/drawings.py b/gns3server/api/routes/controller/drawings.py
index 22ca1b95..1d8c47f1 100644
--- a/gns3server/api/routes/controller/drawings.py
+++ b/gns3server/api/routes/controller/drawings.py
@@ -49,6 +49,9 @@ async def get_drawings(project_id: UUID) -> List[schemas.Drawing]:
"""
project = await Controller.instance().get_loaded_project(str(project_id))
+ if project.status == "closed":
+ # allow to retrieve drawings from a closed project
+ return project.drawings.values()
return [v.asdict() for v in project.drawings.values()]
diff --git a/gns3server/api/routes/controller/images.py b/gns3server/api/routes/controller/images.py
index f4780db0..afd592de 100644
--- a/gns3server/api/routes/controller/images.py
+++ b/gns3server/api/routes/controller/images.py
@@ -23,13 +23,15 @@ import logging
import urllib.parse
from fastapi import APIRouter, Request, Depends, status
+from fastapi.encoders import jsonable_encoder
from starlette.requests import ClientDisconnect
from sqlalchemy.orm.exc import MultipleResultsFound
from typing import List, Optional
from gns3server import schemas
from gns3server.config import Config
-from gns3server.utils.images import InvalidImageError, write_image
+from gns3server.compute.qemu import Qemu
+from gns3server.utils.images import InvalidImageError, write_image, read_image_info, default_images_directory
from gns3server.db.repositories.images import ImagesRepository
from gns3server.db.repositories.templates import TemplatesRepository
from gns3server.db.repositories.rbac import RbacRepository
@@ -50,6 +52,53 @@ log = logging.getLogger(__name__)
router = APIRouter()
+@router.post(
+ "/qemu/{image_path:path}",
+ response_model=schemas.Image,
+ status_code=status.HTTP_201_CREATED,
+ dependencies=[Depends(has_privilege("Image.Allocate"))]
+)
+async def create_qemu_image(
+ image_path: str,
+ image_data: schemas.QemuDiskImageCreate,
+ images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
+
+) -> schemas.Image:
+ """
+ Create a new blank Qemu image.
+
+ Required privilege: Image.Allocate
+ """
+
+ allow_raw_image = Config.instance().settings.Server.allow_raw_images
+ if image_data.format == schemas.QemuDiskImageFormat.raw and not allow_raw_image:
+ raise ControllerBadRequestError("Raw images are not allowed")
+
+ disk_image_path = urllib.parse.unquote(image_path)
+ image_dir, image_name = os.path.split(disk_image_path)
+ # check if the path is within the default images directory
+ base_images_directory = os.path.expanduser(Config.instance().settings.Server.images_path)
+ full_path = os.path.abspath(os.path.join(base_images_directory, image_dir, image_name))
+ if os.path.commonprefix([base_images_directory, full_path]) != base_images_directory:
+ raise ControllerForbiddenError(f"Cannot write disk image, '{disk_image_path}' is forbidden")
+
+ if not image_dir:
+ # put the image in the default images directory for Qemu
+ directory = default_images_directory(image_type="qemu")
+ os.makedirs(directory, exist_ok=True)
+ disk_image_path = os.path.abspath(os.path.join(directory, disk_image_path))
+
+ if await images_repo.get_image(disk_image_path):
+ raise ControllerBadRequestError(f"Disk image '{disk_image_path}' already exists")
+
+ options = jsonable_encoder(image_data, exclude_unset=True)
+ # FIXME: should we have the create_disk_image in the compute code since
+ # this code is used to create images on the controller?
+ await Qemu.instance().create_disk_image(disk_image_path, options)
+
+ image_info = await read_image_info(disk_image_path, "qemu")
+ return await images_repo.add_image(**image_info)
+
@router.get(
"",
response_model=List[schemas.Image],
diff --git a/gns3server/api/routes/controller/links.py b/gns3server/api/routes/controller/links.py
index 28743b75..618ea198 100644
--- a/gns3server/api/routes/controller/links.py
+++ b/gns3server/api/routes/controller/links.py
@@ -70,6 +70,9 @@ async def get_links(project_id: UUID) -> List[schemas.Link]:
"""
project = await Controller.instance().get_loaded_project(str(project_id))
+ if project.status == "closed":
+ # allow to retrieve links from a closed project
+ return project.links.values()
return [v.asdict() for v in project.links.values()]
diff --git a/gns3server/api/routes/controller/nodes.py b/gns3server/api/routes/controller/nodes.py
index b096b634..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
@@ -141,6 +142,9 @@ def get_nodes(project: Project = Depends(dep_project)) -> List[schemas.Node]:
Required privilege: Node.Audit
"""
+ if project.status == "closed":
+ # allow to retrieve nodes from a closed project
+ return project.nodes.values()
return [v.asdict() for v in project.nodes.values()]
@@ -507,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"
)
@@ -554,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/api/routes/controller/pools.py b/gns3server/api/routes/controller/pools.py
index 17e1c17c..6c8e865f 100644
--- a/gns3server/api/routes/controller/pools.py
+++ b/gns3server/api/routes/controller/pools.py
@@ -191,6 +191,7 @@ async def add_resource_to_pool(
if not resource_pool:
raise ControllerNotFoundError(f"Resource pool '{resource_pool_id}' not found")
+ # TODO: consider if a resource can belong to multiple pools
resources = await pools_repo.get_pool_resources(resource_pool_id)
for resource in resources:
if resource.resource_id == resource_id:
@@ -198,8 +199,13 @@ async def add_resource_to_pool(
# we only support projects in resource pools for now
project = Controller.instance().get_project(str(resource_id))
- resource_create = schemas.ResourceCreate(resource_id=resource_id, resource_type="project", name=project.name)
- resource = await pools_repo.create_resource(resource_create)
+
+ resource = await pools_repo.get_resource(resource_id)
+ if not resource:
+ # the resource is not in the database yet, create it
+ resource_create = schemas.ResourceCreate(resource_id=resource_id, resource_type="project", name=project.name)
+ resource = await pools_repo.create_resource(resource_create)
+
await pools_repo.add_resource_to_pool(resource_pool_id, resource)
@@ -226,3 +232,8 @@ async def remove_resource_from_pool(
resource_pool = await pools_repo.remove_resource_from_pool(resource_pool_id, resource)
if not resource_pool:
raise ControllerNotFoundError(f"Resource pool '{resource_pool_id}' not found")
+
+ # TODO: consider if a resource can belong to multiple pools
+ success = await pools_repo.delete_resource(resource.resource_id)
+ if not success:
+ raise ControllerError(f"Resource '{resource_id}' could not be deleted")
diff --git a/gns3server/api/routes/controller/symbols.py b/gns3server/api/routes/controller/symbols.py
index 6d73c623..c6dee131 100644
--- a/gns3server/api/routes/controller/symbols.py
+++ b/gns3server/api/routes/controller/symbols.py
@@ -39,7 +39,10 @@ log = logging.getLogger(__name__)
router = APIRouter()
-@router.get("", dependencies=[Depends(has_privilege("Symbol.Audit"))])
+@router.get(
+ "",
+ dependencies=[Depends(has_privilege("Symbol.Audit"))]
+)
def get_symbols() -> List[dict]:
"""
Return all symbols.
@@ -54,7 +57,8 @@ def get_symbols() -> List[dict]:
@router.get(
"/{symbol_id:path}/raw",
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find symbol"}},
- dependencies=[Depends(has_privilege("Symbol.Audit"))]
+ # FIXME: this is a temporary workaround due to a bug in the web-ui: https://github.com/GNS3/gns3-web-ui/issues/1466
+ # dependencies=[Depends(has_privilege("Symbol.Audit"))]
)
async def get_symbol(symbol_id: str) -> FileResponse:
"""
diff --git a/gns3server/api/routes/controller/templates.py b/gns3server/api/routes/controller/templates.py
index 9d67a74d..cd5aae17 100644
--- a/gns3server/api/routes/controller/templates.py
+++ b/gns3server/api/routes/controller/templates.py
@@ -68,7 +68,8 @@ async def create_template(
"/{template_id}",
response_model=schemas.Template,
response_model_exclude_unset=True,
- dependencies=[Depends(has_privilege("Template.Audit"))]
+ dependencies=[Depends(get_current_active_user)],
+ #dependencies=[Depends(has_privilege("Template.Audit"))] # FIXME: this is a temporary workaround due to a bug in the web-ui
)
async def get_template(
template_id: UUID,
@@ -141,7 +142,8 @@ async def delete_template(
"",
response_model=List[schemas.Template],
response_model_exclude_unset=True,
- dependencies=[Depends(has_privilege("Template.Audit"))]
+ dependencies=[Depends(get_current_active_user)],
+ #dependencies=[Depends(has_privilege("Template.Audit"))] # FIXME: this is a temporary workaround due to a bug in the web-ui
)
async def get_templates(
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
diff --git a/gns3server/appliances/almalinux.gns3a b/gns3server/appliances/almalinux.gns3a
index 56e3f9cf..f0ad0526 100644
--- a/gns3server/appliances/almalinux.gns3a
+++ b/gns3server/appliances/almalinux.gns3a
@@ -21,7 +21,8 @@
"hda_disk_interface": "sata",
"arch": "x86_64",
"console_type": "telnet",
- "kvm": "allow"
+ "kvm": "allow",
+ "options": "-cpu host -nographic"
},
"images": [
{
diff --git a/gns3server/appliances/alpine-linux-virt.gns3a b/gns3server/appliances/alpine-linux-virt.gns3a
index fcfc7bf6..8b5752ba 100644
--- a/gns3server/appliances/alpine-linux-virt.gns3a
+++ b/gns3server/appliances/alpine-linux-virt.gns3a
@@ -25,6 +25,14 @@
"kvm": "allow"
},
"images": [
+ {
+ "filename": "alpine-virt-3.18.4.qcow2",
+ "version": "3.18.4",
+ "md5sum": "99d393c16c870e12c4215aadd82ca998",
+ "filesize": 51066880,
+ "download_url": "https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/",
+ "direct_download_url": "https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/alpine-virt-3.18.4.qcow2/download"
+ },
{
"filename": "alpine-virt-3.16.img",
"version": "3.16",
@@ -35,6 +43,12 @@
}
],
"versions": [
+ {
+ "name": "3.18.4",
+ "images": {
+ "hda_disk_image": "alpine-virt-3.18.4.qcow2"
+ }
+ },
{
"name": "3.16",
"images": {
diff --git a/gns3server/appliances/alpine-linux.gns3a b/gns3server/appliances/alpine-linux.gns3a
index 4ad16883..c5a207d0 100644
--- a/gns3server/appliances/alpine-linux.gns3a
+++ b/gns3server/appliances/alpine-linux.gns3a
@@ -5,6 +5,7 @@
"description": "Alpine Linux is a security-oriented, lightweight Linux distribution based on musl libc and busybox.",
"vendor_name": "Alpine Linux Development Team",
"vendor_url": "http://alpinelinux.org",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/Alpine Linux.png",
"documentation_url": "http://wiki.alpinelinux.org",
"product_name": "Alpine Linux",
"registry_version": 4,
diff --git a/gns3server/appliances/bird2.gns3a b/gns3server/appliances/bird2.gns3a
index 4d573e11..4a076d78 100644
--- a/gns3server/appliances/bird2.gns3a
+++ b/gns3server/appliances/bird2.gns3a
@@ -29,7 +29,7 @@
"md5sum": "435218a2e90cba921cc7fde1d64a9419",
"filesize": 287965184,
"download_url": "https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/",
- "direct_download_url": "http://downloads.sourceforge.net/project/gns-3/Qemu%20Appliances/bird2-debian-2.0.12.qcow2"
+ "direct_download_url": "https://downloads.sourceforge.net/project/gns-3/Qemu%20Appliances/bird2-debian-2.0.12.qcow2"
}
],
"versions": [
diff --git a/gns3server/appliances/centos-cloud.gns3a b/gns3server/appliances/centos-cloud.gns3a
index c9666c40..74c645ff 100644
--- a/gns3server/appliances/centos-cloud.gns3a
+++ b/gns3server/appliances/centos-cloud.gns3a
@@ -12,7 +12,7 @@
"status": "stable",
"maintainer": "GNS3 Team",
"maintainer_email": "developers@gns3.net",
- "usage": "Username: centos\nPassword: centos",
+ "usage": "Username: centos or cloud-user\nPassword: centos",
"port_name_format": "Ethernet{0}",
"qemu": {
"adapter_type": "virtio-net-pci",
@@ -23,16 +23,16 @@
"console_type": "telnet",
"boot_priority": "c",
"kvm": "require",
- "options": "-nographic"
+ "options": "-cpu host -nographic"
},
"images": [
{
- "filename": "CentOS-Stream-GenericCloud-9-20230727.1.x86_64.qcow2",
- "version": "Stream-9 (20230727.1)",
- "md5sum": "b66b7e4951cb5491ae44d5616d56b7cf",
- "filesize": 1128764416,
+ "filename": "CentOS-Stream-GenericCloud-9-20230704.1.x86_64.qcow2",
+ "version": "Stream-9 (20230704.1)",
+ "md5sum": "e04511e019325a97837edd9eafe02b48",
+ "filesize": 1087868416,
"download_url": "https://cloud.centos.org/centos/9-stream/x86_64/images",
- "direct_download_url": "https://cloud.centos.org/centos/9-stream/x86_64/images/CentOS-Stream-GenericCloud-9-20230727.1.x86_64.qcow2"
+ "direct_download_url": "https://cloud.centos.org/centos/9-stream/x86_64/images/CentOS-Stream-GenericCloud-9-20230704.1.x86_64.qcow2"
},
{
"filename": "CentOS-Stream-GenericCloud-8-20230710.0.x86_64.qcow2",
@@ -77,9 +77,9 @@
],
"versions": [
{
- "name": "Stream-9 (20230727.1)",
+ "name": "Stream-9 (20230704.1)",
"images": {
- "hda_disk_image": "CentOS-Stream-GenericCloud-9-20230727.1.x86_64.qcow2",
+ "hda_disk_image": "CentOS-Stream-GenericCloud-9-20230704.1.x86_64.qcow2",
"cdrom_image": "centos-cloud-init-data.iso"
}
},
diff --git a/gns3server/appliances/chromium.gns3a b/gns3server/appliances/chromium.gns3a
index 1b3d0e80..e9b8d140 100644
--- a/gns3server/appliances/chromium.gns3a
+++ b/gns3server/appliances/chromium.gns3a
@@ -5,6 +5,7 @@
"description": "The chromium browser",
"vendor_name": "Chromium",
"vendor_url": "https://www.chromium.org/",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/Chromium.jpg",
"product_name": "Chromium",
"registry_version": 4,
"status": "stable",
diff --git a/gns3server/appliances/cloudrouter.gns3a b/gns3server/appliances/cloudrouter.gns3a
index 20c72ca2..2ee29033 100644
--- a/gns3server/appliances/cloudrouter.gns3a
+++ b/gns3server/appliances/cloudrouter.gns3a
@@ -5,6 +5,7 @@
"description": "The CloudRouter Project is a collaborative open source project focused on developing a powerful, easy to use router designed for the cloud.\nCompute resources are rapidly migrating from physical infrastructure to a combination of physical, virtual and cloud environments. A similar transition is emerging in the networking space, with network control logic shifting from proprietary hardware-based platforms to open source software-based platforms. CloudRouter is a software-based router distribution designed to run on physical, virtual and cloud environments, supporting software-defined networking infrastructure. It includes the features of traditional hardware routers, as well as support for emerging technologies such as containers and software-defined interconnection. CloudRouter aims to facilitate migration to the cloud without giving up control over network routing and governance.",
"vendor_name": "CloudRouter Community",
"vendor_url": "https://cloudrouter.org/",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/CloudRouter.png",
"documentation_url": "https://cloudrouter.atlassian.net/wiki/display/CPD/CloudRouter+Project+Information",
"product_name": "CloudRouter",
"product_url": "https://cloudrouter.org/about/",
diff --git a/gns3server/appliances/coreos.gns3a b/gns3server/appliances/coreos.gns3a
index cdd5d0cf..16dc7d45 100644
--- a/gns3server/appliances/coreos.gns3a
+++ b/gns3server/appliances/coreos.gns3a
@@ -5,6 +5,7 @@
"description": "CoreOS is designed for security, consistency, and reliability. Instead of installing packages via yum or apt, CoreOS uses Linux containers to manage your services at a higher level of abstraction. A single service's code and all dependencies are packaged within a container that can be run on one or many CoreOS machines.",
"vendor_name": "CoreOS, Inc",
"vendor_url": "https://coreos.com/",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/CoreOS.png",
"documentation_url": "https://coreos.com/docs/",
"product_name": "CoreOS",
"registry_version": 4,
diff --git a/gns3server/appliances/cumulus-vx.gns3a b/gns3server/appliances/cumulus-vx.gns3a
index 8183d425..9732c282 100644
--- a/gns3server/appliances/cumulus-vx.gns3a
+++ b/gns3server/appliances/cumulus-vx.gns3a
@@ -5,6 +5,7 @@
"description": "Cumulus VX is a community-supported virtual appliance that enables cloud admins and network engineers to preview and test Cumulus Networks technology at zero cost. You can build sandbox environments to learn Open Networking concepts, prototype network operations and script & develop applications risk-free. With Cumulus VX, you can get started with Open Networking at your pace, on your time, and in your environment!",
"vendor_name": "Cumulus Network",
"vendor_url": "https://www.cumulusnetworks.com",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/Cumulus VX.jpg",
"documentation_url": "http://docs.cumulusnetworks.com/",
"product_name": "Cumulus VX",
"product_url": "https://cumulusnetworks.com/cumulus-vx/",
diff --git a/gns3server/appliances/debian.gns3a b/gns3server/appliances/debian.gns3a
index bb3909f4..91525a42 100644
--- a/gns3server/appliances/debian.gns3a
+++ b/gns3server/appliances/debian.gns3a
@@ -10,7 +10,7 @@
"status": "experimental",
"maintainer": "Bernhard Ehlers",
"maintainer_email": "dev-ehlers@mailbox.org",
- "usage": "Username:\tdebian\nPassword:\tdebian\nTo become root, use \"sudo -s\".\n\nNetwork configuration:\n- In \"/etc/network/interfaces\" comment out \"source-directory /run/network/interfaces.d\"\n- Remove \"/etc/network/interfaces.d/50-cloud-init\"\n- Create \"/etc/network/interfaces.d/10-ens4\", for example:\n\nauto ens4\n#iface ens4 inet dhcp\niface ens4 inet static\n address 10.1.1.100/24\n gateway 10.1.1.1\n dns-nameservers 10.1.1.1\n",
+ "usage": "Username:\tdebian\nPassword:\tdebian\nTo become root, use \"sudo -s\".\n",
"symbol": "linux_guest.svg",
"port_name_format": "ens{port4}",
"qemu": {
@@ -24,58 +24,33 @@
},
"images": [
{
- "filename": "debian-12-genericcloud-amd64-20230723-1450.qcow2",
- "version": "12.1",
- "md5sum": "6d1efcaa206de01eeeb590d773421c5c",
- "filesize": 280166400,
- "download_url": "https://cloud.debian.org/images/cloud/bookworm/",
- "direct_download_url": "https://cloud.debian.org/images/cloud/bookworm/20230723-1450/debian-12-genericcloud-amd64-20230723-1450.qcow2"
+ "filename": "debian-12.2.qcow2",
+ "version": "12.2",
+ "md5sum": "adf7716ec4a4e4e9e5ccfc7a1d7bd103",
+ "filesize": 286654464,
+ "download_url": "https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/",
+ "direct_download_url": "https://downloads.sourceforge.net/project/gns-3/Qemu%20Appliances/debian-12.2.qcow2"
},
{
- "filename": "debian-11-genericcloud-amd64-20230601-1398.qcow2",
- "version": "11.7",
- "md5sum": "1b24a841dc5ca9bcf40b94ad4b4775d4",
- "filesize": 259063808,
- "download_url": "https://cloud.debian.org/images/cloud/bullseye/",
- "direct_download_url": "https://cloud.debian.org/images/cloud/bullseye/20230601-1398/debian-11-genericcloud-amd64-20230601-1398.qcow2"
- },
- {
- "filename": "debian-10-genericcloud-amd64-20230601-1398.qcow2",
- "version": "10.13",
- "md5sum": "ca799fb4011712f4686c422c1a9731cf",
- "filesize": 228130816,
- "download_url": "https://cloud.debian.org/images/cloud/buster/",
- "direct_download_url": "https://cloud.debian.org/images/cloud/buster/20230601-1398/debian-10-genericcloud-amd64-20230601-1398.qcow2"
- },
- {
- "filename": "debian-cloud-init-data.iso",
- "version": "1.0",
- "md5sum": "43f6bf70c178a9d3c270b5c24971e578",
- "filesize": 374784,
- "download_url": "https://github.com/GNS3/gns3-registry/tree/master/cloud-init/Debian",
- "direct_download_url": "https://github.com/GNS3/gns3-registry/raw/master/cloud-init/Debian/debian-cloud-init-data.iso"
+ "filename": "debian-11.8.qcow2",
+ "version": "11.8",
+ "md5sum": "95bf44716c7fa1a1da290fd3c98591f2",
+ "filesize": 264933376,
+ "download_url": "https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/",
+ "direct_download_url": "https://downloads.sourceforge.net/project/gns-3/Qemu%20Appliances/debian-11.8.qcow2"
}
],
"versions": [
{
- "name": "12.1",
+ "name": "12.2",
"images": {
- "hda_disk_image": "debian-12-genericcloud-amd64-20230723-1450.qcow2",
- "cdrom_image": "debian-cloud-init-data.iso"
+ "hda_disk_image": "debian-12.2.qcow2"
}
},
{
- "name": "11.7",
+ "name": "11.8",
"images": {
- "hda_disk_image": "debian-11-genericcloud-amd64-20230601-1398.qcow2",
- "cdrom_image": "debian-cloud-init-data.iso"
- }
- },
- {
- "name": "10.13",
- "images": {
- "hda_disk_image": "debian-10-genericcloud-amd64-20230601-1398.qcow2",
- "cdrom_image": "debian-cloud-init-data.iso"
+ "hda_disk_image": "debian-11.8.qcow2"
}
}
]
diff --git a/gns3server/appliances/empty-vm.gns3a b/gns3server/appliances/empty-vm.gns3a
index 52f73331..d0575449 100644
--- a/gns3server/appliances/empty-vm.gns3a
+++ b/gns3server/appliances/empty-vm.gns3a
@@ -33,6 +33,22 @@
"download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/",
"direct_download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/empty8G.qcow2/download"
},
+ {
+ "filename": "empty10G.qcow2",
+ "version": "10G",
+ "md5sum": "1d4589798b8a63a6afa7150492ca3193",
+ "filesize": 196768,
+ "download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/",
+ "direct_download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/empty10G.qcow2/download"
+ },
+ {
+ "filename": "empty20G.qcow2",
+ "version": "20G",
+ "md5sum": "df9e4a1169c597117fd8999f0bc3de91",
+ "filesize": 196928,
+ "download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/",
+ "direct_download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/empty20G.qcow2/download"
+ },
{
"filename": "empty30G.qcow2",
"version": "30G",
@@ -41,6 +57,22 @@
"download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/",
"direct_download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/empty30G.qcow2/download"
},
+ {
+ "filename": "empty40G.qcow2",
+ "version": "40G",
+ "md5sum": "4a9e538aa1946a27d91a8d53a8dbc546",
+ "filesize": 197248,
+ "download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/",
+ "direct_download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/empty50G.qcow2/download"
+ },
+ {
+ "filename": "empty50G.qcow2",
+ "version": "50G",
+ "md5sum": "9a17e67e685907fbc0c351689ab289b2",
+ "filesize": 197408,
+ "download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/",
+ "direct_download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/empty40G.qcow2/download"
+ },
{
"filename": "empty100G.qcow2",
"version": "100G",
@@ -49,6 +81,14 @@
"download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/",
"direct_download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/empty100G.qcow2/download"
},
+ {
+ "filename": "empty150G.qcow2",
+ "version": "150G",
+ "md5sum": "7db590ad39f84fdfc91516d162af26f6",
+ "filesize": 199008,
+ "download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/",
+ "direct_download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/empty150G.qcow2/download"
+ },
{
"filename": "empty200G.qcow2",
"version": "200G",
@@ -56,6 +96,30 @@
"filesize": 200192,
"download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/",
"direct_download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/empty200G.qcow2/download"
+ },
+ {
+ "filename": "empty250G.qcow2",
+ "version": "250G",
+ "md5sum": "7d7272f02edd189aafd81f9c29a35255",
+ "filesize": 200608,
+ "download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/",
+ "direct_download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/empty250G.qcow2/download"
+ },
+ {
+ "filename": "empty500G.qcow2",
+ "version": "500G",
+ "md5sum": "658c825441b9b3080ba00f9eec002eaa",
+ "filesize": 204608,
+ "download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/",
+ "direct_download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/empty500G.qcow2/download"
+ },
+ {
+ "filename": "empty1T.qcow2",
+ "version": "1T",
+ "md5sum": "34997a22f618827aaa62bcfd5b8ce2bb",
+ "filesize": 212992,
+ "download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/",
+ "direct_download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/empty1T.qcow2/download"
}
],
"versions": [
@@ -65,23 +129,71 @@
"hda_disk_image": "empty8G.qcow2"
}
},
+ {
+ "name": "10G",
+ "images": {
+ "hda_disk_image": "empty10G.qcow2"
+ }
+ },
+ {
+ "name": "20G",
+ "images": {
+ "hda_disk_image": "empty20G.qcow2"
+ }
+ },
{
"name": "30G",
"images": {
"hda_disk_image": "empty30G.qcow2"
}
},
+ {
+ "name": "40G",
+ "images": {
+ "hda_disk_image": "empty40G.qcow2"
+ }
+ },
+ {
+ "name": "50G",
+ "images": {
+ "hda_disk_image": "empty50G.qcow2"
+ }
+ },
{
"name": "100G",
"images": {
"hda_disk_image": "empty100G.qcow2"
}
},
+ {
+ "name": "150G",
+ "images": {
+ "hda_disk_image": "empty150G.qcow2"
+ }
+ },
{
"name": "200G",
"images": {
"hda_disk_image": "empty200G.qcow2"
}
+ },
+ {
+ "name": "250G",
+ "images": {
+ "hda_disk_image": "empty250G.qcow2"
+ }
+ },
+ {
+ "name": "500G",
+ "images": {
+ "hda_disk_image": "empty500G.qcow2"
+ }
+ },
+ {
+ "name": "1T",
+ "images": {
+ "hda_disk_image": "empty1T.qcow2"
+ }
}
]
}
diff --git a/gns3server/appliances/extreme-networks-voss.gns3a b/gns3server/appliances/extreme-networks-voss.gns3a
index abe36275..a5ef0a64 100644
--- a/gns3server/appliances/extreme-networks-voss.gns3a
+++ b/gns3server/appliances/extreme-networks-voss.gns3a
@@ -5,6 +5,7 @@
"description": "The VOSS VM is a software emulation of a VSP8K switch.",
"vendor_name": "Extreme Networks",
"vendor_url": "http://www.extremenetworks.com",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/VOSS VM.jpeg",
"documentation_url": "http://www.extremenetworks.com/support/documentation",
"product_name": "VOSS_VM",
"registry_version": 4,
diff --git a/gns3server/appliances/firefox.gns3a b/gns3server/appliances/firefox.gns3a
index 8e43cfd6..071f6984 100644
--- a/gns3server/appliances/firefox.gns3a
+++ b/gns3server/appliances/firefox.gns3a
@@ -5,6 +5,7 @@
"description": "A light Linux based on TinyCore Linux with Firefox preinstalled",
"vendor_name": "Mozilla Foundation",
"vendor_url": "http://www.mozilla.org",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/Firefox.png",
"documentation_url": "https://support.mozilla.org",
"product_name": "Firefox",
"product_url": "https://www.mozilla.org/firefox",
diff --git a/gns3server/appliances/fortianalyzer.gns3a b/gns3server/appliances/fortianalyzer.gns3a
index 57f14c9b..58486e57 100644
--- a/gns3server/appliances/fortianalyzer.gns3a
+++ b/gns3server/appliances/fortianalyzer.gns3a
@@ -5,6 +5,7 @@
"description": "FortiAnalyzer Network Security Logging, Analysis, and Reporting Appliances securely aggregate log data from Fortinet Security Appliances. A comprehensive suite of easily customable reports allows you to quickly analyze and visualize network threats, inefficiencies and usage.",
"vendor_name": "Fortinet",
"vendor_url": "http://www.fortinet.com/",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/FortiAnalyzer.jpg",
"documentation_url": "http://docs.fortinet.com/fortianalyzer/",
"product_name": "FortiAnalyzer",
"product_url": "https://www.fortinet.com/products-services/products/management-reporting/fortianalyzer.html",
diff --git a/gns3server/appliances/fortiauthenticator.gns3a b/gns3server/appliances/fortiauthenticator.gns3a
index 24ec0f4b..6ce0bb96 100644
--- a/gns3server/appliances/fortiauthenticator.gns3a
+++ b/gns3server/appliances/fortiauthenticator.gns3a
@@ -5,6 +5,7 @@
"description": "FortiAuthenticator user identity management appliances strengthen enterprise security by simplifying and centralizing the management and storage of user identity information.",
"vendor_name": "Fortinet",
"vendor_url": "http://www.fortinet.com/",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/FortiAuthenticator.jpg",
"documentation_url": "http://docs.fortinet.com/fortiauthenticator/admin-guides",
"product_name": "FortiAuthenticator",
"product_url": "https://www.fortinet.com/products/identity-access-management/fortiauthenticator.html",
diff --git a/gns3server/appliances/forticache.gns3a b/gns3server/appliances/forticache.gns3a
index c41ae745..9638b0fc 100644
--- a/gns3server/appliances/forticache.gns3a
+++ b/gns3server/appliances/forticache.gns3a
@@ -5,6 +5,7 @@
"description": "FortiCache VM high performance Web Caching virtual appliances address bandwidth saturation, high latency, and poor performance caused by caching popular internet content locally for carriers, service providers, enterprises and educational networks. FortiCache VM appliances reduce the cost and impact of cached content on the network, while increasing performance and end- user satisfaction by improving the speed of delivery of popular repeated content.",
"vendor_name": "Fortinet",
"vendor_url": "http://www.fortinet.com/",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/FortiCache.jpg",
"documentation_url": "http://docs.fortinet.com/forticache/admin-guides",
"product_name": "FortiCache",
"product_url": "https://www.fortinet.com/products-services/products/wan-appliances/forticache.html",
diff --git a/gns3server/appliances/fortigate.gns3a b/gns3server/appliances/fortigate.gns3a
index c730d176..9f85fd5e 100644
--- a/gns3server/appliances/fortigate.gns3a
+++ b/gns3server/appliances/fortigate.gns3a
@@ -5,6 +5,7 @@
"description": "FortiGate Virtual Appliance offers the same level of advanced threat prevention features like the physical appliances in private, hybrid and public cloud deployment.",
"vendor_name": "Fortinet",
"vendor_url": "http://www.fortinet.com/",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/FortiGate.jpg",
"documentation_url": "http://docs.fortinet.com/p/inside-fortios",
"product_name": "FortiGate",
"product_url": "http://www.fortinet.com/products/fortigate/virtual-appliances.html",
diff --git a/gns3server/appliances/fortimail.gns3a b/gns3server/appliances/fortimail.gns3a
index 774b17b5..a29f740d 100644
--- a/gns3server/appliances/fortimail.gns3a
+++ b/gns3server/appliances/fortimail.gns3a
@@ -5,6 +5,7 @@
"description": "FortiMail is a complete Secure Email Gateway offering suitable for any size organization. It provides a single solution to protect against inbound attacks - including advanced malware -, as well as outbound threats and data loss with a wide range of top-rated security capabilities.",
"vendor_name": "Fortinet",
"vendor_url": "http://www.fortinet.com/",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/FortiMail.jpg",
"documentation_url": "http://docs.fortinet.com/fortimail/admin-guides",
"product_name": "FortiMail",
"product_url": "http://www.fortinet.com/products/fortimail/index.html",
diff --git a/gns3server/appliances/fortimanager.gns3a b/gns3server/appliances/fortimanager.gns3a
index a8333452..636bb14d 100644
--- a/gns3server/appliances/fortimanager.gns3a
+++ b/gns3server/appliances/fortimanager.gns3a
@@ -5,6 +5,7 @@
"description": "FortiManager Security Management appliances allow you to centrally manage any number of Fortinet Network Security devices, from several to thousands, including FortiGate, FortiWiFi, and FortiCarrier.",
"vendor_name": "Fortinet",
"vendor_url": "http://www.fortinet.com/",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/FortiManager.jpg",
"documentation_url": "http://docs.fortinet.com/p/inside-fortios",
"product_name": "FortiManager",
"product_url": "http://www.fortinet.com/products/fortimanager/virtual-security-management.html",
diff --git a/gns3server/appliances/fortisandbox.gns3a b/gns3server/appliances/fortisandbox.gns3a
index 7173fba2..ddc3f055 100644
--- a/gns3server/appliances/fortisandbox.gns3a
+++ b/gns3server/appliances/fortisandbox.gns3a
@@ -5,6 +5,7 @@
"description": "Today's threats are increasingly sophisticated and often bypass traditional malware security by masking their malicious activity. A sandbox augments your security architecture by validating threats in a separate, secure environment. FortiSandbox offers a powerful combination of advanced detection, automated mitigation, actionable insight, and flexible deployment to stop targeted attacks and subsequent data loss. It's also a key component of our Advanced Threat Protection solution.",
"vendor_name": "Fortinet",
"vendor_url": "http://www.fortinet.com/",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/FortiSandbox.jpg",
"documentation_url": "http://docs.fortinet.com/fortisandbox/admin-guides",
"product_name": "FortiSandbox",
"product_url": "https://www.fortinet.com/products/sandbox/fortisandbox.html",
diff --git a/gns3server/appliances/fortisiem-super_worker.gns3a b/gns3server/appliances/fortisiem-super_worker.gns3a
index 4500c4bf..fd066541 100644
--- a/gns3server/appliances/fortisiem-super_worker.gns3a
+++ b/gns3server/appliances/fortisiem-super_worker.gns3a
@@ -5,6 +5,7 @@
"description": "Breaches to network security continue to occur across all industry verticals, even to the most respected brands. The time it takes to discover, isolate, and remediate the incident continues to be measured in hundreds of days-having material impacts on security and compliance standards. It is no wonder that many organizations are struggling. As recent surveys have shown, enterprises have an average of 32 different vendors' devices in their network, with no automated ability to cross-correlate the data that each is collecting. It is also easy to see why organizations are strapped for the cyber security personnel they need to manage all the data in these complex environments.\n\nFrom its inception, FortiSIEM was built to reduce complexity in managing network and security operations. FortiSIEM provides organizations of all sizes with a comprehensive, holistic, and scalable solution for managing security, performance, and compliance from IoT to the cloud.",
"vendor_name": "Fortinet",
"vendor_url": "http://www.fortinet.com/",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/FortiSIEM.jpg",
"documentation_url": "http://docs.fortinet.com/fortisiem/admin-guides",
"product_name": "FortiSIEM",
"product_url": "https://www.fortinet.com/products/siem/fortisiem.html",
diff --git a/gns3server/appliances/fortiweb.gns3a b/gns3server/appliances/fortiweb.gns3a
index 451ce1dc..1c322a6b 100644
--- a/gns3server/appliances/fortiweb.gns3a
+++ b/gns3server/appliances/fortiweb.gns3a
@@ -5,6 +5,7 @@
"description": "FortiWeb Web Application Firewalls provide specialized, layered web application threat protection for medium/large enterprises, application service providers, and SaaS providers.",
"vendor_name": "Fortinet",
"vendor_url": "http://www.fortinet.com/",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/FortiWeb.jpg",
"documentation_url": "http://docs.fortinet.com/fortiweb",
"product_name": "FortiWeb",
"product_url": "http://www.fortinet.com/products/fortiweb/index.html",
diff --git a/gns3server/appliances/freebsd.gns3a b/gns3server/appliances/freebsd.gns3a
index ad1821d5..c85f8f95 100644
--- a/gns3server/appliances/freebsd.gns3a
+++ b/gns3server/appliances/freebsd.gns3a
@@ -5,6 +5,7 @@
"description": "FreeBSD is an advanced computer operating system used to power modern servers, desktops, and embedded platforms. A large community has continually developed it for more than thirty years. Its advanced networking, security, and storage features have made FreeBSD the platform of choice for many of the busiest web sites and most pervasive embedded networking and storage devices.",
"vendor_name": "FreeBSD",
"vendor_url": "http://www.freebsd.org",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/FreeBSD.jpg",
"documentation_url": "https://www.freebsd.org/docs.html",
"product_name": "FreeBSD",
"registry_version": 4,
diff --git a/gns3server/appliances/hp-vsr1001.gns3a b/gns3server/appliances/hp-vsr1001.gns3a
index ccbc2c9c..5c876b52 100644
--- a/gns3server/appliances/hp-vsr1001.gns3a
+++ b/gns3server/appliances/hp-vsr1001.gns3a
@@ -5,6 +5,7 @@
"description": "The HPE VSR1000 Virtual Services Router Series is a software application, running on a server, which provides functionality similar to that of a physical router: robust routing between networked devices using a number of popular routing protocols. It also delivers the critical network services associated with today's enterprise routers such as VPN gateway, firewall and other security and traffic management functions.\n\nThe virtual services router (VSR) application runs on a hypervqcor on the server, and supports VMware vSphere and Linux KVM hypervqcors. From one to eight virtual CPUs are supported, depending on license.\n\nBecause the VSR1000 Series application runs the same HPE Comware version 7 operating system as HPE switches and routers, it enables significant operational savings. And being virtual, additional agility and ease of deployment is realized, as resources on the VSR can be dynamically allocated and upgraded upon demand as performance requirements grow.\n\nA variety of deployment models are supported including enterprise branch CPE routing, and cloud offload for small to medium workloads.",
"vendor_name": "HPE",
"vendor_url": "http://www.hpe.com",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/HPE VSR1001.jpg",
"documentation_url": "https://support.hpe.com/hpesc/public/home/documentHome?document_type=135&sp4ts.oid=5195141",
"product_name": "VSR1001",
"product_url": "https://www.hpe.com/us/en/product-catalog/networking/networking-routers/pip.hpe-flexnetwork-vsr1000-virtual-services-router-series.5443163.html",
diff --git a/gns3server/appliances/kerio-connect.gns3a b/gns3server/appliances/kerio-connect.gns3a
index 0e9f168e..98da7e62 100644
--- a/gns3server/appliances/kerio-connect.gns3a
+++ b/gns3server/appliances/kerio-connect.gns3a
@@ -5,6 +5,7 @@
"description": "Kerio Connect makes email, calendars, contacts and task management easy and affordable. With Kerio Connect, you have immediate, secure access to your communications anytime, anywhere, on any device - without complexity or expensive overhead.",
"vendor_name": "Kerio Technologies Inc.",
"vendor_url": "http://www.kerio.com",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/Kerio Connect.jpg",
"documentation_url": "http://kb.kerio.com/product/kerio-connect/",
"product_name": "Kerio Connect",
"product_url": "http://www.kerio.com/products/kerio-connect",
diff --git a/gns3server/appliances/kerio-control.gns3a b/gns3server/appliances/kerio-control.gns3a
index 440a0a46..3a64daa1 100644
--- a/gns3server/appliances/kerio-control.gns3a
+++ b/gns3server/appliances/kerio-control.gns3a
@@ -5,6 +5,7 @@
"description": "Protect your network from viruses, malware and malicious activity with Kerio Control, the easy-to-administer yet powerful all-in-one security solution.\nKerio Control brings together next-generation firewall capabilities - including a network firewall and router, intrusion detection and prevention (IPS), gateway anti-virus, VPN, and web content and application filtering. These comprehensive capabilities and unmatched deployment flexibility make Kerio Control the ideal choice for small and mid-sized businesses.",
"vendor_name": "Kerio Technologies Inc.",
"vendor_url": "http://www.kerio.com",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/Kerio Control.jpg",
"documentation_url": "http://kb.kerio.com/product/kerio-control/",
"product_name": "Kerio Control",
"product_url": "http://www.kerio.com/products/kerio-control",
diff --git a/gns3server/appliances/kerio-operator.gns3a b/gns3server/appliances/kerio-operator.gns3a
index 3733c12b..332c2ef4 100644
--- a/gns3server/appliances/kerio-operator.gns3a
+++ b/gns3server/appliances/kerio-operator.gns3a
@@ -5,6 +5,7 @@
"description": "Stay connected to your customers and colleagues without being chained to your desk.\nKerio Operator is a VoIP based phone system that provides powerful yet affordable enterprise-class voice and video communication capabilities for small and mid-sized businesses globally.",
"vendor_name": "Kerio Technologies Inc.",
"vendor_url": "http://www.kerio.com",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/Kerio Operator.jpg",
"documentation_url": "http://kb.kerio.com/product/kerio-operator/",
"product_name": "Kerio Operator",
"product_url": "http://www.kerio.com/products/kerio-operator",
diff --git a/gns3server/appliances/mikrotik-chr.gns3a b/gns3server/appliances/mikrotik-chr.gns3a
index b3970598..54806d22 100644
--- a/gns3server/appliances/mikrotik-chr.gns3a
+++ b/gns3server/appliances/mikrotik-chr.gns3a
@@ -27,6 +27,15 @@
"options": "-nographic"
},
"images": [
+ {
+ "filename": "chr-7.11.2.img",
+ "version": "7.11.2",
+ "md5sum": "fbffd097d2c5df41fc3335c3977f782c",
+ "filesize": 134217728,
+ "download_url": "http://www.mikrotik.com/download",
+ "direct_download_url": "https://download.mikrotik.com/routeros/7.11.2/chr-7.11.2.img.zip",
+ "compression": "zip"
+ },
{
"filename": "chr-7.10.1.img",
"version": "7.10.1",
@@ -72,6 +81,15 @@
"direct_download_url": "https://download.mikrotik.com/routeros/7.1.5/chr-7.1.5.img.zip",
"compression": "zip"
},
+ {
+ "filename": "chr-6.49.10.img",
+ "version": "6.49.10",
+ "md5sum": "49ae1ecfe310aea1df37b824aa13cf84",
+ "filesize": 67108864,
+ "download_url": "http://www.mikrotik.com/download",
+ "direct_download_url": "https://download.mikrotik.com/routeros/6.49.10/chr-6.49.10.img.zip",
+ "compression": "zip"
+ },
{
"filename": "chr-6.49.6.img",
"version": "6.49.6",
@@ -92,6 +110,12 @@
}
],
"versions": [
+ {
+ "name": "7.11.2",
+ "images": {
+ "hda_disk_image": "chr-7.11.2.img"
+ }
+ },
{
"name": "7.10.1",
"images": {
@@ -122,6 +146,12 @@
"hda_disk_image": "chr-7.1.5.img"
}
},
+ {
+ "name": "6.49.10",
+ "images": {
+ "hda_disk_image": "chr-6.49.10.img"
+ }
+ },
{
"name": "6.49.6",
"images": {
diff --git a/gns3server/appliances/ntopng.gns3a b/gns3server/appliances/ntopng.gns3a
index c54cc217..3d13352b 100644
--- a/gns3server/appliances/ntopng.gns3a
+++ b/gns3server/appliances/ntopng.gns3a
@@ -5,6 +5,7 @@
"description": "ntopng is the next generation version of the original ntop, a network traffic probe that shows the network usage, similar to what the popular top Unix command does. ntopng is based on libpcap and it has been written in a portable way in order to virtually run on every Unix platform, MacOSX and on Windows as well. ntopng users can use a a web browser to navigate through ntop (that acts as a web server) traffic information and get a dump of the network status. In the latter case, ntopng can be seen as a simple RMON-like agent with an embedded web interface.",
"vendor_name": "ntop",
"vendor_url": "https://www.ntop.org/",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/ntopng.jpg",
"documentation_url": "https://www.ntop.org/guides/ntopng/",
"product_name": "ntopng",
"registry_version": 4,
diff --git a/gns3server/appliances/op5-monitor.gns3a b/gns3server/appliances/op5-monitor.gns3a
index 32881514..9bfeab89 100644
--- a/gns3server/appliances/op5-monitor.gns3a
+++ b/gns3server/appliances/op5-monitor.gns3a
@@ -5,6 +5,7 @@
"description": "Over 200,000 IT staff across medium to large enterprises worldwide are currently using OP5 Monitor as their preferred network monitoring software.\nOP5 Monitor allows you to take control of your IT, enabling your network to be more responsive, more reliable and even faster than ever before. With unparalleled scalability, OP5 Monitor grows as your company grows, so you'll understand why we say this is the last network monitor you'll ever need to purchase.",
"vendor_name": "OP5",
"vendor_url": "https://www.op5.com/",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/OP5 Monitor.jpg",
"documentation_url": "https://kb.op5.com/display/MAN/Documentation+Home#sthash.pohb5bis.dpbs",
"product_name": "OP5 Monitor",
"product_url": "https://www.op5.com/op5-monitor/",
diff --git a/gns3server/appliances/open-media-vault.gns3a b/gns3server/appliances/open-media-vault.gns3a
index 262a8db0..9b634b93 100644
--- a/gns3server/appliances/open-media-vault.gns3a
+++ b/gns3server/appliances/open-media-vault.gns3a
@@ -26,6 +26,14 @@
"kvm": "require"
},
"images": [
+ {
+ "filename": "openmediavault_6.5.0-amd64.iso",
+ "version": "6.5.0",
+ "md5sum": "aa40e5ca50748b139cba2f4ac704a72d",
+ "filesize": 941621248,
+ "download_url": "https://www.openmediavault.org/download.html",
+ "direct_download_url": "https://sourceforge.net/projects/openmediavault/files/6.5.0/openmediavault_6.5.0-amd64.iso"
+ },
{
"filename": "openmediavault_6.0.24-amd64.iso",
"version": "6.0.24",
@@ -60,6 +68,14 @@
}
],
"versions": [
+ {
+ "name": "6.5.0",
+ "images": {
+ "hda_disk_image": "empty30G.qcow2",
+ "hdb_disk_image": "empty30G.qcow2",
+ "cdrom_image": "openmediavault_6.5.0-amd64.iso"
+ }
+ },
{
"name": "6.0.24",
"images": {
diff --git a/gns3server/appliances/opensuse.gns3a b/gns3server/appliances/opensuse.gns3a
index 8c1d8731..b254cfd9 100644
--- a/gns3server/appliances/opensuse.gns3a
+++ b/gns3server/appliances/opensuse.gns3a
@@ -5,6 +5,7 @@
"description": "openSUSE is a free and Linux-based operating system for PC, Laptop or Server. The openSUSE project is a community program sponsored by Novell. It is a general purpose operating system built on top of the Linux kernel, developed by the community-supported openSUSE Project and sponsored by SUSE and a number of other companies.",
"vendor_name": "SUSE LLC.",
"vendor_url": "https://www.opensuse.org/",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/openSUSE.png",
"documentation_url": "https://en.opensuse.org/Main_Page",
"product_name": "openSUSE",
"product_url": "https://www.opensuse.org/#Leap",
diff --git a/gns3server/appliances/openvswitch.gns3a b/gns3server/appliances/openvswitch.gns3a
index 14c35d2c..f3e19b9f 100644
--- a/gns3server/appliances/openvswitch.gns3a
+++ b/gns3server/appliances/openvswitch.gns3a
@@ -5,6 +5,7 @@
"description": "Open vSwitch is a production quality, multilayer virtual switch licensed under the open source Apache 2.0 license. It is designed to enable massive network automation through programmatic extension, while still supporting standard management interfaces and protocols (e.g. NetFlow, sFlow, IPFIX, RSPAN, CLI, LACP, 802.1ag). In addition, it is designed to support distribution across multiple physical servers similar to VMware's vNetwork distributed vswitch or Cisco's Nexus 1000V.",
"vendor_name": "Open vSwitch",
"vendor_url": "http://openvswitch.org/",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/Open vSwitch.jpg",
"documentation_url": "http://openvswitch.org/support/",
"product_name": "Open vSwitch",
"product_url": "http://openvswitch.org/",
diff --git a/gns3server/appliances/openwrt.gns3a b/gns3server/appliances/openwrt.gns3a
index e6428658..add7eb58 100644
--- a/gns3server/appliances/openwrt.gns3a
+++ b/gns3server/appliances/openwrt.gns3a
@@ -23,6 +23,15 @@
"kvm": "allow"
},
"images": [
+ {
+ "filename": "openwrt-23.05.0-x86-64-generic-ext4-combined.img",
+ "version": "23.05.0",
+ "md5sum": "8d53c7aa2605a8848b0b2ca759fc924f",
+ "filesize": 126353408,
+ "download_url": "https://downloads.openwrt.org/releases/23.05.0/targets/x86/64/",
+ "direct_download_url": "https://downloads.openwrt.org/releases/23.05.0/targets/x86/64/openwrt-23.05.0-x86-64-generic-ext4-combined.img.gz",
+ "compression": "gzip"
+ },
{
"filename": "openwrt-22.03.0-x86-64-generic-ext4-combined.img",
"version": "22.03.0",
@@ -170,7 +179,7 @@
{
"filename": "openwrt-18.06.5-x86-64-combined-ext4.img",
"version": "18.06.5",
- "md5sum": "6fce24c15f0bc75af16c133b839aea30",
+ "md5sum": "a0f72f4e75e15bef06396fa31eb1bc82",
"filesize": 285736960,
"download_url": "https://downloads.openwrt.org/releases/18.06.5/targets/x86/64/",
"direct_download_url": "https://downloads.openwrt.org/releases/18.06.5/targets/x86/64/openwrt-18.06.5-x86-64-combined-ext4.img.gz",
@@ -179,7 +188,7 @@
{
"filename": "openwrt-18.06.2-x86-64-combined-ext4.img",
"version": "18.06.2",
- "md5sum": "d112cd432bf51e2ddadbf9513f272fd9",
+ "md5sum": "9996a3c070b3e2ea582d28293bd78055",
"filesize": 285736960,
"download_url": "https://downloads.openwrt.org/releases/18.06.2/targets/x86/64/",
"direct_download_url": "https://downloads.openwrt.org/releases/18.06.2/targets/x86/64/openwrt-18.06.2-x86-64-combined-ext4.img.gz",
@@ -214,6 +223,12 @@
}
],
"versions": [
+ {
+ "name": "23.05.0",
+ "images": {
+ "hda_disk_image": "openwrt-23.05.0-x86-64-generic-ext4-combined.img"
+ }
+ },
{
"name": "22.03.0",
"images": {
diff --git a/gns3server/appliances/oracle-linux-cloud.gns3a b/gns3server/appliances/oracle-linux-cloud.gns3a
index a9709bcc..f4517beb 100644
--- a/gns3server/appliances/oracle-linux-cloud.gns3a
+++ b/gns3server/appliances/oracle-linux-cloud.gns3a
@@ -26,6 +26,14 @@
"options": "-cpu host -nographic"
},
"images": [
+ {
+ "filename": "OL9U2_x86_64-kvm-b197.qcow",
+ "version": "9.2",
+ "md5sum": "2ff3d0bc8a243ad89c96215f303f1c73",
+ "filesize": 560791552,
+ "download_url": "https://yum.oracle.com/oracle-linux-templates.html",
+ "direct_download_url": "https://yum.oracle.com/templates/OracleLinux/OL9/u2/x86_64/OL9U2_x86_64-kvm-b197.qcow"
+ },
{
"filename": "OL9U1_x86_64-kvm-b158.qcow",
"version": "9.1",
@@ -34,6 +42,14 @@
"download_url": "https://yum.oracle.com/oracle-linux-templates.html",
"direct_download_url": "https://yum.oracle.com/templates/OracleLinux/OL9/u1/x86_64/OL9U1_x86_64-kvm-b158.qcow"
},
+ {
+ "filename": "OL8U8_x86_64-kvm-b198.qcow",
+ "version": "8.8",
+ "md5sum": "717622f373d77349cc102a3a325efbd3",
+ "filesize": 934215680,
+ "download_url": "https://yum.oracle.com/oracle-linux-templates.html",
+ "direct_download_url": "https://yum.oracle.com/templates/OracleLinux/OL8/u8/x86_64/OL8U8_x86_64-kvm-b198.qcow"
+ },
{
"filename": "OL8U7_x86_64-kvm-b148.qcow",
"version": "8.7",
@@ -42,6 +58,14 @@
"download_url": "https://yum.oracle.com/oracle-linux-templates.html",
"direct_download_url": "https://yum.oracle.com/templates/OracleLinux/OL8/u7/x86_64/OL8U7_x86_64-kvm-b148.qcow"
},
+ {
+ "filename": "OL7U9_x86_64-kvm-b145.qcow",
+ "version": "7.9",
+ "md5sum": "e60d4145a69b34026db6121109ca9131",
+ "filesize": 725221376,
+ "download_url": "https://yum.oracle.com/oracle-linux-templates.html",
+ "direct_download_url": "https://yum.oracle.com/templates/OracleLinux/OL7/u9/x86_64/OL7U9_x86_64-kvm-b145.qcow"
+ },
{
"filename": "oracle-cloud-init-data.iso",
"version": "1.1",
@@ -52,6 +76,13 @@
}
],
"versions": [
+{
+ "name": "9.2",
+ "images": {
+ "hda_disk_image": "OL9U2_x86_64-kvm-b197.qcow",
+ "cdrom_image": "oracle-cloud-init-data.iso"
+ }
+ },
{
"name": "9.1",
"images": {
@@ -59,12 +90,26 @@
"cdrom_image": "oracle-cloud-init-data.iso"
}
},
+ {
+ "name": "8.8",
+ "images": {
+ "hda_disk_image": "OL8U8_x86_64-kvm-b198.qcow",
+ "cdrom_image": "oracle-cloud-init-data.iso"
+ }
+ },
{
"name": "8.7",
"images": {
"hda_disk_image": "OL8U7_x86_64-kvm-b148.qcow",
"cdrom_image": "oracle-cloud-init-data.iso"
}
+ },
+ {
+ "name": "7.9",
+ "images": {
+ "hda_disk_image": "OL7U9_x86_64-kvm-b145.qcow",
+ "cdrom_image": "oracle-cloud-init-data.iso"
+ }
}
]
}
diff --git a/gns3server/appliances/ostinato.gns3a b/gns3server/appliances/ostinato.gns3a
index fe00eddf..ec9a991b 100644
--- a/gns3server/appliances/ostinato.gns3a
+++ b/gns3server/appliances/ostinato.gns3a
@@ -5,6 +5,7 @@
"description": "Packet crafter and traffic generator for network engineers",
"vendor_name": "Ostinato",
"vendor_url": "https://ostinato.org/",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/Ostinato.png",
"documentation_url": "https://ostinato.org/docs",
"product_name": "Ostinato",
"product_url": "https://ostinato.org/",
diff --git a/gns3server/appliances/packetfence-zen.gns3a b/gns3server/appliances/packetfence-zen.gns3a
index 06cedbe8..68394f5e 100644
--- a/gns3server/appliances/packetfence-zen.gns3a
+++ b/gns3server/appliances/packetfence-zen.gns3a
@@ -5,6 +5,7 @@
"description": "PacketFence is a fully supported, trusted, Free and Open Source network access control (NAC) solution. Boasting an impressive feature set including a captive-portal for registration and remediation, centralized wired and wireless management, 802.1X support, layer-2 isolation of problematic devices, integration with the Snort IDS and the Nessus vulnerability scanner; PacketFence can be used to effectively secure networks - from small to very large heterogeneous networks.",
"vendor_name": "Inverse inc.",
"vendor_url": "https://packetfence.org/",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/PacketFence ZEN.jpg",
"documentation_url": "https://packetfence.org/support/index.html#/documentation",
"product_name": "PacketFence ZEN",
"product_url": "https://packetfence.org/about.html",
diff --git a/gns3server/appliances/rhel.gns3a b/gns3server/appliances/rhel.gns3a
index cff50307..291802b5 100644
--- a/gns3server/appliances/rhel.gns3a
+++ b/gns3server/appliances/rhel.gns3a
@@ -23,7 +23,7 @@
"console_type": "telnet",
"boot_priority": "c",
"kvm": "require",
- "options": "-nographic"
+ "options": "-cpu host -nographic"
},
"images": [
{
diff --git a/gns3server/appliances/rockylinux.gns3a b/gns3server/appliances/rockylinux.gns3a
index 2dd3fd1b..fcbfad71 100644
--- a/gns3server/appliances/rockylinux.gns3a
+++ b/gns3server/appliances/rockylinux.gns3a
@@ -27,12 +27,20 @@
},
"images": [
{
- "filename": "Rocky-8-GenericCloud-8.5-20211114.2.x86_64.qcow2",
- "version": "8.5",
- "md5sum": "44982ddace75a1dba17942401086d72c",
- "filesize": 1502701568,
- "download_url": "https://download.rockylinux.org/pub/rocky/8/images/",
- "direct_download_url": "https://download.rockylinux.org/pub/rocky/8/images/Rocky-8-GenericCloud-8.5-20211114.2.x86_64.qcow2"
+ "filename": "Rocky-9-GenericCloud-Base-9.2-20230513.0.x86_64.qcow2",
+ "version": "9.2",
+ "md5sum": "2022bdb49a691119f1fd3cc76de0a846",
+ "filesize": 989265920,
+ "download_url": "https://download.rockylinux.org/pub/rocky/9/images/x86_64/",
+ "direct_download_url": "https://download.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base-9.2-20230513.0.x86_64.qcow2"
+ },
+ {
+ "filename": "Rocky-8-GenericCloud-Base-8.8-20230518.0.x86_64.qcow2",
+ "version": "8.8",
+ "md5sum": "3ad7d355909cc37100c037562e4b3b6d",
+ "filesize": 1800536064,
+ "download_url": "https://download.rockylinux.org/pub/rocky/8/images/x86_64/",
+ "direct_download_url": "https://download.rockylinux.org/pub/rocky/8/images/x86_64/Rocky-8-GenericCloud-Base-8.8-20230518.0.x86_64.qcow2"
},
{
"filename": "rocky-cloud-init-data.iso",
@@ -45,9 +53,16 @@
],
"versions": [
{
- "name": "8.5",
+ "name": "9.2",
"images": {
- "hda_disk_image": "Rocky-8-GenericCloud-8.5-20211114.2.x86_64.qcow2",
+ "hda_disk_image": "Rocky-9-GenericCloud-Base-9.2-20230513.0.x86_64.qcow2",
+ "cdrom_image": "rocky-cloud-init-data.iso"
+ }
+ },
+ {
+ "name": "8.8",
+ "images": {
+ "hda_disk_image": "Rocky-8-GenericCloud-Base-8.8-20230518.0.x86_64.qcow2",
"cdrom_image": "rocky-cloud-init-data.iso"
}
}
diff --git a/gns3server/appliances/security-onion.gns3a b/gns3server/appliances/security-onion.gns3a
index cccca609..f4f6cf1a 100644
--- a/gns3server/appliances/security-onion.gns3a
+++ b/gns3server/appliances/security-onion.gns3a
@@ -5,6 +5,7 @@
"description": "Security Onion is a Linux distro for intrusion detection, network security monitoring, and log management. It's based on Ubuntu and contains Snort, Suricata, Bro, OSSEC, Sguil, Squert, ELSA, Xplico, NetworkMiner, and many other security tools. The easy-to-use Setup wizard allows you to build an army of distributed sensors for your enterprise in minutes!",
"vendor_name": "Security Onion Solutions, LLC",
"vendor_url": "https://securityonion.net/",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/Security Onion.png",
"documentation_url": "https://github.com/Security-Onion-Solutions/security-onion/wiki",
"product_name": "Security Onion",
"product_url": "https://securityonion.net/",
diff --git a/gns3server/appliances/ubuntu-cloud.gns3a b/gns3server/appliances/ubuntu-cloud.gns3a
index 49d007cf..a9496741 100644
--- a/gns3server/appliances/ubuntu-cloud.gns3a
+++ b/gns3server/appliances/ubuntu-cloud.gns3a
@@ -5,6 +5,7 @@
"description": "The term 'Ubuntu Cloud Guest' refers to the Official Ubuntu images that are available at http://cloud-images.ubuntu.com . These images are built by Canonical. They are then registered on EC2, and compressed tarfiles are made also available for download. For using those images on a public cloud such as Amazon EC2, you simply choose an image and launch it. To use those images on a private cloud, or to run the image on a local hypervisor (such as KVM) you would need to download those images and either publish them to your private cloud, or launch them directly on a hypervisor. The following sections explain in more details how to perform each of those actions",
"vendor_name": "Canonical Inc.",
"vendor_url": "https://www.ubuntu.com",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/Ubuntu Cloud Guest.png",
"documentation_url": "https://help.ubuntu.com/community/UEC/Images",
"product_name": "Ubuntu Cloud Guest",
"product_url": "https://www.ubuntu.com/cloud",
diff --git a/gns3server/appliances/ubuntu-docker.gns3a b/gns3server/appliances/ubuntu-docker.gns3a
index bd6dcca7..110fba40 100644
--- a/gns3server/appliances/ubuntu-docker.gns3a
+++ b/gns3server/appliances/ubuntu-docker.gns3a
@@ -5,6 +5,7 @@
"description": "Ubuntu is a Debian-based Linux operating system, with Unity as its default desktop environment. It is based on free software and named after the Southern African philosophy of ubuntu (literally, \"human-ness\"), which often is translated as \"humanity towards others\" or \"the belief in a universal bond of sharing that connects all humanity\".",
"vendor_name": "Canonical",
"vendor_url": "http://www.ubuntu.com",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/Ubuntu Docker Guest.png",
"product_name": "Ubuntu",
"registry_version": 4,
"status": "stable",
diff --git a/gns3server/appliances/ubuntu-gui.gns3a b/gns3server/appliances/ubuntu-gui.gns3a
index fc1e5d2d..9e31f420 100644
--- a/gns3server/appliances/ubuntu-gui.gns3a
+++ b/gns3server/appliances/ubuntu-gui.gns3a
@@ -5,6 +5,7 @@
"description": "Ubuntu is a full-featured Linux operating system which is based on Debian distribution and freely available with both community and professional support, it comes with Unity as its default desktop environment. There are other flavors of Ubuntu available with other desktops as default like Ubuntu Gnome, Lubuntu, Xubuntu, and so on. A tightly-integrated selection of excellent applications is included, and an incredible variety of add-on software is just a few clicks away. A default installation of Ubuntu contains a wide range of software that includes LibreOffice, Firefox, Empathy, Transmission, etc.",
"vendor_name": "Canonical Inc.",
"vendor_url": "https://www.ubuntu.com",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/Ubuntu Desktop Guest.png",
"documentation_url": "https://help.ubuntu.com",
"product_name": "Ubuntu",
"product_url": "https://www.ubuntu.com/desktop",
diff --git a/gns3server/appliances/untangle.gns3a b/gns3server/appliances/untangle.gns3a
index cdf0bbad..8612469a 100644
--- a/gns3server/appliances/untangle.gns3a
+++ b/gns3server/appliances/untangle.gns3a
@@ -5,6 +5,7 @@
"description": "Untangle's NG Firewall enables you to quickly and easily create the network policies that deliver the perfect balance between security and productivity. Untangle combines Unified Threat Management (UTM)-to address all of the key network threats-with policy management tools that enable you to define access and control by individuals, groups or company-wide. And with industry-leading reports, you'll have complete visibility into and control over everything that's happening on your network.",
"vendor_name": "Untangle",
"vendor_url": "https://www.untangle.com/",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/Untangle NG.jpg",
"documentation_url": "http://wiki.untangle.com/index.php/Main_Page",
"product_name": "Untangle NG",
"product_url": "https://www.untangle.com/untangle-ng-firewall/",
diff --git a/gns3server/appliances/vyos.gns3a b/gns3server/appliances/vyos.gns3a
index fd6f97f1..925e39ef 100644
--- a/gns3server/appliances/vyos.gns3a
+++ b/gns3server/appliances/vyos.gns3a
@@ -60,7 +60,7 @@
"md5sum": "3fece6363f9766f862e26d292d0ed5a3",
"filesize": 430964736,
"download_url": "https://support.vyos.io/en/downloads/files/vyos-1-2-9-s1-generic-iso-image",
- "direct_download_url": "https://s3-us.vyos.io/1.2.9-S1/vyos-1.2.9-S1-amd64.iso"
+ "direct_download_url": "https://legacy-lts-images.vyos.io/1.2.9-S1/vyos-1.2.9-S1-amd64.iso"
},
{
"filename": "vyos-1.2.9-S1-10G-qemu.qcow2",
@@ -68,7 +68,7 @@
"md5sum": "0a70d78b80a3716d42487c02ef44f41f",
"filesize": 426967040,
"download_url": "https://support.vyos.io/en/downloads/files/vyos-1-2-9-s1-for-kvm",
- "direct_download_url": "https://s3-us.vyos.io/1.2.9-S1/vyos-1.2.9-S1-10G-qemu.qcow2"
+ "direct_download_url": "https://legacy-lts-images.vyos.io/1.2.9-S1/vyos-1.2.9-S1-10G-qemu.qcow2"
},
{
"filename": "vyos-1.2.9-amd64.iso",
@@ -76,7 +76,7 @@
"md5sum": "586be23b6256173e174c82d8f1f699a1",
"filesize": 430964736,
"download_url": "https://support.vyos.io/en/downloads/files/vyos-1-2-9-generic-iso-image",
- "direct_download_url": "https://s3-us.vyos.io/1.2.9/vyos-1.2.9-amd64.iso"
+ "direct_download_url": "https://legacy-lts-images.vyos.io/1.2.9/vyos-1.2.9-amd64.iso"
},
{
"filename": "vyos-1.2.9-10G-qemu.qcow2",
@@ -84,7 +84,7 @@
"md5sum": "76871c7b248c32f75177c419128257ac",
"filesize": 427360256,
"download_url": "https://support.vyos.io/en/downloads/files/vyos-1-2-9-10g-qemu-qcow2",
- "direct_download_url": "https://s3-us.vyos.io/1.2.9/vyos-1.2.9-10G-qemu.qcow2"
+ "direct_download_url": "https://legacy-lts-images.vyos.io/1.2.9/vyos-1.2.9-10G-qemu.qcow2"
},
{
"filename": "vyos-1.2.8-amd64.iso",
@@ -98,7 +98,7 @@
"version": "1.1.8",
"md5sum": "95a141d4b592b81c803cdf7e9b11d8ea",
"filesize": 241172480,
- "direct_download_url": "https://s3-us.vyos.io/vyos-1.1.8-amd64.iso"
+ "direct_download_url": "https://legacy-lts-images.vyos.io/vyos-1.1.8-amd64.iso"
},
{
"filename": "empty8G.qcow2",
diff --git a/gns3server/appliances/windows-11-dev-env.gns3a b/gns3server/appliances/windows-11-dev-env.gns3a
index b0888e46..b9b453e8 100644
--- a/gns3server/appliances/windows-11-dev-env.gns3a
+++ b/gns3server/appliances/windows-11-dev-env.gns3a
@@ -29,6 +29,14 @@
"kvm": "require"
},
"images": [
+ {
+ "filename": "WinDev2308Eval-disk1.vmdk",
+ "version": "2308",
+ "md5sum": "6a9b4ed6d7481f7bbf8a054c797b1eee",
+ "filesize": 24945341952,
+ "download_url": "https://download.microsoft.com/download/7/1/3/7135f2ab-8528-49fc-9252-8d5d94c697ef/WinDev2308Eval.VMWare.zip",
+ "compression": "zip"
+ },
{
"filename": "WinDev2212Eval-disk1.vmdk",
"version": "2212",
@@ -48,6 +56,13 @@
}
],
"versions": [
+ {
+ "name": "2308",
+ "images": {
+ "bios_image": "OVMF-edk2-stable202305.fd",
+ "hda_disk_image": "WinDev2308Eval-disk1.vmdk"
+ }
+ },
{
"name": "2212",
"images": {
diff --git a/gns3server/appliances/windows.gns3a b/gns3server/appliances/windows.gns3a
index 98c41571..1f0f111c 100644
--- a/gns3server/appliances/windows.gns3a
+++ b/gns3server/appliances/windows.gns3a
@@ -5,6 +5,7 @@
"description": "Microsoft Windows, or simply Windows, is a metafamily of graphical operating systems developed, marketed, and sold by Microsoft. It consists of several families of operating systems, each of which cater to a certain sector of the computing industry with the OS typically associated with IBM PC compatible architecture.",
"vendor_name": "Microsoft",
"vendor_url": "http://www.microsoft.com/",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/Windows.jpg",
"documentation_url": "https://technet.microsoft.com/en-us/library/cc498727.aspx",
"product_name": "Windows",
"product_url": "https://www.microsoft.com/en-us/windows",
diff --git a/gns3server/appliances/windows_server.gns3a b/gns3server/appliances/windows_server.gns3a
index fec9bcf0..6fe23e68 100644
--- a/gns3server/appliances/windows_server.gns3a
+++ b/gns3server/appliances/windows_server.gns3a
@@ -5,6 +5,7 @@
"description": "Microsoft Windows, or simply Windows, is a metafamily of graphical operating systems developed, marketed, and sold by Microsoft. It consists of several families of operating systems, each of which cater to a certain sector of the computing industry with the OS typically associated with IBM PC compatible architecture.",
"vendor_name": "Microsoft",
"vendor_url": "http://www.microsoft.com/",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/Windows Server.jpg",
"documentation_url": "https://technet.microsoft.com/en-us/library/cc498727.aspx",
"product_name": "Windows Server",
"product_url": "https://www.microsoft.com/en-us/windows",
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/qemu/__init__.py b/gns3server/compute/qemu/__init__.py
index e98bb9a8..a9fe0553 100644
--- a/gns3server/compute/qemu/__init__.py
+++ b/gns3server/compute/qemu/__init__.py
@@ -21,6 +21,8 @@ Qemu server module.
import asyncio
import os
import platform
+import shutil
+import shlex
import sys
import re
import subprocess
@@ -159,6 +161,44 @@ class Qemu(BaseManager):
return qemus
+ @staticmethod
+ async def create_disk_image(disk_image_path, options):
+ """
+ Create a Qemu disk (used by the controller to create empty disk images)
+
+ :param disk_image_path: disk image path
+ :param options: disk creation options
+ """
+
+ qemu_img_path = shutil.which("qemu-img")
+ if not qemu_img_path:
+ raise QemuError(f"Could not find qemu-img binary")
+
+ try:
+ if os.path.exists(disk_image_path):
+ raise QemuError(f"Could not create disk image '{disk_image_path}', file already exists")
+ except UnicodeEncodeError:
+ raise QemuError(
+ f"Could not create disk image '{disk_image_path}', "
+ "Disk image name contains characters not supported by the filesystem"
+ )
+
+ img_format = options.pop("format")
+ img_size = options.pop("size")
+ command = [qemu_img_path, "create", "-f", img_format]
+ for option in sorted(options.keys()):
+ command.extend(["-o", f"{option}={options[option]}"])
+ command.append(disk_image_path)
+ command.append(f"{img_size}M")
+ command_string = " ".join(shlex.quote(s) for s in command)
+ output = ""
+ try:
+ log.info(f"Executing qemu-img with: {command_string}")
+ output = await subprocess_check_output(*command, stderr=True)
+ log.info(f"Qemu disk image'{disk_image_path}' created")
+ except (OSError, subprocess.SubprocessError) as e:
+ raise QemuError(f"Could not create '{disk_image_path}' disk image: {e}\n{output}")
+
@staticmethod
async def get_qemu_version(qemu_path):
"""
@@ -178,25 +218,6 @@ class Qemu(BaseManager):
except (OSError, subprocess.SubprocessError) as e:
raise QemuError(f"Error while looking for the Qemu version: {e}")
- @staticmethod
- async def _get_qemu_img_version(qemu_img_path):
- """
- Gets the Qemu-img version.
-
- :param qemu_img_path: path to Qemu-img executable.
- """
-
- try:
- output = await subprocess_check_output(qemu_img_path, "--version")
- match = re.search(r"version\s+([0-9a-z\-\.]+)", output)
- if match:
- version = match.group(1)
- return version
- else:
- raise QemuError("Could not determine the Qemu-img version for '{}'".format(qemu_img_path))
- except (OSError, subprocess.SubprocessError) as e:
- raise QemuError("Error while looking for the Qemu-img version: {}".format(e))
-
@staticmethod
async def get_swtpm_version(swtpm_path):
"""
diff --git a/gns3server/compute/virtualbox/__init__.py b/gns3server/compute/virtualbox/__init__.py
index 628a24c2..5627fb5e 100644
--- a/gns3server/compute/virtualbox/__init__.py
+++ b/gns3server/compute/virtualbox/__init__.py
@@ -100,9 +100,14 @@ class VirtualBox(BaseManager):
command.extend(args)
command_string = " ".join(command)
log.info(f"Executing VBoxManage with command: {command_string}")
+ env = os.environ.copy()
+ env["LANG"] = "en" # force english output because we rely on it to parse the output
try:
process = await asyncio.create_subprocess_exec(
- *command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
+ *command,
+ stdout=asyncio.subprocess.PIPE,
+ stderr=asyncio.subprocess.PIPE,
+ env=env
)
except (OSError, subprocess.SubprocessError) as e:
raise VirtualBoxError(f"Could not execute VBoxManage: {e}")
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/controller/__init__.py b/gns3server/controller/__init__.py
index 4ecc9f08..ab3bb0b0 100644
--- a/gns3server/controller/__init__.py
+++ b/gns3server/controller/__init__.py
@@ -315,7 +315,7 @@ class Controller:
if not os.path.exists(os.path.join(dst_path, filename)):
shutil.copy(os.path.join(resource_path, filename), os.path.join(dst_path, filename))
else:
- for entry in importlib_resources.files(f'gns3server.{resource_name}').iterdir():
+ for entry in importlib_resources.files('gns3server').joinpath(resource_name).iterdir():
full_path = os.path.join(dst_path, entry.name)
if entry.is_file() and not os.path.exists(full_path):
log.debug(f'Installing {resource_name} resource file "{entry.name}" to "{full_path}"')
diff --git a/gns3server/controller/gns3vm/virtualbox_gns3_vm.py b/gns3server/controller/gns3vm/virtualbox_gns3_vm.py
index 06ca5370..79d97d94 100644
--- a/gns3server/controller/gns3vm/virtualbox_gns3_vm.py
+++ b/gns3server/controller/gns3vm/virtualbox_gns3_vm.py
@@ -15,11 +15,13 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+import re
import sys
import aiohttp
import logging
import asyncio
import socket
+import ipaddress
from .base_gns3_vm import BaseGNS3VM
from .gns3_vm_error import GNS3VMError
@@ -77,9 +79,6 @@ class VirtualBoxGNS3VM(BaseGNS3VM):
except ValueError:
continue
self._system_properties[name.strip()] = value.strip()
- if "API Version" in self._system_properties:
- # API version is not consistent between VirtualBox versions, the key is named "API Version" in VirtualBox 7
- self._system_properties["API version"] = self._system_properties.pop("API Version")
async def _check_requirements(self):
"""
@@ -164,6 +163,44 @@ class VirtualBoxGNS3VM(BaseGNS3VM):
return True
return False
+ async def _add_dhcp_server(self, vboxnet):
+ """
+ Add a DHCP server for vboxnet.
+
+ :param vboxnet: vboxnet name
+ """
+
+ hostonlyifs = await self._execute("list", ["hostonlyifs"])
+ pattern = r"IPAddress:\s+(\d+\.\d+\.\d+\.\d+)\nNetworkMask:\s+(\d+\.\d+\.\d+\.\d+)"
+ match = re.search(pattern, hostonlyifs)
+
+ if match:
+ ip_address = match.group(1)
+ netmask = match.group(2)
+ else:
+ raise GNS3VMError("Could not find IP address and netmask for vboxnet {}".format(vboxnet))
+
+ try:
+ interface = ipaddress.IPv4Interface(f"{ip_address}/{netmask}")
+ subnet = ipaddress.IPv4Network(str(interface.network))
+ dhcp_server_ip = str(interface.ip + 1)
+ netmask = str(subnet.netmask)
+ lower_ip = str(interface.ip + 2)
+ upper_ip = str(subnet.network_address + subnet.num_addresses - 2)
+ except ValueError:
+ raise GNS3VMError("Invalid IP address and netmask for vboxnet {}: {}/{}".format(vboxnet, ip_address, netmask))
+
+ dhcp_server_args = [
+ "add",
+ "--network=HostInterfaceNetworking-{}".format(vboxnet),
+ "--server-ip={}".format(dhcp_server_ip),
+ "--netmask={}".format(netmask),
+ "--lower-ip={}".format(lower_ip),
+ "--upper-ip={}".format(upper_ip),
+ "--enable"
+ ]
+ await self._execute("dhcpserver", dhcp_server_args)
+
async def _check_vboxnet_exists(self, vboxnet, vboxnet_type):
"""
Check if the vboxnet interface exists
@@ -266,12 +303,20 @@ class VirtualBoxGNS3VM(BaseGNS3VM):
await self.set_hostonly_network(interface_number, first_available_vboxnet)
vboxnet = first_available_vboxnet
else:
- raise GNS3VMError('VirtualBox host-only network "{}" does not exist, please make the sure the network adapter {} configuration is valid for "{}"'.format(vboxnet,
- interface_number,
- self._vmname))
+ try:
+ await self._execute("hostonlyif", ["create"])
+ except GNS3VMError:
+ raise GNS3VMError('VirtualBox host-only network "{}" does not exist and could not be automatically created, please make the sure the network adapter {} configuration is valid for "{}"'.format(
+ vboxnet,
+ interface_number,
+ self._vmname
+ ))
if backend_type == "hostonlyadapter" and not (await self._check_dhcp_server(vboxnet)):
- raise GNS3VMError('DHCP must be enabled on VirtualBox host-only network "{}"'.format(vboxnet))
+ try:
+ await self._add_dhcp_server(vboxnet)
+ except GNS3VMError as e:
+ raise GNS3VMError("Could not add DHCP server for vboxnet {}: {}, please configure manually".format(vboxnet, e))
vm_state = await self._get_state()
log.info(f'"{self._vmname}" state is {vm_state}')
diff --git a/gns3server/controller/node.py b/gns3server/controller/node.py
index 68da72a7..839f36c4 100644
--- a/gns3server/controller/node.py
+++ b/gns3server/controller/node.py
@@ -559,7 +559,7 @@ class Node:
# None properties are not be sent because it can mean the emulator doesn't support it
for key in list(data.keys()):
- if data[key] is None or data[key] is {} or key in self.CONTROLLER_ONLY_PROPERTIES:
+ if data[key] is None or data[key] == {} or key in self.CONTROLLER_ONLY_PROPERTIES:
del data[key]
return data
diff --git a/gns3server/controller/ports/port_factory.py b/gns3server/controller/ports/port_factory.py
index efa97350..070e391c 100644
--- a/gns3server/controller/ports/port_factory.py
+++ b/gns3server/controller/ports/port_factory.py
@@ -42,7 +42,7 @@ PORTS = {
class PortFactory:
"""
- Factory to create an Port object based on the type
+ Factory to create a Port object based on the type
"""
def __new__(cls, name, interface_number, adapter_number, port_number, port_type, **kwargs):
diff --git a/gns3server/crash_report.py b/gns3server/crash_report.py
index a529d78f..3ccd6cc1 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://45f39fa6ea64493b8966a263049e844c@o19455.ingest.sentry.io/38482"
+ DSN = "https://c6696321127aaa1b5bfd332536eb3676@o19455.ingest.sentry.io/38482"
_instance = None
def __init__(self):
diff --git a/gns3server/db/repositories/pools.py b/gns3server/db/repositories/pools.py
index ff95fc0f..e32c94fb 100644
--- a/gns3server/db/repositories/pools.py
+++ b/gns3server/db/repositories/pools.py
@@ -80,6 +80,18 @@ class ResourcePoolsRepository(BaseRepository):
await self._db_session.commit()
return result.rowcount > 0
+ async def get_resource_memberships(self, resource_id: UUID) -> List[models.UserGroup]:
+ """
+ Get all resource memberships in resource pools.
+ """
+
+ query = select(models.ResourcePool).\
+ join(models.ResourcePool.resources).\
+ filter(models.Resource.resource_id == resource_id)
+
+ result = await self._db_session.execute(query)
+ return result.scalars().all()
+
async def get_resource_pool(self, resource_pool_id: UUID) -> Optional[models.ResourcePool]:
"""
Get a resource pool by its ID.
diff --git a/gns3server/schemas/__init__.py b/gns3server/schemas/__init__.py
index d38b31d3..d331de54 100644
--- a/gns3server/schemas/__init__.py
+++ b/gns3server/schemas/__init__.py
@@ -82,4 +82,4 @@ from .compute.vmware_nodes import VMwareCreate, VMwareUpdate, VMware
from .compute.virtualbox_nodes import VirtualBoxCreate, VirtualBoxUpdate, VirtualBox
# Schemas for both controller and compute
-from .qemu_disk_image import QemuDiskImageCreate, QemuDiskImageUpdate
+from .qemu_disk_image import QemuDiskImageFormat, QemuDiskImageCreate, QemuDiskImageUpdate
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/schemas/controller/nodes.py b/gns3server/schemas/controller/nodes.py
index c05014bc..d732c80a 100644
--- a/gns3server/schemas/controller/nodes.py
+++ b/gns3server/schemas/controller/nodes.py
@@ -134,19 +134,6 @@ class NodeBase(BaseModel):
first_port_name: Optional[str] = Field(None, description="Name of the first port")
custom_adapters: Optional[List[CustomAdapter]] = None
- @model_validator(mode='before')
- @classmethod
- def set_default_port_name_format_and_port_segment_size(cls, data: Any) -> Any:
-
- if "port_name_format" not in data:
- if data.get('node_type') == NodeType.iou:
- data['port_name_format'] = "Ethernet{segment0}/{port0}"
- data['port_segment_size'] = 4
- else:
- data['port_name_format'] = "Ethernet{0}"
- data['port_segment_size'] = 0
- return data
-
class NodeCreate(NodeBase):
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/26.49028ab13de5de406c90.js b/gns3server/static/web-ui/26.49028ab13de5de406c90.js
deleted file mode 100644
index 0c692c06..00000000
--- a/gns3server/static/web-ui/26.49028ab13de5de406c90.js
+++ /dev/null
@@ -1 +0,0 @@
-"use strict";(self.webpackChunkgns3_web_ui=self.webpackChunkgns3_web_ui||[]).push([[26],{91026:function(W,g,a){a.r(g),a.d(g,{TopologySummaryComponent:function(){return U}});var t=a(38999),_=a(96852),h=a(14200),f=a(36889),v=a(3941),y=a(15132),p=a(40098),x=a(39095),c=a(88802),S=a(73044),d=a(59412),T=a(93386);function C(i,e){if(1&i){var o=t.EpF();t.TgZ(0,"div",2),t.NdJ("mousemove",function(r){return t.CHM(o),t.oxw().dragWidget(r)},!1,t.evT)("mouseup",function(){return t.CHM(o),t.oxw().toggleDragging(!1)},!1,t.evT),t.qZA()}}function b(i,e){1&i&&(t.O4$(),t.TgZ(0,"svg",28),t._UZ(1,"rect",29),t.qZA())}function E(i,e){1&i&&(t.O4$(),t.TgZ(0,"svg",28),t._UZ(1,"rect",30),t.qZA())}function Z(i,e){1&i&&(t.O4$(),t.TgZ(0,"svg",28),t._UZ(1,"rect",31),t.qZA())}function O(i,e){if(1&i&&(t.TgZ(0,"div"),t._uU(1),t.qZA()),2&i){var o=t.oxw().$implicit;t.xp6(1),t.lnq(" ",o.console_type,"://",o.console_host,":",o.console," ")}}function P(i,e){1&i&&(t.TgZ(0,"div"),t._uU(1," none "),t.qZA())}function M(i,e){if(1&i&&(t.TgZ(0,"div",25),t.TgZ(1,"div"),t.YNc(2,b,2,0,"svg",26),t.YNc(3,E,2,0,"svg",26),t.YNc(4,Z,2,0,"svg",26),t._uU(5),t.qZA(),t.YNc(6,O,2,3,"div",27),t.YNc(7,P,2,0,"div",27),t.qZA()),2&i){var o=e.$implicit;t.xp6(2),t.Q6J("ngIf","started"===o.status),t.xp6(1),t.Q6J("ngIf","suspended"===o.status),t.xp6(1),t.Q6J("ngIf","stopped"===o.status),t.xp6(1),t.hij(" ",o.name," "),t.xp6(1),t.Q6J("ngIf",null!=o.console&&null!=o.console&&"none"!=o.console_type),t.xp6(1),t.Q6J("ngIf",null==o.console||"none"===o.console_type)}}function w(i,e){1&i&&(t.O4$(),t.TgZ(0,"svg",28),t._UZ(1,"rect",29),t.qZA())}function A(i,e){1&i&&(t.O4$(),t.TgZ(0,"svg",28),t._UZ(1,"rect",31),t.qZA())}function F(i,e){if(1&i&&(t.TgZ(0,"div",25),t.TgZ(1,"div"),t.YNc(2,w,2,0,"svg",26),t.YNc(3,A,2,0,"svg",26),t._uU(4),t.qZA(),t.TgZ(5,"div"),t._uU(6),t.qZA(),t.TgZ(7,"div"),t._uU(8),t.qZA(),t.qZA()),2&i){var o=e.$implicit,s=t.oxw(2);t.xp6(2),t.Q6J("ngIf",o.connected),t.xp6(1),t.Q6J("ngIf",!o.connected),t.xp6(1),t.hij(" ",o.name," "),t.xp6(2),t.hij(" ",o.host," "),t.xp6(2),t.hij(" ",s.server.location," ")}}var I=function(i){return{lightTheme:i}},D=function(){return{right:!0,left:!0,bottom:!0,top:!0}};function N(i,e){if(1&i){var o=t.EpF();t.TgZ(0,"div",3),t.NdJ("mousedown",function(){return t.CHM(o),t.oxw().toggleDragging(!0)})("resizeStart",function(){return t.CHM(o),t.oxw().toggleDragging(!1)})("resizeEnd",function(n){return t.CHM(o),t.oxw().onResizeEnd(n)}),t.TgZ(1,"div",4),t.TgZ(2,"mat-tab-group"),t.TgZ(3,"mat-tab",5),t.NdJ("click",function(){return t.CHM(o),t.oxw().toggleTopologyVisibility(!0)}),t.TgZ(4,"div",6),t.TgZ(5,"div",7),t.TgZ(6,"mat-select",8),t.TgZ(7,"mat-optgroup",9),t.TgZ(8,"mat-option",10),t.NdJ("onSelectionChange",function(){return t.CHM(o),t.oxw().applyStatusFilter("started")}),t._uU(9,"started"),t.qZA(),t.TgZ(10,"mat-option",11),t.NdJ("onSelectionChange",function(){return t.CHM(o),t.oxw().applyStatusFilter("suspended")}),t._uU(11,"suspended"),t.qZA(),t.TgZ(12,"mat-option",12),t.NdJ("onSelectionChange",function(){return t.CHM(o),t.oxw().applyStatusFilter("stopped")}),t._uU(13,"stopped"),t.qZA(),t.qZA(),t.TgZ(14,"mat-optgroup",13),t.TgZ(15,"mat-option",14),t.NdJ("onSelectionChange",function(){return t.CHM(o),t.oxw().applyCaptureFilter("capture")}),t._uU(16,"active capture(s)"),t.qZA(),t.TgZ(17,"mat-option",15),t.NdJ("onSelectionChange",function(){return t.CHM(o),t.oxw().applyCaptureFilter("packet")}),t._uU(18,"active packet captures"),t.qZA(),t.qZA(),t.qZA(),t.qZA(),t.TgZ(19,"div",16),t.TgZ(20,"mat-select",17),t.NdJ("selectionChange",function(){return t.CHM(o),t.oxw().setSortingOrder()})("valueChange",function(n){return t.CHM(o),t.oxw().sortingOrder=n}),t.TgZ(21,"mat-option",18),t._uU(22,"sort by name ascending"),t.qZA(),t.TgZ(23,"mat-option",19),t._uU(24,"sort by name descending"),t.qZA(),t.qZA(),t.qZA(),t._UZ(25,"mat-divider",20),t.TgZ(26,"div",21),t.YNc(27,M,8,6,"div",22),t.qZA(),t.qZA(),t.qZA(),t.TgZ(28,"mat-tab",23),t.NdJ("click",function(){return t.CHM(o),t.oxw().toggleTopologyVisibility(!1)}),t.TgZ(29,"div",6),t.TgZ(30,"div",24),t.YNc(31,F,9,5,"div",22),t.qZA(),t.qZA(),t.qZA(),t.qZA(),t.qZA(),t.qZA()}if(2&i){var s=t.oxw();t.Q6J("ngStyle",s.style)("ngClass",t.VKq(9,I,s.isLightThemeEnabled))("validateResize",s.validate)("resizeEdges",t.DdM(11,D))("enableGhostResize",!0),t.xp6(20),t.Q6J("value",s.sortingOrder),t.xp6(6),t.Q6J("ngStyle",s.styleInside),t.xp6(1),t.Q6J("ngForOf",s.filteredNodes),t.xp6(4),t.Q6J("ngForOf",s.computes)}}var U=function(){function i(e,o,s,r,n){this.nodesDataSource=e,this.projectService=o,this.computeService=s,this.linksDataSource=r,this.themeService=n,this.closeTopologySummary=new t.vpe,this.style={},this.styleInside={height:"280px"},this.subscriptions=[],this.nodes=[],this.filteredNodes=[],this.sortingOrder="asc",this.startedStatusFilterEnabled=!1,this.suspendedStatusFilterEnabled=!1,this.stoppedStatusFilterEnabled=!1,this.captureFilterEnabled=!1,this.packetFilterEnabled=!1,this.computes=[],this.isTopologyVisible=!0,this.isDraggingEnabled=!1,this.isLightThemeEnabled=!1}return i.prototype.ngOnInit=function(){var e=this;this.isLightThemeEnabled="light"===this.themeService.getActualTheme(),this.subscriptions.push(this.nodesDataSource.changes.subscribe(function(o){e.nodes=o,e.nodes.forEach(function(s){("0.0.0.0"===s.console_host||"0:0:0:0:0:0:0:0"===s.console_host||"::"===s.console_host)&&(s.console_host=e.server.host)}),e.filteredNodes=o.sort("asc"===e.sortingOrder?e.compareAsc:e.compareDesc)})),this.projectService.getStatistics(this.server,this.project.project_id).subscribe(function(o){e.projectsStatistics=o}),this.computeService.getComputes(this.server).subscribe(function(o){e.computes=o}),this.revertPosition()},i.prototype.revertPosition=function(){var e=localStorage.getItem("leftPosition"),o=localStorage.getItem("rightPosition"),s=localStorage.getItem("topPosition"),r=localStorage.getItem("widthOfWidget"),n=localStorage.getItem("heightOfWidget");this.style=s?{position:"fixed",left:+e+"px",right:+o+"px",top:+s+"px",width:+r+"px",height:+n+"px"}:{top:"60px",right:"0px",width:"320px",height:"400px"}},i.prototype.toggleDragging=function(e){this.isDraggingEnabled=e},i.prototype.dragWidget=function(e){var o=Number(e.movementX),s=Number(e.movementY),r=Number(this.style.width.split("px")[0]),n=Number(this.style.height.split("px")[0]),l=Number(this.style.top.split("px")[0])+s;if(this.style.left){var u=Number(this.style.left.split("px")[0])+o;this.style={position:"fixed",left:u+"px",top:l+"px",width:r+"px",height:n+"px"},localStorage.setItem("leftPosition",u.toString()),localStorage.setItem("topPosition",l.toString()),localStorage.setItem("widthOfWidget",r.toString()),localStorage.setItem("heightOfWidget",n.toString())}else{var m=Number(this.style.right.split("px")[0])-o;this.style={position:"fixed",right:m+"px",top:l+"px",width:r+"px",height:n+"px"},localStorage.setItem("rightPosition",m.toString()),localStorage.setItem("topPosition",l.toString()),localStorage.setItem("widthOfWidget",r.toString()),localStorage.setItem("heightOfWidget",n.toString())}},i.prototype.validate=function(e){return!(e.rectangle.width&&e.rectangle.height&&(e.rectangle.width<290||e.rectangle.height<260))},i.prototype.onResizeEnd=function(e){this.style={position:"fixed",left:e.rectangle.left+"px",top:e.rectangle.top+"px",width:e.rectangle.width+"px",height:e.rectangle.height+"px"},this.styleInside={height:e.rectangle.height-120+"px"}},i.prototype.toggleTopologyVisibility=function(e){this.isTopologyVisible=e,this.revertPosition()},i.prototype.compareAsc=function(e,o){return e.name
-
+
@@ -46,6 +46,6 @@
gtag('config', 'G-5D6FZL9923');
-
+