e0e362d700
SDK Tests / changes (push) Successful in 2m29s
Real E2E Tests / changes (push) Successful in 2m29s
Deploy Docs Pages / build (push) Has been cancelled
Deploy Docs Pages / deploy (push) Has been cancelled
Real E2E Tests / JavaScript E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Python E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Java E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / C# E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Go E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Real E2E CI (push) Has been cancelled
SDK Tests / SDK CI (push) Has been cancelled
SDK Tests / CLI Tests (push) Has been cancelled
SDK Tests / Python SDK Quality (code-interpreter) (push) Has been cancelled
SDK Tests / Python SDK Quality (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (sandbox) (push) Has been cancelled
SDK Tests / CLI Quality (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Go SDK Quality And Tests (push) Has been cancelled
129 lines
3.9 KiB
Go
129 lines
3.9 KiB
Go
// Copyright 2026 Alibaba Group Holding Ltd.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//go:build linux && bwrap
|
|
|
|
package bwrap_test
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/alibaba/opensandbox/execd/pkg/isolation"
|
|
"github.com/alibaba/opensandbox/execd/pkg/runtime"
|
|
)
|
|
|
|
// TestBinds_SourceToDest verifies a host path can be bind-mounted at a distinct
|
|
// destination inside the namespace and written through.
|
|
func TestBinds_SourceToDest(t *testing.T) {
|
|
r := newRunner(t)
|
|
|
|
srcDir := t.TempDir()
|
|
// Destination must be an existing mount point inside the namespace. Use a
|
|
// separate temp dir under /tmp (bind-mounted into the namespace) so bwrap
|
|
// can bind onto it without needing to create a dir under a read-only mount.
|
|
destDir := t.TempDir()
|
|
|
|
opts := &runtime.IsolatedSessionOptions{
|
|
Profile: "balanced",
|
|
WorkspacePath: t.TempDir(),
|
|
WorkspaceMode: "rw",
|
|
Binds: []isolation.BindMount{
|
|
{Source: srcDir, Dest: destDir},
|
|
},
|
|
}
|
|
|
|
id, err := r.CreateIsolatedSession(opts)
|
|
require.NoError(t, err)
|
|
defer r.DeleteIsolatedSession(id)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
// Write via the in-namespace destination path.
|
|
err = r.RunInIsolatedSession(ctx, id, "echo 'mapped-data' > "+destDir+"/out.txt", nil, nil)
|
|
require.NoError(t, err, "writing to mapped bind dest should succeed")
|
|
|
|
// Verify it landed on the host source dir.
|
|
data, err := os.ReadFile(filepath.Join(srcDir, "out.txt"))
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "mapped-data\n", string(data))
|
|
}
|
|
|
|
// TestBinds_ReadOnly verifies a read-only bind rejects writes but allows reads.
|
|
func TestBinds_ReadOnly(t *testing.T) {
|
|
r := newRunner(t)
|
|
|
|
srcDir := t.TempDir()
|
|
require.NoError(t, os.WriteFile(filepath.Join(srcDir, "ro.txt"), []byte("readonly-value\n"), 0o644))
|
|
// Destination is a separate existing mount point under /tmp.
|
|
destDir := t.TempDir()
|
|
|
|
opts := &runtime.IsolatedSessionOptions{
|
|
Profile: "balanced",
|
|
WorkspacePath: t.TempDir(),
|
|
WorkspaceMode: "rw",
|
|
Binds: []isolation.BindMount{
|
|
{Source: srcDir, Dest: destDir, ReadOnly: true},
|
|
},
|
|
}
|
|
|
|
id, err := r.CreateIsolatedSession(opts)
|
|
require.NoError(t, err)
|
|
defer r.DeleteIsolatedSession(id)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
// Read should succeed.
|
|
var lines []string
|
|
err = r.RunInIsolatedSession(ctx, id, "cat "+destDir+"/ro.txt", nil,
|
|
func(line string) { lines = append(lines, line) })
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []string{"readonly-value"}, lines)
|
|
|
|
// Write should fail (non-zero exit).
|
|
err = r.RunInIsolatedSession(ctx, id, "echo x > "+destDir+"/new.txt", nil, nil)
|
|
require.Error(t, err, "writing to a read-only bind should fail")
|
|
}
|
|
|
|
// TestBinds_SourceNotInAllowlist verifies binds are rejected when the source
|
|
// path is outside the writable allowlist.
|
|
func TestBinds_SourceNotInAllowlist(t *testing.T) {
|
|
r := newRunnerWithConfig(t, isolation.Config{
|
|
UpperRoot: t.TempDir(),
|
|
UpperMaxBytes: 1 << 30,
|
|
AllowedWritable: []string{"/tmp/allowed-only"},
|
|
})
|
|
|
|
opts := &runtime.IsolatedSessionOptions{
|
|
Profile: "balanced",
|
|
WorkspacePath: t.TempDir(),
|
|
WorkspaceMode: "rw",
|
|
Binds: []isolation.BindMount{
|
|
{Source: "/etc", Dest: "/mnt/etc"},
|
|
},
|
|
}
|
|
|
|
_, err := r.CreateIsolatedSession(opts)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "not in allowlist")
|
|
}
|