Skip to content

Commit a4e26a8

Browse files
authored
Implement get platform certificates endpoint (#619)
Problem: There was no endpoint to get the confidential platform certificates These are required in order to start the VM key exchange. Solution: Create that endpoint and return the platform certificates generated by the `sevctl` command.
1 parent b1ca017 commit a4e26a8

File tree

5 files changed

+96
-1
lines changed

5 files changed

+96
-1
lines changed

src/aleph/vm/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,7 @@ def check(self):
387387
assert (
388388
check_system_module("kvm_amd/parameters/sev_es") == "Y"
389389
), "SEV-ES feature isn't enabled, enable it in BIOS"
390+
assert is_command_available("sevctl"), "Command `sevctl` not found, run `cargo install sevctl`"
390391

391392
assert self.ENABLE_QEMU_SUPPORT, "Qemu Support is needed for confidential computing and it's disabled, "
392393
"enable it setting the env variable `ENABLE_QEMU_SUPPORT=True` in configuration"

src/aleph/vm/orchestrator/resources.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from pydantic import BaseModel, Field
1212

1313
from aleph.vm.conf import settings
14+
from aleph.vm.sevclient import SevClient
1415
from aleph.vm.utils import cors_allow_all
1516

1617

@@ -122,6 +123,18 @@ async def about_system_usage(_: web.Request):
122123
return web.json_response(text=usage.json(exclude_none=True))
123124

124125

126+
@cors_allow_all
127+
async def about_certificates(request: web.Request):
128+
"""Public endpoint to expose platform certificates for confidential computing."""
129+
130+
if not settings.ENABLE_CONFIDENTIAL_COMPUTING:
131+
return web.HTTPBadRequest(reason="Confidential computing setting not enabled on that server")
132+
133+
sev_client: SevClient = request.app["sev_client"]
134+
135+
return web.FileResponse(await sev_client.get_certificates())
136+
137+
125138
class Allocation(BaseModel):
126139
"""An allocation is the set of resources that are currently allocated on this orchestrator.
127140
It contains the item_hashes of all persistent VMs, instances, on-demand VMs and jobs.

src/aleph/vm/orchestrator/supervisor.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@
1818

1919
from aleph.vm.conf import settings
2020
from aleph.vm.pool import VmPool
21+
from aleph.vm.sevclient import SevClient
2122
from aleph.vm.version import __version__
2223

2324
from .metrics import create_tables, setup_engine
24-
from .resources import about_system_usage
25+
from .resources import about_certificates, about_system_usage
2526
from .tasks import (
2627
start_payment_monitoring_task,
2728
start_watch_for_messages_task,
@@ -95,6 +96,7 @@ def setup_webapp():
9596
web.get("/about/executions/details", about_executions),
9697
web.get("/about/executions/records", about_execution_records),
9798
web.get("/about/usage/system", about_system_usage),
99+
web.get("/about/certificates", about_certificates),
98100
web.get("/about/config", about_config),
99101
# /control APIs are used to control the VMs and access their logs
100102
web.post("/control/allocation/notify", notify_allocation),
@@ -159,6 +161,12 @@ def run():
159161
app["secret_token"] = secret_token
160162
app["vm_pool"] = pool
161163

164+
# Store sevctl app singleton only if confidential feature is enabled
165+
if settings.ENABLE_CONFIDENTIAL_COMPUTING:
166+
sev_client = SevClient(settings.CONFIDENTIAL_DIRECTORY)
167+
app["sev_client"] = sev_client
168+
# TODO: Review and check sevctl first initialization steps, like (sevctl generate and sevctl provision)
169+
162170
logger.debug(f"Login to /about pages {protocol}://{hostname}/about/login?token={secret_token}")
163171

164172
try:

src/aleph/vm/sevclient.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from pathlib import Path
2+
3+
from aleph.vm.utils import run_in_subprocess
4+
5+
6+
class SevClient:
7+
def __init__(self, sev_dir: Path):
8+
self.sev_dir = sev_dir
9+
self.certificates_dir = sev_dir / "platform"
10+
self.certificates_dir.mkdir(exist_ok=True, parents=True)
11+
self.certificates_archive = self.certificates_dir / "certs_export.cert"
12+
13+
async def sevctl_cmd(self, *args) -> bytes:
14+
return await run_in_subprocess(
15+
["sevctl", *args],
16+
check=True,
17+
)
18+
19+
async def get_certificates(self) -> Path:
20+
if not self.certificates_archive.is_file():
21+
_ = await self.sevctl_cmd("export", str(self.certificates_archive))
22+
23+
return self.certificates_archive

tests/supervisor/test_views.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1+
import tempfile
2+
from pathlib import Path
3+
from unittest import mock
4+
from unittest.mock import call
5+
16
import pytest
27
from aiohttp import web
38

49
from aleph.vm.conf import settings
510
from aleph.vm.orchestrator.supervisor import setup_webapp
11+
from aleph.vm.sevclient import SevClient
612

713

814
@pytest.mark.asyncio
@@ -121,3 +127,47 @@ def get_persistent_executions(self):
121127
)
122128
assert response.status == 200
123129
assert await response.json() == {"success": True, "successful": [], "failing": [], "errors": {}}
130+
131+
132+
@pytest.mark.asyncio
133+
async def test_about_certificates_missing_setting(aiohttp_client):
134+
"""Test that the certificates system endpoint returns an error if the setting isn't enabled"""
135+
settings.ENABLE_CONFIDENTIAL_COMPUTING = False
136+
137+
app = setup_webapp()
138+
app["sev_client"] = SevClient(Path().resolve())
139+
client = await aiohttp_client(app)
140+
response: web.Response = await client.get("/about/certificates")
141+
assert response.status == 400
142+
assert await response.text() == "400: Confidential computing setting not enabled on that server"
143+
144+
145+
@pytest.mark.asyncio
146+
async def test_about_certificates(aiohttp_client):
147+
"""Test that the certificates system endpoint responds. No auth needed"""
148+
149+
settings.ENABLE_QEMU_SUPPORT = True
150+
settings.ENABLE_CONFIDENTIAL_COMPUTING = True
151+
settings.setup()
152+
153+
with mock.patch(
154+
"pathlib.Path.is_file",
155+
return_value=False,
156+
) as is_file_mock:
157+
with mock.patch(
158+
"aleph.vm.sevclient.run_in_subprocess",
159+
return_value=True,
160+
) as export_mock:
161+
with tempfile.TemporaryDirectory() as tmp_dir:
162+
app = setup_webapp()
163+
sev_client = SevClient(Path(tmp_dir))
164+
app["sev_client"] = sev_client
165+
# Create mock file to return it
166+
Path(sev_client.certificates_archive).touch(exist_ok=True)
167+
168+
client = await aiohttp_client(app)
169+
response: web.Response = await client.get("/about/certificates")
170+
assert response.status == 200
171+
is_file_mock.assert_has_calls([call(), call()])
172+
certificates_expected_dir = sev_client.certificates_archive
173+
export_mock.assert_called_once_with(["sevctl", "export", str(certificates_expected_dir)], check=True)

0 commit comments

Comments
 (0)