Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/daemon/internal/cli/connect_bootstrap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func TestBootstrapConnectionDoesNotReadOrOverwritePrivateProfile(t *testing.T) {
t.Fatal(err)
}
input := runtimebootstrap.Connection{Version: runtimebootstrap.Version, CoreURL: "https://core.example/api/v1", DeviceID: "da912024-1543-4242-a2c1-5f4f7ebbc6c7", Credential: "bootstrap-secret",
EnvironmentID: uuid.NewString(), SessionID: uuid.NewString(), Workspace: t.TempDir()}
EnvironmentID: uuid.NewString(), Workspace: t.TempDir()}
raw, err := input.Marshal()
if err != nil {
t.Fatal(err)
Expand Down
1 change: 0 additions & 1 deletion docs/runtime-bootstrap.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ oac-daemon connect --bootstrap-file /home/runtime/runtime-bootstrap.json
| `device_id` | Canonical nonzero UUID of the daemon identity Core issued |
| `credential` | Nonempty daemon credential Core issued, without whitespace or NUL |
| `environment_id` | Canonical nonzero UUID of the Environment the Runtime serves |
| `session_id` | Canonical nonzero UUID of a Session of the Environment. The Runtime binds to the Environment and ignores it |
| `workspace` | Clean absolute directory of the workspace, `runtimebootstrap.RuntimeWorkspace` in the packaged Runtime layout. The Runtime serves it as `runtimebootstrap.PublicWorkspace` (`/workspace`), the path applications and Core use |
| `suspend_control` | Required boolean. Core sets it exactly when the Provider declares [checkpoint support](./sandbox-provider.md), and the Provider forwards it unchanged |

Expand Down
3 changes: 1 addition & 2 deletions docs/zh/runtime-bootstrap.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "Runtime 引导"
source: docs/runtime-bootstrap.md
source_hash: 50bfbec00d4aec47aab573e3f0c99ff3bd6ebababf97b859d37101a60657b786
source_hash: 794beca77df7755794769b9b9d3b4da841a3a58594cb3664405535760dbb581b
---

Runtime 引导协议连接沙箱提供商与其启动的托管 Runtime。提供商向 Runtime 交付一个引导文件,该文件是此次启动中 Runtime 唯一的认证输入,也是其 Environment 绑定的唯一来源。
Expand Down Expand Up @@ -53,7 +53,6 @@ oac-daemon connect --bootstrap-file /home/runtime/runtime-bootstrap.json
| `device_id` | Core 签发的 daemon 身份的规范非零 UUID |
| `credential` | Core 签发的非空 daemon 凭据,不含空白或 NUL |
| `environment_id` | Runtime 所服务 Environment 的规范非零 UUID |
| `session_id` | 该 Environment 的一个 Session 的规范非零 UUID。Runtime 绑定到 Environment,忽略此字段 |
| `workspace` | 工作区的干净绝对目录;打包的 Runtime 布局中为 `runtimebootstrap.RuntimeWorkspace`。Runtime 将其作为 `runtimebootstrap.PublicWorkspace`(`/workspace`)提供,这是应用与 Core 使用的路径 |
| `suspend_control` | 必填布尔值。当且仅当提供商声明[检查点支持](./sandbox-provider.md)时,Core 将其置为 true,提供商原样转交 |

Expand Down
12 changes: 4 additions & 8 deletions internal/runtimebootstrap/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
"github.com/google/uuid"
)

const Version = 3
const Version = 4
const MaxBytes = 16 * 1024

