gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/runsc/cmd/spec.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package cmd
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"io"
    21  	"os"
    22  	"path/filepath"
    23  
    24  	"github.com/google/subcommands"
    25  	specs "github.com/opencontainers/runtime-spec/specs-go"
    26  	"gvisor.dev/gvisor/runsc/cmd/util"
    27  	"gvisor.dev/gvisor/runsc/flag"
    28  )
    29  
    30  func writeSpec(w io.Writer, cwd string, netns string, args []string) error {
    31  	spec := &specs.Spec{
    32  		Version: "1.0.0",
    33  		Process: &specs.Process{
    34  			User: specs.User{
    35  				UID: 0,
    36  				GID: 0,
    37  			},
    38  			Args: args,
    39  			Env: []string{
    40  				"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
    41  				"TERM=xterm",
    42  			},
    43  			Cwd: cwd,
    44  			Capabilities: &specs.LinuxCapabilities{
    45  				Bounding: []string{
    46  					"CAP_AUDIT_WRITE",
    47  					"CAP_KILL",
    48  					"CAP_NET_BIND_SERVICE",
    49  				},
    50  				Effective: []string{
    51  					"CAP_AUDIT_WRITE",
    52  					"CAP_KILL",
    53  					"CAP_NET_BIND_SERVICE",
    54  				},
    55  				Inheritable: []string{
    56  					"CAP_AUDIT_WRITE",
    57  					"CAP_KILL",
    58  					"CAP_NET_BIND_SERVICE",
    59  				},
    60  				Permitted: []string{
    61  					"CAP_AUDIT_WRITE",
    62  					"CAP_KILL",
    63  					"CAP_NET_BIND_SERVICE",
    64  				},
    65  				// TODO(gvisor.dev/issue/3166): support ambient capabilities
    66  			},
    67  			Rlimits: []specs.POSIXRlimit{
    68  				{
    69  					Type: "RLIMIT_NOFILE",
    70  					Hard: 1024,
    71  					Soft: 1024,
    72  				},
    73  			},
    74  		},
    75  		Root: &specs.Root{
    76  			Path:     "rootfs",
    77  			Readonly: true,
    78  		},
    79  		Hostname: "runsc",
    80  		Mounts: []specs.Mount{
    81  			{
    82  				Destination: "/proc",
    83  				Type:        "proc",
    84  				Source:      "proc",
    85  			},
    86  			{
    87  				Destination: "/dev",
    88  				Type:        "tmpfs",
    89  				Source:      "tmpfs",
    90  			},
    91  			{
    92  				Destination: "/sys",
    93  				Type:        "sysfs",
    94  				Source:      "sysfs",
    95  				Options: []string{
    96  					"nosuid",
    97  					"noexec",
    98  					"nodev",
    99  					"ro",
   100  				},
   101  			},
   102  		},
   103  		Linux: &specs.Linux{
   104  			Namespaces: []specs.LinuxNamespace{
   105  				{
   106  					Type: "pid",
   107  				},
   108  				{
   109  					Type: "network",
   110  					Path: netns,
   111  				},
   112  				{
   113  					Type: "ipc",
   114  				},
   115  				{
   116  					Type: "uts",
   117  				},
   118  				{
   119  					Type: "mount",
   120  				},
   121  			},
   122  		},
   123  	}
   124  
   125  	e := json.NewEncoder(w)
   126  	e.SetIndent("", "    ")
   127  	return e.Encode(spec)
   128  }
   129  
   130  // Spec implements subcommands.Command for the "spec" command.
   131  type Spec struct {
   132  	bundle string
   133  	cwd    string
   134  	netns  string
   135  }
   136  
   137  // Name implements subcommands.Command.Name.
   138  func (*Spec) Name() string {
   139  	return "spec"
   140  }
   141  
   142  // Synopsis implements subcommands.Command.Synopsis.
   143  func (*Spec) Synopsis() string {
   144  	return "create a new OCI bundle specification file"
   145  }
   146  
   147  // Usage implements subcommands.Command.Usage.
   148  func (*Spec) Usage() string {
   149  	return `spec [options] [-- args...] - create a new OCI bundle specification file.
   150  
   151  The spec command creates a new specification file (config.json) for a new OCI
   152  bundle.
   153  
   154  The specification file is a starter file that runs the command specified by
   155  'args' in the container. If 'args' is not specified the default is to run the
   156  'sh' program.
   157  
   158  While a number of flags are provided to change values in the specification, you
   159  can examine the file and edit it to suit your needs after this command runs.
   160  You can find out more about the format of the specification file by visiting
   161  the OCI runtime spec repository:
   162  https://github.com/opencontainers/runtime-spec/
   163  
   164  EXAMPLE:
   165      $ mkdir -p bundle/rootfs
   166      $ cd bundle
   167      $ runsc spec -- /hello
   168      $ docker export $(docker create hello-world) | tar -xf - -C rootfs
   169      $ sudo runsc run hello
   170  
   171  `
   172  }
   173  
   174  // SetFlags implements subcommands.Command.SetFlags.
   175  func (s *Spec) SetFlags(f *flag.FlagSet) {
   176  	f.StringVar(&s.bundle, "bundle", ".", "path to the root of the OCI bundle")
   177  	f.StringVar(&s.cwd, "cwd", "/", "working directory that will be set for the executable, "+
   178  		"this value MUST be an absolute path")
   179  	f.StringVar(&s.netns, "netns", "", "network namespace path")
   180  }
   181  
   182  // Execute implements subcommands.Command.Execute.
   183  func (s *Spec) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcommands.ExitStatus {
   184  	// Grab the arguments.
   185  	containerArgs := f.Args()
   186  	if len(containerArgs) == 0 {
   187  		containerArgs = []string{"sh"}
   188  	}
   189  
   190  	confPath := filepath.Join(s.bundle, "config.json")
   191  	if _, err := os.Stat(confPath); !os.IsNotExist(err) {
   192  		util.Fatalf("file %q already exists", confPath)
   193  	}
   194  
   195  	configFile, err := os.OpenFile(confPath, os.O_WRONLY|os.O_CREATE, 0664)
   196  	if err != nil {
   197  		util.Fatalf("opening file %q: %v", confPath, err)
   198  	}
   199  
   200  	err = writeSpec(configFile, s.cwd, s.netns, containerArgs)
   201  	if err != nil {
   202  		util.Fatalf("writing to %q: %v", confPath, err)
   203  	}
   204  
   205  	return subcommands.ExitSuccess
   206  }