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 }