// PublicWorkspace is the workspace path applications and Core use for a hosted Environment.
Expand All @@ -35,16 +35,14 @@ var ErrInvalid = errors.New("invalid Runtime bootstrap input")
// Connection is a current-version launch input, not Runtime's private auth store.
// CoreURL includes the machine API base path. The provider owns delivery and
// file permissions; Runtime owns validation and its authentication representation.
// EnvironmentID and Workspace bind the Runtime to its Environment; the Runtime
// ignores SessionID.
// EnvironmentID and Workspace bind the Runtime to its Environment.
// SuspendControl is true exactly when the Provider declares checkpoint support.
type Connection struct {
Version int `json:"version"`
CoreURL string `json:"core_url"`
DeviceID string `json:"device_id"`
Credential string `json:"credential"`
EnvironmentID string `json:"environment_id"`
SessionID string `json:"session_id"`
Workspace string `json:"workspace"`
SuspendControl bool `json:"suspend_control"`
}
Expand All @@ -60,7 +58,7 @@ func (c Connection) Validate() error {
u.Hostname() == "" || u.User != nil || u.RawQuery != "" || u.ForceQuery || u.Fragment != "" ||
u.RawPath != "" || u.Path != "/api/v1" ||
strings.ContainsAny(c.CoreURL, "?#") || strings.ContainsFunc(c.CoreURL, unicode.IsSpace) ||
!canonicalUUID(c.DeviceID) || !canonicalUUID(c.EnvironmentID) || !canonicalUUID(c.SessionID) ||
!canonicalUUID(c.DeviceID) || !canonicalUUID(c.EnvironmentID) ||
!filepath.IsAbs(c.Workspace) || filepath.Clean(c.Workspace) != c.Workspace || c.Workspace == "/" ||
c.Credential == "" || strings.ContainsFunc(c.Credential, func(r rune) bool { return r == 0 || unicode.IsSpace(r) }) {
return ErrInvalid
Expand Down Expand Up @@ -110,8 +108,6 @@ func Decode(raw []byte) (Connection, error) {
err = d.Decode(&c.Credential)
case "environment_id":
err = d.Decode(&c.EnvironmentID)
case "session_id":
err = d.Decode(&c.SessionID)
case "workspace":
err = d.Decode(&c.Workspace)
case "suspend_control":
Expand All @@ -132,7 +128,7 @@ func Decode(raw []byte) (Connection, error) {
if token, err = d.Token(); err != nil || token != json.Delim('}') {
return Connection{}, ErrInvalid
}
if len(seen) != 8 || d.Decode(new(any)) != io.EOF || c.Validate() != nil {
if len(seen) != 7 || d.Decode(new(any)) != io.EOF || c.Validate() != nil {
return Connection{}, ErrInvalid
}
return c, nil
Expand Down
11 changes: 5 additions & 6 deletions internal/runtimebootstrap/bootstrap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

func TestConnectionRoundTrip(t *testing.T) {
input := Connection{Version: Version, CoreURL: "https://core.example/api/v1", DeviceID: "da912024-1543-4242-a2c1-5f4f7ebbc6c7", Credential: "test-credential",
EnvironmentID: "0d1f4f1c-6c3e-4e0b-9a52-3e3d8b0e7a11", SessionID: "6a1b7c0e-2f4d-4c55-8f6e-9d2a1b3c4d5e", Workspace: RuntimeWorkspace, SuspendControl: true}
EnvironmentID: "0d1f4f1c-6c3e-4e0b-9a52-3e3d8b0e7a11", Workspace: RuntimeWorkspace, SuspendControl: true}
raw, err := input.Marshal()
if err != nil {
t.Fatal(err)
Expand All @@ -20,14 +20,14 @@ func TestConnectionRoundTrip(t *testing.T) {
}

func TestConnectionRejectsAmbiguousOrForeignInput(t *testing.T) {
good := `{"version":3,"core_url":"https://core.example/api/v1","device_id":"da912024-1543-4242-a2c1-5f4f7ebbc6c7","credential":"test-secret","environment_id":"0d1f4f1c-6c3e-4e0b-9a52-3e3d8b0e7a11","session_id":"6a1b7c0e-2f4d-4c55-8f6e-9d2a1b3c4d5e","workspace":"/environment/workspace","suspend_control":false}`
good := `{"version":4,"core_url":"https://core.example/api/v1","device_id":"da912024-1543-4242-a2c1-5f4f7ebbc6c7","credential":"test-secret","environment_id":"0d1f4f1c-6c3e-4e0b-9a52-3e3d8b0e7a11","workspace":"/environment/workspace","suspend_control":false}`
if _, err := Decode([]byte(good)); err != nil {
t.Fatal("valid input rejected", err)
}
for name, raw := range map[string]string{
"null": "null", "array": "[]", "trailing": good + "{}",
"duplicate": strings.Replace(good, `"version":3`, `"version":3,"version":3`, 1),
"unknown": strings.Replace(good, `"version":3`, `"extra":1,"version":3`, 1),
"duplicate": strings.Replace(good, `"version":4`, `"version":4,"version":4`, 1),
"unknown": strings.Replace(good, `"version":4`, `"extra":1,"version":4`, 1),
"case": strings.Replace(good, "credential", "Credential", 1),
"old auth": `{"server_url":"https://core.example","runtime_id":"old","runner_credential":"test-secret"}`,
"oversized": strings.Repeat(" ", MaxBytes) + good,
Expand All @@ -39,13 +39,12 @@ func TestConnectionRejectsAmbiguousOrForeignInput(t *testing.T) {
})
}
for field, values := range map[string][]any{
"version": {nil, 0, 2, "3"},
"version": {nil, 0, 3, "4"},
"suspend_control": {nil, 1, "true"},
"core_url": {"http://user:pass@core.example/api/v1", "https://core.example/api/v1?", "https://core.example/api/v1#", "https://core.example", "https://core.example/api/v1/", "https://core.example/api/v1?q=1", ""},
"device_id": {"", "invalid", "00000000-0000-0000-0000-000000000000"},
"credential": {nil, "", "test\nsecret", "test\x00secret"},
"environment_id": {"", "invalid", "00000000-0000-0000-0000-000000000000"},
"session_id": {"", "invalid", "00000000-0000-0000-0000-000000000000"},
"workspace": {"", "workspace", "/", "/environment/../workspace", "/environment/workspace/"},
} {
for _, value := range values {
Expand Down
2 changes: 1 addition & 1 deletion services/core/deploy/e2b/helper_contract_generated.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@
REFERENCE_FIELDS = ["TenantID","EnvironmentID","AllocationID"]
REQUEST_FIELDS = ["Version","Operation","Config","Reference","References","Bootstrap","RuntimeBootstrap","Command","Compute","Suspend","Resume","Snapshot","Deadline"]
RESPONSE_FIELDS = ["Version","Info","Command","State","ErrorCode","DeploymentValid","TemplateBuild","Templates","Builds","Observation"]
RUNTIME_BOOTSTRAP_VERSION = 3
RUNTIME_BOOTSTRAP_VERSION = 4
SDK_VERSION = "2.51.0"
SUSPEND_CONTROL_FILE = "/run/oac/daemon-suspend.json"
4 changes: 2 additions & 2 deletions services/core/deploy/e2b/managed_init_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
def payload():
value = {key: str(uuid4()) for key in ['InstallationID', 'TenantID', 'EnvironmentID',
'AllocationID', 'SessionID', 'DeviceID']}
return dict(value, RuntimeBootstrap={'version': 3, 'core_url': 'https://core.example/api/v1',
return dict(value, RuntimeBootstrap={'version': 4, 'core_url': 'https://core.example/api/v1',
'device_id': value['DeviceID'], 'credential': 'private-managed-token',
'environment_id': value['EnvironmentID'], 'session_id': value['SessionID'], 'workspace': '/environment/workspace', 'suspend_control': True})
'environment_id': value['EnvironmentID'], 'workspace': '/environment/workspace', 'suspend_control': True})


class ManagedStartupTest(unittest.TestCase):
Expand Down
2 changes: 1 addition & 1 deletion services/core/internal/sandbox/runtime_bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ func (b Bootstrap) RuntimeConnection() runtimebootstrap.Connection {
return runtimebootstrap.Connection{
Version: runtimebootstrap.Version, CoreURL: b.CoreURL,
DeviceID: b.DeviceID, Credential: b.Credential,
EnvironmentID: b.EnvironmentID, SessionID: b.SessionID, Workspace: runtimebootstrap.RuntimeWorkspace,
EnvironmentID: b.EnvironmentID, Workspace: runtimebootstrap.RuntimeWorkspace,
SuspendControl: b.SuspendControl,
}
}
2 changes: 1 addition & 1 deletion services/core/tools/e2b-provider/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ A helper holds its allocation's lock until the SDK operation returns, even after
2. Record the sandbox ID and connection material, check the sandbox domain, then read the sandbox by ID and check its ownership metadata, template and resources before writing any credential. A mismatch records a settled rejection and returns `CreateSettled` with the error.
3. Check that `/opt/oac-e2b/managed_init.py` is readable, write the managed bootstrap input to `/root/.oac/e2b/managed-bootstrap.json` and run `managed_init.py` as root.

`managed_init.py` prepares the image as the [application-managed startup](../../deploy/e2b/README.md#startup-and-security-boundary) does, writes the [Runtime bootstrap](../../../../docs/runtime-bootstrap.md) file to `/home/runtime/runtime-bootstrap.json` (mode 0600, owned by UID 1000), sets the Environment and Session variables and starts `oac-daemon connect --profile default --bootstrap-file /home/runtime/runtime-bootstrap.json` as UID/GID 1000. It records process handoff in `/root/.oac/e2b/managed-ready.json` and refuses to run again once any launch record exists. `BootstrapComplete` becomes true when a later inspection reads that record with the expected identity; it does not prove enrollment or native readiness.
`managed_init.py` prepares the image as the [application-managed startup](../../deploy/e2b/README.md#startup-and-security-boundary) does, writes the [Runtime bootstrap](../../../../docs/runtime-bootstrap.md) file to `/home/runtime/runtime-bootstrap.json` (mode 0600, owned by UID 1000), sets the Runtime process environment and starts `oac-daemon connect --profile default --bootstrap-file /home/runtime/runtime-bootstrap.json` as UID/GID 1000. It records process handoff in `/root/.oac/e2b/managed-ready.json` and refuses to run again once any launch record exists. `BootstrapComplete` becomes true when a later inspection reads that record with the expected identity; it does not prove enrollment or native readiness.

An unknown Create is never repeated. A Create whose connection material was lost can be discovered and destroyed but cannot resume bootstrap, and an unconfirmed startup requires reclaiming the whole allocation.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@
REFERENCE_FIELDS = ["TenantID","EnvironmentID","AllocationID"]
REQUEST_FIELDS = ["Version","Operation","Config","Reference","References","Bootstrap","RuntimeBootstrap","Command","Compute","Suspend","Resume","Snapshot","Deadline"]
RESPONSE_FIELDS = ["Version","Info","Command","State","ErrorCode","DeploymentValid","TemplateBuild","Templates","Builds","Observation"]
RUNTIME_BOOTSTRAP_VERSION = 3
RUNTIME_BOOTSTRAP_VERSION = 4
SDK_VERSION = "2.51.0"
SUSPEND_CONTROL_FILE = "/run/oac/daemon-suspend.json"
12 changes: 7 additions & 5 deletions services/core/tools/e2b-provider/provider_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def setUp(self):
CoreURL='https://core.example/api/v1', Credential='private-runtime-secret', SuspendControl=True,
Egress={'public': True, 'private_domains': [], 'domains': [], 'endpoints': []})}
self.request['RuntimeBootstrap'] = {
'version': 3, 'suspend_control': True, 'core_url': self.request['Bootstrap']['CoreURL'],
'version': provider.RUNTIME_BOOTSTRAP_VERSION, 'suspend_control': True, 'core_url': self.request['Bootstrap']['CoreURL'],
'device_id': self.request['Bootstrap']['DeviceID'],
'credential': self.request['Bootstrap']['Credential']}
self.cloud = Mock(sandbox_id='owned-id', sandbox_domain='e2b.app', _envd_version='0.5.0',
Expand Down Expand Up @@ -357,13 +357,15 @@ def exit_code(self, files):
with tempfile.TemporaryDirectory() as root:
for name, text in files.items():
Path(root, name).write_text(text)
return subprocess.run([sys.executable, '-I', '-c', provider.TEMPLATE_CHECK, root, '3']).returncode
return subprocess.run([sys.executable, '-I', '-c', provider.TEMPLATE_CHECK, root, str(provider.RUNTIME_BOOTSTRAP_VERSION)]).returncode

def test_template_needs_startup_script_and_current_bootstrap_version(self):
self.assertEqual(self.exit_code({'managed_init.py': '', 'helper_contract_generated.py': 'RUNTIME_BOOTSTRAP_VERSION = 3'}), 0)
self.assertEqual(self.exit_code({'managed_init.py': '', 'helper_contract_generated.py': 'RUNTIME_BOOTSTRAP_VERSION = 2'}), 78)
current = 'RUNTIME_BOOTSTRAP_VERSION = %d' % provider.RUNTIME_BOOTSTRAP_VERSION
previous = 'RUNTIME_BOOTSTRAP_VERSION = %d' % (provider.RUNTIME_BOOTSTRAP_VERSION - 1)
self.assertEqual(self.exit_code({'managed_init.py': '', 'helper_contract_generated.py': current}), 0)
self.assertEqual(self.exit_code({'managed_init.py': '', 'helper_contract_generated.py': previous}), 78)
self.assertEqual(self.exit_code({'managed_init.py': '', 'helper_contract_generated.py': ''}), 78)
self.assertEqual(self.exit_code({'helper_contract_generated.py': 'RUNTIME_BOOTSTRAP_VERSION = 3'}), 78)
self.assertEqual(self.exit_code({'helper_contract_generated.py': current}), 78)


class CheckpointTest(Fixture):
Expand Down
Loading
Loading