Skip to content

Commit

Permalink
Added CORS support on supervisor endpoints (#542)
Browse files Browse the repository at this point in the history
* Fix: Implemented CORS support on supervisor endpoints.

* Fix: Apply code review suggestions.

* Fix: Apply same new decorator to operator endpoints.
  • Loading branch information
nesitor authored Feb 20, 2024
1 parent a04783d commit efb5b30
Show file tree
Hide file tree
Showing 6 changed files with 42 additions and 18 deletions.
2 changes: 1 addition & 1 deletion packaging/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ debian-package-code:
cp ../examples/instance_message_from_aleph.json ./aleph-vm/opt/aleph-vm/examples/instance_message_from_aleph.json
cp -r ../examples/data ./aleph-vm/opt/aleph-vm/examples/data
mkdir -p ./aleph-vm/opt/aleph-vm/examples/volumes
pip3 install --target ./aleph-vm/opt/aleph-vm/ 'aleph-message==0.4.2' 'jwskate==0.8.0' 'eth-account==0.9.0' 'sentry-sdk==1.31.0' 'qmp==1.1.0' 'superfluid==0.2.1' 'sqlalchemy[asyncio]' 'aiosqlite==0.19.0' 'alembic==1.13.1'
pip3 install --target ./aleph-vm/opt/aleph-vm/ 'aleph-message==0.4.2' 'jwskate==0.8.0' 'eth-account==0.9.0' 'sentry-sdk==1.31.0' 'qmp==1.1.0' 'superfluid==0.2.1' 'sqlalchemy[asyncio]' 'aiosqlite==0.19.0' 'alembic==1.13.1' 'aiohttp_cors==0.7.0'
python3 -m compileall ./aleph-vm/opt/aleph-vm/

debian-package-resources: firecracker-bins vmlinux download-ipfs-kubo
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ dependencies = [
"superfluid~=0.2.1",
"sqlalchemy[asyncio]",
"aiosqlite==0.19.0",
"alembic==1.13.1"
"alembic==1.13.1",
"aiohttp_cors~=0.7.0",
]

[project.urls]
Expand Down
16 changes: 4 additions & 12 deletions src/aleph/vm/orchestrator/supervisor.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from secrets import token_urlsafe
from typing import Callable

import aiohttp_cors
from aiohttp import web

from aleph.vm.conf import settings
Expand Down Expand Up @@ -68,9 +69,6 @@ async def server_version_middleware(
return resp


app = web.Application(middlewares=[server_version_middleware])


async def allow_cors_on_endpoint(request: web.Request):
"""Allow CORS on endpoints that VM owners use to control their machine."""
return web.Response(
Expand All @@ -84,6 +82,9 @@ async def allow_cors_on_endpoint(request: web.Request):
)


app = web.Application(middlewares=[server_version_middleware])
cors = aiohttp_cors.setup(app)

app.add_routes(
[
# /about APIs return information about the VM Orchestrator
Expand All @@ -108,15 +109,6 @@ async def allow_cors_on_endpoint(request: web.Request):
web.get("/status/check/version", status_check_version),
web.get("/status/check/ipv6", status_check_ipv6),
web.get("/status/config", status_public_config),
# Allow CORS on endpoints expected to be called from a web browser
web.options("/about/executions/list", allow_cors_on_endpoint),
web.options("/about/usage/system", allow_cors_on_endpoint),
web.options("/control/allocation/notify", allow_cors_on_endpoint),
web.options(
"/control/machine/{ref}/{view:.*}",
allow_cors_on_endpoint,
),
web.options("/status/check/ipv6", allow_cors_on_endpoint),
# Raise an HTTP Error 404 if attempting to access an unknown URL within these paths.
web.get("/about/{suffix:.*}", lambda _: web.HTTPNotFound()),
web.get("/control/{suffix:.*}", lambda _: web.HTTPNotFound()),
Expand Down
20 changes: 16 additions & 4 deletions src/aleph/vm/orchestrator/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from hashlib import sha256
from json import JSONDecodeError
from pathlib import Path
from secrets import compare_digest
from string import Template
from typing import Optional

Expand Down Expand Up @@ -46,6 +47,7 @@
from aleph.vm.utils import (
HostNotFoundError,
b32_to_b16,
cors_allow_all,
dumps_for_json,
get_ref_from_dns,
)
Expand Down Expand Up @@ -110,16 +112,18 @@ def authenticate_request(request: web.Request) -> None:
raise web.HTTPUnauthorized(reason="Invalid token", text="401 Invalid token")


@cors_allow_all
async def about_login(request: web.Request) -> web.Response:
token = request.query.get("token")
if token == request.app["secret_token"]:
if compare_digest(token, request.app["secret_token"]):
response = web.HTTPFound("/about/config")
response.cookies["token"] = token
return response
else:
return web.json_response({"success": False}, status=401)


@cors_allow_all
async def about_executions(request: web.Request) -> web.Response:
authenticate_request(request)
pool: VmPool = request.app["vm_pool"]
Expand All @@ -129,6 +133,7 @@ async def about_executions(request: web.Request) -> web.Response:
)


@cors_allow_all
async def list_executions(request: web.Request) -> web.Response:
pool: VmPool = request.app["vm_pool"]
return web.json_response(
Expand All @@ -143,10 +148,10 @@ async def list_executions(request: web.Request) -> web.Response:
if execution.is_running
},
dumps=dumps_for_json,
headers={"Access-Control-Allow-Origin": "*"},
)


@cors_allow_all
async def about_config(request: web.Request) -> web.Response:
authenticate_request(request)
return web.json_response(
Expand All @@ -155,9 +160,10 @@ async def about_config(request: web.Request) -> web.Response:
)


@cors_allow_all
async def about_execution_records(_: web.Request):
records = await get_execution_records()
return web.json_response(records, dumps=dumps_for_json, headers={"Access-Control-Allow-Origin": "*"})
return web.json_response(records, dumps=dumps_for_json)


async def index(request: web.Request):
Expand All @@ -174,6 +180,7 @@ async def index(request: web.Request):
return web.Response(content_type="text/html", body=body)


@cors_allow_all
async def status_check_fastapi(request: web.Request, vm_id: Optional[ItemHash] = None):
"""Check that the FastAPI diagnostic VM runs correctly"""

Expand Down Expand Up @@ -215,11 +222,13 @@ async def status_check_fastapi(request: web.Request, vm_id: Optional[ItemHash] =
)


@cors_allow_all
async def status_check_fastapi_legacy(request: web.Request):
"""Check that the legacy FastAPI VM runs correctly"""
return await status_check_fastapi(request, vm_id=ItemHash(settings.LEGACY_CHECK_FASTAPI_VM_ID))


@cors_allow_all
async def status_check_host(request: web.Request):
"""Check that the platform is supported and configured correctly"""

Expand All @@ -239,6 +248,7 @@ async def status_check_host(request: web.Request):
return web.json_response(result, status=result_status, headers={"Access-Control-Allow-Origin": "*"})


@cors_allow_all
async def status_check_ipv6(request: web.Request):
"""Check that the platform has IPv6 egress connectivity"""
timeout = aiohttp.ClientTimeout(total=2)
Expand All @@ -252,6 +262,7 @@ async def status_check_ipv6(request: web.Request):
return web.json_response(result, headers={"Access-Control-Allow-Origin": "*"})


@cors_allow_all
async def status_check_version(request: web.Request):
"""Check if the software is running a version equal or newer than the given one"""
reference_str: Optional[str] = request.query.get("reference")
Expand All @@ -277,6 +288,7 @@ async def status_check_version(request: web.Request):
return web.HTTPForbidden(text=f"Outdated: version {current} < {reference}")


@cors_allow_all
async def status_public_config(request: web.Request):
"""Expose the public fields from the configuration"""
return web.json_response(
Expand Down Expand Up @@ -414,6 +426,7 @@ async def update_allocations(request: web.Request):
)


@cors_allow_all
async def notify_allocation(request: web.Request):
"""Notify instance allocation, only used for Pay as you Go feature"""
try:
Expand Down Expand Up @@ -501,5 +514,4 @@ async def notify_allocation(request: web.Request):
"errors": {vm_hash: repr(error) for vm_hash, error in scheduling_errors.items()},
},
status=status_code,
headers={"Access-Control-Allow-Origin": "*"},
)
7 changes: 7 additions & 0 deletions src/aleph/vm/orchestrator/views/operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import aiohttp.web_exceptions
from aiohttp import web
from aiohttp.web_urldispatcher import UrlMappingMatchInfo
from aiohttp_cors import ResourceOptions, custom_cors
from aleph_message.exceptions import UnknownHashError
from aleph_message.models import ItemHash
from aleph_message.models.execution import BaseExecutableContent
Expand All @@ -17,6 +18,7 @@
require_jwk_authentication,
)
from aleph.vm.pool import VmPool
from aleph.vm.utils import cors_allow_all

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -50,6 +52,7 @@ def is_sender_authorized(authenticated_sender: str, message: BaseExecutableConte
return False


@cors_allow_all
async def stream_logs(request: web.Request) -> web.StreamResponse:
"""Stream the logs of a VM.
Expand Down Expand Up @@ -105,6 +108,7 @@ async def authenticate_for_vm_or_403(execution, request, vm_hash, ws):
raise web.HTTPForbidden(body="Unauthorized sender")


@cors_allow_all
@require_jwk_authentication
async def operate_expire(request: web.Request, authenticated_sender: str) -> web.Response:
"""Stop the virtual machine, smoothly if possible.
Expand All @@ -131,6 +135,7 @@ async def operate_expire(request: web.Request, authenticated_sender: str) -> web
return web.Response(status=200, body=f"Expiring VM with ref {vm_hash} in {timeout} seconds")


@cors_allow_all
@require_jwk_authentication
async def operate_stop(request: web.Request, authenticated_sender: str) -> web.Response:
"""Stop the virtual machine, smoothly if possible."""
Expand All @@ -155,6 +160,7 @@ async def operate_stop(request: web.Request, authenticated_sender: str) -> web.R
return web.Response(status=200, body="Already stopped, nothing to do")


@cors_allow_all
@require_jwk_authentication
async def operate_reboot(request: web.Request, authenticated_sender: str) -> web.Response:
"""
Expand All @@ -181,6 +187,7 @@ async def operate_reboot(request: web.Request, authenticated_sender: str) -> web
return web.Response(status=200, body="Starting VM (was not running) with ref {vm_hash}")


@cors_allow_all
@require_jwk_authentication
async def operate_erase(request: web.Request, authenticated_sender: str) -> web.Response:
"""Delete all data stored by a virtual machine.
Expand Down
12 changes: 12 additions & 0 deletions src/aleph/vm/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import aiodns
import msgpack
from aiohttp_cors import ResourceOptions, custom_cors
from aleph_message.models import ExecutableContent, InstanceContent, ProgramContent
from aleph_message.models.execution.base import MachineType
from eth_typing import HexAddress, HexStr
Expand All @@ -31,6 +32,17 @@ def get_message_executable_content(message_dict: Dict) -> ExecutableContent:
raise ValueError(f"Unknown message type {message_dict['type']}")


def cors_allow_all(function):
default_config = {
"*": ResourceOptions(
allow_credentials=True,
allow_headers="*",
expose_headers="*",
)
}
return custom_cors(config=default_config)(function)


class MsgpackSerializable:
def __post_init__(self, *args, **kwargs):
if not is_dataclass(self):
Expand Down

0 comments on commit efb5b30

Please sign in to comment.