github.com/moby/docker@v26.1.3+incompatible/plugin/v2/plugin_linux.go (about)

     1  // FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
     2  //go:build go1.19
     3  
     4  package v2 // import "github.com/docker/docker/plugin/v2"
     5  
     6  import (
     7  	"os"
     8  	"path/filepath"
     9  	"runtime"
    10  	"strings"
    11  
    12  	"github.com/containerd/containerd/pkg/userns"
    13  	"github.com/docker/docker/api/types"
    14  	"github.com/docker/docker/internal/rootless/mountopts"
    15  	"github.com/docker/docker/internal/sliceutil"
    16  	"github.com/docker/docker/oci"
    17  	specs "github.com/opencontainers/runtime-spec/specs-go"
    18  	"github.com/pkg/errors"
    19  )
    20  
    21  // InitSpec creates an OCI spec from the plugin's config.
    22  func (p *Plugin) InitSpec(execRoot string) (*specs.Spec, error) {
    23  	s := oci.DefaultSpec()
    24  
    25  	s.Root = &specs.Root{
    26  		Path:     p.Rootfs,
    27  		Readonly: false, // TODO: all plugins should be readonly? settable in config?
    28  	}
    29  
    30  	userMounts := make(map[string]struct{}, len(p.PluginObj.Settings.Mounts))
    31  	for _, m := range p.PluginObj.Settings.Mounts {
    32  		userMounts[m.Destination] = struct{}{}
    33  	}
    34  
    35  	execRoot = filepath.Join(execRoot, p.PluginObj.ID)
    36  	if err := os.MkdirAll(execRoot, 0o700); err != nil {
    37  		return nil, errors.WithStack(err)
    38  	}
    39  
    40  	if p.PluginObj.Config.PropagatedMount != "" {
    41  		pRoot := filepath.Join(filepath.Dir(p.Rootfs), "propagated-mount")
    42  		s.Mounts = append(s.Mounts, specs.Mount{
    43  			Source:      pRoot,
    44  			Destination: p.PluginObj.Config.PropagatedMount,
    45  			Type:        "bind",
    46  			Options:     []string{"rbind", "rw", "rshared"},
    47  		})
    48  		s.Linux.RootfsPropagation = "rshared"
    49  	}
    50  
    51  	mounts := append(p.PluginObj.Config.Mounts, types.PluginMount{
    52  		Source:      &execRoot,
    53  		Destination: defaultPluginRuntimeDestination,
    54  		Type:        "bind",
    55  		Options:     []string{"rbind", "rshared"},
    56  	})
    57  
    58  	if p.PluginObj.Config.Network.Type != "" {
    59  		// TODO: if net == bridge, use libnetwork controller to create a new plugin-specific bridge, bind mount /etc/hosts and /etc/resolv.conf look at the docker code (allocateNetwork, initialize)
    60  		if p.PluginObj.Config.Network.Type == "host" {
    61  			oci.RemoveNamespace(&s, specs.LinuxNamespaceType("network"))
    62  		}
    63  		etcHosts := "/etc/hosts"
    64  		resolvConf := "/etc/resolv.conf"
    65  		mounts = append(mounts,
    66  			types.PluginMount{
    67  				Source:      &etcHosts,
    68  				Destination: etcHosts,
    69  				Type:        "bind",
    70  				Options:     []string{"rbind", "ro"},
    71  			},
    72  			types.PluginMount{
    73  				Source:      &resolvConf,
    74  				Destination: resolvConf,
    75  				Type:        "bind",
    76  				Options:     []string{"rbind", "ro"},
    77  			})
    78  	}
    79  	if p.PluginObj.Config.PidHost {
    80  		oci.RemoveNamespace(&s, specs.LinuxNamespaceType("pid"))
    81  	}
    82  
    83  	if p.PluginObj.Config.IpcHost {
    84  		oci.RemoveNamespace(&s, specs.LinuxNamespaceType("ipc"))
    85  	}
    86  
    87  	for _, mnt := range mounts {
    88  		m := specs.Mount{
    89  			Destination: mnt.Destination,
    90  			Type:        mnt.Type,
    91  			Options:     mnt.Options,
    92  		}
    93  		if mnt.Source == nil {
    94  			return nil, errors.New("mount source is not specified")
    95  		}
    96  		m.Source = *mnt.Source
    97  		s.Mounts = append(s.Mounts, m)
    98  	}
    99  
   100  	for i, m := range s.Mounts {
   101  		if strings.HasPrefix(m.Destination, "/dev/") {
   102  			if _, ok := userMounts[m.Destination]; ok {
   103  				s.Mounts = append(s.Mounts[:i], s.Mounts[i+1:]...)
   104  			}
   105  		}
   106  	}
   107  
   108  	if p.PluginObj.Config.Linux.AllowAllDevices {
   109  		s.Linux.Resources.Devices = []specs.LinuxDeviceCgroup{{Allow: true, Access: "rwm"}}
   110  	}
   111  	for _, dev := range p.PluginObj.Settings.Devices {
   112  		path := *dev.Path
   113  		d, dPermissions, err := oci.DevicesFromPath(path, path, "rwm")
   114  		if err != nil {
   115  			return nil, errors.WithStack(err)
   116  		}
   117  		s.Linux.Devices = append(s.Linux.Devices, d...)
   118  		s.Linux.Resources.Devices = append(s.Linux.Resources.Devices, dPermissions...)
   119  	}
   120  
   121  	envs := make([]string, 1, len(p.PluginObj.Settings.Env)+1)
   122  	envs[0] = "PATH=" + oci.DefaultPathEnv(runtime.GOOS)
   123  	envs = append(envs, p.PluginObj.Settings.Env...)
   124  
   125  	args := append(p.PluginObj.Config.Entrypoint, p.PluginObj.Settings.Args...)
   126  	cwd := p.PluginObj.Config.WorkDir
   127  	if len(cwd) == 0 {
   128  		cwd = "/"
   129  	}
   130  	s.Process.Terminal = false
   131  	s.Process.Args = args
   132  	s.Process.Cwd = cwd
   133  	s.Process.Env = envs
   134  
   135  	caps := s.Process.Capabilities
   136  	caps.Bounding = append(caps.Bounding, p.PluginObj.Config.Linux.Capabilities...)
   137  	caps.Permitted = append(caps.Permitted, p.PluginObj.Config.Linux.Capabilities...)
   138  	caps.Inheritable = append(caps.Inheritable, p.PluginObj.Config.Linux.Capabilities...)
   139  	caps.Effective = append(caps.Effective, p.PluginObj.Config.Linux.Capabilities...)
   140  
   141  	if p.modifyRuntimeSpec != nil {
   142  		p.modifyRuntimeSpec(&s)
   143  	}
   144  
   145  	// Rootless mode requires modifying the mount flags
   146  	// https://github.com/moby/moby/issues/47248#issuecomment-1927776700
   147  	// https://github.com/moby/moby/pull/47558
   148  	if userns.RunningInUserNS() {
   149  		for i := range s.Mounts {
   150  			m := &s.Mounts[i]
   151  			for _, o := range m.Options {
   152  				switch o {
   153  				case "bind", "rbind":
   154  					if _, err := os.Lstat(m.Source); err != nil {
   155  						if errors.Is(err, os.ErrNotExist) {
   156  							continue
   157  						}
   158  						return nil, err
   159  					}
   160  					// UnprivilegedMountFlags gets the set of mount flags that are set on the mount that contains the given
   161  					// path and are locked by CL_UNPRIVILEGED. This is necessary to ensure that
   162  					// bind-mounting "with options" will not fail with user namespaces, due to
   163  					// kernel restrictions that require user namespace mounts to preserve
   164  					// CL_UNPRIVILEGED locked flags.
   165  					unpriv, err := mountopts.UnprivilegedMountFlags(m.Source)
   166  					if err != nil {
   167  						return nil, errors.Wrapf(err, "failed to get unprivileged mount flags for %+v", m)
   168  					}
   169  					m.Options = sliceutil.Dedup(append(m.Options, unpriv...))
   170  				}
   171  			}
   172  		}
   173  	}
   174  
   175  	return &s, nil
   176  }