mirror of
https://github.com/usestrix/strix.git
synced 2026-08-25 20:32:38 +02:00
perf: bootstrap Caido concurrently with the scan start (#1143)
Co-authored-by: Ahmed Allam <ahmed39652003@gmail.com>
This commit is contained in:
co-authored by
Ahmed Allam
parent
1ce43d1b94
commit
1c499c5b2d
@@ -96,15 +96,17 @@ async def bootstrap_caido(
|
||||
access_token = await _login_as_guest(session, container_url=container_url)
|
||||
|
||||
client = Client(host_url, auth=TokenAuthOptions(token=access_token))
|
||||
await client.connect()
|
||||
|
||||
try:
|
||||
# connect() is inside the guard as well: a cancellation there (scan
|
||||
# teardown while the bootstrap is still in flight) would otherwise
|
||||
# leave the half-connected transport behind.
|
||||
await client.connect()
|
||||
project = await client.project.create(
|
||||
CreateProjectOptions(name="sandbox", temporary=True),
|
||||
)
|
||||
await client.project.select(project.id)
|
||||
except BaseException:
|
||||
# The connected client never reaches the session bundle if project
|
||||
# The client never reaches the session bundle if connect or project
|
||||
# setup fails, so close it here to avoid leaking the transport.
|
||||
with contextlib.suppress(Exception):
|
||||
await client.aclose()
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
"""Handle for a Caido bootstrap running concurrently with the scan start.
|
||||
|
||||
The Caido sidecar login + project setup costs a couple of seconds of
|
||||
guest-side polling, and nothing needs the client until the first proxy
|
||||
tool call (or the first traffic poll). :class:`CaidoBootstrapHandle`
|
||||
wraps the in-flight bootstrap task so session bring-up can return as
|
||||
soon as the container is up; consumers resolve the client at first use.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from caido_sdk_client import Client
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CaidoBootstrapHandle:
|
||||
"""Resolves to the connected Caido client once the bootstrap finishes.
|
||||
|
||||
A failed bootstrap is surfaced (once) to every ``get()`` caller as the
|
||||
original exception; proxy tools degrade to their "client unavailable"
|
||||
result instead of the failure killing the scan at bring-up.
|
||||
"""
|
||||
|
||||
def __init__(self, task: asyncio.Task[Client]) -> None:
|
||||
self._task = task
|
||||
|
||||
async def get(self) -> Client:
|
||||
"""Wait for the bootstrap and return the client.
|
||||
|
||||
Shielded so one caller's cancellation (e.g. a tool timeout) does not
|
||||
cancel the shared bootstrap for everyone else.
|
||||
"""
|
||||
return await asyncio.shield(self._task)
|
||||
|
||||
def peek(self) -> Client | None:
|
||||
"""Return the client if the bootstrap already finished cleanly."""
|
||||
if self._task.done() and not self._task.cancelled() and self._task.exception() is None:
|
||||
return self._task.result()
|
||||
return None
|
||||
|
||||
async def aclose(self) -> None:
|
||||
"""Cancel an in-flight bootstrap or close the finished client."""
|
||||
if not self._task.done():
|
||||
self._task.cancel()
|
||||
with contextlib.suppress(asyncio.CancelledError, Exception):
|
||||
await self._task
|
||||
return
|
||||
client = self.peek()
|
||||
if client is not None:
|
||||
with contextlib.suppress(Exception):
|
||||
await client.aclose()
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
@@ -15,6 +16,7 @@ from strix.config import load_settings
|
||||
from strix.core.paths import run_dir_for, runtime_state_dir
|
||||
from strix.runtime.backends import backend_supports_bind_mounts, get_backend
|
||||
from strix.runtime.caido_bootstrap import bootstrap_caido
|
||||
from strix.runtime.caido_handle import CaidoBootstrapHandle
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -333,10 +335,19 @@ async def create_or_reuse(
|
||||
host_caido_url = f"{scheme}://{caido_endpoint.host}:{caido_endpoint.port}"
|
||||
logger.debug("Caido host endpoint resolved: %s", host_caido_url)
|
||||
|
||||
caido_client = await bootstrap_caido(
|
||||
session,
|
||||
host_url=host_caido_url,
|
||||
container_url=container_caido_url,
|
||||
# The Caido login + project setup polls the guest for a couple of seconds
|
||||
# and nothing needs the client before the first proxy tool call, so it
|
||||
# runs concurrently with the rest of scan start; consumers resolve the
|
||||
# handle at first use (see CaidoBootstrapHandle).
|
||||
caido_client = CaidoBootstrapHandle(
|
||||
asyncio.create_task(
|
||||
bootstrap_caido(
|
||||
session,
|
||||
host_url=host_caido_url,
|
||||
container_url=container_caido_url,
|
||||
),
|
||||
name=f"caido-bootstrap-{scan_id}",
|
||||
)
|
||||
)
|
||||
|
||||
bundle = {
|
||||
|
||||
Reference in New Issue
Block a user