Skip to content

Commit

Permalink
Fix: Implemented CORS support on supervisor endpoints.
Browse files Browse the repository at this point in the history
  • Loading branch information
nesitor committed Feb 14, 2024
1 parent 76e8adc commit e4a5a6b
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 9 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
7 changes: 4 additions & 3 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 Down
107 changes: 103 additions & 4 deletions src/aleph/vm/orchestrator/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
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

import aiodns
import aiohttp
from aiohttp import web
from aiohttp.web_exceptions import HTTPNotFound
from aiohttp_cors import ResourceOptions, custom_cors
from aleph_message.exceptions import UnknownHashError
from aleph_message.models import ItemHash, MessageType
from pydantic import ValidationError
Expand Down Expand Up @@ -110,16 +112,34 @@ def authenticate_request(request: web.Request) -> None:
raise web.HTTPUnauthorized(reason="Invalid token", text="401 Invalid token")


@custom_cors(
{
"*": ResourceOptions(
allow_credentials=True,
allow_headers="*",
expose_headers="*",
)
}
)
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)


@custom_cors(
{
"*": ResourceOptions(
allow_credentials=True,
allow_headers="*",
expose_headers="*",
)
}
)
async def about_executions(request: web.Request) -> web.Response:
authenticate_request(request)
pool: VmPool = request.app["vm_pool"]
Expand All @@ -129,6 +149,15 @@ async def about_executions(request: web.Request) -> web.Response:
)


@custom_cors(
{
"*": ResourceOptions(
allow_credentials=True,
allow_headers="*",
expose_headers="*",
)
}
)
async def list_executions(request: web.Request) -> web.Response:
pool: VmPool = request.app["vm_pool"]
return web.json_response(
Expand All @@ -143,7 +172,6 @@ async def list_executions(request: web.Request) -> web.Response:
if execution.is_running
},
dumps=dumps_for_json,
headers={"Access-Control-Allow-Origin": "*"},
)


Expand All @@ -155,9 +183,18 @@ async def about_config(request: web.Request) -> web.Response:
)


@custom_cors(
{
"*": ResourceOptions(
allow_credentials=True,
allow_headers="*",
expose_headers="*",
)
}
)
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 +211,15 @@ async def index(request: web.Request):
return web.Response(content_type="text/html", body=body)


@custom_cors(
{
"*": ResourceOptions(
allow_credentials=True,
allow_headers="*",
expose_headers="*",
)
}
)
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 +261,29 @@ async def status_check_fastapi(request: web.Request, vm_id: Optional[ItemHash] =
)


@custom_cors(
{
"*": ResourceOptions(
allow_credentials=True,
allow_headers="*",
expose_headers="*",
)
}
)
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))


@custom_cors(
{
"*": ResourceOptions(
allow_credentials=True,
allow_headers="*",
expose_headers="*",
)
}
)
async def status_check_host(request: web.Request):
"""Check that the platform is supported and configured correctly"""

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


@custom_cors(
{
"*": ResourceOptions(
allow_credentials=True,
allow_headers="*",
expose_headers="*",
)
}
)
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 +325,15 @@ async def status_check_ipv6(request: web.Request):
return web.json_response(result, headers={"Access-Control-Allow-Origin": "*"})


@custom_cors(
{
"*": ResourceOptions(
allow_credentials=True,
allow_headers="*",
expose_headers="*",
)
}
)
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 +359,15 @@ async def status_check_version(request: web.Request):
return web.HTTPForbidden(text=f"Outdated: version {current} < {reference}")


@custom_cors(
{
"*": ResourceOptions(
allow_credentials=True,
allow_headers="*",
expose_headers="*",
)
}
)
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 +505,15 @@ async def update_allocations(request: web.Request):
)


@custom_cors(
{
"*": ResourceOptions(
allow_credentials=True,
allow_headers="*",
expose_headers="*",
)
}
)
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 +601,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": "*"},
)
46 changes: 46 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 Down Expand Up @@ -50,6 +51,15 @@ def is_sender_authorized(authenticated_sender: str, message: BaseExecutableConte
return False


@custom_cors(
{
"*": ResourceOptions(
allow_credentials=True,
allow_headers="*",
expose_headers="*",
)
}
)
async def stream_logs(request: web.Request) -> web.StreamResponse:
"""Stream the logs of a VM.
Expand Down Expand Up @@ -105,6 +115,15 @@ async def authenticate_for_vm_or_403(execution, request, vm_hash, ws):
raise web.HTTPForbidden(body="Unauthorized sender")


@custom_cors(
{
"*": ResourceOptions(
allow_credentials=True,
allow_headers="*",
expose_headers="*",
)
}
)
@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 +150,15 @@ 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")


@custom_cors(
{
"*": ResourceOptions(
allow_credentials=True,
allow_headers="*",
expose_headers="*",
)
}
)
@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 +183,15 @@ async def operate_stop(request: web.Request, authenticated_sender: str) -> web.R
return web.Response(status=200, body="Already stopped, nothing to do")


@custom_cors(
{
"*": ResourceOptions(
allow_credentials=True,
allow_headers="*",
expose_headers="*",
)
}
)
@require_jwk_authentication
async def operate_reboot(request: web.Request, authenticated_sender: str) -> web.Response:
"""
Expand All @@ -181,6 +218,15 @@ 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}")


@custom_cors(
{
"*": ResourceOptions(
allow_credentials=True,
allow_headers="*",
expose_headers="*",
)
}
)
@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

0 comments on commit e4a5a6b

Please sign in to comment.