// InitSpec creates an OCI spec from the plugin's config. func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) { rootfs := filepath.Join(libRoot, p.PluginObj.ID, "rootfs") s.Root = specs.Root{ Path: rootfs, Readonly: false, // TODO: all plugins should be readonly? settable in manifest? } mounts := append(p.PluginObj.Config.Mounts, types.PluginMount{ Source: &p.RuntimeSourcePath, Destination: defaultPluginRuntimeDestination, Type: "bind", Options: []string{"rbind", "rshared"}, }) for _, mount := range mounts { m := specs.Mount{ Destination: mount.Destination, Type: mount.Type, Options: mount.Options, } // TODO: if nil, then it's required and user didn't set it if mount.Source != nil { m.Source = *mount.Source } if m.Source != "" && m.Type == "bind" { fi, err := os.Lstat(filepath.Join(rootfs, m.Destination)) // TODO: followsymlinks if err != nil { return nil, err } if fi.IsDir() { if err := os.MkdirAll(m.Source, 0700); err != nil { return nil, err } } } s.Mounts = append(s.Mounts, m) } envs := make([]string, 1, len(p.PluginObj.Config.Env)+1) envs[0] = "PATH=" + system.DefaultPathEnv envs = append(envs, p.PluginObj.Config.Env...) args := append(p.PluginObj.Manifest.Entrypoint, p.PluginObj.Config.Args...) cwd := p.PluginObj.Manifest.Workdir if len(cwd) == 0 { cwd = "/" } s.Process = specs.Process{ Terminal: false, Args: args, Cwd: cwd, Env: envs, } return &s, nil }
func (daemon *Daemon) populateCommonSpec(s *specs.Spec, c *container.Container) error { linkedEnv, err := daemon.setupLinkedContainers(c) if err != nil { return err } s.Root = specs.Root{ Path: c.BaseFS, Readonly: c.HostConfig.ReadonlyRootfs, } rootUID, rootGID := daemon.GetRemappedUIDGID() if err := c.SetupWorkingDirectory(rootUID, rootGID); err != nil { return err } cwd := c.Config.WorkingDir if len(cwd) == 0 { cwd = "/" } s.Process.Args = append([]string{c.Path}, c.Args...) // only add the custom init if it is specified and the container is running in its // own private pid namespace. It does not make sense to add if it is running in the // host namespace or another container's pid namespace where we already have an init if c.HostConfig.PidMode.IsPrivate() { if (c.HostConfig.Init != nil && *c.HostConfig.Init) || (c.HostConfig.Init == nil && daemon.configStore.Init) { s.Process.Args = append([]string{"/dev/init", c.Path}, c.Args...) var path string if daemon.configStore.InitPath == "" && c.HostConfig.InitPath == "" { path, err = exec.LookPath(DefaultInitBinary) if err != nil { return err } } if daemon.configStore.InitPath != "" { path = daemon.configStore.InitPath } if c.HostConfig.InitPath != "" { path = c.HostConfig.InitPath } s.Mounts = append(s.Mounts, specs.Mount{ Destination: "/dev/init", Type: "bind", Source: path, Options: []string{"bind", "ro"}, }) } } s.Process.Cwd = cwd s.Process.Env = c.CreateDaemonEnvironment(c.Config.Tty, linkedEnv) s.Process.Terminal = c.Config.Tty s.Hostname = c.FullHostname() return nil }
func setNamespaces(daemon *Daemon, s *specs.Spec, c *container.Container) error { userNS := false // user if c.HostConfig.UsernsMode.IsPrivate() { uidMap, gidMap := daemon.GetUIDGIDMaps() if uidMap != nil { userNS = true ns := specs.Namespace{Type: "user"} setNamespace(s, ns) s.Linux.UIDMappings = specMapping(uidMap) s.Linux.GIDMappings = specMapping(gidMap) } } // network if !c.Config.NetworkDisabled { ns := specs.Namespace{Type: "network"} parts := strings.SplitN(string(c.HostConfig.NetworkMode), ":", 2) if parts[0] == "container" { nc, err := daemon.getNetworkedContainer(c.ID, c.HostConfig.NetworkMode.ConnectedContainer()) if err != nil { return err } ns.Path = fmt.Sprintf("/proc/%d/ns/net", nc.State.GetPID()) if userNS { // to share a net namespace, they must also share a user namespace nsUser := specs.Namespace{Type: "user"} nsUser.Path = fmt.Sprintf("/proc/%d/ns/user", nc.State.GetPID()) setNamespace(s, nsUser) } } else if c.HostConfig.NetworkMode.IsHost() { ns.Path = c.NetworkSettings.SandboxKey } setNamespace(s, ns) } // ipc if c.HostConfig.IpcMode.IsContainer() { ns := specs.Namespace{Type: "ipc"} ic, err := daemon.getIpcContainer(c) if err != nil { return err } ns.Path = fmt.Sprintf("/proc/%d/ns/ipc", ic.State.GetPID()) setNamespace(s, ns) if userNS { // to share an IPC namespace, they must also share a user namespace nsUser := specs.Namespace{Type: "user"} nsUser.Path = fmt.Sprintf("/proc/%d/ns/user", ic.State.GetPID()) setNamespace(s, nsUser) } } else if c.HostConfig.IpcMode.IsHost() { oci.RemoveNamespace(s, specs.NamespaceType("ipc")) } else { ns := specs.Namespace{Type: "ipc"} setNamespace(s, ns) } // pid if c.HostConfig.PidMode.IsContainer() { ns := specs.Namespace{Type: "pid"} pc, err := daemon.getPidContainer(c) if err != nil { return err } ns.Path = fmt.Sprintf("/proc/%d/ns/pid", pc.State.GetPID()) setNamespace(s, ns) if userNS { // to share a PID namespace, they must also share a user namespace nsUser := specs.Namespace{Type: "user"} nsUser.Path = fmt.Sprintf("/proc/%d/ns/user", pc.State.GetPID()) setNamespace(s, nsUser) } } else if c.HostConfig.PidMode.IsHost() { oci.RemoveNamespace(s, specs.NamespaceType("pid")) } else { ns := specs.Namespace{Type: "pid"} setNamespace(s, ns) } // uts if c.HostConfig.UTSMode.IsHost() { oci.RemoveNamespace(s, specs.NamespaceType("uts")) s.Hostname = "" } return nil }
func setMounts(daemon *Daemon, s *specs.Spec, c *container.Container, mounts []container.Mount) error { userMounts := make(map[string]struct{}) for _, m := range mounts { userMounts[m.Destination] = struct{}{} } // Filter out mounts that are overridden by user supplied mounts var defaultMounts []specs.Mount _, mountDev := userMounts["/dev"] for _, m := range s.Mounts { if _, ok := userMounts[m.Destination]; !ok { if mountDev && strings.HasPrefix(m.Destination, "/dev/") { continue } defaultMounts = append(defaultMounts, m) } } s.Mounts = defaultMounts for _, m := range mounts { for _, cm := range s.Mounts { if cm.Destination == m.Destination { return fmt.Errorf("Duplicate mount point '%s'", m.Destination) } } if m.Source == "tmpfs" { data := m.Data options := []string{"noexec", "nosuid", "nodev", string(volume.DefaultPropagationMode)} if data != "" { options = append(options, strings.Split(data, ",")...) } merged, err := mount.MergeTmpfsOptions(options) if err != nil { return err } s.Mounts = append(s.Mounts, specs.Mount{Destination: m.Destination, Source: m.Source, Type: "tmpfs", Options: merged}) continue } mt := specs.Mount{Destination: m.Destination, Source: m.Source, Type: "bind"} // Determine property of RootPropagation based on volume // properties. If a volume is shared, then keep root propagation // shared. This should work for slave and private volumes too. // // For slave volumes, it can be either [r]shared/[r]slave. // // For private volumes any root propagation value should work. pFlag := mountPropagationMap[m.Propagation] if pFlag == mount.SHARED || pFlag == mount.RSHARED { if err := ensureShared(m.Source); err != nil { return err } rootpg := mountPropagationMap[s.Linux.RootfsPropagation] if rootpg != mount.SHARED && rootpg != mount.RSHARED { s.Linux.RootfsPropagation = mountPropagationReverseMap[mount.SHARED] } } else if pFlag == mount.SLAVE || pFlag == mount.RSLAVE { if err := ensureSharedOrSlave(m.Source); err != nil { return err } rootpg := mountPropagationMap[s.Linux.RootfsPropagation] if rootpg != mount.SHARED && rootpg != mount.RSHARED && rootpg != mount.SLAVE && rootpg != mount.RSLAVE { s.Linux.RootfsPropagation = mountPropagationReverseMap[mount.RSLAVE] } } opts := []string{"rbind"} if !m.Writable { opts = append(opts, "ro") } if pFlag != 0 { opts = append(opts, mountPropagationReverseMap[pFlag]) } mt.Options = opts s.Mounts = append(s.Mounts, mt) } if s.Root.Readonly { for i, m := range s.Mounts { switch m.Destination { case "/proc", "/dev/pts", "/dev/mqueue": // /dev is remounted by runc continue } if _, ok := userMounts[m.Destination]; !ok { if !stringutils.InSlice(m.Options, "ro") { s.Mounts[i].Options = append(s.Mounts[i].Options, "ro") } } } } if c.HostConfig.Privileged { if !s.Root.Readonly { // clear readonly for /sys for i := range s.Mounts { if s.Mounts[i].Destination == "/sys" { clearReadOnly(&s.Mounts[i]) } } } s.Linux.ReadonlyPaths = nil s.Linux.MaskedPaths = nil } // TODO: until a kernel/mount solution exists for handling remount in a user namespace, // we must clear the readonly flag for the cgroups mount (@mrunalp concurs) if uidMap, _ := daemon.GetUIDGIDMaps(); uidMap != nil || c.HostConfig.Privileged { for i, m := range s.Mounts { if m.Type == "cgroup" { clearReadOnly(&s.Mounts[i]) } } } return nil }
// InitSpec creates an OCI spec from the plugin's config. func (p *Plugin) InitSpec(s specs.Spec) (*specs.Spec, error) { s.Root = specs.Root{ Path: p.Rootfs, Readonly: false, // TODO: all plugins should be readonly? settable in config? } userMounts := make(map[string]struct{}, len(p.PluginObj.Settings.Mounts)) for _, m := range p.PluginObj.Settings.Mounts { userMounts[m.Destination] = struct{}{} } if err := os.MkdirAll(p.runtimeSourcePath, 0755); err != nil { return nil, err } mounts := append(p.PluginObj.Config.Mounts, types.PluginMount{ Source: &p.runtimeSourcePath, Destination: defaultPluginRuntimeDestination, Type: "bind", Options: []string{"rbind", "rshared"}, }) if p.PluginObj.Config.Network.Type != "" { // 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) if p.PluginObj.Config.Network.Type == "host" { oci.RemoveNamespace(&s, specs.NamespaceType("network")) } etcHosts := "/etc/hosts" resolvConf := "/etc/resolv.conf" mounts = append(mounts, types.PluginMount{ Source: &etcHosts, Destination: etcHosts, Type: "bind", Options: []string{"rbind", "ro"}, }, types.PluginMount{ Source: &resolvConf, Destination: resolvConf, Type: "bind", Options: []string{"rbind", "ro"}, }) } for _, mnt := range mounts { m := specs.Mount{ Destination: mnt.Destination, Type: mnt.Type, Options: mnt.Options, } if mnt.Source == nil { return nil, errors.New("mount source is not specified") } m.Source = *mnt.Source s.Mounts = append(s.Mounts, m) } for i, m := range s.Mounts { if strings.HasPrefix(m.Destination, "/dev/") { if _, ok := userMounts[m.Destination]; ok { s.Mounts = append(s.Mounts[:i], s.Mounts[i+1:]...) } } } if p.PluginObj.Config.PropagatedMount != "" { p.PropagatedMount = filepath.Join(p.Rootfs, p.PluginObj.Config.PropagatedMount) s.Linux.RootfsPropagation = "rshared" } if p.PluginObj.Config.Linux.DeviceCreation { rwm := "rwm" s.Linux.Resources.Devices = []specs.DeviceCgroup{{Allow: true, Access: &rwm}} } for _, dev := range p.PluginObj.Settings.Devices { path := *dev.Path d, dPermissions, err := oci.DevicesFromPath(path, path, "rwm") if err != nil { return nil, err } s.Linux.Devices = append(s.Linux.Devices, d...) s.Linux.Resources.Devices = append(s.Linux.Resources.Devices, dPermissions...) } envs := make([]string, 1, len(p.PluginObj.Settings.Env)+1) envs[0] = "PATH=" + system.DefaultPathEnv envs = append(envs, p.PluginObj.Settings.Env...) args := append(p.PluginObj.Config.Entrypoint, p.PluginObj.Settings.Args...) cwd := p.PluginObj.Config.Workdir if len(cwd) == 0 { cwd = "/" } s.Process.Terminal = false s.Process.Args = args s.Process.Cwd = cwd s.Process.Env = envs s.Process.Capabilities = append(s.Process.Capabilities, p.PluginObj.Config.Linux.Capabilities...) return &s, nil }