Merge pull request #2300 from GNS3/fix/1468

Fix compute authentication for websocket endpoints
This commit is contained in:
Jeremy Grossmann 2023-10-22 16:03:21 +10:00 committed by GitHub
commit f3ad97c398
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 700 additions and 211 deletions

View File

@ -3,4 +3,5 @@ flake8==6.1.0
pytest-timeout==2.2.0 pytest-timeout==2.2.0
pytest-asyncio==0.21.1 pytest-asyncio==0.21.1
requests==2.31.0 requests==2.31.0
httpx==0.25.0 httpx==0.24.1 # version 0.24.1 is required by httpx_ws
httpx_ws==0.4.2

View File

@ -199,14 +199,12 @@ compute_api.include_router(
compute_api.include_router( compute_api.include_router(
docker_nodes.router, docker_nodes.router,
dependencies=[Depends(compute_authentication)],
prefix="/projects/{project_id}/docker/nodes", prefix="/projects/{project_id}/docker/nodes",
tags=["Docker nodes"] tags=["Docker nodes"]
) )
compute_api.include_router( compute_api.include_router(
dynamips_nodes.router, dynamips_nodes.router,
dependencies=[Depends(compute_authentication)],
prefix="/projects/{project_id}/dynamips/nodes", prefix="/projects/{project_id}/dynamips/nodes",
tags=["Dynamips nodes"] tags=["Dynamips nodes"]
) )
@ -234,7 +232,6 @@ compute_api.include_router(
compute_api.include_router( compute_api.include_router(
iou_nodes.router, iou_nodes.router,
dependencies=[Depends(compute_authentication)],
prefix="/projects/{project_id}/iou/nodes", prefix="/projects/{project_id}/iou/nodes",
tags=["IOU nodes"]) tags=["IOU nodes"])
@ -247,28 +244,24 @@ compute_api.include_router(
compute_api.include_router( compute_api.include_router(
qemu_nodes.router, qemu_nodes.router,
dependencies=[Depends(compute_authentication)],
prefix="/projects/{project_id}/qemu/nodes", prefix="/projects/{project_id}/qemu/nodes",
tags=["Qemu nodes"] tags=["Qemu nodes"]
) )
compute_api.include_router( compute_api.include_router(
virtualbox_nodes.router, virtualbox_nodes.router,
dependencies=[Depends(compute_authentication)],
prefix="/projects/{project_id}/virtualbox/nodes", prefix="/projects/{project_id}/virtualbox/nodes",
tags=["VirtualBox nodes"] tags=["VirtualBox nodes"]
) )
compute_api.include_router( compute_api.include_router(
vmware_nodes.router, vmware_nodes.router,
dependencies=[Depends(compute_authentication)],
prefix="/projects/{project_id}/vmware/nodes", prefix="/projects/{project_id}/vmware/nodes",
tags=["VMware nodes"] tags=["VMware nodes"]
) )
compute_api.include_router( compute_api.include_router(
vpcs_nodes.router, vpcs_nodes.router,
dependencies=[Depends(compute_authentication)],
prefix="/projects/{project_id}/vpcs/nodes", prefix="/projects/{project_id}/vpcs/nodes",
tags=["VPCS nodes"] tags=["VPCS nodes"]
) )

View File

@ -15,12 +15,17 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import secrets 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 import HTTPBasic, HTTPBasicCredentials
from fastapi.security.utils import get_authorization_scheme_param
from gns3server.config import Config from gns3server.config import Config
from typing import Optional from typing import Optional, Union
log = logging.getLogger(__name__)
security = HTTPBasic() security = HTTPBasic()
@ -35,3 +40,44 @@ def compute_authentication(credentials: Optional[HTTPBasicCredentials] = Depends
detail="Invalid compute username or password", detail="Invalid compute username or password",
headers={"WWW-Authenticate": "Basic"}, 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

View File

@ -20,15 +20,18 @@ API routes for Docker nodes.
import os 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.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from uuid import UUID from uuid import UUID
from typing import Union
from gns3server import schemas from gns3server import schemas
from gns3server.compute.docker import Docker from gns3server.compute.docker import Docker
from gns3server.compute.docker.docker_vm import DockerVM 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"}} responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Docker node"}}
router = APIRouter(responses=responses) router = APIRouter(responses=responses)
@ -49,6 +52,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> DockerVM:
response_model=schemas.Docker, response_model=schemas.Docker,
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Docker node"}}, 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: 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() 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: def get_docker_node(node: DockerVM = Depends(dep_node)) -> schemas.Docker:
""" """
Return a Docker node. Return a Docker node.
@ -94,7 +102,11 @@ def get_docker_node(node: DockerVM = Depends(dep_node)) -> schemas.Docker:
return node.asdict() 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: async def update_docker_node(node_data: schemas.DockerUpdate, node: DockerVM = Depends(dep_node)) -> schemas.Docker:
""" """
Update a Docker node. Update a Docker node.
@ -131,7 +143,11 @@ async def update_docker_node(node_data: schemas.DockerUpdate, node: DockerVM = D
return node.asdict() 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: async def start_docker_node(node: DockerVM = Depends(dep_node)) -> None:
""" """
Start a Docker node. Start a Docker node.
@ -140,7 +156,11 @@ async def start_docker_node(node: DockerVM = Depends(dep_node)) -> None:
await node.start() 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: async def stop_docker_node(node: DockerVM = Depends(dep_node)) -> None:
""" """
Stop a Docker node. Stop a Docker node.
@ -149,7 +169,11 @@ async def stop_docker_node(node: DockerVM = Depends(dep_node)) -> None:
await node.stop() 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: async def suspend_docker_node(node: DockerVM = Depends(dep_node)) -> None:
""" """
Suspend a Docker node. Suspend a Docker node.
@ -158,7 +182,11 @@ async def suspend_docker_node(node: DockerVM = Depends(dep_node)) -> None:
await node.pause() 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: async def reload_docker_node(node: DockerVM = Depends(dep_node)) -> None:
""" """
Reload a Docker node. Reload a Docker node.
@ -167,7 +195,11 @@ async def reload_docker_node(node: DockerVM = Depends(dep_node)) -> None:
await node.restart() 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: async def pause_docker_node(node: DockerVM = Depends(dep_node)) -> None:
""" """
Pause a Docker node. Pause a Docker node.
@ -176,7 +208,11 @@ async def pause_docker_node(node: DockerVM = Depends(dep_node)) -> None:
await node.pause() 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: async def unpause_docker_node(node: DockerVM = Depends(dep_node)) -> None:
""" """
Unpause a Docker node. Unpause a Docker node.
@ -185,7 +221,11 @@ async def unpause_docker_node(node: DockerVM = Depends(dep_node)) -> None:
await node.unpause() 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: async def delete_docker_node(node: DockerVM = Depends(dep_node)) -> None:
""" """
Delete a Docker node. Delete a Docker node.
@ -194,7 +234,12 @@ async def delete_docker_node(node: DockerVM = Depends(dep_node)) -> None:
await node.delete() 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( async def duplicate_docker_node(
destination_node_id: UUID = Body(..., embed=True), destination_node_id: UUID = Body(..., embed=True),
node: DockerVM = Depends(dep_node) node: DockerVM = Depends(dep_node)
@ -211,6 +256,7 @@ async def duplicate_docker_node(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", "/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO, response_model=schemas.UDPNIO,
dependencies=[Depends(compute_authentication)]
) )
async def create_docker_node_nio( async def create_docker_node_nio(
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: DockerVM = Depends(dep_node) 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", "/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO, response_model=schemas.UDPNIO,
dependencies=[Depends(compute_authentication)]
) )
async def update_docker_node_nio( async def update_docker_node_nio(
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: DockerVM = Depends(dep_node) 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() 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( async def delete_docker_node_nio(
adapter_number: int, adapter_number: int,
port_number: int, port_number: int,
@ -259,7 +310,10 @@ async def delete_docker_node_nio(
await node.adapter_remove_nio_binding(adapter_number) 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( async def start_docker_node_capture(
adapter_number: int, adapter_number: int,
port_number: int, port_number: int,
@ -278,7 +332,8 @@ async def start_docker_node_capture(
@router.post( @router.post(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", "/{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( async def stop_docker_node_capture(
adapter_number: int, adapter_number: int,
@ -293,7 +348,10 @@ async def stop_docker_node_capture(
await node.stop_capture(adapter_number) 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( async def stream_pcap_file(
adapter_number: int, adapter_number: int,
port_number: int, port_number: int,
@ -310,15 +368,23 @@ async def stream_pcap_file(
@router.websocket("/{node_id}/console/ws") @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. 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: async def reset_console(node: DockerVM = Depends(dep_node)) -> None:
await node.reset_console() await node.reset_console()

View File

@ -20,16 +20,18 @@ API routes for Dynamips nodes.
import os 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.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from typing import List from typing import List, Union
from uuid import UUID from uuid import UUID
from gns3server.compute.dynamips import Dynamips from gns3server.compute.dynamips import Dynamips
from gns3server.compute.dynamips.nodes.router import Router from gns3server.compute.dynamips.nodes.router import Router
from gns3server import schemas 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"}} responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Dynamips node"}}
router = APIRouter(responses=responses) router = APIRouter(responses=responses)
@ -53,6 +55,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> Router:
response_model=schemas.Dynamips, response_model=schemas.Dynamips,
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Dynamips node"}}, 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: 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() 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: def get_router(node: Router = Depends(dep_node)) -> schemas.Dynamips:
""" """
Return Dynamips router. Return Dynamips router.
@ -93,7 +100,11 @@ def get_router(node: Router = Depends(dep_node)) -> schemas.Dynamips:
return node.asdict() 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: async def update_router(node_data: schemas.DynamipsUpdate, node: Router = Depends(dep_node)) -> schemas.Dynamips:
""" """
Update a Dynamips router. Update a Dynamips router.
@ -104,7 +115,11 @@ async def update_router(node_data: schemas.DynamipsUpdate, node: Router = Depend
return node.asdict() 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: async def delete_router(node: Router = Depends(dep_node)) -> None:
""" """
Delete a Dynamips router. 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) 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: async def start_router(node: Router = Depends(dep_node)) -> None:
""" """
Start a Dynamips router. Start a Dynamips router.
@ -126,7 +145,11 @@ async def start_router(node: Router = Depends(dep_node)) -> None:
await node.start() 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: async def stop_router(node: Router = Depends(dep_node)) -> None:
""" """
Stop a Dynamips router. Stop a Dynamips router.
@ -135,13 +158,21 @@ async def stop_router(node: Router = Depends(dep_node)) -> None:
await node.stop() 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: async def suspend_router(node: Router = Depends(dep_node)) -> None:
await node.suspend() 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: async def resume_router(node: Router = Depends(dep_node)) -> None:
""" """
Resume a suspended Dynamips router. Resume a suspended Dynamips router.
@ -150,7 +181,11 @@ async def resume_router(node: Router = Depends(dep_node)) -> None:
await node.resume() 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: async def reload_router(node: Router = Depends(dep_node)) -> None:
""" """
Reload a suspended Dynamips router. 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", "/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO, response_model=schemas.UDPNIO,
dependencies=[Depends(compute_authentication)]
) )
async def create_nio( async def create_nio(
adapter_number: int, adapter_number: int,
@ -183,6 +219,7 @@ async def create_nio(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", "/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO, response_model=schemas.UDPNIO,
dependencies=[Depends(compute_authentication)]
) )
async def update_nio( async def update_nio(
adapter_number: int, adapter_number: int,
@ -201,7 +238,11 @@ async def update_nio(
return nio.asdict() 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: 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. 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() 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( async def start_capture(
adapter_number: int, adapter_number: int,
port_number: int, port_number: int,
@ -228,7 +272,9 @@ async def start_capture(
@router.post( @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: 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) 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( async def stream_pcap_file(
adapter_number: int, adapter_number: int,
port_number: int, port_number: int,
@ -253,7 +302,10 @@ async def stream_pcap_file(
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap") 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]: async def get_idlepcs(node: Router = Depends(dep_node)) -> List[str]:
""" """
Retrieve Dynamips idle-pc proposals 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() 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: async def get_auto_idlepc(node: Router = Depends(dep_node)) -> dict:
""" """
Get an automatically guessed best idle-pc value. 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} 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: async def duplicate_router(destination_node_id: UUID, node: Router = Depends(dep_node)) -> schemas.Dynamips:
""" """
Duplicate a router. 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") @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. 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: async def reset_console(node: Router = Depends(dep_node)) -> None:
await node.reset_console() await node.reset_console()

View File

@ -30,6 +30,8 @@ from gns3server import schemas
from gns3server.compute.iou import IOU from gns3server.compute.iou import IOU
from gns3server.compute.iou.iou_vm import IOUVM 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"}} responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or IOU node"}}
router = APIRouter(responses=responses) router = APIRouter(responses=responses)
@ -50,6 +52,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> IOUVM:
response_model=schemas.IOU, response_model=schemas.IOU,
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create IOU node"}}, 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: 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() 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: def get_iou_node(node: IOUVM = Depends(dep_node)) -> schemas.IOU:
""" """
Return an IOU node. Return an IOU node.
@ -91,7 +98,11 @@ def get_iou_node(node: IOUVM = Depends(dep_node)) -> schemas.IOU:
return node.asdict() 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: async def update_iou_node(node_data: schemas.IOUUpdate, node: IOUVM = Depends(dep_node)) -> schemas.IOU:
""" """
Update an IOU node. Update an IOU node.
@ -112,7 +123,11 @@ async def update_iou_node(node_data: schemas.IOUUpdate, node: IOUVM = Depends(de
return node.asdict() 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: async def delete_iou_node(node: IOUVM = Depends(dep_node)) -> None:
""" """
Delete an IOU node. 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) 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( async def duplicate_iou_node(
destination_node_id: UUID = Body(..., embed=True), destination_node_id: UUID = Body(..., embed=True),
node: IOUVM = Depends(dep_node) node: IOUVM = Depends(dep_node)
@ -134,7 +154,11 @@ async def duplicate_iou_node(
return new_node.asdict() 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: async def start_iou_node(start_data: schemas.IOUStart, node: IOUVM = Depends(dep_node)) -> None:
""" """
Start an IOU node. Start an IOU node.
@ -148,7 +172,11 @@ async def start_iou_node(start_data: schemas.IOUStart, node: IOUVM = Depends(dep
await node.start() 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: async def stop_iou_node(node: IOUVM = Depends(dep_node)) -> None:
""" """
Stop an IOU node. Stop an IOU node.
@ -157,7 +185,11 @@ async def stop_iou_node(node: IOUVM = Depends(dep_node)) -> None:
await node.stop() 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: def suspend_iou_node(node: IOUVM = Depends(dep_node)) -> None:
""" """
Suspend an IOU node. Suspend an IOU node.
@ -167,7 +199,11 @@ def suspend_iou_node(node: IOUVM = Depends(dep_node)) -> None:
pass 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: async def reload_iou_node(node: IOUVM = Depends(dep_node)) -> None:
""" """
Reload an IOU node. 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", "/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
dependencies=[Depends(compute_authentication)]
) )
async def create_iou_node_nio( async def create_iou_node_nio(
adapter_number: int, adapter_number: int,
@ -200,6 +237,7 @@ async def create_iou_node_nio(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", "/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
dependencies=[Depends(compute_authentication)]
) )
async def update_iou_node_nio( async def update_iou_node_nio(
adapter_number: int, adapter_number: int,
@ -218,7 +256,11 @@ async def update_iou_node_nio(
return nio.asdict() 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: 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. 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) 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( async def start_iou_node_capture(
adapter_number: int, adapter_number: int,
port_number: int, port_number: int,
@ -244,7 +289,9 @@ async def start_iou_node_capture(
@router.post( @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: 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) 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( async def stream_pcap_file(
adapter_number: int, adapter_number: int,
port_number: int, port_number: int,
@ -269,16 +319,26 @@ async def stream_pcap_file(
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap") return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
@router.websocket("/{node_id}/console/ws") @router.websocket(
async def console_ws(websocket: WebSocket, node: IOUVM = Depends(dep_node)) -> None: "/{node_id}/console/ws",
)
async def console_ws(
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication),
node: IOUVM = Depends(dep_node)
) -> None:
""" """
Console WebSocket. 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: async def reset_console(node: IOUVM = Depends(dep_node)) -> None:
await node.reset_console() await node.reset_console()

View File

@ -18,14 +18,13 @@
API routes for compute notifications. API routes for compute notifications.
""" """
import base64
import binascii
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, status, HTTPException from fastapi import APIRouter, Depends, WebSocket, WebSocketDisconnect
from fastapi.security.utils import get_authorization_scheme_param from typing import Union
from websockets.exceptions import ConnectionClosed, WebSocketException from websockets.exceptions import ConnectionClosed, WebSocketException
from gns3server.compute.notification_manager import NotificationManager from gns3server.compute.notification_manager import NotificationManager
from .dependencies.authentication import ws_compute_authentication
import logging import logging
@ -35,53 +34,27 @@ router = APIRouter()
@router.websocket("/notifications/ws") @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. Receive project notifications about the project from WebSocket.
""" """
await websocket.accept() if websocket:
log.info(f"New client {websocket.client.host}:{websocket.client.port} has connected to compute WebSocket")
# 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
try: try:
data = base64.b64decode(param).decode("ascii") with NotificationManager.instance().queue() as queue:
except (ValueError, UnicodeDecodeError, binascii.Error): while True:
raise invalid_user_credentials_exc notification = await queue.get_json(5)
username, separator, password = data.partition(":") await websocket.send_text(notification)
if not separator: except (ConnectionClosed, WebSocketDisconnect):
raise invalid_user_credentials_exc log.info(f"Client {websocket.client.host}:{websocket.client.port} has disconnected from compute WebSocket")
except invalid_user_credentials_exc as e: except WebSocketException as e:
websocket_error = {"action": "log.error", "event": {"message": f"Could not authenticate while connecting to " log.warning(f"Error while sending to controller event to WebSocket client: {e}")
f"compute WebSocket: {e.detail}"}} finally:
await websocket.send_json(websocket_error) try:
return await websocket.close(code=1008) await websocket.close()
except OSError:
log.info(f"New client {websocket.client.host}:{websocket.client.port} has connected to compute WebSocket") pass # ignore OSError: [Errno 107] Transport endpoint is not connected
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
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -20,15 +20,17 @@ API routes for Qemu nodes.
import os 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.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from typing import Union
from uuid import UUID from uuid import UUID
from gns3server import schemas from gns3server import schemas
from gns3server.compute.qemu import Qemu from gns3server.compute.qemu import Qemu
from gns3server.compute.qemu.qemu_vm import QemuVM 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"}} 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, response_model=schemas.Qemu,
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Qemu node"}}, 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: 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() 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: def get_qemu_node(node: QemuVM = Depends(dep_node)) -> schemas.Qemu:
""" """
Return a Qemu node. Return a Qemu node.
@ -87,7 +94,11 @@ def get_qemu_node(node: QemuVM = Depends(dep_node)) -> schemas.Qemu:
return node.asdict() 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: async def update_qemu_node(node_data: schemas.QemuUpdate, node: QemuVM = Depends(dep_node)) -> schemas.Qemu:
""" """
Update a Qemu node. Update a Qemu node.
@ -103,7 +114,11 @@ async def update_qemu_node(node_data: schemas.QemuUpdate, node: QemuVM = Depends
return node.asdict() 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: async def delete_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
""" """
Delete a Qemu node. 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) 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( async def duplicate_qemu_node(
destination_node_id: UUID = Body(..., embed=True), destination_node_id: UUID = Body(..., embed=True),
node: QemuVM = Depends(dep_node) node: QemuVM = Depends(dep_node)
@ -127,7 +147,8 @@ async def duplicate_qemu_node(
@router.post( @router.post(
"/{node_id}/disk_image/{disk_name}", "/{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( async def create_qemu_disk_image(
disk_name: str, disk_name: str,
@ -144,7 +165,8 @@ async def create_qemu_disk_image(
@router.put( @router.put(
"/{node_id}/disk_image/{disk_name}", "/{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( async def update_qemu_disk_image(
disk_name: str, disk_name: str,
@ -161,7 +183,8 @@ async def update_qemu_disk_image(
@router.delete( @router.delete(
"/{node_id}/disk_image/{disk_name}", "/{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( async def delete_qemu_disk_image(
disk_name: str, disk_name: str,
@ -174,7 +197,11 @@ async def delete_qemu_disk_image(
node.delete_disk_image(disk_name) 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: async def start_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
""" """
Start a Qemu node. Start a Qemu node.
@ -183,7 +210,11 @@ async def start_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
await node.start() 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: async def stop_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
""" """
Stop a Qemu node. Stop a Qemu node.
@ -192,7 +223,11 @@ async def stop_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
await node.stop() 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: async def reload_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
""" """
Reload a Qemu node. Reload a Qemu node.
@ -201,7 +236,11 @@ async def reload_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
await node.reload() 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: async def suspend_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
""" """
Suspend a Qemu node. Suspend a Qemu node.
@ -210,7 +249,11 @@ async def suspend_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
await node.suspend() 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: async def resume_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
""" """
Resume a Qemu node. 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", "/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO, response_model=schemas.UDPNIO,
dependencies=[Depends(compute_authentication)]
) )
async def create_qemu_node_nio( 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", "/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO, response_model=schemas.UDPNIO,
dependencies=[Depends(compute_authentication)]
) )
async def update_qemu_node_nio( async def update_qemu_node_nio(
*, *,
@ -267,7 +312,11 @@ async def update_qemu_node_nio(
return nio.asdict() 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( async def delete_qemu_node_nio(
adapter_number: int, adapter_number: int,
port_number: int = Path(..., ge=0, le=0), 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) 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( async def start_qemu_node_capture(
*, *,
adapter_number: int, adapter_number: int,
@ -300,7 +352,9 @@ async def start_qemu_node_capture(
@router.post( @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( async def stop_qemu_node_capture(
adapter_number: int, adapter_number: int,
@ -315,7 +369,10 @@ async def stop_qemu_node_capture(
await node.stop_capture(adapter_number) 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( async def stream_pcap_file(
adapter_number: int, adapter_number: int,
port_number: int = Path(..., ge=0, le=0), 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") return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
@router.websocket("/{node_id}/console/ws") @router.websocket(
async def console_ws(websocket: WebSocket, node: QemuVM = Depends(dep_node)) -> None: "/{node_id}/console/ws"
)
async def console_ws(
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication),
node: QemuVM = Depends(dep_node)
) -> None:
""" """
Console WebSocket. 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: async def reset_console(node: QemuVM = Depends(dep_node)) -> None:
await node.reset_console() await node.reset_console()

View File

@ -20,16 +20,19 @@ API routes for VirtualBox nodes.
import os 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.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from uuid import UUID from uuid import UUID
from typing import Union
from gns3server import schemas from gns3server import schemas
from gns3server.compute.virtualbox import VirtualBox from gns3server.compute.virtualbox import VirtualBox
from gns3server.compute.virtualbox.virtualbox_error import VirtualBoxError from gns3server.compute.virtualbox.virtualbox_error import VirtualBoxError
from gns3server.compute.virtualbox.virtualbox_vm import VirtualBoxVM 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"}} responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or VirtualBox node"}}
router = APIRouter(responses=responses, deprecated=True) router = APIRouter(responses=responses, deprecated=True)
@ -50,6 +53,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> VirtualBoxVM:
response_model=schemas.VirtualBox, response_model=schemas.VirtualBox,
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VirtualBox node"}}, 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: 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() 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: def get_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> schemas.VirtualBox:
""" """
Return a VirtualBox node. Return a VirtualBox node.
@ -91,7 +99,11 @@ def get_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> schemas.Virtu
return node.asdict() 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( async def update_virtualbox_node(
node_data: schemas.VirtualBoxUpdate, node_data: schemas.VirtualBoxUpdate,
node: VirtualBoxVM = Depends(dep_node) node: VirtualBoxVM = Depends(dep_node)
@ -136,7 +148,11 @@ async def update_virtualbox_node(
return node.asdict() 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: async def delete_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
""" """
Delete a VirtualBox node. 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) 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: async def start_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
""" """
Start a VirtualBox node. Start a VirtualBox node.
@ -154,7 +174,11 @@ async def start_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
await node.start() 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: async def stop_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
""" """
Stop a VirtualBox node. Stop a VirtualBox node.
@ -163,7 +187,11 @@ async def stop_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
await node.stop() 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: async def suspend_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
""" """
Suspend a VirtualBox node. Suspend a VirtualBox node.
@ -172,7 +200,11 @@ async def suspend_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> Non
await node.suspend() 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: async def resume_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
""" """
Resume a VirtualBox node. Resume a VirtualBox node.
@ -181,7 +213,11 @@ async def resume_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None
await node.resume() 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: async def reload_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
""" """
Reload a VirtualBox node. 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", "/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO, response_model=schemas.UDPNIO,
dependencies=[Depends(compute_authentication)]
) )
async def create_virtualbox_node_nio( 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", "/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO, response_model=schemas.UDPNIO,
dependencies=[Depends(compute_authentication)]
) )
async def update_virtualbox_node_nio( async def update_virtualbox_node_nio(
*, *,
@ -238,7 +276,11 @@ async def update_virtualbox_node_nio(
return nio.asdict() 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( async def delete_virtualbox_node_nio(
adapter_number: int, adapter_number: int,
port_number: int = Path(..., ge=0, le=0), 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) 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( async def start_virtualbox_node_capture(
*, *,
adapter_number: int, adapter_number: int,
@ -271,7 +316,9 @@ async def start_virtualbox_node_capture(
@router.post( @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( async def stop_virtualbox_node_capture(
adapter_number: int, adapter_number: int,
@ -286,7 +333,10 @@ async def stop_virtualbox_node_capture(
await node.stop_capture(adapter_number) 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( async def stream_pcap_file(
adapter_number: int, adapter_number: int,
port_number: int = Path(..., ge=0, le=0), 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") return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
@router.websocket("/{node_id}/console/ws") @router.websocket(
async def console_ws(websocket: WebSocket, node: VirtualBoxVM = Depends(dep_node)) -> None: "/{node_id}/console/ws"
)
async def console_ws(
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication),
node: VirtualBoxVM = Depends(dep_node)
) -> None:
""" """
Console WebSocket. Console WebSocket.
""" """
@ -311,7 +366,11 @@ async def console_ws(websocket: WebSocket, node: VirtualBoxVM = Depends(dep_node
await node.start_websocket_console(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: VirtualBoxVM = Depends(dep_node)) -> None: async def reset_console(node: VirtualBoxVM = Depends(dep_node)) -> None:
await node.reset_console() await node.reset_console()

View File

@ -20,16 +20,18 @@ API routes for VMware nodes.
import os 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.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from uuid import UUID from uuid import UUID
from typing import Union
from gns3server import schemas from gns3server import schemas
from gns3server.compute.vmware import VMware from gns3server.compute.vmware import VMware
from gns3server.compute.project_manager import ProjectManager
from gns3server.compute.vmware.vmware_vm import VMwareVM 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"}} responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or VMware node"}}
router = APIRouter(responses=responses, deprecated=True) router = APIRouter(responses=responses, deprecated=True)
@ -50,6 +52,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> VMwareVM:
response_model=schemas.VMware, response_model=schemas.VMware,
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VMware node"}}, 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: 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() 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: def get_vmware_node(node: VMwareVM = Depends(dep_node)) -> schemas.VMware:
""" """
Return a VMware node. Return a VMware node.
@ -85,7 +92,11 @@ def get_vmware_node(node: VMwareVM = Depends(dep_node)) -> schemas.VMware:
return node.asdict() 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: def update_vmware_node(node_data: schemas.VMwareUpdate, node: VMwareVM = Depends(dep_node)) -> schemas.VMware:
""" """
Update a VMware node. Update a VMware node.
@ -102,7 +113,11 @@ def update_vmware_node(node_data: schemas.VMwareUpdate, node: VMwareVM = Depends
return node.asdict() 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: async def delete_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
""" """
Delete a VMware node. 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) 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: async def start_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
""" """
Start a VMware node. Start a VMware node.
@ -120,7 +139,11 @@ async def start_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
await node.start() 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: async def stop_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
""" """
Stop a VMware node. Stop a VMware node.
@ -129,7 +152,11 @@ async def stop_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
await node.stop() 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: async def suspend_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
""" """
Suspend a VMware node. Suspend a VMware node.
@ -138,7 +165,11 @@ async def suspend_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
await node.suspend() 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: async def resume_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
""" """
Resume a VMware node. Resume a VMware node.
@ -147,7 +178,11 @@ async def resume_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
await node.resume() 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: async def reload_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
""" """
Reload a VMware node. 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", "/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO, response_model=schemas.UDPNIO,
dependencies=[Depends(compute_authentication)]
) )
async def create_vmware_node_nio( 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", "/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO, response_model=schemas.UDPNIO,
dependencies=[Depends(compute_authentication)]
) )
async def update_vmware_node_nio( async def update_vmware_node_nio(
*, *,
@ -202,7 +239,11 @@ async def update_vmware_node_nio(
return nio.asdict() 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( async def delete_vmware_node_nio(
adapter_number: int, adapter_number: int,
port_number: int = Path(..., ge=0, le=0), 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) 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( async def start_vmware_node_capture(
*, *,
adapter_number: int, adapter_number: int,
@ -235,7 +279,9 @@ async def start_vmware_node_capture(
@router.post( @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( async def stop_vmware_node_capture(
adapter_number: int, adapter_number: int,
@ -250,7 +296,10 @@ async def stop_vmware_node_capture(
await node.stop_capture(adapter_number) 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( async def stream_pcap_file(
adapter_number: int, adapter_number: int,
port_number: int = Path(..., ge=0, le=0), 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") 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: def allocate_vmnet(node: VMwareVM = Depends(dep_node)) -> dict:
""" """
Allocate a VMware VMnet interface on the server. 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") @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. 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: async def reset_console(node: VMwareVM = Depends(dep_node)) -> None:
await node.reset_console() await node.reset_console()

View File

@ -20,15 +20,18 @@ API routes for VPCS nodes.
import os 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.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from typing import Union
from uuid import UUID from uuid import UUID
from gns3server import schemas from gns3server import schemas
from gns3server.compute.vpcs import VPCS from gns3server.compute.vpcs import VPCS
from gns3server.compute.vpcs.vpcs_vm import VPCSVM 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"}} responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or VMware node"}}
router = APIRouter(responses=responses) router = APIRouter(responses=responses)
@ -49,6 +52,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> VPCSVM:
response_model=schemas.VPCS, response_model=schemas.VPCS,
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VMware node"}}, 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: 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() 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: def get_vpcs_node(node: VPCSVM = Depends(dep_node)) -> schemas.VPCS:
""" """
Return a VPCS node. Return a VPCS node.
@ -78,7 +86,11 @@ def get_vpcs_node(node: VPCSVM = Depends(dep_node)) -> schemas.VPCS:
return node.asdict() 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: def update_vpcs_node(node_data: schemas.VPCSUpdate, node: VPCSVM = Depends(dep_node)) -> schemas.VPCS:
""" """
Update a VPCS node. Update a VPCS node.
@ -92,7 +104,11 @@ def update_vpcs_node(node_data: schemas.VPCSUpdate, node: VPCSVM = Depends(dep_n
return node.asdict() 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: async def delete_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
""" """
Delete a VPCS node. 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) 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( async def duplicate_vpcs_node(
destination_node_id: UUID = Body(..., embed=True), destination_node_id: UUID = Body(..., embed=True),
node: VPCSVM = Depends(dep_node)) -> None: node: VPCSVM = Depends(dep_node)) -> None:
@ -113,7 +134,11 @@ async def duplicate_vpcs_node(
return new_node.asdict() 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: async def start_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
""" """
Start a VPCS node. Start a VPCS node.
@ -122,7 +147,11 @@ async def start_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
await node.start() 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: async def stop_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
""" """
Stop a VPCS node. Stop a VPCS node.
@ -131,7 +160,11 @@ async def stop_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
await node.stop() 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: async def suspend_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
""" """
Suspend a VPCS node. Suspend a VPCS node.
@ -141,7 +174,11 @@ async def suspend_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
pass 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: async def reload_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
""" """
Reload a VPCS node. 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", "/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO, response_model=schemas.UDPNIO,
dependencies=[Depends(compute_authentication)]
) )
async def create_vpcs_node_nio( 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", "/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO, response_model=schemas.UDPNIO,
dependencies=[Depends(compute_authentication)]
) )
async def update_vpcs_node_nio( async def update_vpcs_node_nio(
*, *,
@ -196,7 +235,11 @@ async def update_vpcs_node_nio(
return nio.asdict() 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( async def delete_vpcs_node_nio(
*, *,
adapter_number: int = Path(..., ge=0, le=0), 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) 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( async def start_vpcs_node_capture(
*, *,
adapter_number: int = Path(..., ge=0, le=0), adapter_number: int = Path(..., ge=0, le=0),
@ -230,7 +276,9 @@ async def start_vpcs_node_capture(
@router.post( @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( async def stop_vpcs_node_capture(
*, *,
@ -246,13 +294,10 @@ async def stop_vpcs_node_capture(
await node.stop_capture(port_number) await node.stop_capture(port_number)
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT) @router.get(
async def reset_console(node: VPCSVM = Depends(dep_node)) -> None: "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream",
dependencies=[Depends(compute_authentication)]
await node.reset_console() )
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
async def stream_pcap_file( async def stream_pcap_file(
*, *,
adapter_number: int = Path(..., ge=0, le=0), 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") return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
@router.websocket("/{node_id}/console/ws") @router.websocket(
async def console_ws(websocket: WebSocket, node: VPCSVM = Depends(dep_node)) -> None: "/{node_id}/console/ws"
)
async def console_ws(
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication),
node: VPCSVM = Depends(dep_node)) -> None:
""" """
Console WebSocket. Console WebSocket.
""" """
await node.start_websocket_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()

View File

@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import re import logging
from fastapi import Request, Query, Depends, HTTPException, WebSocket, status from fastapi import Request, Query, Depends, HTTPException, WebSocket, status
from fastapi.security import OAuth2PasswordBearer from fastapi.security import OAuth2PasswordBearer
@ -26,6 +26,7 @@ from gns3server.db.repositories.rbac import RbacRepository
from gns3server.services import auth_service from gns3server.services import auth_service
from .database import get_repository from .database import get_repository
log = logging.getLogger(__name__)
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/v3/access/users/login", auto_error=False) 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 return user
except HTTPException as e: except HTTPException as e:
websocket_error = {"action": "log.error", "event": {"message": f"Could not authenticate while connecting to " err_msg = f"Could not authenticate while connecting to controller WebSocket: {e.detail}"
f"WebSocket: {e.detail}"}} websocket_error = {"action": "log.error", "event": {"message": err_msg}}
await websocket.send_json(websocket_error) await websocket.send_json(websocket_error)
await websocket.close(code=1008) log.error(err_msg)
return await websocket.close(code=1008)

View File

@ -29,6 +29,7 @@ from typing import List, Callable
from uuid import UUID from uuid import UUID
from gns3server.controller import Controller from gns3server.controller import Controller
from gns3server.config import Config
from gns3server.controller.node import Node from gns3server.controller.node import Node
from gns3server.controller.project import Project from gns3server.controller.project import Project
from gns3server.utils import force_unix_path from gns3server.utils import force_unix_path
@ -510,16 +511,22 @@ async def post_file(file_path: str, request: Request, node: Node = Depends(dep_n
# FIXME: response with correct status code (from compute) # FIXME: response with correct status code (from compute)
@router.websocket("/{node_id}/console/ws", dependencies=[Depends(has_privilege_on_websocket("Node.Console"))]) @router.websocket("/{node_id}/console/ws")
async def ws_console(websocket: WebSocket, node: Node = Depends(dep_node)) -> None: 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. WebSocket console.
Required privilege: Node.Console Required privilege: Node.Console
""" """
if current_user is None:
return
compute = node.compute compute = node.compute
await websocket.accept()
log.info( log.info(
f"New client {websocket.client.host}:{websocket.client.port} has connected to controller console WebSocket" f"New client {websocket.client.host}:{websocket.client.port} has connected to controller console WebSocket"
) )
@ -557,9 +564,20 @@ async def ws_console(websocket: WebSocket, node: Node = Depends(dep_node)) -> No
try: try:
# receive WebSocket data from compute console WebSocket and forward to client. # 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: log.info(f"Forwarding console WebSocket to '{ws_console_compute_url}'")
asyncio.ensure_future(ws_receive(ws_console_compute)) server_config = Config.instance().settings.Server
async for msg in ws_console_compute: 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: if msg.type == aiohttp.WSMsgType.TEXT:
await websocket.send_text(msg.data) await websocket.send_text(msg.data)
elif msg.type == aiohttp.WSMsgType.BINARY: elif msg.type == aiohttp.WSMsgType.BINARY:

View File

@ -485,6 +485,11 @@ class BaseNode:
:param ws: Websocket object :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": if self.status != "started":
raise NodeError(f"Node {self.name} is not 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") raise NodeError(f"Node {self.name} console type is not telnet")
try: try:
(telnet_reader, telnet_writer) = await asyncio.open_connection( host = self._manager.port_manager.console_host
self._manager.port_manager.console_host, self.console 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: except ConnectionError as e:
raise NodeError(f"Cannot connect to node {self.name} telnet server: {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): async def ws_forward(telnet_writer):
try: try:

View File

@ -20,6 +20,10 @@ from fastapi import FastAPI, status
from fastapi.routing import APIRoute, APIWebSocketRoute from fastapi.routing import APIRoute, APIWebSocketRoute
from starlette.routing import Mount from starlette.routing import Mount
from httpx import AsyncClient from httpx import AsyncClient
from httpx_ws import aconnect_ws
from httpx_ws.transport import ASGIWebSocketTransport
pytestmark = pytest.mark.asyncio pytestmark = pytest.mark.asyncio
@ -37,6 +41,7 @@ ALLOWED_CONTROLLER_ENDPOINTS = [
("/v3/symbols/default_symbols", "GET") ("/v3/symbols/default_symbols", "GET")
] ]
# Controller endpoints have a OAuth2 bearer token authentication # Controller endpoints have a OAuth2 bearer token authentication
async def test_controller_endpoints_require_authentication(app: FastAPI, unauthorized_client: AsyncClient) -> None: async def test_controller_endpoints_require_authentication(app: FastAPI, unauthorized_client: AsyncClient) -> None:
@ -47,7 +52,14 @@ async def test_controller_endpoints_require_authentication(app: FastAPI, unautho
response = await getattr(unauthorized_client, method.lower())(route.path) response = await getattr(unauthorized_client, method.lower())(route.path)
assert response.status_code == status.HTTP_401_UNAUTHORIZED assert response.status_code == status.HTTP_401_UNAUTHORIZED
elif isinstance(route, APIWebSocketRoute): elif isinstance(route, APIWebSocketRoute):
pass # TODO: test websocket route authentication params = {"token": "wrong_token"}
async with AsyncClient(base_url="http://test-api", transport=ASGIWebSocketTransport(app)) as client:
async with aconnect_ws(route.path, client, params=params) as ws:
json_notification = await ws.receive_json()
assert json_notification['event'] == {
'message': 'Could not authenticate while connecting to controller WebSocket: Could not validate credentials'
}
# Compute endpoints have a basic HTTP authentication # Compute endpoints have a basic HTTP authentication
async def test_compute_endpoints_require_authentication(app: FastAPI, unauthorized_client: AsyncClient) -> None: async def test_compute_endpoints_require_authentication(app: FastAPI, unauthorized_client: AsyncClient) -> None:
@ -55,9 +67,14 @@ async def test_compute_endpoints_require_authentication(app: FastAPI, unauthoriz
for route in app.routes: for route in app.routes:
if isinstance(route, Mount): if isinstance(route, Mount):
for compute_route in route.routes: for compute_route in route.routes:
if isinstance(compute_route, APIRoute): # APIWebSocketRoute if isinstance(compute_route, APIRoute):
for method in list(compute_route.methods): for method in list(compute_route.methods):
response = await getattr(unauthorized_client, method.lower())(route.path + compute_route.path) response = await getattr(unauthorized_client, method.lower())(route.path + compute_route.path)
assert response.status_code == status.HTTP_401_UNAUTHORIZED assert response.status_code == status.HTTP_401_UNAUTHORIZED
elif isinstance(compute_route, APIWebSocketRoute): elif isinstance(compute_route, APIWebSocketRoute):
pass # TODO: test websocket route authentication async with AsyncClient(base_url="http://test-api", transport=ASGIWebSocketTransport(app)) as client:
async with aconnect_ws(route.path + compute_route.path, client, auth=("wrong_user", "password123")) as ws:
json_notification = await ws.receive_json()
assert json_notification['event'] == {
'message': 'Could not authenticate while connecting to compute WebSocket: Could not validate credentials'
}