mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
295 lines
8.1 KiB
Go
295 lines
8.1 KiB
Go
// Copyright 2026 The gVisor Authors.
|
|
//
|
|
// 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.
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"math/rand"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/google/subcommands"
|
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
|
"google.golang.org/protobuf/encoding/prototext"
|
|
"google.golang.org/protobuf/proto"
|
|
"gvisor.dev/gvisor/runsc/cmd/util"
|
|
"gvisor.dev/gvisor/runsc/config"
|
|
"gvisor.dev/gvisor/runsc/container"
|
|
"gvisor.dev/gvisor/runsc/flag"
|
|
"gvisor.dev/gvisor/runsc/specutils"
|
|
|
|
sandboxexecpb "gvisor.dev/gvisor/sandboxexec/proto/sandbox_options_go_proto"
|
|
)
|
|
|
|
// SandboxExec implements subcommands.Command for the "sandboxexec" command.
|
|
type SandboxExec struct {
|
|
optionsFile string
|
|
}
|
|
|
|
// Name implements subcommands.Command.Name.
|
|
func (*SandboxExec) Name() string {
|
|
return "sandboxexec"
|
|
}
|
|
|
|
// Synopsis implements subcommands.Command.Synopsis.
|
|
func (*SandboxExec) Synopsis() string {
|
|
return "Run a command in a sandbox with options defined in a proto."
|
|
}
|
|
|
|
// Usage implements subcommands.Command.Usage.
|
|
func (*SandboxExec) Usage() string {
|
|
return `sandboxexec [flags] <cmd> [args...] - runs a command in a sandbox.
|
|
|
|
This command starts a sandbox using configuration options provided via a
|
|
SandboxOptions proto message.
|
|
`
|
|
}
|
|
|
|
// SetFlags implements subcommands.Command.SetFlags.
|
|
func (c *SandboxExec) SetFlags(f *flag.FlagSet) {
|
|
f.StringVar(&c.optionsFile, "options", "", "path to the SandboxOptions proto file (binary or textproto)")
|
|
}
|
|
|
|
// FetchSpec implements util.SubCommand.FetchSpec.
|
|
func (c *SandboxExec) FetchSpec(conf *config.Config, f *flag.FlagSet) (string, *specs.Spec, error) {
|
|
// We don't use a pre-existing spec, we build it in Execute.
|
|
// But FetchSpec is called by cli.Run. We can return a dummy ID and nil spec,
|
|
// or build it here.
|
|
// If we return nil spec, cli.Run will skip FixConfig.
|
|
// Let's return a dummy ID and nil spec, and do the real work in Execute.
|
|
return "sandboxexec-dummy", nil, nil
|
|
}
|
|
|
|
// Execute implements subcommands.Command.Execute.
|
|
func (c *SandboxExec) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcommands.ExitStatus {
|
|
if len(f.Args()) == 0 {
|
|
f.Usage()
|
|
return subcommands.ExitUsageError
|
|
}
|
|
|
|
conf := args[0].(*config.Config)
|
|
|
|
var opts sandboxexecpb.SandboxOptions
|
|
if c.optionsFile != "" {
|
|
data, err := os.ReadFile(c.optionsFile)
|
|
if err != nil {
|
|
return util.Errorf("reading options file: %v", err)
|
|
}
|
|
if err := prototext.Unmarshal(data, &opts); err != nil {
|
|
// Try binary
|
|
if err := proto.Unmarshal(data, &opts); err != nil {
|
|
return util.Errorf("parsing proto (tried textproto and binary): %v", err)
|
|
}
|
|
}
|
|
} else {
|
|
stat, _ := os.Stdin.Stat()
|
|
if (stat.Mode() & os.ModeCharDevice) == 0 {
|
|
data, err := io.ReadAll(os.Stdin)
|
|
if err == nil && len(data) > 0 {
|
|
if err := prototext.Unmarshal(data, &opts); err != nil {
|
|
if err := proto.Unmarshal(data, &opts); err != nil {
|
|
return util.Errorf("parsing proto from stdin: %v", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Construct Spec
|
|
cid := fmt.Sprintf("runsc-%06d", rand.Int31n(1000000))
|
|
spec, err := createSpec(&opts, f.Args(), conf)
|
|
if err != nil {
|
|
return util.Errorf("creating spec: %v", err)
|
|
}
|
|
|
|
// Start Container
|
|
tmpDir, err := os.MkdirTemp("", "runsc-sandboxexec")
|
|
if err != nil {
|
|
return util.Errorf("creating tmp dir: %v", err)
|
|
}
|
|
if err := os.Mkdir(filepath.Join(tmpDir, "rootfs"), 0755); err != nil {
|
|
return util.Errorf("creating rootfs dir: %v", err)
|
|
}
|
|
|
|
// Write spec to bundle dir
|
|
out, err := json.Marshal(spec)
|
|
if err != nil {
|
|
return util.Errorf("marshaling spec: %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(tmpDir, "config.json"), out, 0644); err != nil {
|
|
return util.Errorf("writing config.json: %v", err)
|
|
}
|
|
|
|
devNull, err := os.OpenFile("/dev/null", os.O_RDWR, 0644)
|
|
if err != nil {
|
|
return util.Errorf("opening /dev/null: %v", err)
|
|
}
|
|
defer devNull.Close()
|
|
|
|
passFiles := make(map[int]*os.File)
|
|
if opts.GetStdoutFile() != "" {
|
|
f, err := os.OpenFile(opts.GetStdoutFile(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
|
if err != nil {
|
|
return util.Errorf("opening stdout file: %v", err)
|
|
}
|
|
passFiles[1] = f
|
|
defer f.Close()
|
|
} else {
|
|
passFiles[1] = devNull
|
|
}
|
|
|
|
if opts.GetStderrFile() != "" {
|
|
f, err := os.OpenFile(opts.GetStderrFile(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
|
if err != nil {
|
|
return util.Errorf("opening stderr file: %v", err)
|
|
}
|
|
passFiles[2] = f
|
|
defer f.Close()
|
|
} else {
|
|
passFiles[2] = devNull
|
|
}
|
|
|
|
containerArgs := container.Args{
|
|
ID: cid,
|
|
Spec: spec,
|
|
BundleDir: tmpDir,
|
|
Attached: false, // Run in background
|
|
PassFiles: passFiles,
|
|
}
|
|
|
|
cont, err := container.New(conf, containerArgs)
|
|
if err != nil {
|
|
return util.Errorf("creating container: %v", err)
|
|
}
|
|
|
|
if err := cont.Start(conf); err != nil {
|
|
cont.Destroy()
|
|
return util.Errorf("starting container: %v", err)
|
|
}
|
|
|
|
// Return PID and Control Socket Path
|
|
fmt.Printf("PID: %d\n", cont.SandboxPid())
|
|
fmt.Printf("Control Socket: %s\n", cont.Sandbox.GetControlSocketPath())
|
|
|
|
return subcommands.ExitSuccess
|
|
}
|
|
|
|
func createSpec(opts *sandboxexecpb.SandboxOptions, args []string, conf *config.Config) (*specs.Spec, error) {
|
|
spec := &specs.Spec{
|
|
Root: &specs.Root{
|
|
Path: "rootfs",
|
|
},
|
|
Process: &specs.Process{
|
|
Cwd: ".",
|
|
Args: args,
|
|
Capabilities: specutils.AllCapabilities(),
|
|
},
|
|
}
|
|
|
|
if conf.Rootless {
|
|
if spec.Linux == nil {
|
|
spec.Linux = &specs.Linux{}
|
|
}
|
|
spec.Linux.Namespaces = append(spec.Linux.Namespaces, specs.LinuxNamespace{Type: specs.UserNamespace})
|
|
spec.Linux.UIDMappings = []specs.LinuxIDMapping{
|
|
{ContainerID: 0, HostID: uint32(os.Getuid()), Size: 1},
|
|
}
|
|
spec.Linux.GIDMappings = []specs.LinuxIDMapping{
|
|
{ContainerID: 0, HostID: uint32(os.Getgid()), Size: 1},
|
|
}
|
|
}
|
|
|
|
envMap := make(map[string]string)
|
|
|
|
for _, ev := range opts.GetEnvVars() {
|
|
name := ev.GetName()
|
|
switch x := ev.GetPolicyOrValue().(type) {
|
|
case *sandboxexecpb.EnvVar_Value:
|
|
envMap[name] = x.Value
|
|
case *sandboxexecpb.EnvVar_Policy:
|
|
switch x.Policy {
|
|
case sandboxexecpb.EnvVar_ENV_VAR_POLICY_FORWARD:
|
|
if val, ok := os.LookupEnv(name); ok {
|
|
envMap[name] = val
|
|
}
|
|
case sandboxexecpb.EnvVar_ENV_VAR_POLICY_UNSET:
|
|
delete(envMap, name)
|
|
}
|
|
}
|
|
}
|
|
|
|
var env []string
|
|
for k, v := range envMap {
|
|
env = append(env, fmt.Sprintf("%s=%s", k, v))
|
|
}
|
|
spec.Process.Env = env
|
|
|
|
spec.Mounts = append(spec.Mounts, specs.Mount{
|
|
Destination: "/proc",
|
|
Type: "proc",
|
|
}, specs.Mount{
|
|
Destination: "/sys",
|
|
Type: "sysfs",
|
|
}, specs.Mount{
|
|
Destination: "/dev",
|
|
Type: "devtmpfs",
|
|
}, specs.Mount{
|
|
Destination: "/dev/pts",
|
|
Type: "devpts",
|
|
}, specs.Mount{
|
|
Destination: "/sys/fs/cgroup",
|
|
Type: "cgroupfs",
|
|
}, specs.Mount{
|
|
Destination: "/tmp",
|
|
Type: "tmpfs",
|
|
})
|
|
|
|
for _, m := range opts.GetMounts() {
|
|
specMount := specs.Mount{
|
|
Destination: m.GetTarget(),
|
|
}
|
|
switch x := m.GetMount().(type) {
|
|
case *sandboxexecpb.Mount_HostMount:
|
|
specMount.Type = "bind"
|
|
specMount.Source = x.HostMount.GetHostPath()
|
|
|
|
case *sandboxexecpb.Mount_SandboxTmpfsMount:
|
|
specMount.Type = "tmpfs"
|
|
specMount.Source = "tmpfs"
|
|
}
|
|
spec.Mounts = append(spec.Mounts, specMount)
|
|
}
|
|
|
|
if opts.GetNetworkingOptions() != nil {
|
|
netOpts := opts.GetNetworkingOptions()
|
|
if netOpts.GetEnableExternalNetworking() {
|
|
conf.Network = config.NetworkSandbox
|
|
} else {
|
|
conf.Network = config.NetworkNone
|
|
}
|
|
}
|
|
|
|
// Use memory overlay for root to avoid permission denied on host root,
|
|
// but allow submounts to be writable if they are bind mounts.
|
|
if err := conf.Overlay2.Set("root:memory"); err != nil {
|
|
return nil, fmt.Errorf("failed to set Overlay2 to root:memory: %v", err)
|
|
}
|
|
|
|
return spec, nil
|
|
}
|