opensandbox-server 0.2.0__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- opensandbox_server-0.2.1/PKG-INFO +43 -0
- opensandbox_server-0.2.1/README.md +4 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/api/schema.py +13 -4
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/examples/example.config.k8s.toml +2 -2
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/examples/example.config.k8s.zh.toml +2 -2
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/examples/example.config.toml +2 -2
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/examples/example.config.zh.toml +2 -2
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/extensions/__init__.py +4 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/extensions/keys.py +9 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/constants.py +16 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/docker/docker_service.py +106 -23
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/docker/networking.py +10 -2
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/docker/runtime.py +36 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/helpers.py +35 -1
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/k8s/agent_sandbox_provider.py +32 -7
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/k8s/batchsandbox_provider.py +29 -7
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/k8s/client.py +54 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/k8s/create_helpers.py +32 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/k8s/egress_helper.py +12 -7
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/k8s/kubernetes_service.py +506 -58
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/k8s/provider_common.py +34 -2
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/k8s/security_context.py +14 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/k8s/windows_profile.py +16 -4
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/k8s/workload_provider.py +3 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/runtime_resolver.py +17 -1
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/validators.py +38 -1
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/pyproject.toml +6 -4
- opensandbox_server-0.2.0/PKG-INFO +0 -327
- opensandbox_server-0.2.0/README.md +0 -290
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/.gitignore +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/LICENSE +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/__init__.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/api/__init__.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/api/devops.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/api/lifecycle.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/api/pool.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/api/proxy.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/cli.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/config.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/examples/.gitkeep +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/examples/__init__.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/examples/e2e.batchsandbox-template.yaml +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/examples/example.batchsandbox-template.yaml +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/extensions/codec.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/extensions/validation.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/integrations/__init__.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/integrations/renew_intent/__init__.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/integrations/renew_intent/constants.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/integrations/renew_intent/consumer.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/integrations/renew_intent/controller.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/integrations/renew_intent/intent.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/integrations/renew_intent/logutil.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/integrations/renew_intent/proxy_renew.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/integrations/renew_intent/redis_client.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/integrations/renew_intent/runner.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/logging_config.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/main.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/middleware/__init__.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/middleware/auth.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/middleware/request_id.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/py.typed +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/repositories/__init__.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/repositories/snapshots/__init__.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/repositories/snapshots/factory.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/repositories/snapshots/sqlite.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/__init__.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/docker/__init__.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/docker/container_ops.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/docker/docker_diagnostics.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/docker/metadata.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/docker/ossfs_mixin.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/docker/port_allocator.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/docker/snapshot_runtime.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/docker/volumes.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/docker/windows_profile.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/endpoint_auth.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/extension_service.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/factory.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/k8s/__init__.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/k8s/agent_sandbox_template.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/k8s/batchsandbox_template.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/k8s/endpoint_resolver.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/k8s/error_helpers.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/k8s/image_pull_secret_helper.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/k8s/informer.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/k8s/k8s_diagnostics.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/k8s/label_selector.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/k8s/list_helpers.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/k8s/pool_service.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/k8s/provider_factory.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/k8s/rate_limiter.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/k8s/snapshot_runtime.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/k8s/status_helpers.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/k8s/template_manager.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/k8s/volume_helper.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/k8s/workload_access.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/k8s/workload_mapper.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/sandbox_service.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/signing.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/snapshot_models.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/snapshot_repository.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/snapshot_restore.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/snapshot_runtime.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/snapshot_runtime_factory.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/snapshot_service.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/windows_common.py +0 -0
- {opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/startup_guard.py +0 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: opensandbox-server
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: FastAPI control plane for OpenSandbox that manages sandbox lifecycle on Docker (ready) and Kubernetes (planned) runtimes.
|
|
5
|
+
Project-URL: Homepage, https://github.com/opensandbox-group/OpenSandbox
|
|
6
|
+
Project-URL: Repository, https://github.com/opensandbox-group/OpenSandbox
|
|
7
|
+
Project-URL: Issues, https://github.com/opensandbox-group/OpenSandbox/issues
|
|
8
|
+
Author-email: OpenSandbox Team <pangjiping.pjp@alibaba-inc.com>
|
|
9
|
+
License: Apache-2.0
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: control-plane,fastapi,opensandbox,sandbox,server
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Requires-Dist: docker
|
|
26
|
+
Requires-Dist: fastapi>=0.137.0
|
|
27
|
+
Requires-Dist: httpx[socks]
|
|
28
|
+
Requires-Dist: kubernetes
|
|
29
|
+
Requires-Dist: pydantic
|
|
30
|
+
Requires-Dist: pydantic-settings
|
|
31
|
+
Requires-Dist: python-multipart>=0.0.30
|
|
32
|
+
Requires-Dist: pyyaml
|
|
33
|
+
Requires-Dist: redis>=5
|
|
34
|
+
Requires-Dist: starlette>=1.3.1
|
|
35
|
+
Requires-Dist: tomli; python_version < '3.11'
|
|
36
|
+
Requires-Dist: uvicorn[standard]
|
|
37
|
+
Requires-Dist: websockets>=14.0
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
|
|
40
|
+
# OpenSandbox Server
|
|
41
|
+
|
|
42
|
+
Documentation: [docs/components/server.md](../docs/components/server.md)
|
|
43
|
+
|
|
@@ -198,9 +198,9 @@ class PVC(BaseModel):
|
|
|
198
198
|
description=(
|
|
199
199
|
"When true, the volume is automatically removed when the sandbox is "
|
|
200
200
|
"deleted. Only applies to volumes that were auto-created by the server "
|
|
201
|
-
"
|
|
202
|
-
"
|
|
203
|
-
"reclaim policy."
|
|
201
|
+
"on this request; pre-existing volumes are never removed. For "
|
|
202
|
+
"Kubernetes, the resulting PVC delete then triggers the bound PV's "
|
|
203
|
+
"StorageClass reclaim policy (Retain/Delete)."
|
|
204
204
|
),
|
|
205
205
|
)
|
|
206
206
|
|
|
@@ -424,7 +424,16 @@ class CreateSandboxRequest(BaseModel):
|
|
|
424
424
|
resource_limits: Optional[ResourceLimits] = Field(
|
|
425
425
|
None,
|
|
426
426
|
alias="resourceLimits",
|
|
427
|
-
description="Runtime resource constraints for the sandbox instance. Optional when poolRef is provided.",
|
|
427
|
+
description="Runtime resource constraints (hard caps) for the sandbox instance. Optional when poolRef is provided.",
|
|
428
|
+
)
|
|
429
|
+
resource_requests: Optional[ResourceLimits] = Field(
|
|
430
|
+
None,
|
|
431
|
+
alias="resourceRequests",
|
|
432
|
+
description=(
|
|
433
|
+
"Resource reservations (guaranteed minimums) for the sandbox instance. "
|
|
434
|
+
"When provided, used as Kubernetes resource requests, enabling Burstable QoS. "
|
|
435
|
+
"When omitted, resourceLimits values are used for both limits and requests."
|
|
436
|
+
),
|
|
428
437
|
)
|
|
429
438
|
env: Optional[Dict[str, Optional[str]]] = Field(
|
|
430
439
|
None,
|
|
@@ -32,7 +32,7 @@ level = "INFO"
|
|
|
32
32
|
|
|
33
33
|
[runtime]
|
|
34
34
|
type = "kubernetes"
|
|
35
|
-
execd_image = "opensandbox/execd:v1.0.
|
|
35
|
+
execd_image = "opensandbox/execd:v1.0.20"
|
|
36
36
|
|
|
37
37
|
[storage]
|
|
38
38
|
# Allowlist of host path prefixes permitted for bind mounts.
|
|
@@ -75,7 +75,7 @@ batchsandbox_template_file = "~/batchsandbox-template.yaml"
|
|
|
75
75
|
mode = "direct"
|
|
76
76
|
|
|
77
77
|
[egress]
|
|
78
|
-
image = "opensandbox/egress:v1.1.
|
|
78
|
+
image = "opensandbox/egress:v1.1.3"
|
|
79
79
|
mode = "dns"
|
|
80
80
|
# Default is true (recommended for dual-stack CNI). Set false only if you need IPv6 in the netns (see server/configuration.md).
|
|
81
81
|
# disable_ipv6 = false
|
|
@@ -32,7 +32,7 @@ level = "INFO"
|
|
|
32
32
|
|
|
33
33
|
[runtime]
|
|
34
34
|
type = "kubernetes"
|
|
35
|
-
execd_image = "sandbox-registry.cn-zhangjiakou.cr.aliyuncs.com/opensandbox/execd:v1.0.
|
|
35
|
+
execd_image = "sandbox-registry.cn-zhangjiakou.cr.aliyuncs.com/opensandbox/execd:v1.0.20"
|
|
36
36
|
|
|
37
37
|
[storage]
|
|
38
38
|
# 允许进行 bind mount 的宿主机路径前缀白名单。
|
|
@@ -76,7 +76,7 @@ batchsandbox_template_file = "~/batchsandbox-template.yaml"
|
|
|
76
76
|
mode = "direct"
|
|
77
77
|
|
|
78
78
|
[egress]
|
|
79
|
-
image = "sandbox-registry.cn-zhangjiakou.cr.aliyuncs.com/opensandbox/egress:v1.1.
|
|
79
|
+
image = "sandbox-registry.cn-zhangjiakou.cr.aliyuncs.com/opensandbox/egress:v1.1.3"
|
|
80
80
|
mode = "dns"
|
|
81
81
|
# Default is true (recommended for dual-stack CNI). Set false only if you need IPv6 in the netns (see server/configuration.md).
|
|
82
82
|
# disable_ipv6 = false
|
|
@@ -32,7 +32,7 @@ level = "INFO"
|
|
|
32
32
|
|
|
33
33
|
[runtime]
|
|
34
34
|
type = "docker"
|
|
35
|
-
execd_image = "opensandbox/execd:v1.0.
|
|
35
|
+
execd_image = "opensandbox/execd:v1.0.20"
|
|
36
36
|
|
|
37
37
|
[storage]
|
|
38
38
|
# Allowlist of host path prefixes permitted for bind mounts.
|
|
@@ -63,7 +63,7 @@ seccomp_profile = ""
|
|
|
63
63
|
mode = "direct"
|
|
64
64
|
|
|
65
65
|
[egress]
|
|
66
|
-
image = "opensandbox/egress:v1.1.
|
|
66
|
+
image = "opensandbox/egress:v1.1.3"
|
|
67
67
|
mode = "dns"
|
|
68
68
|
|
|
69
69
|
# 🧪 [EXPERIMENTAL] Renew-on-access. Off by default — see server/README.md.
|
|
@@ -32,7 +32,7 @@ level = "INFO"
|
|
|
32
32
|
|
|
33
33
|
[runtime]
|
|
34
34
|
type = "docker"
|
|
35
|
-
execd_image = "sandbox-registry.cn-zhangjiakou.cr.aliyuncs.com/opensandbox/execd:v1.0.
|
|
35
|
+
execd_image = "sandbox-registry.cn-zhangjiakou.cr.aliyuncs.com/opensandbox/execd:v1.0.20"
|
|
36
36
|
|
|
37
37
|
[storage]
|
|
38
38
|
allowed_host_paths = []
|
|
@@ -61,7 +61,7 @@ seccomp_profile = ""
|
|
|
61
61
|
mode = "direct"
|
|
62
62
|
|
|
63
63
|
[egress]
|
|
64
|
-
image = "sandbox-registry.cn-zhangjiakou.cr.aliyuncs.com/opensandbox/egress:v1.1.
|
|
64
|
+
image = "sandbox-registry.cn-zhangjiakou.cr.aliyuncs.com/opensandbox/egress:v1.1.3"
|
|
65
65
|
mode = "dns"
|
|
66
66
|
|
|
67
67
|
# 🧪 [EXPERIMENTAL] 按访问续期。默认关闭 — 见 server/README_zh.md。
|
{opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/extensions/__init__.py
RENAMED
|
@@ -23,8 +23,10 @@ from opensandbox_server.extensions.codec import (
|
|
|
23
23
|
from opensandbox_server.extensions.keys import (
|
|
24
24
|
ACCESS_RENEW_EXTEND_SECONDS_KEY,
|
|
25
25
|
ACCESS_RENEW_EXTEND_SECONDS_METADATA_KEY,
|
|
26
|
+
BOOTSTRAP_EXECD_ISOLATION_KEY,
|
|
26
27
|
EXTENSIONS_ANNOTATION_PREFIX,
|
|
27
28
|
ANNOTATION_METADATA_PREFIX,
|
|
29
|
+
ISOLATION_UPPER_MOUNT_PATH,
|
|
28
30
|
)
|
|
29
31
|
from opensandbox_server.extensions.validation import (
|
|
30
32
|
ACCESS_RENEW_EXTEND_SECONDS_MAX,
|
|
@@ -42,4 +44,6 @@ __all__ = [
|
|
|
42
44
|
"apply_extensions_to_annotations",
|
|
43
45
|
"EXTENSIONS_ANNOTATION_PREFIX",
|
|
44
46
|
"ANNOTATION_METADATA_PREFIX",
|
|
47
|
+
"BOOTSTRAP_EXECD_ISOLATION_KEY",
|
|
48
|
+
"ISOLATION_UPPER_MOUNT_PATH",
|
|
45
49
|
]
|
|
@@ -18,6 +18,15 @@
|
|
|
18
18
|
ACCESS_RENEW_EXTEND_SECONDS_KEY = "access.renew.extend.seconds"
|
|
19
19
|
ACCESS_RENEW_EXTEND_SECONDS_METADATA_KEY = "opensandbox.io/access-renew-extend-seconds"
|
|
20
20
|
|
|
21
|
+
# bootstrap.execd.isolation extension key
|
|
22
|
+
# When set to "enable", the sandbox container receives CAP_SYS_ADMIN so that
|
|
23
|
+
# execd can use bwrap namespace isolation (OSEP-0013).
|
|
24
|
+
BOOTSTRAP_EXECD_ISOLATION_KEY = "bootstrap.execd.isolation"
|
|
25
|
+
|
|
26
|
+
# Default mount path for the isolation upper directory.
|
|
27
|
+
# Must match the execd DefaultConfig().UpperRoot value.
|
|
28
|
+
ISOLATION_UPPER_MOUNT_PATH = "/var/lib/execd/isolation"
|
|
29
|
+
|
|
21
30
|
# Extensions to annotations transformation prefix
|
|
22
31
|
# User-specified extension keys starting with EXTENSIONS_ANNOTATION_PREFIX
|
|
23
32
|
# are automatically propagated to Pod annotations with ANNOTATION_METADATA_PREFIX
|
{opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/constants.py
RENAMED
|
@@ -42,6 +42,19 @@ EGRESS_MODE_ENV = "OPENSANDBOX_EGRESS_MODE"
|
|
|
42
42
|
# Must match components/egress/pkg/constants/configuration.go EnvEgressToken
|
|
43
43
|
OPENSANDBOX_EGRESS_TOKEN = "OPENSANDBOX_EGRESS_TOKEN"
|
|
44
44
|
OPENSANDBOX_EGRESS_MITMPROXY_TRANSPARENT = "OPENSANDBOX_EGRESS_MITMPROXY_TRANSPARENT"
|
|
45
|
+
|
|
46
|
+
EGRESS_ENV_PREFIX = "OPENSANDBOX_EGRESS_"
|
|
47
|
+
OPENSANDBOX_EGRESS_MITMPROXY_SSL_INSECURE = "OPENSANDBOX_EGRESS_MITMPROXY_SSL_INSECURE"
|
|
48
|
+
ALLOWED_EGRESS_ENV_VARS = frozenset({
|
|
49
|
+
"OPENSANDBOX_EGRESS_LOG_LEVEL",
|
|
50
|
+
"OPENSANDBOX_EGRESS_DNS_UPSTREAM_TIMEOUT",
|
|
51
|
+
OPENSANDBOX_EGRESS_MITMPROXY_SSL_INSECURE,
|
|
52
|
+
OPENSANDBOX_EGRESS_MITMPROXY_TRANSPARENT,
|
|
53
|
+
"OPENSANDBOX_EGRESS_MITMPROXY_SCRIPT",
|
|
54
|
+
"OPENSANDBOX_EGRESS_DENY_WEBHOOK",
|
|
55
|
+
"OPENSANDBOX_EGRESS_METRICS_EXTRA_ATTRS",
|
|
56
|
+
"OPENSANDBOX_EGRESS_CREDENTIAL_VAULT_REQUIRE_TLS",
|
|
57
|
+
})
|
|
45
58
|
OPENSANDBOX_RUNTIME_VOLUME_NAME = "opensandbox-bin"
|
|
46
59
|
OPENSANDBOX_RUNTIME_MOUNT_PATH = "/opt/opensandbox"
|
|
47
60
|
|
|
@@ -146,6 +159,9 @@ __all__ = [
|
|
|
146
159
|
"EGRESS_MODE_ENV",
|
|
147
160
|
"OPENSANDBOX_EGRESS_TOKEN",
|
|
148
161
|
"OPENSANDBOX_EGRESS_MITMPROXY_TRANSPARENT",
|
|
162
|
+
"EGRESS_ENV_PREFIX",
|
|
163
|
+
"OPENSANDBOX_EGRESS_MITMPROXY_SSL_INSECURE",
|
|
164
|
+
"ALLOWED_EGRESS_ENV_VARS",
|
|
149
165
|
"OPENSANDBOX_RUNTIME_VOLUME_NAME",
|
|
150
166
|
"OPENSANDBOX_RUNTIME_MOUNT_PATH",
|
|
151
167
|
"SandboxErrorCodes",
|
|
@@ -39,6 +39,8 @@ from fastapi import HTTPException, status
|
|
|
39
39
|
|
|
40
40
|
from opensandbox_server.extensions import (
|
|
41
41
|
ACCESS_RENEW_EXTEND_SECONDS_METADATA_KEY,
|
|
42
|
+
BOOTSTRAP_EXECD_ISOLATION_KEY,
|
|
43
|
+
ISOLATION_UPPER_MOUNT_PATH,
|
|
42
44
|
)
|
|
43
45
|
from opensandbox_server.api.schema import (
|
|
44
46
|
CreateSandboxRequest,
|
|
@@ -83,6 +85,7 @@ from opensandbox_server.services.docker.windows_profile import (
|
|
|
83
85
|
)
|
|
84
86
|
from opensandbox_server.services.extension_service import ExtensionService
|
|
85
87
|
from opensandbox_server.services.constants import (
|
|
88
|
+
OPENSANDBOX_EGRESS_MITMPROXY_SSL_INSECURE,
|
|
86
89
|
OPENSANDBOX_EGRESS_MITMPROXY_TRANSPARENT,
|
|
87
90
|
OPENSANDBOX_RUNTIME_MOUNT_PATH,
|
|
88
91
|
SANDBOX_EGRESS_AUTH_TOKEN_METADATA_KEY,
|
|
@@ -102,6 +105,7 @@ from opensandbox_server.services.endpoint_auth import (
|
|
|
102
105
|
from opensandbox_server.services.helpers import (
|
|
103
106
|
matches_filter,
|
|
104
107
|
parse_timestamp,
|
|
108
|
+
split_egress_env,
|
|
105
109
|
)
|
|
106
110
|
from opensandbox_server.services.docker.ossfs_mixin import OSSFSMixin
|
|
107
111
|
from opensandbox_server.services.sandbox_service import SandboxService
|
|
@@ -157,6 +161,7 @@ class DockerSandboxService(DockerDiagnosticsMixin, DockerRuntimeMixin, DockerVol
|
|
|
157
161
|
self.network_mode = (self.app_config.docker.network_mode or HOST_NETWORK_MODE).lower()
|
|
158
162
|
self._execd_archive_cache: Dict[str, bytes] = {}
|
|
159
163
|
self._bootstrap_script_cache: Dict[str, bytes] = {}
|
|
164
|
+
self._bwrap_archive_cache: Dict[str, bytes] = {}
|
|
160
165
|
self._windows_profile_cache: Dict[str, bytes] = {}
|
|
161
166
|
self._daemon_platform: Optional[PlatformSpec] = None
|
|
162
167
|
self._metadata_store = DockerMetadataStore()
|
|
@@ -402,11 +407,18 @@ class DockerSandboxService(DockerDiagnosticsMixin, DockerRuntimeMixin, DockerVol
|
|
|
402
407
|
except DockerException as exc:
|
|
403
408
|
logger.warning("Failed to remove expired sandbox %s: %s", sandbox_id, exc)
|
|
404
409
|
|
|
410
|
+
managed_volumes_raw = labels.get(SANDBOX_MANAGED_VOLUMES_LABEL, "[]")
|
|
411
|
+
try:
|
|
412
|
+
managed_volumes: list[str] = json.loads(managed_volumes_raw)
|
|
413
|
+
except (TypeError, json.JSONDecodeError):
|
|
414
|
+
managed_volumes = []
|
|
415
|
+
|
|
405
416
|
self._remove_expiration_tracking(sandbox_id)
|
|
406
417
|
# Ensure sidecar is also cleaned up on expiration
|
|
407
418
|
self._cleanup_egress_sidecar(sandbox_id)
|
|
408
419
|
self._cleanup_windows_oem_volume(sandbox_id, labels)
|
|
409
420
|
self._release_ossfs_mounts(mount_keys)
|
|
421
|
+
self._cleanup_managed_volumes(sandbox_id, managed_volumes)
|
|
410
422
|
self._metadata_store.delete(sandbox_id)
|
|
411
423
|
|
|
412
424
|
def _restore_existing_sandboxes(self) -> None:
|
|
@@ -630,11 +642,33 @@ class DockerSandboxService(DockerDiagnosticsMixin, DockerRuntimeMixin, DockerVol
|
|
|
630
642
|
self._ensure_secure_access_support(request)
|
|
631
643
|
self._ensure_network_policy_support(request)
|
|
632
644
|
self._validate_network_exists()
|
|
645
|
+
|
|
646
|
+
try:
|
|
647
|
+
sandbox_env, egress_env = split_egress_env(request.env)
|
|
648
|
+
except ValueError as e:
|
|
649
|
+
raise HTTPException(
|
|
650
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
651
|
+
detail={
|
|
652
|
+
"code": SandboxErrorCodes.INVALID_PARAMETER,
|
|
653
|
+
"message": str(e),
|
|
654
|
+
},
|
|
655
|
+
) from e
|
|
656
|
+
|
|
633
657
|
pvc_inspect_cache, auto_created_volumes = self._validate_volumes(request)
|
|
634
658
|
sandbox_id, created_at, expires_at = self._prepare_creation_context(request)
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
659
|
+
try:
|
|
660
|
+
return self._provision_sandbox(
|
|
661
|
+
sandbox_id, request, created_at, expires_at, pvc_inspect_cache, auto_created_volumes,
|
|
662
|
+
sandbox_env=sandbox_env, egress_env=egress_env,
|
|
663
|
+
)
|
|
664
|
+
except ValueError as e:
|
|
665
|
+
raise HTTPException(
|
|
666
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
667
|
+
detail={
|
|
668
|
+
"code": SandboxErrorCodes.INVALID_PARAMETER,
|
|
669
|
+
"message": str(e),
|
|
670
|
+
},
|
|
671
|
+
) from e
|
|
638
672
|
|
|
639
673
|
def _async_provision_worker(
|
|
640
674
|
self,
|
|
@@ -761,8 +795,13 @@ class DockerSandboxService(DockerDiagnosticsMixin, DockerRuntimeMixin, DockerVol
|
|
|
761
795
|
expires_at: Optional[datetime],
|
|
762
796
|
pvc_inspect_cache: Optional[dict[str, dict]] = None,
|
|
763
797
|
auto_created_volumes: Optional[list[str]] = None,
|
|
798
|
+
sandbox_env: Optional[Dict[str, Optional[str]]] = None,
|
|
799
|
+
egress_env: Optional[Dict[str, Optional[str]]] = None,
|
|
764
800
|
) -> CreateSandboxResponse:
|
|
765
|
-
|
|
801
|
+
if sandbox_env is None and egress_env is None:
|
|
802
|
+
sandbox_env, egress_env = split_egress_env(request.env)
|
|
803
|
+
patched_request = request.model_copy(update={"env": sandbox_env or None})
|
|
804
|
+
labels, environment = self._build_labels_and_env(sandbox_id, patched_request, expires_at)
|
|
766
805
|
if auto_created_volumes:
|
|
767
806
|
labels[SANDBOX_MANAGED_VOLUMES_LABEL] = json.dumps(
|
|
768
807
|
auto_created_volumes, separators=(",", ":"),
|
|
@@ -772,6 +811,25 @@ class DockerSandboxService(DockerDiagnosticsMixin, DockerRuntimeMixin, DockerVol
|
|
|
772
811
|
egress_token: Optional[str] = None
|
|
773
812
|
requested_windows_profile = is_windows_platform(request.platform)
|
|
774
813
|
|
|
814
|
+
credential_proxy_enabled = bool(
|
|
815
|
+
request.credential_proxy and request.credential_proxy.enabled
|
|
816
|
+
)
|
|
817
|
+
|
|
818
|
+
if credential_proxy_enabled and egress_env.get(OPENSANDBOX_EGRESS_MITMPROXY_SSL_INSECURE):
|
|
819
|
+
raise ValueError(
|
|
820
|
+
f"'{OPENSANDBOX_EGRESS_MITMPROXY_SSL_INSECURE}' cannot be set when credential proxy is enabled"
|
|
821
|
+
)
|
|
822
|
+
|
|
823
|
+
if egress_env and not request.network_policy:
|
|
824
|
+
dropped_keys = sorted(egress_env.keys())
|
|
825
|
+
logger.warning(
|
|
826
|
+
"Sandbox %s has OPENSANDBOX_EGRESS_ env vars %s but no networkPolicy; "
|
|
827
|
+
"these variables will be ignored because no egress sidecar is created",
|
|
828
|
+
sandbox_id,
|
|
829
|
+
dropped_keys,
|
|
830
|
+
)
|
|
831
|
+
egress_env = {}
|
|
832
|
+
|
|
775
833
|
if requested_windows_profile:
|
|
776
834
|
validate_windows_resource_limits((request.resource_limits.root if request.resource_limits else None) or {})
|
|
777
835
|
validate_windows_runtime_prerequisites()
|
|
@@ -785,9 +843,6 @@ class DockerSandboxService(DockerDiagnosticsMixin, DockerRuntimeMixin, DockerVol
|
|
|
785
843
|
)
|
|
786
844
|
|
|
787
845
|
sidecar_container = None
|
|
788
|
-
credential_proxy_enabled = bool(
|
|
789
|
-
request.credential_proxy and request.credential_proxy.enabled
|
|
790
|
-
)
|
|
791
846
|
runtime_volume_name: Optional[str] = None
|
|
792
847
|
try:
|
|
793
848
|
# For dockur/windows profile, resourceLimits are translated to
|
|
@@ -814,21 +869,21 @@ class DockerSandboxService(DockerDiagnosticsMixin, DockerRuntimeMixin, DockerVol
|
|
|
814
869
|
container_exposed_ports: Optional[list[str]] = exposed_ports
|
|
815
870
|
|
|
816
871
|
if request.network_policy:
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
labels={SANDBOX_MANAGED_VOLUMES_LABEL: "server"},
|
|
825
|
-
)
|
|
826
|
-
auto_created_volumes = list(auto_created_volumes or [])
|
|
827
|
-
auto_created_volumes.append(runtime_volume_name)
|
|
828
|
-
labels[SANDBOX_MANAGED_VOLUMES_LABEL] = json.dumps(
|
|
829
|
-
auto_created_volumes,
|
|
830
|
-
separators=(",", ":"),
|
|
872
|
+
runtime_volume_name = f"opensandbox-runtime-{sandbox_id}"
|
|
873
|
+
with self._docker_operation(
|
|
874
|
+
"create egress runtime volume", sandbox_id
|
|
875
|
+
):
|
|
876
|
+
self.docker_client.volumes.create(
|
|
877
|
+
name=runtime_volume_name,
|
|
878
|
+
labels={SANDBOX_MANAGED_VOLUMES_LABEL: "server"},
|
|
831
879
|
)
|
|
880
|
+
auto_created_volumes = list(auto_created_volumes or [])
|
|
881
|
+
auto_created_volumes.append(runtime_volume_name)
|
|
882
|
+
labels[SANDBOX_MANAGED_VOLUMES_LABEL] = json.dumps(
|
|
883
|
+
auto_created_volumes,
|
|
884
|
+
separators=(",", ":"),
|
|
885
|
+
)
|
|
886
|
+
if credential_proxy_enabled:
|
|
832
887
|
environment = [
|
|
833
888
|
entry
|
|
834
889
|
for entry in environment
|
|
@@ -859,6 +914,7 @@ class DockerSandboxService(DockerDiagnosticsMixin, DockerRuntimeMixin, DockerVol
|
|
|
859
914
|
),
|
|
860
915
|
runtime_volume_name=runtime_volume_name,
|
|
861
916
|
credential_proxy_enabled=credential_proxy_enabled,
|
|
917
|
+
extra_env=egress_env or None,
|
|
862
918
|
)
|
|
863
919
|
labels[SANDBOX_EMBEDDING_PROXY_PORT_LABEL] = str(host_execd_port)
|
|
864
920
|
labels[SANDBOX_HTTP_PORT_LABEL] = str(host_http_port)
|
|
@@ -891,9 +947,16 @@ class DockerSandboxService(DockerDiagnosticsMixin, DockerRuntimeMixin, DockerVol
|
|
|
891
947
|
|
|
892
948
|
# Inject volume bind mounts into Docker host config
|
|
893
949
|
if runtime_volume_name:
|
|
894
|
-
|
|
895
|
-
|
|
950
|
+
runtime_mount_suffix = f":{OPENSANDBOX_RUNTIME_MOUNT_PATH}:"
|
|
951
|
+
runtime_mount_end = f":{OPENSANDBOX_RUNTIME_MOUNT_PATH}"
|
|
952
|
+
has_runtime_mount = any(
|
|
953
|
+
runtime_mount_suffix in bind or bind.endswith(runtime_mount_end)
|
|
954
|
+
for bind in volume_binds
|
|
896
955
|
)
|
|
956
|
+
if not has_runtime_mount:
|
|
957
|
+
volume_binds.append(
|
|
958
|
+
f"{runtime_volume_name}:{OPENSANDBOX_RUNTIME_MOUNT_PATH}:rw"
|
|
959
|
+
)
|
|
897
960
|
if volume_binds:
|
|
898
961
|
host_config_kwargs["binds"] = volume_binds
|
|
899
962
|
if requested_windows_profile:
|
|
@@ -907,6 +970,26 @@ class DockerSandboxService(DockerDiagnosticsMixin, DockerRuntimeMixin, DockerVol
|
|
|
907
970
|
)
|
|
908
971
|
environment = inject_windows_user_ports(environment, exposed_ports)
|
|
909
972
|
|
|
973
|
+
# Inject CAP_SYS_ADMIN + unconfined AppArmor when bwrap isolation is requested.
|
|
974
|
+
# bwrap needs pivot_root/mount which require CAP_SYS_ADMIN and are blocked
|
|
975
|
+
# by Docker's default AppArmor profile.
|
|
976
|
+
if (request.extensions or {}).get(BOOTSTRAP_EXECD_ISOLATION_KEY) == "enable":
|
|
977
|
+
cap_add = set(host_config_kwargs.get("cap_add") or [])
|
|
978
|
+
cap_add.add("SYS_ADMIN")
|
|
979
|
+
host_config_kwargs["cap_add"] = sorted(cap_add)
|
|
980
|
+
security_opt = list(host_config_kwargs.get("security_opt") or [])
|
|
981
|
+
security_opt = [s for s in security_opt if not s.startswith(("apparmor=", "seccomp="))]
|
|
982
|
+
security_opt.append("apparmor=unconfined")
|
|
983
|
+
security_opt.append("seccomp=unconfined")
|
|
984
|
+
host_config_kwargs["security_opt"] = security_opt
|
|
985
|
+
tmpfs = dict(host_config_kwargs.get("tmpfs") or {})
|
|
986
|
+
tmpfs[ISOLATION_UPPER_MOUNT_PATH] = ""
|
|
987
|
+
host_config_kwargs["tmpfs"] = tmpfs
|
|
988
|
+
logger.warning(
|
|
989
|
+
"sandbox %s: granting CAP_SYS_ADMIN + apparmor/seccomp=unconfined + tmpfs for bwrap isolation (bootstrap.execd.isolation=enable)",
|
|
990
|
+
sandbox_id,
|
|
991
|
+
)
|
|
992
|
+
|
|
910
993
|
created_container = self._create_and_start_container(
|
|
911
994
|
sandbox_id,
|
|
912
995
|
image_uri,
|
|
@@ -28,7 +28,7 @@ import socket
|
|
|
28
28
|
import time
|
|
29
29
|
import urllib.error
|
|
30
30
|
import urllib.request
|
|
31
|
-
from typing import Any, Optional
|
|
31
|
+
from typing import Any, Dict, Optional
|
|
32
32
|
|
|
33
33
|
from docker.errors import DockerException, NotFound as DockerNotFound
|
|
34
34
|
from fastapi import HTTPException, status
|
|
@@ -53,7 +53,7 @@ from opensandbox_server.services.endpoint_auth import (
|
|
|
53
53
|
build_egress_auth_headers,
|
|
54
54
|
merge_endpoint_headers,
|
|
55
55
|
)
|
|
56
|
-
from opensandbox_server.services.validators import ensure_egress_configured
|
|
56
|
+
from opensandbox_server.services.validators import ensure_egress_configured, ensure_egress_runtime_compatible
|
|
57
57
|
|
|
58
58
|
logger = logging.getLogger(__name__)
|
|
59
59
|
|
|
@@ -139,6 +139,7 @@ class DockerNetworkingMixin:
|
|
|
139
139
|
|
|
140
140
|
# Common validation: egress.image must be configured
|
|
141
141
|
ensure_egress_configured(request.network_policy, self.app_config.egress)
|
|
142
|
+
ensure_egress_runtime_compatible(request.network_policy, self.app_config.secure_runtime)
|
|
142
143
|
|
|
143
144
|
def _ensure_secure_access_support(self, request) -> None:
|
|
144
145
|
"""Validate that secure access can be honored under the current Docker runtime."""
|
|
@@ -370,6 +371,7 @@ class DockerNetworkingMixin:
|
|
|
370
371
|
egress_api_host_port: Optional[int] = None,
|
|
371
372
|
runtime_volume_name: Optional[str] = None,
|
|
372
373
|
credential_proxy_enabled: bool = False,
|
|
374
|
+
extra_env: Optional[Dict[str, Optional[str]]] = None,
|
|
373
375
|
):
|
|
374
376
|
sidecar_name = f"sandbox-egress-{sandbox_id}"
|
|
375
377
|
sidecar_labels = {
|
|
@@ -393,6 +395,12 @@ class DockerNetworkingMixin:
|
|
|
393
395
|
if credential_proxy_enabled:
|
|
394
396
|
sidecar_env.append(f"{OPENSANDBOX_EGRESS_MITMPROXY_TRANSPARENT}=true")
|
|
395
397
|
|
|
398
|
+
if extra_env:
|
|
399
|
+
skip_keys = {OPENSANDBOX_EGRESS_MITMPROXY_TRANSPARENT} if credential_proxy_enabled else set()
|
|
400
|
+
for key, value in extra_env.items():
|
|
401
|
+
if key not in skip_keys and value is not None:
|
|
402
|
+
sidecar_env.append(f"{key}={value}")
|
|
403
|
+
|
|
396
404
|
sidecar_port_bindings: dict[str, tuple[str, int]] = {
|
|
397
405
|
"44772": ("0.0.0.0", host_execd_port),
|
|
398
406
|
"8080": ("0.0.0.0", host_http_port),
|
{opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/docker/runtime.py
RENAMED
|
@@ -117,6 +117,14 @@ class DockerRuntimeMixin:
|
|
|
117
117
|
with self._docker_operation("execd cache read bootstrap", "execd-cache"):
|
|
118
118
|
bs_stream, _ = container.get_archive("/bootstrap.sh")
|
|
119
119
|
self._bootstrap_script_cache[cache_key] = b"".join(bs_stream)
|
|
120
|
+
# Cache bwrap binary (best-effort — may not exist in older images).
|
|
121
|
+
if cache_key not in self._bwrap_archive_cache:
|
|
122
|
+
try:
|
|
123
|
+
with self._docker_operation("execd cache read bwrap", "execd-cache"):
|
|
124
|
+
bwrap_stream, _ = container.get_archive("/usr/local/bin/bwrap")
|
|
125
|
+
self._bwrap_archive_cache[cache_key] = b"".join(bwrap_stream)
|
|
126
|
+
except DockerException:
|
|
127
|
+
logger.warning("bwrap not found in execd image — isolation will be unavailable, upgrade execd image to v1.1.0+")
|
|
120
128
|
except DockerException as exc:
|
|
121
129
|
raise HTTPException(
|
|
122
130
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
@@ -227,6 +235,33 @@ class DockerRuntimeMixin:
|
|
|
227
235
|
},
|
|
228
236
|
) from exc
|
|
229
237
|
|
|
238
|
+
def _copy_bwrap_to_container(
|
|
239
|
+
self,
|
|
240
|
+
container,
|
|
241
|
+
sandbox_id: str,
|
|
242
|
+
platform: Optional[PlatformSpec] = None,
|
|
243
|
+
) -> None:
|
|
244
|
+
"""Copy bwrap binary into /opt/opensandbox/bin/ (best-effort).
|
|
245
|
+
|
|
246
|
+
Bwrap may not exist in older execd images, so failure is non-fatal —
|
|
247
|
+
isolation will simply be unavailable at runtime.
|
|
248
|
+
"""
|
|
249
|
+
cache_key = self._normalize_platform_key(platform)
|
|
250
|
+
archive = self._bwrap_archive_cache.get(cache_key)
|
|
251
|
+
if archive is None:
|
|
252
|
+
logger.warning("bwrap archive not cached for %s — isolation will be unavailable, upgrade execd image to v1.1.0+", cache_key)
|
|
253
|
+
return
|
|
254
|
+
|
|
255
|
+
try:
|
|
256
|
+
with self._docker_operation("copy bwrap to sandbox", sandbox_id):
|
|
257
|
+
container.put_archive(path=OPENSANDBOX_DIR, data=archive)
|
|
258
|
+
except DockerException as exc:
|
|
259
|
+
logger.warning(
|
|
260
|
+
"Failed to copy bwrap into sandbox %s: %s (isolation will be unavailable)",
|
|
261
|
+
sandbox_id,
|
|
262
|
+
exc,
|
|
263
|
+
)
|
|
264
|
+
|
|
230
265
|
def _prepare_sandbox_runtime(
|
|
231
266
|
self,
|
|
232
267
|
container,
|
|
@@ -236,3 +271,4 @@ class DockerRuntimeMixin:
|
|
|
236
271
|
"""Copy execd artifacts and bootstrap launcher into the sandbox container."""
|
|
237
272
|
self._copy_execd_to_container(container, sandbox_id, platform)
|
|
238
273
|
self._install_bootstrap_script(container, sandbox_id, platform)
|
|
274
|
+
self._copy_bwrap_to_container(container, sandbox_id, platform)
|
{opensandbox_server-0.2.0 → opensandbox_server-0.2.1}/opensandbox_server/services/helpers.py
RENAMED
|
@@ -27,7 +27,12 @@ from datetime import datetime, timezone
|
|
|
27
27
|
from typing import Dict, Optional
|
|
28
28
|
|
|
29
29
|
from opensandbox_server.api.schema import Endpoint, Sandbox, SandboxFilter
|
|
30
|
-
from opensandbox_server.services.constants import
|
|
30
|
+
from opensandbox_server.services.constants import (
|
|
31
|
+
ALLOWED_EGRESS_ENV_VARS,
|
|
32
|
+
EGRESS_ENV_PREFIX,
|
|
33
|
+
OPENSANDBOX_EGRESS_MITMPROXY_TRANSPARENT,
|
|
34
|
+
OPEN_SANDBOX_INGRESS_HEADER,
|
|
35
|
+
)
|
|
31
36
|
from opensandbox_server.config import (
|
|
32
37
|
GATEWAY_ROUTE_MODE_HEADER,
|
|
33
38
|
GATEWAY_ROUTE_MODE_URI,
|
|
@@ -234,6 +239,34 @@ def format_ingress_endpoint(
|
|
|
234
239
|
raise RuntimeError(f"Unsupported route mode: {route_mode}")
|
|
235
240
|
|
|
236
241
|
|
|
242
|
+
def split_egress_env(
|
|
243
|
+
env: Optional[Dict[str, Optional[str]]],
|
|
244
|
+
) -> tuple[Dict[str, Optional[str]], Dict[str, Optional[str]]]:
|
|
245
|
+
"""Split request env into (sandbox_env, egress_env) by OPENSANDBOX_EGRESS_ prefix.
|
|
246
|
+
|
|
247
|
+
Only env vars listed in ALLOWED_EGRESS_ENV_VARS are forwarded to the egress
|
|
248
|
+
sidecar. Any other OPENSANDBOX_EGRESS_ key raises ValueError.
|
|
249
|
+
"""
|
|
250
|
+
if not env:
|
|
251
|
+
return {}, {}
|
|
252
|
+
|
|
253
|
+
sandbox_env: Dict[str, Optional[str]] = {}
|
|
254
|
+
egress_env: Dict[str, Optional[str]] = {}
|
|
255
|
+
for key, value in env.items():
|
|
256
|
+
if key.startswith(EGRESS_ENV_PREFIX):
|
|
257
|
+
if key not in ALLOWED_EGRESS_ENV_VARS:
|
|
258
|
+
raise ValueError(
|
|
259
|
+
f"Environment variable '{key}' is not allowed; "
|
|
260
|
+
f"permitted OPENSANDBOX_EGRESS_ keys: {sorted(ALLOWED_EGRESS_ENV_VARS)}"
|
|
261
|
+
)
|
|
262
|
+
egress_env[key] = value
|
|
263
|
+
if key == OPENSANDBOX_EGRESS_MITMPROXY_TRANSPARENT:
|
|
264
|
+
sandbox_env[key] = value
|
|
265
|
+
else:
|
|
266
|
+
sandbox_env[key] = value
|
|
267
|
+
return sandbox_env, egress_env
|
|
268
|
+
|
|
269
|
+
|
|
237
270
|
__all__ = [
|
|
238
271
|
"parse_memory_limit",
|
|
239
272
|
"parse_nano_cpus",
|
|
@@ -242,4 +275,5 @@ __all__ = [
|
|
|
242
275
|
"normalize_external_endpoint_url",
|
|
243
276
|
"format_ingress_endpoint",
|
|
244
277
|
"matches_filter",
|
|
278
|
+
"split_egress_env",
|
|
245
279
|
]
|