feat(cloud): add edge host enrollment

This commit is contained in:
2026-07-13 13:54:16 +08:00
parent cd56facbbf
commit e61dcca801
40 changed files with 2302 additions and 48 deletions
+17 -2
View File
@@ -11,7 +11,12 @@ from fastapi import FastAPI, status
from fastapi import Request
from fastapi.responses import JSONResponse
from cloud.auth import create_auth_provider
from cloud.auth import (
ChainedAuthProvider,
ConfiguredEnrollmentTokenProvider,
RepositoryHostAuthProvider,
create_auth_provider,
)
from cloud.config import CloudConfig
from cloud.control_config import (
CloudControlConfig,
@@ -73,11 +78,20 @@ def create_app(
control_config = config or load_control_config()
validate_control_config(control_config)
build_database = database_factory or _default_database_factory
auth_provider = create_auth_provider(
configured_auth_provider = create_auth_provider(
control_config.credentials,
allow_insecure_anonymous=control_config.allow_insecure_anonymous,
)
repository = _RepositoryProxy()
auth_provider = ChainedAuthProvider(
(
configured_auth_provider,
RepositoryHostAuthProvider(repository), # type: ignore[arg-type]
)
)
enrollment_auth_provider = ConfiguredEnrollmentTokenProvider(
control_config.enrollment_credentials
)
domain_config = CloudConfig(
lease_duration_seconds=control_config.lease_duration_seconds,
)
@@ -208,6 +222,7 @@ def create_app(
create_internal_router(
pool=pool,
auth_provider=auth_provider,
enrollment_auth_provider=enrollment_auth_provider,
lease_duration_seconds=control_config.lease_duration_seconds,
)
)
+151
View File
@@ -8,6 +8,7 @@ from fastapi.testclient import TestClient
import cloud_api.app as app_module
from cloud_api.app import create_app
from cloud.auth import BearerCredential, EnrollmentCredential, digest_token
from cloud.control_config import CloudConfigurationError, CloudControlConfig
from cloud.database import CloudDatabase
from cloud.pool import PooledDevice
@@ -23,6 +24,156 @@ def test_create_app_returns_independent_cloud_application() -> None:
paths = set(app.openapi()["paths"])
assert "/v1/tasks" in paths
assert "/internal/v1/hosts/{host_id}/heartbeat" in paths
assert "/internal/v1/enrollments" in paths
assert "/internal/v1/hosts/{host_id}/devices/enroll" in paths
def test_managed_host_enrollment_device_mapping_and_restart_authentication(
tmp_path,
) -> None:
database_url = f"sqlite:///{(tmp_path / 'enrollment.sqlite3').as_posix()}"
enrollment_token = "one-time-enrollment-token"
host_token = "host-token-" + ("x" * 40)
config = CloudControlConfig(
database_url=database_url,
credentials=(
BearerCredential(
principal_id="operator",
token="operator-token",
scopes=frozenset({"pool:read"}),
),
),
enrollment_credentials=(
EnrollmentCredential(
principal_id="installer-a",
token=enrollment_token,
),
),
)
enrollment_payload = {
"agent_instance_id": "agent-instance-a",
"host_token": host_token,
"display_name": "Edge Mac",
}
app = create_app(config=config)
with TestClient(app) as client:
enrolled = client.post(
"/internal/v1/enrollments",
headers={"Authorization": f"Bearer {enrollment_token}"},
json=enrollment_payload,
)
assert enrolled.status_code == 201
host_id = enrolled.json()["host_id"]
assert host_id.startswith("host-")
retried = client.post(
"/internal/v1/enrollments",
headers={"Authorization": f"Bearer {enrollment_token}"},
json=enrollment_payload,
)
assert retried.status_code == 201
assert retried.json()["host_id"] == host_id
reused = client.post(
"/internal/v1/enrollments",
headers={"Authorization": f"Bearer {enrollment_token}"},
json={
**enrollment_payload,
"agent_instance_id": "agent-instance-b",
},
)
assert reused.status_code == 409
device = client.post(
f"/internal/v1/hosts/{host_id}/devices/enroll",
headers={"Authorization": f"Bearer {host_token}"},
json={
"local_device_id": "local-device-a",
"driver_type": "wda",
"name": "iPhone",
"capability_tags": ["ios"],
},
)
assert device.status_code == 201
device_id = device.json()["device_id"]
assert device_id.startswith("device-")
device_retry = client.post(
f"/internal/v1/hosts/{host_id}/devices/enroll",
headers={"Authorization": f"Bearer {host_token}"},
json={
"local_device_id": "local-device-a",
"driver_type": "wda",
"name": "Renamed iPhone",
"capability_tags": ["ios", "physical"],
},
)
assert device_retry.json()["device_id"] == device_id
rejected_snapshot = client.put(
f"/internal/v1/hosts/{host_id}/heartbeat",
headers={"Authorization": f"Bearer {host_token}"},
json={
"host_id": host_id,
"devices": [
{
"device_id": "caller-selected-device",
"driver_type": "wda",
"status": "idle",
}
],
},
)
assert rejected_snapshot.status_code == 409
heartbeat = client.put(
f"/internal/v1/hosts/{host_id}/heartbeat",
headers={"Authorization": f"Bearer {host_token}"},
json={
"host_id": host_id,
"devices": [
{
"device_id": device_id,
"driver_type": "wda",
"status": "idle",
}
],
},
)
assert heartbeat.status_code == 200
public_attempt = client.get(
"/v1/devices",
headers={"Authorization": f"Bearer {host_token}"},
)
assert public_attempt.status_code == 403
restarted = create_app(config=config)
with TestClient(restarted) as client:
heartbeat = client.put(
f"/internal/v1/hosts/{host_id}/heartbeat",
headers={"Authorization": f"Bearer {host_token}"},
json={"host_id": host_id, "devices": []},
)
assert heartbeat.status_code == 200
assert (
restarted.state.cloud_services.repository.authenticate_enrolled_host(
digest_token(host_token)
)
== host_id
)
assert restarted.state.cloud_services.repository.revoke_enrolled_host(
host_id,
revoked_at=utc_now(),
)
rejected = client.put(
f"/internal/v1/hosts/{host_id}/heartbeat",
headers={"Authorization": f"Bearer {host_token}"},
json={"host_id": host_id, "devices": []},
)
assert rejected.status_code == 401
def test_cloud_application_owns_database_lifecycle() -> None: