Example #1
0
// 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
}
Example #2
0
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
}
Example #3
0
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
}
Example #4
0
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
}
Example #5
0
// 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
}