refactor(gateway): replace flat-file import shims with installed package #410
+14
-17
@@ -34,13 +34,13 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Python
|
# No actions/setup-python: the runner image already ships Python 3.12,
|
||||||
uses: actions/setup-python@v5
|
# and older act_runner engines mishandle setup-python's PATH (coverage
|
||||||
with:
|
# lands in one interpreter, `python3` resolves to another). Install
|
||||||
python-version: "3.12"
|
# straight into the ephemeral job container's system Python —
|
||||||
|
# --break-system-packages is safe because the container is disposable.
|
||||||
- name: Install dev requirements
|
- name: Install dev requirements
|
||||||
run: python3 -m pip install -r requirements-dev.txt
|
run: python3 -m pip install --break-system-packages -r requirements-dev.txt
|
||||||
|
|
||||||
- name: Run unit tests
|
- name: Run unit tests
|
||||||
run: python3 -m coverage run -m unittest discover -t . -s tests/unit -v
|
run: python3 -m coverage run -m unittest discover -t . -s tests/unit -v
|
||||||
@@ -54,11 +54,8 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Python
|
# No actions/setup-python (see the note in the `unit` job); the
|
||||||
uses: actions/setup-python@v5
|
# container's system Python 3.12 runs the stdlib test suite directly.
|
||||||
with:
|
|
||||||
python-version: "3.12"
|
|
||||||
|
|
||||||
- name: Show environment
|
- name: Show environment
|
||||||
run: |
|
run: |
|
||||||
python3 --version
|
python3 --version
|
||||||
@@ -88,13 +85,13 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Python
|
# No actions/setup-python: the runner image already ships Python 3.12,
|
||||||
uses: actions/setup-python@v5
|
# and older act_runner engines mishandle setup-python's PATH (coverage
|
||||||
with:
|
# lands in one interpreter, `python3` resolves to another). Install
|
||||||
python-version: "3.12"
|
# straight into the ephemeral job container's system Python —
|
||||||
|
# --break-system-packages is safe because the container is disposable.
|
||||||
- name: Install dev requirements
|
- name: Install dev requirements
|
||||||
run: python3 -m pip install -r requirements-dev.txt
|
run: python3 -m pip install --break-system-packages -r requirements-dev.txt
|
||||||
|
|
||||||
- name: Combined coverage report (unit + integration)
|
- name: Combined coverage report (unit + integration)
|
||||||
run: PYTHON=python3 bash scripts/coverage.sh critical
|
run: PYTHON=python3 bash scripts/coverage.sh critical
|
||||||
|
|||||||
+16
-27
@@ -16,10 +16,12 @@
|
|||||||
# Layout:
|
# Layout:
|
||||||
#
|
#
|
||||||
# /usr/bin/gitleaks gitleaks binary
|
# /usr/bin/gitleaks gitleaks binary
|
||||||
# /app/egress_addon.py + siblings mitmproxy addon (egress)
|
# /app/egress_addon.py mitmproxy addon entry point
|
||||||
# /app/egress-entrypoint.sh mitmdump launcher
|
# /app/egress-entrypoint.sh mitmdump launcher
|
||||||
# /app/supervise_server.py + .py supervise MCP server
|
# /usr/local/lib/python*/bot_bottle/ installed package (all daemons + shared modules)
|
||||||
# /app/gateway_init.py PID 1 supervisor
|
# /app/egress_addon.py one-line shim: re-exports addons from package
|
||||||
|
# (mitmdump -s requires a file path, not a module)
|
||||||
|
# /etc/egress/routes.yaml bind-mounted at run time
|
||||||
# /etc/git-gate/pre-receive docker-cp'd at start time
|
# /etc/git-gate/pre-receive docker-cp'd at start time
|
||||||
# /git-gate-entrypoint.sh docker-cp'd at start time
|
# /git-gate-entrypoint.sh docker-cp'd at start time
|
||||||
# /git-gate/creds/* docker-cp'd at start time
|
# /git-gate/creds/* docker-cp'd at start time
|
||||||
@@ -87,27 +89,16 @@ RUN arch="${TARGETARCH:-$(dpkg --print-architecture)}" \
|
|||||||
&& tar -xzf /tmp/gitleaks.tar.gz -C /usr/bin gitleaks \
|
&& tar -xzf /tmp/gitleaks.tar.gz -C /usr/bin gitleaks \
|
||||||
&& rm /tmp/gitleaks.tar.gz
|
&& rm /tmp/gitleaks.tar.gz
|
||||||
|
|
||||||
# Project Python: addon + server modules + the init supervisor.
|
# Install bot_bottle as a proper package so entry-point scripts can use
|
||||||
# Kept flat under /app/ so mitmdump's loader resolves them as
|
# `from bot_bottle.X import Y` absolute imports. A rename or a missing
|
||||||
# top-level siblings (absolute imports), matching the prior
|
# module is caught at pip-install time — not at container runtime.
|
||||||
# Dockerfile.egress / Dockerfile.supervise layout.
|
COPY pyproject.toml /src/
|
||||||
COPY bot_bottle/egress_addon_core.py /app/egress_addon_core.py
|
COPY bot_bottle/ /src/bot_bottle/
|
||||||
COPY bot_bottle/egress_dlp_config.py /app/egress_dlp_config.py
|
RUN pip install --no-cache-dir /src/
|
||||||
COPY bot_bottle/egress_addon.py /app/egress_addon.py
|
|
||||||
COPY bot_bottle/policy_resolver.py /app/policy_resolver.py
|
# mitmdump -s requires a file path, not a module. Write a one-line shim that
|
||||||
COPY bot_bottle/dlp_detectors.py /app/dlp_detectors.py
|
# re-exports `addons` from the installed package; mitmdump finds it there.
|
||||||
COPY bot_bottle/yaml_subset.py /app/yaml_subset.py
|
RUN printf 'from bot_bottle.egress_addon import addons\n' > /app/egress_addon.py
|
||||||
COPY bot_bottle/paths.py /app/paths.py
|
|
||||||
COPY bot_bottle/migrations.py /app/migrations.py
|
|
||||||
COPY bot_bottle/db_store.py /app/db_store.py
|
|
||||||
COPY bot_bottle/supervise_types.py /app/supervise_types.py
|
|
||||||
COPY bot_bottle/queue_store.py /app/queue_store.py
|
|
||||||
COPY bot_bottle/audit_store.py /app/audit_store.py
|
|
||||||
COPY bot_bottle/store_manager.py /app/store_manager.py
|
|
||||||
COPY bot_bottle/supervise.py /app/supervise.py
|
|
||||||
COPY bot_bottle/supervise_server.py /app/supervise_server.py
|
|
||||||
COPY bot_bottle/gateway_init.py /app/gateway_init.py
|
|
||||||
COPY bot_bottle/git_http_backend.py /app/git_http_backend.py
|
|
||||||
COPY bot_bottle/egress_entrypoint.sh /app/egress-entrypoint.sh
|
COPY bot_bottle/egress_entrypoint.sh /app/egress-entrypoint.sh
|
||||||
RUN chmod +x /app/egress-entrypoint.sh
|
RUN chmod +x /app/egress-entrypoint.sh
|
||||||
|
|
||||||
@@ -126,10 +117,8 @@ RUN mkdir -p \
|
|||||||
# subset the bottle uses.
|
# subset the bottle uses.
|
||||||
EXPOSE 8888 9099 9418 9420 9100
|
EXPOSE 8888 9099 9418 9420 9100
|
||||||
|
|
||||||
# WORKDIR matches Dockerfile.supervise's prior layout so the
|
|
||||||
# in-app same-dir import in supervise_server.py stays deterministic.
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# PID 1 is the supervisor. It owns signal handling and exit-code
|
# PID 1 is the supervisor. It owns signal handling and exit-code
|
||||||
# propagation; no `exec` chain in the entrypoint itself.
|
# propagation; no `exec` chain in the entrypoint itself.
|
||||||
ENTRYPOINT ["python3", "/app/gateway_init.py"]
|
ENTRYPOINT ["python3", "-m", "bot_bottle.gateway_init"]
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
"""Shared wire-protocol constants for gateway-bundled modules.
|
||||||
|
|
||||||
|
Single source of truth for values that appear across the egress addon,
|
||||||
|
git-http backend, supervise server, and git-gate renderer. Importing
|
||||||
|
from this module instead of duplicating the literals means a rename is
|
||||||
|
a one-line change and is caught by the type checker at the import site."""
|
||||||
|
|
||||||
|
# App-layer identity token header. Delivered as proxy credentials
|
||||||
|
# (HTTPS_PROXY=http://<bottle_id>:<token>@gw) by launch; the egress
|
||||||
|
# addon reads and strips it, the supervise server and git-http backend
|
||||||
|
# read it for attribution, and none of them forward it upstream.
|
||||||
|
IDENTITY_HEADER = "x-bot-bottle-identity"
|
||||||
|
|
||||||
|
# Shared timeout (seconds) for all git-gate subprocess and CGI calls:
|
||||||
|
# git daemon (--timeout/--init-timeout), the access-hook subprocess in
|
||||||
|
# git_http_backend, and the git http-backend CGI subprocess.
|
||||||
|
GIT_GATE_TIMEOUT_SECS = 15
|
||||||
@@ -3,9 +3,8 @@
|
|||||||
Pure Python, no mitmproxy dependency. Each detector is a module-level
|
Pure Python, no mitmproxy dependency. Each detector is a module-level
|
||||||
function returning `ScanResult | None`.
|
function returning `ScanResult | None`.
|
||||||
|
|
||||||
Ships flat into the gateway image alongside
|
Available in the gateway via the installed `bot_bottle` package
|
||||||
`egress_addon_core.py` — both this file and the package source use
|
(see `Dockerfile.gateway`).
|
||||||
the same try/except import shim pattern.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -20,10 +19,7 @@ from math import log2
|
|||||||
from collections import Counter
|
from collections import Counter
|
||||||
from urllib.parse import quote as url_quote
|
from urllib.parse import quote as url_quote
|
||||||
|
|
||||||
try:
|
from .egress_addon_core import ScanResult
|
||||||
from egress_addon_core import ScanResult # type: ignore[import-not-found]
|
|
||||||
except ImportError: # pragma: no cover - host-side path
|
|
||||||
from .egress_addon_core import ScanResult
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ import typing
|
|||||||
|
|
||||||
from mitmproxy import http # type: ignore[import-not-found] # pylint: disable=import-error
|
from mitmproxy import http # type: ignore[import-not-found] # pylint: disable=import-error
|
||||||
|
|
||||||
from egress_addon_core import ( # type: ignore[import-not-found] # pylint: disable=import-error
|
from bot_bottle.constants import IDENTITY_HEADER
|
||||||
|
from bot_bottle.dlp_detectors import redact_tokens, strip_crlf
|
||||||
|
from bot_bottle.egress_addon_core import (
|
||||||
LOG_BLOCKS,
|
LOG_BLOCKS,
|
||||||
LOG_FULL,
|
LOG_FULL,
|
||||||
DEFAULT_OUTBOUND_ON_MATCH,
|
DEFAULT_OUTBOUND_ON_MATCH,
|
||||||
@@ -38,24 +40,8 @@ from egress_addon_core import ( # type: ignore[import-not-found] # pylint: dis
|
|||||||
scan_inbound,
|
scan_inbound,
|
||||||
scan_outbound,
|
scan_outbound,
|
||||||
)
|
)
|
||||||
|
from bot_bottle import supervise as _sv
|
||||||
try:
|
from bot_bottle.policy_resolver import PolicyResolver
|
||||||
from dlp_detectors import redact_tokens, strip_crlf # type: ignore[import-not-found]
|
|
||||||
except ImportError: # pragma: no cover - host-side path
|
|
||||||
from bot_bottle.dlp_detectors import ( # type: ignore[import-not-found]
|
|
||||||
redact_tokens,
|
|
||||||
strip_crlf,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
import supervise as _sv # type: ignore[import-not-found]
|
|
||||||
except ImportError: # pragma: no cover - host-side path
|
|
||||||
from bot_bottle import supervise as _sv # type: ignore[import-not-found]
|
|
||||||
|
|
||||||
try:
|
|
||||||
from policy_resolver import PolicyResolver # type: ignore[import-not-found]
|
|
||||||
except ImportError: # pragma: no cover - host-side path
|
|
||||||
from bot_bottle.policy_resolver import PolicyResolver
|
|
||||||
|
|
||||||
|
|
||||||
INTROSPECT_HOST = "_egress.local"
|
INTROSPECT_HOST = "_egress.local"
|
||||||
@@ -66,13 +52,6 @@ INTROSPECT_HOST = "_egress.local"
|
|||||||
# back to — so an unset value is a fatal misconfiguration (see __init__).
|
# back to — so an unset value is a fatal misconfiguration (see __init__).
|
||||||
ORCHESTRATOR_URL_ENV = "BOT_BOTTLE_ORCHESTRATOR_URL"
|
ORCHESTRATOR_URL_ENV = "BOT_BOTTLE_ORCHESTRATOR_URL"
|
||||||
|
|
||||||
# App-layer identity token. Delivered as proxy credentials
|
|
||||||
# (`HTTPS_PROXY=http://<bottle_id>:<token>@gw`): clients honor it as part of
|
|
||||||
# the proxy protocol without app changes, and the addon reads + strips it so
|
|
||||||
# it never leaks upstream. The legacy `x-bot-bottle-identity` request header
|
|
||||||
# is still stripped defensively (git-http uses that header on its own port).
|
|
||||||
IDENTITY_HEADER = "x-bot-bottle-identity"
|
|
||||||
|
|
||||||
# Per-flow key under which `request()` stashes the resolved (Config, supervise
|
# Per-flow key under which `request()` stashes the resolved (Config, supervise
|
||||||
# slug, env) so the later `response()` and `websocket_message()` hooks scan
|
# slug, env) so the later `response()` and `websocket_message()` hooks scan
|
||||||
# against the *calling bottle's* policy — the same one the request was decided
|
# against the *calling bottle's* policy — the same one the request was decided
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ exercise the parse + decision functions without depending on the
|
|||||||
`mitmproxy.http.HTTPFlow` API and is loaded inside the gateway
|
`mitmproxy.http.HTTPFlow` API and is loaded inside the gateway
|
||||||
container.
|
container.
|
||||||
|
|
||||||
Imports: stdlib + `yaml_subset` (which is itself stdlib-only and
|
Imports: stdlib + sibling package modules (`yaml_subset`,
|
||||||
ships flat into the gateway image alongside this file —
|
`egress_dlp_config`). Available in the gateway via the installed
|
||||||
see `Dockerfile.gateway`)."""
|
`bot_bottle` package (see `Dockerfile.gateway`)."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -16,36 +16,20 @@ import re
|
|||||||
import typing
|
import typing
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
try:
|
from .yaml_subset import YamlSubsetError, parse_yaml_subset
|
||||||
from yaml_subset import YamlSubsetError, parse_yaml_subset # type: ignore[import-not-found]
|
|
||||||
except ImportError: # pragma: no cover - host-side path
|
|
||||||
from .yaml_subset import YamlSubsetError, parse_yaml_subset
|
|
||||||
|
|
||||||
# DLP detector-config parsing lives in a sibling module (also flat-bundled
|
# DLP detector-config parsing lives in a sibling module. Re-exported below
|
||||||
# into the gateway — see Dockerfile.gateway). Re-exported below so existing
|
# so existing `from egress_addon_core import ON_MATCH_*` callers keep working.
|
||||||
# `from egress_addon_core import ON_MATCH_*` callers keep working.
|
from .egress_dlp_config import (
|
||||||
try:
|
DEFAULT_OUTBOUND_ON_MATCH,
|
||||||
from egress_dlp_config import ( # type: ignore[import-not-found]
|
INBOUND_DETECTOR_NAMES,
|
||||||
DEFAULT_OUTBOUND_ON_MATCH,
|
ON_MATCH_BLOCK,
|
||||||
INBOUND_DETECTOR_NAMES,
|
ON_MATCH_REDACT,
|
||||||
ON_MATCH_BLOCK,
|
ON_MATCH_SUPERVISE,
|
||||||
ON_MATCH_REDACT,
|
OUTBOUND_DETECTOR_NAMES,
|
||||||
ON_MATCH_SUPERVISE,
|
OUTBOUND_ON_MATCH_VALUES,
|
||||||
OUTBOUND_DETECTOR_NAMES,
|
parse_dlp_block,
|
||||||
OUTBOUND_ON_MATCH_VALUES,
|
)
|
||||||
parse_dlp_block,
|
|
||||||
)
|
|
||||||
except ImportError: # pragma: no cover - host-side path
|
|
||||||
from .egress_dlp_config import (
|
|
||||||
DEFAULT_OUTBOUND_ON_MATCH,
|
|
||||||
INBOUND_DETECTOR_NAMES,
|
|
||||||
ON_MATCH_BLOCK,
|
|
||||||
ON_MATCH_REDACT,
|
|
||||||
ON_MATCH_SUPERVISE,
|
|
||||||
OUTBOUND_DETECTOR_NAMES,
|
|
||||||
OUTBOUND_ON_MATCH_VALUES,
|
|
||||||
parse_dlp_block,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -78,8 +78,8 @@ def _env_for_daemon(name: str, base_env: dict[str, str]) -> dict[str, str]:
|
|||||||
_DAEMONS: tuple[_DaemonSpec, ...] = (
|
_DAEMONS: tuple[_DaemonSpec, ...] = (
|
||||||
_DaemonSpec("egress", ("/bin/sh", "/app/egress-entrypoint.sh")),
|
_DaemonSpec("egress", ("/bin/sh", "/app/egress-entrypoint.sh")),
|
||||||
_DaemonSpec("git-gate", ("/bin/sh", "/git-gate-entrypoint.sh")),
|
_DaemonSpec("git-gate", ("/bin/sh", "/git-gate-entrypoint.sh")),
|
||||||
_DaemonSpec("git-http", ("python3", "/app/git_http_backend.py")),
|
_DaemonSpec("git-http", ("python3", "-m", "bot_bottle.git_http_backend")),
|
||||||
_DaemonSpec("supervise", ("python3", "/app/supervise_server.py")),
|
_DaemonSpec("supervise", ("python3", "-m", "bot_bottle.supervise_server")),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,18 +14,12 @@ import shlex
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from .constants import GIT_GATE_TIMEOUT_SECS, IDENTITY_HEADER
|
||||||
from .manifest import ManifestBottle, ManifestGitEntry
|
from .manifest import ManifestBottle, ManifestGitEntry
|
||||||
|
|
||||||
# Short network alias for git-gate inside the gateway. The
|
# Short network alias for git-gate inside the gateway. The
|
||||||
# agent's `.gitconfig` insteadOf rewrites resolve through this name.
|
# agent's `.gitconfig` insteadOf rewrites resolve through this name.
|
||||||
GIT_GATE_HOSTNAME = "git-gate"
|
GIT_GATE_HOSTNAME = "git-gate"
|
||||||
# App-layer identity token header the agent's git sends to git-http and the
|
|
||||||
# gateway validates (mirrors egress_addon / git_http_backend IDENTITY_HEADER).
|
|
||||||
IDENTITY_HEADER = "x-bot-bottle-identity"
|
|
||||||
# Shared timeout (seconds) for all git-gate subprocess and CGI calls:
|
|
||||||
# git daemon (--timeout/--init-timeout), the access-hook subprocess in
|
|
||||||
# git_http_backend, and the git http-backend CGI subprocess.
|
|
||||||
GIT_GATE_TIMEOUT_SECS = 15
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
|
|||||||
@@ -26,16 +26,8 @@ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from urllib.parse import urlsplit
|
from urllib.parse import urlsplit
|
||||||
|
|
||||||
# policy_resolver ships flat alongside this file in the gateway
|
from bot_bottle.constants import GIT_GATE_TIMEOUT_SECS, IDENTITY_HEADER
|
||||||
# image (see Dockerfile.gateway); the bot_bottle.* fallback is the
|
from bot_bottle.policy_resolver import PolicyResolveError, PolicyResolver
|
||||||
# host-side / test path. Mirrors egress_addon's import shape.
|
|
||||||
try:
|
|
||||||
from policy_resolver import ( # type: ignore[import-not-found]
|
|
||||||
PolicyResolveError,
|
|
||||||
PolicyResolver,
|
|
||||||
)
|
|
||||||
except ImportError: # pragma: no cover - host-side path
|
|
||||||
from bot_bottle.policy_resolver import PolicyResolveError, PolicyResolver
|
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_PORT = 9420
|
DEFAULT_PORT = 9420
|
||||||
@@ -46,12 +38,6 @@ DEFAULT_PORT = 9420
|
|||||||
# repo-root fallback.
|
# repo-root fallback.
|
||||||
ORCHESTRATOR_URL_ENV = "BOT_BOTTLE_ORCHESTRATOR_URL"
|
ORCHESTRATOR_URL_ENV = "BOT_BOTTLE_ORCHESTRATOR_URL"
|
||||||
|
|
||||||
# App-layer identity token (defense-in-depth over the source-IP invariant);
|
|
||||||
# the agent injects it, the backend reads it for attribution and never
|
|
||||||
# forwards it to `git http-backend`. Mirrors egress_addon.IDENTITY_HEADER
|
|
||||||
# (duplicated, not imported: egress_addon pulls in mitmproxy).
|
|
||||||
IDENTITY_HEADER = "x-bot-bottle-identity"
|
|
||||||
|
|
||||||
# The base under which each bottle's `<bottle_id>` repo namespace is nested.
|
# The base under which each bottle's `<bottle_id>` repo namespace is nested.
|
||||||
DEFAULT_REPO_ROOT = "/git"
|
DEFAULT_REPO_ROOT = "/git"
|
||||||
|
|
||||||
@@ -89,13 +75,6 @@ def resolve_sandbox_root(
|
|||||||
return None # bottle_id tried to escape the root → deny
|
return None # bottle_id tried to escape the root → deny
|
||||||
return namespace
|
return namespace
|
||||||
|
|
||||||
# Mirrors git_gate_render.GIT_GATE_TIMEOUT_SECS. Duplicated rather than
|
|
||||||
# imported: this module ships as a flat top-level sibling in the gateway
|
|
||||||
# bundle image (see Dockerfile.gateway), not as part of the bot_bottle
|
|
||||||
# package, so `bot_bottle.git_gate` and its dependency chain aren't
|
|
||||||
# available at runtime.
|
|
||||||
GIT_GATE_TIMEOUT_SECS = 15
|
|
||||||
|
|
||||||
# Bound memory use while still allowing ordinary git push packfiles.
|
# Bound memory use while still allowing ordinary git push packfiles.
|
||||||
MAX_BODY_BYTES = 100 * 1024 * 1024
|
MAX_BODY_BYTES = 100 * 1024 * 1024
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,7 @@ closed too rather than silently serving stale or empty policy.
|
|||||||
|
|
||||||
The resolved value is the policy blob the orchestrator stores verbatim; the
|
The resolved value is the policy blob the orchestrator stores verbatim; the
|
||||||
consumer parses it (e.g. the egress addon's `load_config`). This module is
|
consumer parses it (e.g. the egress addon's `load_config`). This module is
|
||||||
stdlib-only and free of bot-bottle imports so it can be COPYed flat into
|
stdlib-only and free of bot-bottle imports.
|
||||||
the gateway.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|||||||
+16
-34
@@ -37,40 +37,22 @@ from abc import ABC
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
try:
|
from .supervise_types import (
|
||||||
from .supervise_types import (
|
ACTION_OPERATOR_EDIT,
|
||||||
ACTION_OPERATOR_EDIT,
|
AuditEntry,
|
||||||
AuditEntry,
|
Proposal,
|
||||||
Proposal,
|
Response,
|
||||||
Response,
|
STATUSES,
|
||||||
STATUSES,
|
STATUS_APPROVED,
|
||||||
STATUS_APPROVED,
|
STATUS_MODIFIED,
|
||||||
STATUS_MODIFIED,
|
STATUS_REJECTED,
|
||||||
STATUS_REJECTED,
|
TOOLS,
|
||||||
TOOLS,
|
TOOL_EGRESS_ALLOW,
|
||||||
TOOL_EGRESS_ALLOW,
|
TOOL_EGRESS_BLOCK,
|
||||||
TOOL_EGRESS_BLOCK,
|
TOOL_EGRESS_TOKEN_ALLOW,
|
||||||
TOOL_EGRESS_TOKEN_ALLOW,
|
TOOL_GITLEAKS_ALLOW,
|
||||||
TOOL_GITLEAKS_ALLOW,
|
TOOL_LIST_EGRESS_ROUTES,
|
||||||
TOOL_LIST_EGRESS_ROUTES,
|
)
|
||||||
)
|
|
||||||
except ImportError:
|
|
||||||
from supervise_types import ( # type: ignore[import-not-found,no-redef] # pylint: disable=import-error,no-name-in-module
|
|
||||||
ACTION_OPERATOR_EDIT,
|
|
||||||
AuditEntry,
|
|
||||||
Proposal,
|
|
||||||
Response,
|
|
||||||
STATUSES,
|
|
||||||
STATUS_APPROVED,
|
|
||||||
STATUS_MODIFIED,
|
|
||||||
STATUS_REJECTED,
|
|
||||||
TOOLS,
|
|
||||||
TOOL_EGRESS_ALLOW,
|
|
||||||
TOOL_EGRESS_BLOCK,
|
|
||||||
TOOL_EGRESS_TOKEN_ALLOW,
|
|
||||||
TOOL_GITLEAKS_ALLOW,
|
|
||||||
TOOL_LIST_EGRESS_ROUTES,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -26,9 +26,8 @@ Speaks MCP over HTTP+JSON-RPC. Methods handled:
|
|||||||
|
|
||||||
Everything else returns JSON-RPC error -32601 (method not found).
|
Everything else returns JSON-RPC error -32601 (method not found).
|
||||||
|
|
||||||
Stdlib-only. The Dockerfile copies this file + bot_bottle/supervise.py
|
The Dockerfile copies this script to /app/supervise_server.py and installs
|
||||||
into the image; the server imports `supervise` for the queue / Proposal
|
the bot_bottle package so its `from bot_bottle.*` imports resolve.
|
||||||
plumbing.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -42,29 +41,18 @@ import time
|
|||||||
import typing
|
import typing
|
||||||
from dataclasses import dataclass, replace
|
from dataclasses import dataclass, replace
|
||||||
|
|
||||||
try:
|
from bot_bottle.constants import IDENTITY_HEADER
|
||||||
# Same-directory imports inside the bundle container; these files are
|
from bot_bottle.egress_addon_core import (
|
||||||
# COPYed flat under /app by Dockerfile.gateway.
|
LOG_OFF, load_config, resolve_client_context, route_to_yaml_dict,
|
||||||
from egress_addon_core import (
|
)
|
||||||
LOG_OFF, load_config, resolve_client_context, route_to_yaml_dict,
|
from bot_bottle.policy_resolver import PolicyResolveError, PolicyResolver
|
||||||
)
|
from bot_bottle import supervise as _sv
|
||||||
from policy_resolver import PolicyResolveError, PolicyResolver
|
|
||||||
import supervise as _sv
|
|
||||||
except ModuleNotFoundError:
|
|
||||||
# Package imports for host-side tests and tooling.
|
|
||||||
from .egress_addon_core import (
|
|
||||||
LOG_OFF, load_config, resolve_client_context, route_to_yaml_dict,
|
|
||||||
)
|
|
||||||
from .policy_resolver import PolicyResolveError, PolicyResolver
|
|
||||||
from . import supervise as _sv
|
|
||||||
|
|
||||||
|
|
||||||
# --- JSON-RPC / MCP plumbing ----------------------------------------------
|
# --- JSON-RPC / MCP plumbing ----------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
MCP_PROTOCOL_VERSION = "2024-11-05"
|
MCP_PROTOCOL_VERSION = "2024-11-05"
|
||||||
# App-layer identity token header (mirrors egress_addon / git_http_backend).
|
|
||||||
IDENTITY_HEADER = "x-bot-bottle-identity"
|
|
||||||
SERVER_NAME = "bot-bottle-supervise"
|
SERVER_NAME = "bot-bottle-supervise"
|
||||||
SERVER_VERSION = "0.1.0"
|
SERVER_VERSION = "0.1.0"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=68"]
|
||||||
|
build-backend = "setuptools.backends.legacy:build"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "bot-bottle"
|
||||||
|
version = "0.0.0"
|
||||||
|
requires-python = ">=3.11"
|
||||||
@@ -18,7 +18,7 @@ from unittest.mock import patch
|
|||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Gateway-import shims — must run before importing egress_addon
|
# mitmproxy stub — must run before importing egress_addon
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def _ensure_shims() -> None:
|
def _ensure_shims() -> None:
|
||||||
@@ -32,9 +32,6 @@ def _ensure_shims() -> None:
|
|||||||
setattr(_mm, "http", _mh)
|
setattr(_mm, "http", _mh)
|
||||||
sys.modules["mitmproxy"] = _mm
|
sys.modules["mitmproxy"] = _mm
|
||||||
sys.modules["mitmproxy.http"] = _mh
|
sys.modules["mitmproxy.http"] = _mh
|
||||||
if "egress_addon_core" not in sys.modules:
|
|
||||||
import bot_bottle.egress_addon_core as _core
|
|
||||||
sys.modules["egress_addon_core"] = _core
|
|
||||||
|
|
||||||
|
|
||||||
_ensure_shims()
|
_ensure_shims()
|
||||||
|
|||||||
@@ -190,9 +190,6 @@ def _ensure_shims() -> None:
|
|||||||
setattr(mh, "Response", _Response)
|
setattr(mh, "Response", _Response)
|
||||||
if not hasattr(mh, "HTTPFlow"):
|
if not hasattr(mh, "HTTPFlow"):
|
||||||
setattr(mh, "HTTPFlow", object)
|
setattr(mh, "HTTPFlow", object)
|
||||||
if "egress_addon_core" not in sys.modules:
|
|
||||||
import bot_bottle.egress_addon_core as _core
|
|
||||||
sys.modules["egress_addon_core"] = _core
|
|
||||||
|
|
||||||
|
|
||||||
_ensure_shims()
|
_ensure_shims()
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import http.client
|
import http.client
|
||||||
import json
|
import json
|
||||||
import sys
|
|
||||||
import tempfile
|
import tempfile
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
@@ -13,15 +12,9 @@ from unittest.mock import patch
|
|||||||
|
|
||||||
from tests.unit import use_bottle_root
|
from tests.unit import use_bottle_root
|
||||||
|
|
||||||
|
from bot_bottle import supervise as _sv
|
||||||
# The server module loads `supervise` via same-directory import inside
|
from bot_bottle import queue_store as _qs
|
||||||
# the container (Dockerfile.supervise WORKDIRs into /app). For tests
|
from bot_bottle import audit_store as _as
|
||||||
# we mirror that by injecting bot_bottle/ onto sys.path under the
|
|
||||||
# bare name `supervise`.
|
|
||||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent / "bot_bottle"))
|
|
||||||
import supervise as _sv # noqa: E402 # type: ignore
|
|
||||||
import queue_store as _qs # noqa: E402 # type: ignore
|
|
||||||
import audit_store as _as # noqa: E402 # type: ignore
|
|
||||||
|
|
||||||
from bot_bottle import supervise_server # noqa: E402
|
from bot_bottle import supervise_server # noqa: E402
|
||||||
from bot_bottle.supervise_server import (
|
from bot_bottle.supervise_server import (
|
||||||
|
|||||||
Reference in New Issue
Block a user