plugin_linux.go 5.5 KB

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