github.com/demonoid81/containerd@v1.3.4/oci/spec_opts.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package oci
    18  
    19  import (
    20  	"bufio"
    21  	"context"
    22  	"encoding/json"
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"path/filepath"
    27  	"strconv"
    28  	"strings"
    29  
    30  	"github.com/containerd/containerd/containers"
    31  	"github.com/containerd/containerd/content"
    32  	"github.com/containerd/containerd/images"
    33  	"github.com/containerd/containerd/mount"
    34  	"github.com/containerd/containerd/namespaces"
    35  	"github.com/containerd/containerd/platforms"
    36  	"github.com/containerd/continuity/fs"
    37  	v1 "github.com/opencontainers/image-spec/specs-go/v1"
    38  	"github.com/opencontainers/runc/libcontainer/user"
    39  	specs "github.com/opencontainers/runtime-spec/specs-go"
    40  	"github.com/pkg/errors"
    41  	"github.com/syndtr/gocapability/capability"
    42  )
    43  
    44  // SpecOpts sets spec specific information to a newly generated OCI spec
    45  type SpecOpts func(context.Context, Client, *containers.Container, *Spec) error
    46  
    47  // Compose converts a sequence of spec operations into a single operation
    48  func Compose(opts ...SpecOpts) SpecOpts {
    49  	return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
    50  		for _, o := range opts {
    51  			if err := o(ctx, client, c, s); err != nil {
    52  				return err
    53  			}
    54  		}
    55  		return nil
    56  	}
    57  }
    58  
    59  // setProcess sets Process to empty if unset
    60  func setProcess(s *Spec) {
    61  	if s.Process == nil {
    62  		s.Process = &specs.Process{}
    63  	}
    64  }
    65  
    66  // setRoot sets Root to empty if unset
    67  func setRoot(s *Spec) {
    68  	if s.Root == nil {
    69  		s.Root = &specs.Root{}
    70  	}
    71  }
    72  
    73  // setLinux sets Linux to empty if unset
    74  func setLinux(s *Spec) {
    75  	if s.Linux == nil {
    76  		s.Linux = &specs.Linux{}
    77  	}
    78  }
    79  
    80  // nolint
    81  func setResources(s *Spec) {
    82  	if s.Linux != nil {
    83  		if s.Linux.Resources == nil {
    84  			s.Linux.Resources = &specs.LinuxResources{}
    85  		}
    86  	}
    87  	if s.Windows != nil {
    88  		if s.Windows.Resources == nil {
    89  			s.Windows.Resources = &specs.WindowsResources{}
    90  		}
    91  	}
    92  }
    93  
    94  // setCapabilities sets Linux Capabilities to empty if unset
    95  func setCapabilities(s *Spec) {
    96  	setProcess(s)
    97  	if s.Process.Capabilities == nil {
    98  		s.Process.Capabilities = &specs.LinuxCapabilities{}
    99  	}
   100  }
   101  
   102  // WithDefaultSpec returns a SpecOpts that will populate the spec with default
   103  // values.
   104  //
   105  // Use as the first option to clear the spec, then apply options afterwards.
   106  func WithDefaultSpec() SpecOpts {
   107  	return func(ctx context.Context, _ Client, c *containers.Container, s *Spec) error {
   108  		return generateDefaultSpecWithPlatform(ctx, platforms.DefaultString(), c.ID, s)
   109  	}
   110  }
   111  
   112  // WithDefaultSpecForPlatform returns a SpecOpts that will populate the spec
   113  // with default values for a given platform.
   114  //
   115  // Use as the first option to clear the spec, then apply options afterwards.
   116  func WithDefaultSpecForPlatform(platform string) SpecOpts {
   117  	return func(ctx context.Context, _ Client, c *containers.Container, s *Spec) error {
   118  		return generateDefaultSpecWithPlatform(ctx, platform, c.ID, s)
   119  	}
   120  }
   121  
   122  // WithSpecFromBytes loads the spec from the provided byte slice.
   123  func WithSpecFromBytes(p []byte) SpecOpts {
   124  	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   125  		*s = Spec{} // make sure spec is cleared.
   126  		if err := json.Unmarshal(p, s); err != nil {
   127  			return errors.Wrapf(err, "decoding spec config file failed, current supported OCI runtime-spec : v%s", specs.Version)
   128  		}
   129  		return nil
   130  	}
   131  }
   132  
   133  // WithSpecFromFile loads the specification from the provided filename.
   134  func WithSpecFromFile(filename string) SpecOpts {
   135  	return func(ctx context.Context, c Client, container *containers.Container, s *Spec) error {
   136  		p, err := ioutil.ReadFile(filename)
   137  		if err != nil {
   138  			return errors.Wrap(err, "cannot load spec config file")
   139  		}
   140  		return WithSpecFromBytes(p)(ctx, c, container, s)
   141  	}
   142  }
   143  
   144  // WithEnv appends environment variables
   145  func WithEnv(environmentVariables []string) SpecOpts {
   146  	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   147  		if len(environmentVariables) > 0 {
   148  			setProcess(s)
   149  			s.Process.Env = replaceOrAppendEnvValues(s.Process.Env, environmentVariables)
   150  		}
   151  		return nil
   152  	}
   153  }
   154  
   155  // WithDefaultPathEnv sets the $PATH environment variable to the
   156  // default PATH defined in this package.
   157  func WithDefaultPathEnv(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   158  	s.Process.Env = replaceOrAppendEnvValues(s.Process.Env, defaultUnixEnv)
   159  	return nil
   160  }
   161  
   162  // replaceOrAppendEnvValues returns the defaults with the overrides either
   163  // replaced by env key or appended to the list
   164  func replaceOrAppendEnvValues(defaults, overrides []string) []string {
   165  	cache := make(map[string]int, len(defaults))
   166  	results := make([]string, 0, len(defaults))
   167  	for i, e := range defaults {
   168  		parts := strings.SplitN(e, "=", 2)
   169  		results = append(results, e)
   170  		cache[parts[0]] = i
   171  	}
   172  
   173  	for _, value := range overrides {
   174  		// Values w/o = means they want this env to be removed/unset.
   175  		if !strings.Contains(value, "=") {
   176  			if i, exists := cache[value]; exists {
   177  				results[i] = "" // Used to indicate it should be removed
   178  			}
   179  			continue
   180  		}
   181  
   182  		// Just do a normal set/update
   183  		parts := strings.SplitN(value, "=", 2)
   184  		if i, exists := cache[parts[0]]; exists {
   185  			results[i] = value
   186  		} else {
   187  			results = append(results, value)
   188  		}
   189  	}
   190  
   191  	// Now remove all entries that we want to "unset"
   192  	for i := 0; i < len(results); i++ {
   193  		if results[i] == "" {
   194  			results = append(results[:i], results[i+1:]...)
   195  			i--
   196  		}
   197  	}
   198  
   199  	return results
   200  }
   201  
   202  // WithProcessArgs replaces the args on the generated spec
   203  func WithProcessArgs(args ...string) SpecOpts {
   204  	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   205  		setProcess(s)
   206  		s.Process.Args = args
   207  		return nil
   208  	}
   209  }
   210  
   211  // WithProcessCwd replaces the current working directory on the generated spec
   212  func WithProcessCwd(cwd string) SpecOpts {
   213  	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   214  		setProcess(s)
   215  		s.Process.Cwd = cwd
   216  		return nil
   217  	}
   218  }
   219  
   220  // WithTTY sets the information on the spec as well as the environment variables for
   221  // using a TTY
   222  func WithTTY(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   223  	setProcess(s)
   224  	s.Process.Terminal = true
   225  	if s.Linux != nil {
   226  		s.Process.Env = append(s.Process.Env, "TERM=xterm")
   227  	}
   228  
   229  	return nil
   230  }
   231  
   232  // WithTTYSize sets the information on the spec as well as the environment variables for
   233  // using a TTY
   234  func WithTTYSize(width, height int) SpecOpts {
   235  	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   236  		setProcess(s)
   237  		if s.Process.ConsoleSize == nil {
   238  			s.Process.ConsoleSize = &specs.Box{}
   239  		}
   240  		s.Process.ConsoleSize.Width = uint(width)
   241  		s.Process.ConsoleSize.Height = uint(height)
   242  		return nil
   243  	}
   244  }
   245  
   246  // WithHostname sets the container's hostname
   247  func WithHostname(name string) SpecOpts {
   248  	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   249  		s.Hostname = name
   250  		return nil
   251  	}
   252  }
   253  
   254  // WithMounts appends mounts
   255  func WithMounts(mounts []specs.Mount) SpecOpts {
   256  	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   257  		s.Mounts = append(s.Mounts, mounts...)
   258  		return nil
   259  	}
   260  }
   261  
   262  // WithHostNamespace allows a task to run inside the host's linux namespace
   263  func WithHostNamespace(ns specs.LinuxNamespaceType) SpecOpts {
   264  	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   265  		setLinux(s)
   266  		for i, n := range s.Linux.Namespaces {
   267  			if n.Type == ns {
   268  				s.Linux.Namespaces = append(s.Linux.Namespaces[:i], s.Linux.Namespaces[i+1:]...)
   269  				return nil
   270  			}
   271  		}
   272  		return nil
   273  	}
   274  }
   275  
   276  // WithLinuxNamespace uses the passed in namespace for the spec. If a namespace of the same type already exists in the
   277  // spec, the existing namespace is replaced by the one provided.
   278  func WithLinuxNamespace(ns specs.LinuxNamespace) SpecOpts {
   279  	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   280  		setLinux(s)
   281  		for i, n := range s.Linux.Namespaces {
   282  			if n.Type == ns.Type {
   283  				before := s.Linux.Namespaces[:i]
   284  				after := s.Linux.Namespaces[i+1:]
   285  				s.Linux.Namespaces = append(before, ns)
   286  				s.Linux.Namespaces = append(s.Linux.Namespaces, after...)
   287  				return nil
   288  			}
   289  		}
   290  		s.Linux.Namespaces = append(s.Linux.Namespaces, ns)
   291  		return nil
   292  	}
   293  }
   294  
   295  // WithNewPrivileges turns off the NoNewPrivileges feature flag in the spec
   296  func WithNewPrivileges(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   297  	setProcess(s)
   298  	s.Process.NoNewPrivileges = false
   299  
   300  	return nil
   301  }
   302  
   303  // WithImageConfig configures the spec to from the configuration of an Image
   304  func WithImageConfig(image Image) SpecOpts {
   305  	return WithImageConfigArgs(image, nil)
   306  }
   307  
   308  // WithImageConfigArgs configures the spec to from the configuration of an Image with additional args that
   309  // replaces the CMD of the image
   310  func WithImageConfigArgs(image Image, args []string) SpecOpts {
   311  	return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
   312  		ic, err := image.Config(ctx)
   313  		if err != nil {
   314  			return err
   315  		}
   316  		var (
   317  			ociimage v1.Image
   318  			config   v1.ImageConfig
   319  		)
   320  		switch ic.MediaType {
   321  		case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config:
   322  			p, err := content.ReadBlob(ctx, image.ContentStore(), ic)
   323  			if err != nil {
   324  				return err
   325  			}
   326  
   327  			if err := json.Unmarshal(p, &ociimage); err != nil {
   328  				return err
   329  			}
   330  			config = ociimage.Config
   331  		default:
   332  			return fmt.Errorf("unknown image config media type %s", ic.MediaType)
   333  		}
   334  
   335  		setProcess(s)
   336  		if s.Linux != nil {
   337  			defaults := config.Env
   338  			if len(defaults) == 0 {
   339  				defaults = defaultUnixEnv
   340  			}
   341  			s.Process.Env = replaceOrAppendEnvValues(defaults, s.Process.Env)
   342  			cmd := config.Cmd
   343  			if len(args) > 0 {
   344  				cmd = args
   345  			}
   346  			s.Process.Args = append(config.Entrypoint, cmd...)
   347  
   348  			cwd := config.WorkingDir
   349  			if cwd == "" {
   350  				cwd = "/"
   351  			}
   352  			s.Process.Cwd = cwd
   353  			if config.User != "" {
   354  				if err := WithUser(config.User)(ctx, client, c, s); err != nil {
   355  					return err
   356  				}
   357  				return WithAdditionalGIDs(fmt.Sprintf("%d", s.Process.User.UID))(ctx, client, c, s)
   358  			}
   359  			// we should query the image's /etc/group for additional GIDs
   360  			// even if there is no specified user in the image config
   361  			return WithAdditionalGIDs("root")(ctx, client, c, s)
   362  		} else if s.Windows != nil {
   363  			s.Process.Env = replaceOrAppendEnvValues(config.Env, s.Process.Env)
   364  			cmd := config.Cmd
   365  			if len(args) > 0 {
   366  				cmd = args
   367  			}
   368  			s.Process.Args = append(config.Entrypoint, cmd...)
   369  
   370  			s.Process.Cwd = config.WorkingDir
   371  			s.Process.User = specs.User{
   372  				Username: config.User,
   373  			}
   374  		} else {
   375  			return errors.New("spec does not contain Linux or Windows section")
   376  		}
   377  		return nil
   378  	}
   379  }
   380  
   381  // WithRootFSPath specifies unmanaged rootfs path.
   382  func WithRootFSPath(path string) SpecOpts {
   383  	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   384  		setRoot(s)
   385  		s.Root.Path = path
   386  		// Entrypoint is not set here (it's up to caller)
   387  		return nil
   388  	}
   389  }
   390  
   391  // WithRootFSReadonly sets specs.Root.Readonly to true
   392  func WithRootFSReadonly() SpecOpts {
   393  	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   394  		setRoot(s)
   395  		s.Root.Readonly = true
   396  		return nil
   397  	}
   398  }
   399  
   400  // WithNoNewPrivileges sets no_new_privileges on the process for the container
   401  func WithNoNewPrivileges(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   402  	setProcess(s)
   403  	s.Process.NoNewPrivileges = true
   404  	return nil
   405  }
   406  
   407  // WithHostHostsFile bind-mounts the host's /etc/hosts into the container as readonly
   408  func WithHostHostsFile(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   409  	s.Mounts = append(s.Mounts, specs.Mount{
   410  		Destination: "/etc/hosts",
   411  		Type:        "bind",
   412  		Source:      "/etc/hosts",
   413  		Options:     []string{"rbind", "ro"},
   414  	})
   415  	return nil
   416  }
   417  
   418  // WithHostResolvconf bind-mounts the host's /etc/resolv.conf into the container as readonly
   419  func WithHostResolvconf(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   420  	s.Mounts = append(s.Mounts, specs.Mount{
   421  		Destination: "/etc/resolv.conf",
   422  		Type:        "bind",
   423  		Source:      "/etc/resolv.conf",
   424  		Options:     []string{"rbind", "ro"},
   425  	})
   426  	return nil
   427  }
   428  
   429  // WithHostLocaltime bind-mounts the host's /etc/localtime into the container as readonly
   430  func WithHostLocaltime(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   431  	s.Mounts = append(s.Mounts, specs.Mount{
   432  		Destination: "/etc/localtime",
   433  		Type:        "bind",
   434  		Source:      "/etc/localtime",
   435  		Options:     []string{"rbind", "ro"},
   436  	})
   437  	return nil
   438  }
   439  
   440  // WithUserNamespace sets the uid and gid mappings for the task
   441  // this can be called multiple times to add more mappings to the generated spec
   442  func WithUserNamespace(container, host, size uint32) SpecOpts {
   443  	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   444  		var hasUserns bool
   445  		setLinux(s)
   446  		for _, ns := range s.Linux.Namespaces {
   447  			if ns.Type == specs.UserNamespace {
   448  				hasUserns = true
   449  				break
   450  			}
   451  		}
   452  		if !hasUserns {
   453  			s.Linux.Namespaces = append(s.Linux.Namespaces, specs.LinuxNamespace{
   454  				Type: specs.UserNamespace,
   455  			})
   456  		}
   457  		mapping := specs.LinuxIDMapping{
   458  			ContainerID: container,
   459  			HostID:      host,
   460  			Size:        size,
   461  		}
   462  		s.Linux.UIDMappings = append(s.Linux.UIDMappings, mapping)
   463  		s.Linux.GIDMappings = append(s.Linux.GIDMappings, mapping)
   464  		return nil
   465  	}
   466  }
   467  
   468  // WithCgroup sets the container's cgroup path
   469  func WithCgroup(path string) SpecOpts {
   470  	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   471  		setLinux(s)
   472  		s.Linux.CgroupsPath = path
   473  		return nil
   474  	}
   475  }
   476  
   477  // WithNamespacedCgroup uses the namespace set on the context to create a
   478  // root directory for containers in the cgroup with the id as the subcgroup
   479  func WithNamespacedCgroup() SpecOpts {
   480  	return func(ctx context.Context, _ Client, c *containers.Container, s *Spec) error {
   481  		namespace, err := namespaces.NamespaceRequired(ctx)
   482  		if err != nil {
   483  			return err
   484  		}
   485  		setLinux(s)
   486  		s.Linux.CgroupsPath = filepath.Join("/", namespace, c.ID)
   487  		return nil
   488  	}
   489  }
   490  
   491  // WithUser sets the user to be used within the container.
   492  // It accepts a valid user string in OCI Image Spec v1.0.0:
   493  //   user, uid, user:group, uid:gid, uid:group, user:gid
   494  func WithUser(userstr string) SpecOpts {
   495  	return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
   496  		setProcess(s)
   497  		parts := strings.Split(userstr, ":")
   498  		switch len(parts) {
   499  		case 1:
   500  			v, err := strconv.Atoi(parts[0])
   501  			if err != nil {
   502  				// if we cannot parse as a uint they try to see if it is a username
   503  				return WithUsername(userstr)(ctx, client, c, s)
   504  			}
   505  			return WithUserID(uint32(v))(ctx, client, c, s)
   506  		case 2:
   507  			var (
   508  				username  string
   509  				groupname string
   510  			)
   511  			var uid, gid uint32
   512  			v, err := strconv.Atoi(parts[0])
   513  			if err != nil {
   514  				username = parts[0]
   515  			} else {
   516  				uid = uint32(v)
   517  			}
   518  			if v, err = strconv.Atoi(parts[1]); err != nil {
   519  				groupname = parts[1]
   520  			} else {
   521  				gid = uint32(v)
   522  			}
   523  			if username == "" && groupname == "" {
   524  				s.Process.User.UID, s.Process.User.GID = uid, gid
   525  				return nil
   526  			}
   527  			f := func(root string) error {
   528  				if username != "" {
   529  					user, err := getUserFromPath(root, func(u user.User) bool {
   530  						return u.Name == username
   531  					})
   532  					if err != nil {
   533  						return err
   534  					}
   535  					uid = uint32(user.Uid)
   536  				}
   537  				if groupname != "" {
   538  					gid, err = getGIDFromPath(root, func(g user.Group) bool {
   539  						return g.Name == groupname
   540  					})
   541  					if err != nil {
   542  						return err
   543  					}
   544  				}
   545  				s.Process.User.UID, s.Process.User.GID = uid, gid
   546  				return nil
   547  			}
   548  			if c.Snapshotter == "" && c.SnapshotKey == "" {
   549  				if !isRootfsAbs(s.Root.Path) {
   550  					return errors.New("rootfs absolute path is required")
   551  				}
   552  				return f(s.Root.Path)
   553  			}
   554  			if c.Snapshotter == "" {
   555  				return errors.New("no snapshotter set for container")
   556  			}
   557  			if c.SnapshotKey == "" {
   558  				return errors.New("rootfs snapshot not created for container")
   559  			}
   560  			snapshotter := client.SnapshotService(c.Snapshotter)
   561  			mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
   562  			if err != nil {
   563  				return err
   564  			}
   565  			return mount.WithTempMount(ctx, mounts, f)
   566  		default:
   567  			return fmt.Errorf("invalid USER value %s", userstr)
   568  		}
   569  	}
   570  }
   571  
   572  // WithUIDGID allows the UID and GID for the Process to be set
   573  func WithUIDGID(uid, gid uint32) SpecOpts {
   574  	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   575  		setProcess(s)
   576  		s.Process.User.UID = uid
   577  		s.Process.User.GID = gid
   578  		return nil
   579  	}
   580  }
   581  
   582  // WithUserID sets the correct UID and GID for the container based
   583  // on the image's /etc/passwd contents. If /etc/passwd does not exist,
   584  // or uid is not found in /etc/passwd, it sets the requested uid,
   585  // additionally sets the gid to 0, and does not return an error.
   586  func WithUserID(uid uint32) SpecOpts {
   587  	return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) {
   588  		setProcess(s)
   589  		if c.Snapshotter == "" && c.SnapshotKey == "" {
   590  			if !isRootfsAbs(s.Root.Path) {
   591  				return errors.Errorf("rootfs absolute path is required")
   592  			}
   593  			user, err := getUserFromPath(s.Root.Path, func(u user.User) bool {
   594  				return u.Uid == int(uid)
   595  			})
   596  			if err != nil {
   597  				if os.IsNotExist(err) || err == errNoUsersFound {
   598  					s.Process.User.UID, s.Process.User.GID = uid, 0
   599  					return nil
   600  				}
   601  				return err
   602  			}
   603  			s.Process.User.UID, s.Process.User.GID = uint32(user.Uid), uint32(user.Gid)
   604  			return nil
   605  
   606  		}
   607  		if c.Snapshotter == "" {
   608  			return errors.Errorf("no snapshotter set for container")
   609  		}
   610  		if c.SnapshotKey == "" {
   611  			return errors.Errorf("rootfs snapshot not created for container")
   612  		}
   613  		snapshotter := client.SnapshotService(c.Snapshotter)
   614  		mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
   615  		if err != nil {
   616  			return err
   617  		}
   618  		return mount.WithTempMount(ctx, mounts, func(root string) error {
   619  			user, err := getUserFromPath(root, func(u user.User) bool {
   620  				return u.Uid == int(uid)
   621  			})
   622  			if err != nil {
   623  				if os.IsNotExist(err) || err == errNoUsersFound {
   624  					s.Process.User.UID, s.Process.User.GID = uid, 0
   625  					return nil
   626  				}
   627  				return err
   628  			}
   629  			s.Process.User.UID, s.Process.User.GID = uint32(user.Uid), uint32(user.Gid)
   630  			return nil
   631  		})
   632  	}
   633  }
   634  
   635  // WithUsername sets the correct UID and GID for the container
   636  // based on the image's /etc/passwd contents. If /etc/passwd
   637  // does not exist, or the username is not found in /etc/passwd,
   638  // it returns error.
   639  func WithUsername(username string) SpecOpts {
   640  	return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) {
   641  		setProcess(s)
   642  		if s.Linux != nil {
   643  			if c.Snapshotter == "" && c.SnapshotKey == "" {
   644  				if !isRootfsAbs(s.Root.Path) {
   645  					return errors.Errorf("rootfs absolute path is required")
   646  				}
   647  				user, err := getUserFromPath(s.Root.Path, func(u user.User) bool {
   648  					return u.Name == username
   649  				})
   650  				if err != nil {
   651  					return err
   652  				}
   653  				s.Process.User.UID, s.Process.User.GID = uint32(user.Uid), uint32(user.Gid)
   654  				return nil
   655  			}
   656  			if c.Snapshotter == "" {
   657  				return errors.Errorf("no snapshotter set for container")
   658  			}
   659  			if c.SnapshotKey == "" {
   660  				return errors.Errorf("rootfs snapshot not created for container")
   661  			}
   662  			snapshotter := client.SnapshotService(c.Snapshotter)
   663  			mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
   664  			if err != nil {
   665  				return err
   666  			}
   667  			return mount.WithTempMount(ctx, mounts, func(root string) error {
   668  				user, err := getUserFromPath(root, func(u user.User) bool {
   669  					return u.Name == username
   670  				})
   671  				if err != nil {
   672  					return err
   673  				}
   674  				s.Process.User.UID, s.Process.User.GID = uint32(user.Uid), uint32(user.Gid)
   675  				return nil
   676  			})
   677  		} else if s.Windows != nil {
   678  			s.Process.User.Username = username
   679  		} else {
   680  			return errors.New("spec does not contain Linux or Windows section")
   681  		}
   682  		return nil
   683  	}
   684  }
   685  
   686  // WithAdditionalGIDs sets the OCI spec's additionalGids array to any additional groups listed
   687  // for a particular user in the /etc/groups file of the image's root filesystem
   688  // The passed in user can be either a uid or a username.
   689  func WithAdditionalGIDs(userstr string) SpecOpts {
   690  	return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) {
   691  		// For LCOW additional GID's not supported
   692  		if s.Windows != nil {
   693  			return nil
   694  		}
   695  		setProcess(s)
   696  		setAdditionalGids := func(root string) error {
   697  			var username string
   698  			uid, err := strconv.Atoi(userstr)
   699  			if err == nil {
   700  				user, err := getUserFromPath(root, func(u user.User) bool {
   701  					return u.Uid == uid
   702  				})
   703  				if err != nil {
   704  					if os.IsNotExist(err) || err == errNoUsersFound {
   705  						return nil
   706  					}
   707  					return err
   708  				}
   709  				username = user.Name
   710  			} else {
   711  				username = userstr
   712  			}
   713  			gids, err := getSupplementalGroupsFromPath(root, func(g user.Group) bool {
   714  				// we only want supplemental groups
   715  				if g.Name == username {
   716  					return false
   717  				}
   718  				for _, entry := range g.List {
   719  					if entry == username {
   720  						return true
   721  					}
   722  				}
   723  				return false
   724  			})
   725  			if err != nil {
   726  				if os.IsNotExist(err) {
   727  					return nil
   728  				}
   729  				return err
   730  			}
   731  			s.Process.User.AdditionalGids = gids
   732  			return nil
   733  		}
   734  		if c.Snapshotter == "" && c.SnapshotKey == "" {
   735  			if !isRootfsAbs(s.Root.Path) {
   736  				return errors.Errorf("rootfs absolute path is required")
   737  			}
   738  			return setAdditionalGids(s.Root.Path)
   739  		}
   740  		if c.Snapshotter == "" {
   741  			return errors.Errorf("no snapshotter set for container")
   742  		}
   743  		if c.SnapshotKey == "" {
   744  			return errors.Errorf("rootfs snapshot not created for container")
   745  		}
   746  		snapshotter := client.SnapshotService(c.Snapshotter)
   747  		mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
   748  		if err != nil {
   749  			return err
   750  		}
   751  		return mount.WithTempMount(ctx, mounts, setAdditionalGids)
   752  	}
   753  }
   754  
   755  // WithCapabilities sets Linux capabilities on the process
   756  func WithCapabilities(caps []string) SpecOpts {
   757  	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   758  		setCapabilities(s)
   759  
   760  		s.Process.Capabilities.Bounding = caps
   761  		s.Process.Capabilities.Effective = caps
   762  		s.Process.Capabilities.Permitted = caps
   763  		s.Process.Capabilities.Inheritable = caps
   764  
   765  		return nil
   766  	}
   767  }
   768  
   769  // WithAllCapabilities sets all linux capabilities for the process
   770  var WithAllCapabilities = func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
   771  	return WithCapabilities(GetAllCapabilities())(ctx, client, c, s)
   772  }
   773  
   774  // GetAllCapabilities returns all caps up to CAP_LAST_CAP
   775  // or CAP_BLOCK_SUSPEND on RHEL6
   776  func GetAllCapabilities() []string {
   777  	last := capability.CAP_LAST_CAP
   778  	// hack for RHEL6 which has no /proc/sys/kernel/cap_last_cap
   779  	if last == capability.Cap(63) {
   780  		last = capability.CAP_BLOCK_SUSPEND
   781  	}
   782  	var caps []string
   783  	for _, cap := range capability.List() {
   784  		if cap > last {
   785  			continue
   786  		}
   787  		caps = append(caps, "CAP_"+strings.ToUpper(cap.String()))
   788  	}
   789  	return caps
   790  }
   791  
   792  func capsContain(caps []string, s string) bool {
   793  	for _, c := range caps {
   794  		if c == s {
   795  			return true
   796  		}
   797  	}
   798  	return false
   799  }
   800  
   801  func removeCap(caps *[]string, s string) {
   802  	var newcaps []string
   803  	for _, c := range *caps {
   804  		if c == s {
   805  			continue
   806  		}
   807  		newcaps = append(newcaps, c)
   808  	}
   809  	*caps = newcaps
   810  }
   811  
   812  // WithAddedCapabilities adds the provided capabilities
   813  func WithAddedCapabilities(caps []string) SpecOpts {
   814  	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   815  		setCapabilities(s)
   816  		for _, c := range caps {
   817  			for _, cl := range []*[]string{
   818  				&s.Process.Capabilities.Bounding,
   819  				&s.Process.Capabilities.Effective,
   820  				&s.Process.Capabilities.Permitted,
   821  				&s.Process.Capabilities.Inheritable,
   822  			} {
   823  				if !capsContain(*cl, c) {
   824  					*cl = append(*cl, c)
   825  				}
   826  			}
   827  		}
   828  		return nil
   829  	}
   830  }
   831  
   832  // WithDroppedCapabilities removes the provided capabilities
   833  func WithDroppedCapabilities(caps []string) SpecOpts {
   834  	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   835  		setCapabilities(s)
   836  		for _, c := range caps {
   837  			for _, cl := range []*[]string{
   838  				&s.Process.Capabilities.Bounding,
   839  				&s.Process.Capabilities.Effective,
   840  				&s.Process.Capabilities.Permitted,
   841  				&s.Process.Capabilities.Inheritable,
   842  			} {
   843  				removeCap(cl, c)
   844  			}
   845  		}
   846  		return nil
   847  	}
   848  }
   849  
   850  // WithAmbientCapabilities set the Linux ambient capabilities for the process
   851  // Ambient capabilities should only be set for non-root users or the caller should
   852  // understand how these capabilities are used and set
   853  func WithAmbientCapabilities(caps []string) SpecOpts {
   854  	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   855  		setCapabilities(s)
   856  
   857  		s.Process.Capabilities.Ambient = caps
   858  		return nil
   859  	}
   860  }
   861  
   862  var errNoUsersFound = errors.New("no users found")
   863  
   864  func getUserFromPath(root string, filter func(user.User) bool) (user.User, error) {
   865  	ppath, err := fs.RootPath(root, "/etc/passwd")
   866  	if err != nil {
   867  		return user.User{}, err
   868  	}
   869  	users, err := user.ParsePasswdFileFilter(ppath, filter)
   870  	if err != nil {
   871  		return user.User{}, err
   872  	}
   873  	if len(users) == 0 {
   874  		return user.User{}, errNoUsersFound
   875  	}
   876  	return users[0], nil
   877  }
   878  
   879  var errNoGroupsFound = errors.New("no groups found")
   880  
   881  func getGIDFromPath(root string, filter func(user.Group) bool) (gid uint32, err error) {
   882  	gpath, err := fs.RootPath(root, "/etc/group")
   883  	if err != nil {
   884  		return 0, err
   885  	}
   886  	groups, err := user.ParseGroupFileFilter(gpath, filter)
   887  	if err != nil {
   888  		return 0, err
   889  	}
   890  	if len(groups) == 0 {
   891  		return 0, errNoGroupsFound
   892  	}
   893  	g := groups[0]
   894  	return uint32(g.Gid), nil
   895  }
   896  
   897  func getSupplementalGroupsFromPath(root string, filter func(user.Group) bool) ([]uint32, error) {
   898  	gpath, err := fs.RootPath(root, "/etc/group")
   899  	if err != nil {
   900  		return []uint32{}, err
   901  	}
   902  	groups, err := user.ParseGroupFileFilter(gpath, filter)
   903  	if err != nil {
   904  		return []uint32{}, err
   905  	}
   906  	if len(groups) == 0 {
   907  		// if there are no additional groups; just return an empty set
   908  		return []uint32{}, nil
   909  	}
   910  	addlGids := []uint32{}
   911  	for _, grp := range groups {
   912  		addlGids = append(addlGids, uint32(grp.Gid))
   913  	}
   914  	return addlGids, nil
   915  }
   916  
   917  func isRootfsAbs(root string) bool {
   918  	return filepath.IsAbs(root)
   919  }
   920  
   921  // WithMaskedPaths sets the masked paths option
   922  func WithMaskedPaths(paths []string) SpecOpts {
   923  	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   924  		setLinux(s)
   925  		s.Linux.MaskedPaths = paths
   926  		return nil
   927  	}
   928  }
   929  
   930  // WithReadonlyPaths sets the read only paths option
   931  func WithReadonlyPaths(paths []string) SpecOpts {
   932  	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   933  		setLinux(s)
   934  		s.Linux.ReadonlyPaths = paths
   935  		return nil
   936  	}
   937  }
   938  
   939  // WithWriteableSysfs makes any sysfs mounts writeable
   940  func WithWriteableSysfs(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   941  	for i, m := range s.Mounts {
   942  		if m.Type == "sysfs" {
   943  			var options []string
   944  			for _, o := range m.Options {
   945  				if o == "ro" {
   946  					o = "rw"
   947  				}
   948  				options = append(options, o)
   949  			}
   950  			s.Mounts[i].Options = options
   951  		}
   952  	}
   953  	return nil
   954  }
   955  
   956  // WithWriteableCgroupfs makes any cgroup mounts writeable
   957  func WithWriteableCgroupfs(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   958  	for i, m := range s.Mounts {
   959  		if m.Type == "cgroup" {
   960  			var options []string
   961  			for _, o := range m.Options {
   962  				if o == "ro" {
   963  					o = "rw"
   964  				}
   965  				options = append(options, o)
   966  			}
   967  			s.Mounts[i].Options = options
   968  		}
   969  	}
   970  	return nil
   971  }
   972  
   973  // WithSelinuxLabel sets the process SELinux label
   974  func WithSelinuxLabel(label string) SpecOpts {
   975  	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   976  		setProcess(s)
   977  		s.Process.SelinuxLabel = label
   978  		return nil
   979  	}
   980  }
   981  
   982  // WithApparmorProfile sets the Apparmor profile for the process
   983  func WithApparmorProfile(profile string) SpecOpts {
   984  	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   985  		setProcess(s)
   986  		s.Process.ApparmorProfile = profile
   987  		return nil
   988  	}
   989  }
   990  
   991  // WithSeccompUnconfined clears the seccomp profile
   992  func WithSeccompUnconfined(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
   993  	setLinux(s)
   994  	s.Linux.Seccomp = nil
   995  	return nil
   996  }
   997  
   998  // WithParentCgroupDevices uses the default cgroup setup to inherit the container's parent cgroup's
   999  // allowed and denied devices
  1000  func WithParentCgroupDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
  1001  	setLinux(s)
  1002  	if s.Linux.Resources == nil {
  1003  		s.Linux.Resources = &specs.LinuxResources{}
  1004  	}
  1005  	s.Linux.Resources.Devices = nil
  1006  	return nil
  1007  }
  1008  
  1009  // WithDefaultUnixDevices adds the default devices for unix such as /dev/null, /dev/random to
  1010  // the container's resource cgroup spec
  1011  func WithDefaultUnixDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
  1012  	setLinux(s)
  1013  	if s.Linux.Resources == nil {
  1014  		s.Linux.Resources = &specs.LinuxResources{}
  1015  	}
  1016  	intptr := func(i int64) *int64 {
  1017  		return &i
  1018  	}
  1019  	s.Linux.Resources.Devices = append(s.Linux.Resources.Devices, []specs.LinuxDeviceCgroup{
  1020  		{
  1021  			// "/dev/null",
  1022  			Type:   "c",
  1023  			Major:  intptr(1),
  1024  			Minor:  intptr(3),
  1025  			Access: rwm,
  1026  			Allow:  true,
  1027  		},
  1028  		{
  1029  			// "/dev/random",
  1030  			Type:   "c",
  1031  			Major:  intptr(1),
  1032  			Minor:  intptr(8),
  1033  			Access: rwm,
  1034  			Allow:  true,
  1035  		},
  1036  		{
  1037  			// "/dev/full",
  1038  			Type:   "c",
  1039  			Major:  intptr(1),
  1040  			Minor:  intptr(7),
  1041  			Access: rwm,
  1042  			Allow:  true,
  1043  		},
  1044  		{
  1045  			// "/dev/tty",
  1046  			Type:   "c",
  1047  			Major:  intptr(5),
  1048  			Minor:  intptr(0),
  1049  			Access: rwm,
  1050  			Allow:  true,
  1051  		},
  1052  		{
  1053  			// "/dev/zero",
  1054  			Type:   "c",
  1055  			Major:  intptr(1),
  1056  			Minor:  intptr(5),
  1057  			Access: rwm,
  1058  			Allow:  true,
  1059  		},
  1060  		{
  1061  			// "/dev/urandom",
  1062  			Type:   "c",
  1063  			Major:  intptr(1),
  1064  			Minor:  intptr(9),
  1065  			Access: rwm,
  1066  			Allow:  true,
  1067  		},
  1068  		{
  1069  			// "/dev/console",
  1070  			Type:   "c",
  1071  			Major:  intptr(5),
  1072  			Minor:  intptr(1),
  1073  			Access: rwm,
  1074  			Allow:  true,
  1075  		},
  1076  		// /dev/pts/ - pts namespaces are "coming soon"
  1077  		{
  1078  			Type:   "c",
  1079  			Major:  intptr(136),
  1080  			Access: rwm,
  1081  			Allow:  true,
  1082  		},
  1083  		{
  1084  			Type:   "c",
  1085  			Major:  intptr(5),
  1086  			Minor:  intptr(2),
  1087  			Access: rwm,
  1088  			Allow:  true,
  1089  		},
  1090  		{
  1091  			// tuntap
  1092  			Type:   "c",
  1093  			Major:  intptr(10),
  1094  			Minor:  intptr(200),
  1095  			Access: rwm,
  1096  			Allow:  true,
  1097  		},
  1098  	}...)
  1099  	return nil
  1100  }
  1101  
  1102  // WithPrivileged sets up options for a privileged container
  1103  // TODO(justincormack) device handling
  1104  var WithPrivileged = Compose(
  1105  	WithAllCapabilities,
  1106  	WithMaskedPaths(nil),
  1107  	WithReadonlyPaths(nil),
  1108  	WithWriteableSysfs,
  1109  	WithWriteableCgroupfs,
  1110  	WithSelinuxLabel(""),
  1111  	WithApparmorProfile(""),
  1112  	WithSeccompUnconfined,
  1113  )
  1114  
  1115  // WithWindowsHyperV sets the Windows.HyperV section for HyperV isolation of containers.
  1116  func WithWindowsHyperV(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
  1117  	if s.Windows == nil {
  1118  		s.Windows = &specs.Windows{}
  1119  	}
  1120  	if s.Windows.HyperV == nil {
  1121  		s.Windows.HyperV = &specs.WindowsHyperV{}
  1122  	}
  1123  	return nil
  1124  }
  1125  
  1126  // WithMemoryLimit sets the `Linux.LinuxResources.Memory.Limit` section to the
  1127  // `limit` specified if the `Linux` section is not `nil`. Additionally sets the
  1128  // `Windows.WindowsResources.Memory.Limit` section if the `Windows` section is
  1129  // not `nil`.
  1130  func WithMemoryLimit(limit uint64) SpecOpts {
  1131  	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
  1132  		if s.Linux != nil {
  1133  			if s.Linux.Resources == nil {
  1134  				s.Linux.Resources = &specs.LinuxResources{}
  1135  			}
  1136  			if s.Linux.Resources.Memory == nil {
  1137  				s.Linux.Resources.Memory = &specs.LinuxMemory{}
  1138  			}
  1139  			l := int64(limit)
  1140  			s.Linux.Resources.Memory.Limit = &l
  1141  		}
  1142  		if s.Windows != nil {
  1143  			if s.Windows.Resources == nil {
  1144  				s.Windows.Resources = &specs.WindowsResources{}
  1145  			}
  1146  			if s.Windows.Resources.Memory == nil {
  1147  				s.Windows.Resources.Memory = &specs.WindowsMemoryResources{}
  1148  			}
  1149  			s.Windows.Resources.Memory.Limit = &limit
  1150  		}
  1151  		return nil
  1152  	}
  1153  }
  1154  
  1155  // WithAnnotations appends or replaces the annotations on the spec with the
  1156  // provided annotations
  1157  func WithAnnotations(annotations map[string]string) SpecOpts {
  1158  	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
  1159  		if s.Annotations == nil {
  1160  			s.Annotations = make(map[string]string)
  1161  		}
  1162  		for k, v := range annotations {
  1163  			s.Annotations[k] = v
  1164  		}
  1165  		return nil
  1166  	}
  1167  }
  1168  
  1169  // WithLinuxDevices adds the provided linux devices to the spec
  1170  func WithLinuxDevices(devices []specs.LinuxDevice) SpecOpts {
  1171  	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
  1172  		setLinux(s)
  1173  		s.Linux.Devices = append(s.Linux.Devices, devices...)
  1174  		return nil
  1175  	}
  1176  }
  1177  
  1178  var ErrNotADevice = errors.New("not a device node")
  1179  
  1180  // WithLinuxDevice adds the device specified by path to the spec
  1181  func WithLinuxDevice(path, permissions string) SpecOpts {
  1182  	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
  1183  		setLinux(s)
  1184  		setResources(s)
  1185  
  1186  		dev, err := deviceFromPath(path, permissions)
  1187  		if err != nil {
  1188  			return err
  1189  		}
  1190  
  1191  		s.Linux.Devices = append(s.Linux.Devices, *dev)
  1192  
  1193  		s.Linux.Resources.Devices = append(s.Linux.Resources.Devices, specs.LinuxDeviceCgroup{
  1194  			Type:   dev.Type,
  1195  			Allow:  true,
  1196  			Major:  &dev.Major,
  1197  			Minor:  &dev.Minor,
  1198  			Access: permissions,
  1199  		})
  1200  
  1201  		return nil
  1202  	}
  1203  }
  1204  
  1205  // WithEnvFile adds environment variables from a file to the container's spec
  1206  func WithEnvFile(path string) SpecOpts {
  1207  	return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
  1208  		var vars []string
  1209  		f, err := os.Open(path)
  1210  		if err != nil {
  1211  			return err
  1212  		}
  1213  		defer f.Close()
  1214  
  1215  		sc := bufio.NewScanner(f)
  1216  		for sc.Scan() {
  1217  			if sc.Err() != nil {
  1218  				return sc.Err()
  1219  			}
  1220  			vars = append(vars, sc.Text())
  1221  		}
  1222  		return WithEnv(vars)(nil, nil, nil, s)
  1223  	}
  1224  }
  1225  
  1226  // ErrNoShmMount is returned when there is no /dev/shm mount specified in the config
  1227  // and an Opts was trying to set a configuration value on the mount.
  1228  var ErrNoShmMount = errors.New("no /dev/shm mount specified")
  1229  
  1230  // WithDevShmSize sets the size of the /dev/shm mount for the container.
  1231  //
  1232  // The size value is specified in kb, kilobytes.
  1233  func WithDevShmSize(kb int64) SpecOpts {
  1234  	return func(ctx context.Context, _ Client, c *containers.Container, s *Spec) error {
  1235  		for _, m := range s.Mounts {
  1236  			if m.Source == "shm" && m.Type == "tmpfs" {
  1237  				for i, o := range m.Options {
  1238  					if strings.HasPrefix(o, "size=") {
  1239  						m.Options[i] = fmt.Sprintf("size=%dk", kb)
  1240  						return nil
  1241  					}
  1242  				}
  1243  				m.Options = append(m.Options, fmt.Sprintf("size=%dk", kb))
  1244  				return nil
  1245  			}
  1246  		}
  1247  		return ErrNoShmMount
  1248  	}
  1249  }