diff options
Diffstat (limited to 'pkg/specgen')
-rw-r--r-- | pkg/specgen/config_linux_nocgo.go | 11 | ||||
-rw-r--r-- | pkg/specgen/container_validate.go | 12 | ||||
-rw-r--r-- | pkg/specgen/generate/config_linux.go | 323 | ||||
-rw-r--r-- | pkg/specgen/generate/config_linux_cgo.go (renamed from pkg/specgen/config_linux_cgo.go) | 5 | ||||
-rw-r--r-- | pkg/specgen/generate/config_linux_nocgo.go | 14 | ||||
-rw-r--r-- | pkg/specgen/generate/container.go | 8 | ||||
-rw-r--r-- | pkg/specgen/generate/container_create.go | 14 | ||||
-rw-r--r-- | pkg/specgen/generate/namespaces.go | 417 | ||||
-rw-r--r-- | pkg/specgen/generate/oci.go (renamed from pkg/specgen/oci.go) | 47 | ||||
-rw-r--r-- | pkg/specgen/generate/pod_create.go (renamed from pkg/specgen/pod_create.go) | 18 | ||||
-rw-r--r-- | pkg/specgen/generate/security.go (renamed from pkg/specgen/security.go) | 49 | ||||
-rw-r--r-- | pkg/specgen/generate/storage.go (renamed from pkg/specgen/storage.go) | 8 | ||||
-rw-r--r-- | pkg/specgen/namespaces.go | 383 | ||||
-rw-r--r-- | pkg/specgen/pod_validate.go | 5 | ||||
-rw-r--r-- | pkg/specgen/specgen.go | 19 |
15 files changed, 851 insertions, 482 deletions
diff --git a/pkg/specgen/config_linux_nocgo.go b/pkg/specgen/config_linux_nocgo.go deleted file mode 100644 index fc0c58c37..000000000 --- a/pkg/specgen/config_linux_nocgo.go +++ /dev/null @@ -1,11 +0,0 @@ -// +build linux,!cgo - -package specgen - -import ( - spec "github.com/opencontainers/runtime-spec/specs-go" -) - -func (s *SpecGenerator) getSeccompConfig(configSpec *spec.Spec) (*spec.LinuxSeccomp, error) { - return nil, nil -} diff --git a/pkg/specgen/container_validate.go b/pkg/specgen/container_validate.go index aad14ddcb..9152e7ee7 100644 --- a/pkg/specgen/container_validate.go +++ b/pkg/specgen/container_validate.go @@ -68,18 +68,6 @@ func (s *SpecGenerator) Validate() error { if len(s.CapAdd) > 0 && s.Privileged { return exclusiveOptions("CapAdd", "privileged") } - // selinuxprocesslabel and privileged are exclusive - if len(s.SelinuxProcessLabel) > 0 && s.Privileged { - return exclusiveOptions("SelinuxProcessLabel", "privileged") - } - // selinuxmounmtlabel and privileged are exclusive - if len(s.SelinuxMountLabel) > 0 && s.Privileged { - return exclusiveOptions("SelinuxMountLabel", "privileged") - } - // selinuxopts and privileged are exclusive - if len(s.SelinuxOpts) > 0 && s.Privileged { - return exclusiveOptions("SelinuxOpts", "privileged") - } // apparmor and privileged are exclusive if len(s.ApparmorProfile) > 0 && s.Privileged { return exclusiveOptions("AppArmorProfile", "privileged") diff --git a/pkg/specgen/generate/config_linux.go b/pkg/specgen/generate/config_linux.go new file mode 100644 index 000000000..1b2a2ac32 --- /dev/null +++ b/pkg/specgen/generate/config_linux.go @@ -0,0 +1,323 @@ +package generate + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/containers/libpod/pkg/rootless" + "github.com/opencontainers/runc/libcontainer/configs" + "github.com/opencontainers/runc/libcontainer/devices" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-tools/generate" + "github.com/pkg/errors" + "golang.org/x/sys/unix" +) + +func u32Ptr(i int64) *uint32 { u := uint32(i); return &u } +func fmPtr(i int64) *os.FileMode { fm := os.FileMode(i); return &fm } + +// Device transforms a libcontainer configs.Device to a specs.LinuxDevice object. +func Device(d *configs.Device) spec.LinuxDevice { + return spec.LinuxDevice{ + Type: string(d.Type), + Path: d.Path, + Major: d.Major, + Minor: d.Minor, + FileMode: fmPtr(int64(d.FileMode)), + UID: u32Ptr(int64(d.Uid)), + GID: u32Ptr(int64(d.Gid)), + } +} + +func addPrivilegedDevices(g *generate.Generator) error { + hostDevices, err := getDevices("/dev") + if err != nil { + return err + } + g.ClearLinuxDevices() + + if rootless.IsRootless() { + mounts := make(map[string]interface{}) + for _, m := range g.Mounts() { + mounts[m.Destination] = true + } + newMounts := []spec.Mount{} + for _, d := range hostDevices { + devMnt := spec.Mount{ + Destination: d.Path, + Type: TypeBind, + Source: d.Path, + Options: []string{"slave", "nosuid", "noexec", "rw", "rbind"}, + } + if d.Path == "/dev/ptmx" || strings.HasPrefix(d.Path, "/dev/tty") { + continue + } + if _, found := mounts[d.Path]; found { + continue + } + st, err := os.Stat(d.Path) + if err != nil { + if err == unix.EPERM { + continue + } + return errors.Wrapf(err, "stat %s", d.Path) + } + // Skip devices that the user has not access to. + if st.Mode()&0007 == 0 { + continue + } + newMounts = append(newMounts, devMnt) + } + g.Config.Mounts = append(newMounts, g.Config.Mounts...) + g.Config.Linux.Resources.Devices = nil + } else { + for _, d := range hostDevices { + g.AddDevice(Device(d)) + } + // Add resources device - need to clear the existing one first. + g.Config.Linux.Resources.Devices = nil + g.AddLinuxResourcesDevice(true, "", nil, nil, "rwm") + } + + return nil +} + +// DevicesFromPath computes a list of devices +func DevicesFromPath(g *generate.Generator, devicePath string) error { + devs := strings.Split(devicePath, ":") + resolvedDevicePath := devs[0] + // check if it is a symbolic link + if src, err := os.Lstat(resolvedDevicePath); err == nil && src.Mode()&os.ModeSymlink == os.ModeSymlink { + if linkedPathOnHost, err := filepath.EvalSymlinks(resolvedDevicePath); err == nil { + resolvedDevicePath = linkedPathOnHost + } + } + st, err := os.Stat(resolvedDevicePath) + if err != nil { + return errors.Wrapf(err, "cannot stat %s", devicePath) + } + if st.IsDir() { + found := false + src := resolvedDevicePath + dest := src + var devmode string + if len(devs) > 1 { + if len(devs[1]) > 0 && devs[1][0] == '/' { + dest = devs[1] + } else { + devmode = devs[1] + } + } + if len(devs) > 2 { + if devmode != "" { + return errors.Wrapf(unix.EINVAL, "invalid device specification %s", devicePath) + } + devmode = devs[2] + } + + // mount the internal devices recursively + if err := filepath.Walk(resolvedDevicePath, func(dpath string, f os.FileInfo, e error) error { + + if f.Mode()&os.ModeDevice == os.ModeDevice { + found = true + device := fmt.Sprintf("%s:%s", dpath, filepath.Join(dest, strings.TrimPrefix(dpath, src))) + if devmode != "" { + device = fmt.Sprintf("%s:%s", device, devmode) + } + if err := addDevice(g, device); err != nil { + return errors.Wrapf(err, "failed to add %s device", dpath) + } + } + return nil + }); err != nil { + return err + } + if !found { + return errors.Wrapf(unix.EINVAL, "no devices found in %s", devicePath) + } + return nil + } + + return addDevice(g, strings.Join(append([]string{resolvedDevicePath}, devs[1:]...), ":")) +} + +func BlockAccessToKernelFilesystems(privileged, pidModeIsHost bool, g *generate.Generator) { + if !privileged { + for _, mp := range []string{ + "/proc/acpi", + "/proc/kcore", + "/proc/keys", + "/proc/latency_stats", + "/proc/timer_list", + "/proc/timer_stats", + "/proc/sched_debug", + "/proc/scsi", + "/sys/firmware", + "/sys/fs/selinux", + } { + g.AddLinuxMaskedPaths(mp) + } + + if pidModeIsHost && rootless.IsRootless() { + return + } + + for _, rp := range []string{ + "/proc/asound", + "/proc/bus", + "/proc/fs", + "/proc/irq", + "/proc/sys", + "/proc/sysrq-trigger", + } { + g.AddLinuxReadonlyPaths(rp) + } + } +} + +// based on getDevices from runc (libcontainer/devices/devices.go) +func getDevices(path string) ([]*configs.Device, error) { + files, err := ioutil.ReadDir(path) + if err != nil { + if rootless.IsRootless() && os.IsPermission(err) { + return nil, nil + } + return nil, err + } + out := []*configs.Device{} + for _, f := range files { + switch { + case f.IsDir(): + switch f.Name() { + // ".lxc" & ".lxd-mounts" added to address https://github.com/lxc/lxd/issues/2825 + case "pts", "shm", "fd", "mqueue", ".lxc", ".lxd-mounts": + continue + default: + sub, err := getDevices(filepath.Join(path, f.Name())) + if err != nil { + return nil, err + } + if sub != nil { + out = append(out, sub...) + } + continue + } + case f.Name() == "console": + continue + } + device, err := devices.DeviceFromPath(filepath.Join(path, f.Name()), "rwm") + if err != nil { + if err == devices.ErrNotADevice { + continue + } + if os.IsNotExist(err) { + continue + } + return nil, err + } + out = append(out, device) + } + return out, nil +} + +func addDevice(g *generate.Generator, device string) error { + src, dst, permissions, err := ParseDevice(device) + if err != nil { + return err + } + dev, err := devices.DeviceFromPath(src, permissions) + if err != nil { + return errors.Wrapf(err, "%s is not a valid device", src) + } + if rootless.IsRootless() { + if _, err := os.Stat(src); err != nil { + if os.IsNotExist(err) { + return errors.Wrapf(err, "the specified device %s doesn't exist", src) + } + return errors.Wrapf(err, "stat device %s exist", src) + } + perm := "ro" + if strings.Contains(permissions, "w") { + perm = "rw" + } + devMnt := spec.Mount{ + Destination: dst, + Type: TypeBind, + Source: src, + Options: []string{"slave", "nosuid", "noexec", perm, "rbind"}, + } + g.Config.Mounts = append(g.Config.Mounts, devMnt) + return nil + } + dev.Path = dst + linuxdev := spec.LinuxDevice{ + Path: dev.Path, + Type: string(dev.Type), + Major: dev.Major, + Minor: dev.Minor, + FileMode: &dev.FileMode, + UID: &dev.Uid, + GID: &dev.Gid, + } + g.AddDevice(linuxdev) + g.AddLinuxResourcesDevice(true, string(dev.Type), &dev.Major, &dev.Minor, dev.Permissions) + return nil +} + +// ParseDevice parses device mapping string to a src, dest & permissions string +func ParseDevice(device string) (string, string, string, error) { //nolint + src := "" + dst := "" + permissions := "rwm" + arr := strings.Split(device, ":") + switch len(arr) { + case 3: + if !IsValidDeviceMode(arr[2]) { + return "", "", "", fmt.Errorf("invalid device mode: %s", arr[2]) + } + permissions = arr[2] + fallthrough + case 2: + if IsValidDeviceMode(arr[1]) { + permissions = arr[1] + } else { + if arr[1][0] != '/' { + return "", "", "", fmt.Errorf("invalid device mode: %s", arr[1]) + } + dst = arr[1] + } + fallthrough + case 1: + src = arr[0] + default: + return "", "", "", fmt.Errorf("invalid device specification: %s", device) + } + + if dst == "" { + dst = src + } + return src, dst, permissions, nil +} + +// IsValidDeviceMode checks if the mode for device is valid or not. +// IsValid mode is a composition of r (read), w (write), and m (mknod). +func IsValidDeviceMode(mode string) bool { + var legalDeviceMode = map[rune]bool{ + 'r': true, + 'w': true, + 'm': true, + } + if mode == "" { + return false + } + for _, c := range mode { + if !legalDeviceMode[c] { + return false + } + legalDeviceMode[c] = false + } + return true +} diff --git a/pkg/specgen/config_linux_cgo.go b/pkg/specgen/generate/config_linux_cgo.go index ef6c6e951..b06ef5c9a 100644 --- a/pkg/specgen/config_linux_cgo.go +++ b/pkg/specgen/generate/config_linux_cgo.go @@ -1,6 +1,6 @@ // +build linux,cgo -package specgen +package generate import ( "context" @@ -8,13 +8,14 @@ import ( "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/seccomp" + "github.com/containers/libpod/pkg/specgen" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" goSeccomp "github.com/seccomp/containers-golang" "github.com/sirupsen/logrus" ) -func (s *SpecGenerator) getSeccompConfig(configSpec *spec.Spec, img *image.Image) (*spec.LinuxSeccomp, error) { +func getSeccompConfig(s *specgen.SpecGenerator, configSpec *spec.Spec, img *image.Image) (*spec.LinuxSeccomp, error) { var seccompConfig *spec.LinuxSeccomp var err error scp, err := seccomp.LookupPolicy(s.SeccompPolicy) diff --git a/pkg/specgen/generate/config_linux_nocgo.go b/pkg/specgen/generate/config_linux_nocgo.go new file mode 100644 index 000000000..fc8ed206d --- /dev/null +++ b/pkg/specgen/generate/config_linux_nocgo.go @@ -0,0 +1,14 @@ +// +build linux,!cgo + +package generate + +import ( + "errors" + + "github.com/containers/libpod/pkg/specgen" + spec "github.com/opencontainers/runtime-spec/specs-go" +) + +func (s *specgen.SpecGenerator) getSeccompConfig(configSpec *spec.Spec) (*spec.LinuxSeccomp, error) { + return nil, errors.New("not implemented") +} diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go index 78c77fec1..7233acb8a 100644 --- a/pkg/specgen/generate/container.go +++ b/pkg/specgen/generate/container.go @@ -113,6 +113,14 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat if err := finishThrottleDevices(s); err != nil { return err } + // Unless already set via the CLI, check if we need to disable process + // labels or set the defaults. + if len(s.SelinuxOpts) == 0 { + if err := SetLabelOpts(s, r, s.PidNS, s.IpcNS); err != nil { + return err + } + } + return nil } diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index aad59a861..264e0ff8e 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -40,7 +40,7 @@ func MakeContainer(rt *libpod.Runtime, s *specgen.SpecGenerator) (*libpod.Contai options = append(options, libpod.WithRootFSFromImage(newImage.ID(), s.Image, s.RawImageName)) - runtimeSpec, err := s.ToOCISpec(rt, newImage) + runtimeSpec, err := SpecGenToOCI(s, rt, newImage) if err != nil { return nil, err } @@ -80,7 +80,15 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator) ([]lib options = append(options, libpod.WithUserVolumes(destinations)) if len(s.Volumes) != 0 { - options = append(options, libpod.WithNamedVolumes(s.Volumes)) + var volumes []*libpod.ContainerNamedVolume + for _, v := range s.Volumes { + volumes = append(volumes, &libpod.ContainerNamedVolume{ + Name: v.Name, + Dest: v.Dest, + Options: v.Options, + }) + } + options = append(options, libpod.WithNamedVolumes(volumes)) } if len(s.Command) != 0 { @@ -115,7 +123,7 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator) ([]lib options = append(options, libpod.WithPrivileged(s.Privileged)) // Get namespace related options - namespaceOptions, err := s.GenerateNamespaceContainerOpts(rt) + namespaceOptions, err := GenerateNamespaceContainerOpts(s, rt) if err != nil { return nil, err } diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go new file mode 100644 index 000000000..cdd7d86da --- /dev/null +++ b/pkg/specgen/generate/namespaces.go @@ -0,0 +1,417 @@ +package generate + +import ( + "os" + + "github.com/containers/common/pkg/capabilities" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/specgen" + "github.com/cri-o/ocicni/pkg/ocicni" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-tools/generate" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +func GenerateNamespaceContainerOpts(s *specgen.SpecGenerator, rt *libpod.Runtime) ([]libpod.CtrCreateOption, error) { + var portBindings []ocicni.PortMapping + options := make([]libpod.CtrCreateOption, 0) + + // Cgroups + switch { + case s.CgroupNS.IsPrivate(): + ns := s.CgroupNS.Value + if _, err := os.Stat(ns); err != nil { + return nil, err + } + case s.CgroupNS.IsContainer(): + connectedCtr, err := rt.LookupContainer(s.CgroupNS.Value) + if err != nil { + return nil, errors.Wrapf(err, "container %q not found", s.CgroupNS.Value) + } + options = append(options, libpod.WithCgroupNSFrom(connectedCtr)) + // TODO + //default: + // return nil, errors.New("cgroup name only supports private and container") + } + + if s.CgroupParent != "" { + options = append(options, libpod.WithCgroupParent(s.CgroupParent)) + } + + if s.CgroupsMode != "" { + options = append(options, libpod.WithCgroupsMode(s.CgroupsMode)) + } + + // ipc + switch { + case s.IpcNS.IsHost(): + options = append(options, libpod.WithShmDir("/dev/shm")) + case s.IpcNS.IsContainer(): + connectedCtr, err := rt.LookupContainer(s.IpcNS.Value) + if err != nil { + return nil, errors.Wrapf(err, "container %q not found", s.IpcNS.Value) + } + options = append(options, libpod.WithIPCNSFrom(connectedCtr)) + options = append(options, libpod.WithShmDir(connectedCtr.ShmDir())) + } + + // pid + if s.PidNS.IsContainer() { + connectedCtr, err := rt.LookupContainer(s.PidNS.Value) + if err != nil { + return nil, errors.Wrapf(err, "container %q not found", s.PidNS.Value) + } + options = append(options, libpod.WithPIDNSFrom(connectedCtr)) + } + + // uts + switch { + case s.UtsNS.IsPod(): + connectedPod, err := rt.LookupPod(s.UtsNS.Value) + if err != nil { + return nil, errors.Wrapf(err, "pod %q not found", s.UtsNS.Value) + } + options = append(options, libpod.WithUTSNSFromPod(connectedPod)) + case s.UtsNS.IsContainer(): + connectedCtr, err := rt.LookupContainer(s.UtsNS.Value) + if err != nil { + return nil, errors.Wrapf(err, "container %q not found", s.UtsNS.Value) + } + + options = append(options, libpod.WithUTSNSFrom(connectedCtr)) + } + + if s.UseImageHosts { + options = append(options, libpod.WithUseImageHosts()) + } else if len(s.HostAdd) > 0 { + options = append(options, libpod.WithHosts(s.HostAdd)) + } + + // User + + switch { + case s.UserNS.IsPath(): + ns := s.UserNS.Value + if ns == "" { + return nil, errors.Errorf("invalid empty user-defined user namespace") + } + _, err := os.Stat(ns) + if err != nil { + return nil, err + } + if s.IDMappings != nil { + options = append(options, libpod.WithIDMappings(*s.IDMappings)) + } + case s.UserNS.IsContainer(): + connectedCtr, err := rt.LookupContainer(s.UserNS.Value) + if err != nil { + return nil, errors.Wrapf(err, "container %q not found", s.UserNS.Value) + } + options = append(options, libpod.WithUserNSFrom(connectedCtr)) + default: + if s.IDMappings != nil { + options = append(options, libpod.WithIDMappings(*s.IDMappings)) + } + } + + options = append(options, libpod.WithUser(s.User)) + options = append(options, libpod.WithGroups(s.Groups)) + + if len(s.PortMappings) > 0 { + portBindings = s.PortMappings + } + + switch { + case s.NetNS.IsPath(): + ns := s.NetNS.Value + if ns == "" { + return nil, errors.Errorf("invalid empty user-defined network namespace") + } + _, err := os.Stat(ns) + if err != nil { + return nil, err + } + case s.NetNS.IsContainer(): + connectedCtr, err := rt.LookupContainer(s.NetNS.Value) + if err != nil { + return nil, errors.Wrapf(err, "container %q not found", s.NetNS.Value) + } + options = append(options, libpod.WithNetNSFrom(connectedCtr)) + case !s.NetNS.IsHost() && s.NetNS.NSMode != specgen.NoNetwork: + postConfigureNetNS := !s.UserNS.IsHost() + options = append(options, libpod.WithNetNS(portBindings, postConfigureNetNS, string(s.NetNS.NSMode), s.CNINetworks)) + } + + if len(s.DNSSearch) > 0 { + options = append(options, libpod.WithDNSSearch(s.DNSSearch)) + } + if len(s.DNSServer) > 0 { + // TODO I'm not sure how we are going to handle this given the input + if len(s.DNSServer) == 1 { //&& strings.ToLower(s.DNSServer[0].) == "none" { + options = append(options, libpod.WithUseImageResolvConf()) + } else { + var dnsServers []string + for _, d := range s.DNSServer { + dnsServers = append(dnsServers, d.String()) + } + options = append(options, libpod.WithDNS(dnsServers)) + } + } + if len(s.DNSOption) > 0 { + options = append(options, libpod.WithDNSOption(s.DNSOption)) + } + if s.StaticIP != nil { + options = append(options, libpod.WithStaticIP(*s.StaticIP)) + } + + if s.StaticMAC != nil { + options = append(options, libpod.WithStaticMAC(*s.StaticMAC)) + } + return options, nil +} + +func pidConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator) error { + if s.PidNS.IsPath() { + return g.AddOrReplaceLinuxNamespace(string(spec.PIDNamespace), s.PidNS.Value) + } + if s.PidNS.IsHost() { + return g.RemoveLinuxNamespace(string(spec.PIDNamespace)) + } + if s.PidNS.IsContainer() { + logrus.Debugf("using container %s pidmode", s.PidNS.Value) + } + if s.PidNS.IsPod() { + logrus.Debug("using pod pidmode") + } + return nil +} + +func utsConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, runtime *libpod.Runtime) error { + hostname := s.Hostname + var err error + if hostname == "" { + switch { + case s.UtsNS.IsContainer(): + utsCtr, err := runtime.LookupContainer(s.UtsNS.Value) + if err != nil { + return errors.Wrapf(err, "unable to retrieve hostname from dependency container %s", s.UtsNS.Value) + } + hostname = utsCtr.Hostname() + case s.NetNS.IsHost() || s.UtsNS.IsHost(): + hostname, err = os.Hostname() + if err != nil { + return errors.Wrap(err, "unable to retrieve hostname of the host") + } + default: + logrus.Debug("No hostname set; container's hostname will default to runtime default") + } + } + g.RemoveHostname() + if s.Hostname != "" || !s.UtsNS.IsHost() { + // Set the hostname in the OCI configuration only + // if specified by the user or if we are creating + // a new UTS namespace. + g.SetHostname(hostname) + } + g.AddProcessEnv("HOSTNAME", hostname) + + if s.UtsNS.IsPath() { + return g.AddOrReplaceLinuxNamespace(string(spec.UTSNamespace), s.UtsNS.Value) + } + if s.UtsNS.IsHost() { + return g.RemoveLinuxNamespace(string(spec.UTSNamespace)) + } + if s.UtsNS.IsContainer() { + logrus.Debugf("using container %s utsmode", s.UtsNS.Value) + } + return nil +} + +func ipcConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator) error { + if s.IpcNS.IsPath() { + return g.AddOrReplaceLinuxNamespace(string(spec.IPCNamespace), s.IpcNS.Value) + } + if s.IpcNS.IsHost() { + return g.RemoveLinuxNamespace(s.IpcNS.Value) + } + if s.IpcNS.IsContainer() { + logrus.Debugf("Using container %s ipcmode", s.IpcNS.Value) + } + return nil +} + +func cgroupConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator) error { + if s.CgroupNS.IsPath() { + return g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), s.CgroupNS.Value) + } + if s.CgroupNS.IsHost() { + return g.RemoveLinuxNamespace(s.CgroupNS.Value) + } + if s.CgroupNS.IsPrivate() { + return g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), "") + } + if s.CgroupNS.IsContainer() { + logrus.Debugf("Using container %s cgroup mode", s.CgroupNS.Value) + } + return nil +} + +func networkConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator) error { + switch { + case s.NetNS.IsHost(): + logrus.Debug("Using host netmode") + if err := g.RemoveLinuxNamespace(string(spec.NetworkNamespace)); err != nil { + return err + } + + case s.NetNS.NSMode == specgen.NoNetwork: + logrus.Debug("Using none netmode") + case s.NetNS.NSMode == specgen.Bridge: + logrus.Debug("Using bridge netmode") + case s.NetNS.IsContainer(): + logrus.Debugf("using container %s netmode", s.NetNS.Value) + case s.NetNS.IsPath(): + logrus.Debug("Using ns netmode") + if err := g.AddOrReplaceLinuxNamespace(string(spec.NetworkNamespace), s.NetNS.Value); err != nil { + return err + } + case s.NetNS.IsPod(): + logrus.Debug("Using pod netmode, unless pod is not sharing") + case s.NetNS.NSMode == specgen.Slirp: + logrus.Debug("Using slirp4netns netmode") + default: + return errors.Errorf("unknown network mode") + } + + if g.Config.Annotations == nil { + g.Config.Annotations = make(map[string]string) + } + + if s.PublishImagePorts { + g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseTrue + } else { + g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseFalse + } + + return nil +} + +func userConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator) error { + if s.UserNS.IsPath() { + if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), s.UserNS.Value); err != nil { + return err + } + // runc complains if no mapping is specified, even if we join another ns. So provide a dummy mapping + g.AddLinuxUIDMapping(uint32(0), uint32(0), uint32(1)) + g.AddLinuxGIDMapping(uint32(0), uint32(0), uint32(1)) + } + + if s.IDMappings != nil { + if (len(s.IDMappings.UIDMap) > 0 || len(s.IDMappings.GIDMap) > 0) && !s.UserNS.IsHost() { + if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), ""); err != nil { + return err + } + } + for _, uidmap := range s.IDMappings.UIDMap { + g.AddLinuxUIDMapping(uint32(uidmap.HostID), uint32(uidmap.ContainerID), uint32(uidmap.Size)) + } + for _, gidmap := range s.IDMappings.GIDMap { + g.AddLinuxGIDMapping(uint32(gidmap.HostID), uint32(gidmap.ContainerID), uint32(gidmap.Size)) + } + } + return nil +} + +func securityConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, newImage *image.Image) error { + // HANDLE CAPABILITIES + // NOTE: Must happen before SECCOMP + if s.Privileged { + g.SetupPrivileged(true) + } + + useNotRoot := func(user string) bool { + if user == "" || user == "root" || user == "0" { + return false + } + return true + } + configSpec := g.Config + var err error + var caplist []string + bounding := configSpec.Process.Capabilities.Bounding + if useNotRoot(s.User) { + configSpec.Process.Capabilities.Bounding = caplist + } + caplist, err = capabilities.MergeCapabilities(configSpec.Process.Capabilities.Bounding, s.CapAdd, s.CapDrop) + if err != nil { + return err + } + + configSpec.Process.Capabilities.Bounding = caplist + configSpec.Process.Capabilities.Permitted = caplist + configSpec.Process.Capabilities.Inheritable = caplist + configSpec.Process.Capabilities.Effective = caplist + configSpec.Process.Capabilities.Ambient = caplist + if useNotRoot(s.User) { + caplist, err = capabilities.MergeCapabilities(bounding, s.CapAdd, s.CapDrop) + if err != nil { + return err + } + } + configSpec.Process.Capabilities.Bounding = caplist + + // HANDLE SECCOMP + if s.SeccompProfilePath != "unconfined" { + seccompConfig, err := getSeccompConfig(s, configSpec, newImage) + if err != nil { + return err + } + configSpec.Linux.Seccomp = seccompConfig + } + + // Clear default Seccomp profile from Generator for privileged containers + if s.SeccompProfilePath == "unconfined" || s.Privileged { + configSpec.Linux.Seccomp = nil + } + + g.SetRootReadonly(s.ReadOnlyFilesystem) + for sysctlKey, sysctlVal := range s.Sysctl { + g.AddLinuxSysctl(sysctlKey, sysctlVal) + } + + return nil +} + +// GetNamespaceOptions transforms a slice of kernel namespaces +// into a slice of pod create options. Currently, not all +// kernel namespaces are supported, and they will be returned in an error +func GetNamespaceOptions(ns []string) ([]libpod.PodCreateOption, error) { + var options []libpod.PodCreateOption + var erroredOptions []libpod.PodCreateOption + for _, toShare := range ns { + switch toShare { + case "cgroup": + options = append(options, libpod.WithPodCgroups()) + case "net": + options = append(options, libpod.WithPodNet()) + case "mnt": + return erroredOptions, errors.Errorf("Mount sharing functionality not supported on pod level") + case "pid": + options = append(options, libpod.WithPodPID()) + case "user": + return erroredOptions, errors.Errorf("User sharing functionality not supported on pod level") + case "ipc": + options = append(options, libpod.WithPodIPC()) + case "uts": + options = append(options, libpod.WithPodUTS()) + case "": + case "none": + return erroredOptions, nil + default: + return erroredOptions, errors.Errorf("Invalid kernel namespace to share: %s. Options are: net, pid, ipc, uts or none", toShare) + } + } + return options, nil +} diff --git a/pkg/specgen/oci.go b/pkg/specgen/generate/oci.go index 0756782b4..0ed091f9a 100644 --- a/pkg/specgen/oci.go +++ b/pkg/specgen/generate/oci.go @@ -1,4 +1,4 @@ -package specgen +package generate import ( "strings" @@ -6,12 +6,13 @@ import ( "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/rootless" - createconfig "github.com/containers/libpod/pkg/spec" + "github.com/containers/libpod/pkg/specgen" + "github.com/opencontainers/runc/libcontainer/user" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" ) -func (s *SpecGenerator) ToOCISpec(rt *libpod.Runtime, newImage *image.Image) (*spec.Spec, error) { +func SpecGenToOCI(s *specgen.SpecGenerator, rt *libpod.Runtime, newImage *image.Image) (*spec.Spec, error) { var ( inUserNS bool ) @@ -72,7 +73,7 @@ func (s *SpecGenerator) ToOCISpec(rt *libpod.Runtime, newImage *image.Image) (*s } gid5Available := true if isRootless { - nGids, err := createconfig.GetAvailableGids() + nGids, err := GetAvailableGids() if err != nil { return nil, err } @@ -119,7 +120,7 @@ func (s *SpecGenerator) ToOCISpec(rt *libpod.Runtime, newImage *image.Image) (*s g.RemoveMount("/proc") procMount := spec.Mount{ Destination: "/proc", - Type: createconfig.TypeBind, + Type: TypeBind, Source: "/proc", Options: []string{"rbind", "nosuid", "noexec", "nodev"}, } @@ -151,12 +152,12 @@ func (s *SpecGenerator) ToOCISpec(rt *libpod.Runtime, newImage *image.Image) (*s // If privileged, we need to add all the host devices to the // spec. We do not add the user provided ones because we are // already adding them all. - if err := createconfig.AddPrivilegedDevices(&g); err != nil { + if err := addPrivilegedDevices(&g); err != nil { return nil, err } } else { for _, device := range s.Devices { - if err := createconfig.DevicesFromPath(&g, device.Path); err != nil { + if err := DevicesFromPath(&g, device.Path); err != nil { return nil, err } } @@ -169,7 +170,7 @@ func (s *SpecGenerator) ToOCISpec(rt *libpod.Runtime, newImage *image.Image) (*s g.SetProcessApparmorProfile(s.ApparmorProfile) } - createconfig.BlockAccessToKernelFilesystems(s.Privileged, s.PidNS.IsHost(), &g) + BlockAccessToKernelFilesystems(s.Privileged, s.PidNS.IsHost(), &g) for name, val := range s.Env { g.AddProcessEnv(name, val) @@ -183,39 +184,39 @@ func (s *SpecGenerator) ToOCISpec(rt *libpod.Runtime, newImage *image.Image) (*s // NAMESPACES - if err := s.pidConfigureGenerator(&g); err != nil { + if err := pidConfigureGenerator(s, &g); err != nil { return nil, err } - if err := s.userConfigureGenerator(&g); err != nil { + if err := userConfigureGenerator(s, &g); err != nil { return nil, err } - if err := s.networkConfigureGenerator(&g); err != nil { + if err := networkConfigureGenerator(s, &g); err != nil { return nil, err } - if err := s.utsConfigureGenerator(&g, rt); err != nil { + if err := utsConfigureGenerator(s, &g, rt); err != nil { return nil, err } - if err := s.ipcConfigureGenerator(&g); err != nil { + if err := ipcConfigureGenerator(s, &g); err != nil { return nil, err } - if err := s.cgroupConfigureGenerator(&g); err != nil { + if err := cgroupConfigureGenerator(s, &g); err != nil { return nil, err } configSpec := g.Config - if err := s.securityConfigureGenerator(&g, newImage); err != nil { + if err := securityConfigureGenerator(s, &g, newImage); err != nil { return nil, err } // BIND MOUNTS - configSpec.Mounts = createconfig.SupercedeUserMounts(s.Mounts, configSpec.Mounts) + configSpec.Mounts = SupercedeUserMounts(s.Mounts, configSpec.Mounts) // Process mounts to ensure correct options - if err := createconfig.InitFSMounts(configSpec.Mounts); err != nil { + if err := InitFSMounts(configSpec.Mounts); err != nil { return nil, err } @@ -256,3 +257,15 @@ func (s *SpecGenerator) ToOCISpec(rt *libpod.Runtime, newImage *image.Image) (*s return configSpec, nil } + +func GetAvailableGids() (int64, error) { + idMap, err := user.ParseIDMapFile("/proc/self/gid_map") + if err != nil { + return 0, err + } + count := int64(0) + for _, r := range idMap { + count += r.Count + } + return count, nil +} diff --git a/pkg/specgen/pod_create.go b/pkg/specgen/generate/pod_create.go index 06aa24e22..292f9b155 100644 --- a/pkg/specgen/pod_create.go +++ b/pkg/specgen/generate/pod_create.go @@ -1,31 +1,31 @@ -package specgen +package generate import ( "context" - "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/specgen" "github.com/sirupsen/logrus" ) -func (p *PodSpecGenerator) MakePod(rt *libpod.Runtime) (*libpod.Pod, error) { - if err := p.validate(); err != nil { +func MakePod(p *specgen.PodSpecGenerator, rt *libpod.Runtime) (*libpod.Pod, error) { + if err := p.Validate(); err != nil { return nil, err } - options, err := p.createPodOptions() + options, err := createPodOptions(p) if err != nil { return nil, err } return rt.NewPod(context.Background(), options...) } -func (p *PodSpecGenerator) createPodOptions() ([]libpod.PodCreateOption, error) { +func createPodOptions(p *specgen.PodSpecGenerator) ([]libpod.PodCreateOption, error) { var ( options []libpod.PodCreateOption ) if !p.NoInfra { options = append(options, libpod.WithInfraContainer()) - nsOptions, err := shared.GetNamespaceOptions(p.SharedNamespaces) + nsOptions, err := GetNamespaceOptions(p.SharedNamespaces) if err != nil { return nil, err } @@ -62,9 +62,9 @@ func (p *PodSpecGenerator) createPodOptions() ([]libpod.PodCreateOption, error) options = append(options, libpod.WithPodUseImageResolvConf()) } switch p.NetNS.NSMode { - case Bridge: + case specgen.Bridge: logrus.Debugf("Pod using default network mode") - case Host: + case specgen.Host: logrus.Debugf("Pod will use host networking") options = append(options, libpod.WithPodHostNetwork()) default: diff --git a/pkg/specgen/security.go b/pkg/specgen/generate/security.go index 158e4a7b3..ef4b3b47a 100644 --- a/pkg/specgen/security.go +++ b/pkg/specgen/generate/security.go @@ -1,32 +1,27 @@ -package specgen +package generate -// ToCreateOptions convert the SecurityConfig to a slice of container create -// options. -/* -func (c *SecurityConfig) ToCreateOptions() ([]libpod.CtrCreateOption, error) { - options := make([]libpod.CtrCreateOption, 0) - options = append(options, libpod.WithSecLabels(c.LabelOpts)) - options = append(options, libpod.WithPrivileged(c.Privileged)) - return options, nil -} -*/ +import ( + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/specgen" + "github.com/opencontainers/selinux/go-selinux/label" + "github.com/pkg/errors" +) // SetLabelOpts sets the label options of the SecurityConfig according to the // input. -/* -func (c *SecurityConfig) SetLabelOpts(runtime *libpod.Runtime, pidConfig *PidConfig, ipcConfig *IpcConfig) error { - if c.Privileged { - c.LabelOpts = label.DisableSecOpt() +func SetLabelOpts(s *specgen.SpecGenerator, runtime *libpod.Runtime, pidConfig specgen.Namespace, ipcConfig specgen.Namespace) error { + if !runtime.EnableLabeling() || s.Privileged { + s.SelinuxOpts = label.DisableSecOpt() return nil } var labelOpts []string - if pidConfig.PidMode.IsHost() { + if pidConfig.IsHost() { labelOpts = append(labelOpts, label.DisableSecOpt()...) - } else if pidConfig.PidMode.IsContainer() { - ctr, err := runtime.LookupContainer(pidConfig.PidMode.Container()) + } else if pidConfig.IsContainer() { + ctr, err := runtime.LookupContainer(pidConfig.Value) if err != nil { - return errors.Wrapf(err, "container %q not found", pidConfig.PidMode.Container()) + return errors.Wrapf(err, "container %q not found", pidConfig.Value) } secopts, err := label.DupSecOpt(ctr.ProcessLabel()) if err != nil { @@ -35,12 +30,12 @@ func (c *SecurityConfig) SetLabelOpts(runtime *libpod.Runtime, pidConfig *PidCon labelOpts = append(labelOpts, secopts...) } - if ipcConfig.IpcMode.IsHost() { + if ipcConfig.IsHost() { labelOpts = append(labelOpts, label.DisableSecOpt()...) - } else if ipcConfig.IpcMode.IsContainer() { - ctr, err := runtime.LookupContainer(ipcConfig.IpcMode.Container()) + } else if ipcConfig.IsContainer() { + ctr, err := runtime.LookupContainer(ipcConfig.Value) if err != nil { - return errors.Wrapf(err, "container %q not found", ipcConfig.IpcMode.Container()) + return errors.Wrapf(err, "container %q not found", ipcConfig.Value) } secopts, err := label.DupSecOpt(ctr.ProcessLabel()) if err != nil { @@ -49,13 +44,7 @@ func (c *SecurityConfig) SetLabelOpts(runtime *libpod.Runtime, pidConfig *PidCon labelOpts = append(labelOpts, secopts...) } - c.LabelOpts = append(c.LabelOpts, labelOpts...) - return nil -} -*/ - -// SetSecurityOpts the the security options (labels, apparmor, seccomp, etc.). -func SetSecurityOpts(securityOpts []string) error { + s.SelinuxOpts = append(s.SelinuxOpts, labelOpts...) return nil } diff --git a/pkg/specgen/storage.go b/pkg/specgen/generate/storage.go index 1b903f608..c9a36ed46 100644 --- a/pkg/specgen/storage.go +++ b/pkg/specgen/generate/storage.go @@ -1,4 +1,4 @@ -package specgen +package generate //nolint @@ -8,9 +8,9 @@ import ( "path/filepath" "strings" - "github.com/containers/libpod/libpod" - "github.com/containers/buildah/pkg/parse" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/specgen" "github.com/containers/libpod/pkg/util" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" @@ -38,7 +38,7 @@ var ( // TODO: Named volume options - should we default to rprivate? It bakes into a // bind mount under the hood... // TODO: handle options parsing/processing via containers/storage/pkg/mount -func (s *SpecGenerator) parseVolumes(mounts, volMounts, tmpMounts []string) error { //nolint +func parseVolumes(s *specgen.SpecGenerator, mounts, volMounts, tmpMounts []string) error { //nolint // TODO this needs to come from the image and erquires a runtime diff --git a/pkg/specgen/namespaces.go b/pkg/specgen/namespaces.go index 2a7bb3495..2e7f80fe8 100644 --- a/pkg/specgen/namespaces.go +++ b/pkg/specgen/namespaces.go @@ -1,16 +1,7 @@ package specgen import ( - "os" - - "github.com/containers/common/pkg/capabilities" - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/image" - "github.com/cri-o/ocicni/pkg/ocicni" - spec "github.com/opencontainers/runtime-spec/specs-go" - "github.com/opencontainers/runtime-tools/generate" "github.com/pkg/errors" - "github.com/sirupsen/logrus" ) type NamespaceMode string @@ -37,9 +28,9 @@ const ( // Bridge indicates that a CNI network stack // should be used Bridge NamespaceMode = "bridge" - // Slirp indicates that a slirp4ns network stack should + // Slirp indicates that a slirp4netns network stack should // be used - Slirp NamespaceMode = "slirp4ns" + Slirp NamespaceMode = "slirp4netns" ) // Namespace describes the namespace @@ -105,373 +96,3 @@ func (n *Namespace) validate() error { } return nil } - -func (s *SpecGenerator) GenerateNamespaceContainerOpts(rt *libpod.Runtime) ([]libpod.CtrCreateOption, error) { - var portBindings []ocicni.PortMapping - options := make([]libpod.CtrCreateOption, 0) - - // Cgroups - switch { - case s.CgroupNS.IsPrivate(): - ns := s.CgroupNS.Value - if _, err := os.Stat(ns); err != nil { - return nil, err - } - case s.CgroupNS.IsContainer(): - connectedCtr, err := rt.LookupContainer(s.CgroupNS.Value) - if err != nil { - return nil, errors.Wrapf(err, "container %q not found", s.CgroupNS.Value) - } - options = append(options, libpod.WithCgroupNSFrom(connectedCtr)) - // TODO - //default: - // return nil, errors.New("cgroup name only supports private and container") - } - - if s.CgroupParent != "" { - options = append(options, libpod.WithCgroupParent(s.CgroupParent)) - } - - if s.CgroupsMode != "" { - options = append(options, libpod.WithCgroupsMode(s.CgroupsMode)) - } - - // ipc - switch { - case s.IpcNS.IsHost(): - options = append(options, libpod.WithShmDir("/dev/shm")) - case s.IpcNS.IsContainer(): - connectedCtr, err := rt.LookupContainer(s.IpcNS.Value) - if err != nil { - return nil, errors.Wrapf(err, "container %q not found", s.IpcNS.Value) - } - options = append(options, libpod.WithIPCNSFrom(connectedCtr)) - options = append(options, libpod.WithShmDir(connectedCtr.ShmDir())) - } - - // pid - if s.PidNS.IsContainer() { - connectedCtr, err := rt.LookupContainer(s.PidNS.Value) - if err != nil { - return nil, errors.Wrapf(err, "container %q not found", s.PidNS.Value) - } - options = append(options, libpod.WithPIDNSFrom(connectedCtr)) - } - - // uts - switch { - case s.UtsNS.IsPod(): - connectedPod, err := rt.LookupPod(s.UtsNS.Value) - if err != nil { - return nil, errors.Wrapf(err, "pod %q not found", s.UtsNS.Value) - } - options = append(options, libpod.WithUTSNSFromPod(connectedPod)) - case s.UtsNS.IsContainer(): - connectedCtr, err := rt.LookupContainer(s.UtsNS.Value) - if err != nil { - return nil, errors.Wrapf(err, "container %q not found", s.UtsNS.Value) - } - - options = append(options, libpod.WithUTSNSFrom(connectedCtr)) - } - - if s.UseImageHosts { - options = append(options, libpod.WithUseImageHosts()) - } else if len(s.HostAdd) > 0 { - options = append(options, libpod.WithHosts(s.HostAdd)) - } - - // User - - switch { - case s.UserNS.IsPath(): - ns := s.UserNS.Value - if ns == "" { - return nil, errors.Errorf("invalid empty user-defined user namespace") - } - _, err := os.Stat(ns) - if err != nil { - return nil, err - } - if s.IDMappings != nil { - options = append(options, libpod.WithIDMappings(*s.IDMappings)) - } - case s.UserNS.IsContainer(): - connectedCtr, err := rt.LookupContainer(s.UserNS.Value) - if err != nil { - return nil, errors.Wrapf(err, "container %q not found", s.UserNS.Value) - } - options = append(options, libpod.WithUserNSFrom(connectedCtr)) - default: - if s.IDMappings != nil { - options = append(options, libpod.WithIDMappings(*s.IDMappings)) - } - } - - options = append(options, libpod.WithUser(s.User)) - options = append(options, libpod.WithGroups(s.Groups)) - - if len(s.PortMappings) > 0 { - portBindings = s.PortMappings - } - - switch { - case s.NetNS.IsPath(): - ns := s.NetNS.Value - if ns == "" { - return nil, errors.Errorf("invalid empty user-defined network namespace") - } - _, err := os.Stat(ns) - if err != nil { - return nil, err - } - case s.NetNS.IsContainer(): - connectedCtr, err := rt.LookupContainer(s.NetNS.Value) - if err != nil { - return nil, errors.Wrapf(err, "container %q not found", s.NetNS.Value) - } - options = append(options, libpod.WithNetNSFrom(connectedCtr)) - case !s.NetNS.IsHost() && s.NetNS.NSMode != NoNetwork: - postConfigureNetNS := !s.UserNS.IsHost() - options = append(options, libpod.WithNetNS(portBindings, postConfigureNetNS, string(s.NetNS.NSMode), s.CNINetworks)) - } - - if len(s.DNSSearch) > 0 { - options = append(options, libpod.WithDNSSearch(s.DNSSearch)) - } - if len(s.DNSServer) > 0 { - // TODO I'm not sure how we are going to handle this given the input - if len(s.DNSServer) == 1 { //&& strings.ToLower(s.DNSServer[0].) == "none" { - options = append(options, libpod.WithUseImageResolvConf()) - } else { - var dnsServers []string - for _, d := range s.DNSServer { - dnsServers = append(dnsServers, d.String()) - } - options = append(options, libpod.WithDNS(dnsServers)) - } - } - if len(s.DNSOption) > 0 { - options = append(options, libpod.WithDNSOption(s.DNSOption)) - } - if s.StaticIP != nil { - options = append(options, libpod.WithStaticIP(*s.StaticIP)) - } - - if s.StaticMAC != nil { - options = append(options, libpod.WithStaticMAC(*s.StaticMAC)) - } - return options, nil -} - -func (s *SpecGenerator) pidConfigureGenerator(g *generate.Generator) error { - if s.PidNS.IsPath() { - return g.AddOrReplaceLinuxNamespace(string(spec.PIDNamespace), s.PidNS.Value) - } - if s.PidNS.IsHost() { - return g.RemoveLinuxNamespace(string(spec.PIDNamespace)) - } - if s.PidNS.IsContainer() { - logrus.Debugf("using container %s pidmode", s.PidNS.Value) - } - if s.PidNS.IsPod() { - logrus.Debug("using pod pidmode") - } - return nil -} - -func (s *SpecGenerator) utsConfigureGenerator(g *generate.Generator, runtime *libpod.Runtime) error { - hostname := s.Hostname - var err error - if hostname == "" { - switch { - case s.UtsNS.IsContainer(): - utsCtr, err := runtime.LookupContainer(s.UtsNS.Value) - if err != nil { - return errors.Wrapf(err, "unable to retrieve hostname from dependency container %s", s.UtsNS.Value) - } - hostname = utsCtr.Hostname() - case s.NetNS.IsHost() || s.UtsNS.IsHost(): - hostname, err = os.Hostname() - if err != nil { - return errors.Wrap(err, "unable to retrieve hostname of the host") - } - default: - logrus.Debug("No hostname set; container's hostname will default to runtime default") - } - } - g.RemoveHostname() - if s.Hostname != "" || !s.UtsNS.IsHost() { - // Set the hostname in the OCI configuration only - // if specified by the user or if we are creating - // a new UTS namespace. - g.SetHostname(hostname) - } - g.AddProcessEnv("HOSTNAME", hostname) - - if s.UtsNS.IsPath() { - return g.AddOrReplaceLinuxNamespace(string(spec.UTSNamespace), s.UtsNS.Value) - } - if s.UtsNS.IsHost() { - return g.RemoveLinuxNamespace(string(spec.UTSNamespace)) - } - if s.UtsNS.IsContainer() { - logrus.Debugf("using container %s utsmode", s.UtsNS.Value) - } - return nil -} - -func (s *SpecGenerator) ipcConfigureGenerator(g *generate.Generator) error { - if s.IpcNS.IsPath() { - return g.AddOrReplaceLinuxNamespace(string(spec.IPCNamespace), s.IpcNS.Value) - } - if s.IpcNS.IsHost() { - return g.RemoveLinuxNamespace(s.IpcNS.Value) - } - if s.IpcNS.IsContainer() { - logrus.Debugf("Using container %s ipcmode", s.IpcNS.Value) - } - return nil -} - -func (s *SpecGenerator) cgroupConfigureGenerator(g *generate.Generator) error { - if s.CgroupNS.IsPath() { - return g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), s.CgroupNS.Value) - } - if s.CgroupNS.IsHost() { - return g.RemoveLinuxNamespace(s.CgroupNS.Value) - } - if s.CgroupNS.IsPrivate() { - return g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), "") - } - if s.CgroupNS.IsContainer() { - logrus.Debugf("Using container %s cgroup mode", s.CgroupNS.Value) - } - return nil -} - -func (s *SpecGenerator) networkConfigureGenerator(g *generate.Generator) error { - switch { - case s.NetNS.IsHost(): - logrus.Debug("Using host netmode") - if err := g.RemoveLinuxNamespace(string(spec.NetworkNamespace)); err != nil { - return err - } - - case s.NetNS.NSMode == NoNetwork: - logrus.Debug("Using none netmode") - case s.NetNS.NSMode == Bridge: - logrus.Debug("Using bridge netmode") - case s.NetNS.IsContainer(): - logrus.Debugf("using container %s netmode", s.NetNS.Value) - case s.NetNS.IsPath(): - logrus.Debug("Using ns netmode") - if err := g.AddOrReplaceLinuxNamespace(string(spec.NetworkNamespace), s.NetNS.Value); err != nil { - return err - } - case s.NetNS.IsPod(): - logrus.Debug("Using pod netmode, unless pod is not sharing") - case s.NetNS.NSMode == Slirp: - logrus.Debug("Using slirp4netns netmode") - default: - return errors.Errorf("unknown network mode") - } - - if g.Config.Annotations == nil { - g.Config.Annotations = make(map[string]string) - } - - if s.PublishImagePorts { - g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseTrue - } else { - g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseFalse - } - - return nil -} - -func (s *SpecGenerator) userConfigureGenerator(g *generate.Generator) error { - if s.UserNS.IsPath() { - if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), s.UserNS.Value); err != nil { - return err - } - // runc complains if no mapping is specified, even if we join another ns. So provide a dummy mapping - g.AddLinuxUIDMapping(uint32(0), uint32(0), uint32(1)) - g.AddLinuxGIDMapping(uint32(0), uint32(0), uint32(1)) - } - - if s.IDMappings != nil { - if (len(s.IDMappings.UIDMap) > 0 || len(s.IDMappings.GIDMap) > 0) && !s.UserNS.IsHost() { - if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), ""); err != nil { - return err - } - } - for _, uidmap := range s.IDMappings.UIDMap { - g.AddLinuxUIDMapping(uint32(uidmap.HostID), uint32(uidmap.ContainerID), uint32(uidmap.Size)) - } - for _, gidmap := range s.IDMappings.GIDMap { - g.AddLinuxGIDMapping(uint32(gidmap.HostID), uint32(gidmap.ContainerID), uint32(gidmap.Size)) - } - } - return nil -} - -func (s *SpecGenerator) securityConfigureGenerator(g *generate.Generator, newImage *image.Image) error { - // HANDLE CAPABILITIES - // NOTE: Must happen before SECCOMP - if s.Privileged { - g.SetupPrivileged(true) - } - - useNotRoot := func(user string) bool { - if user == "" || user == "root" || user == "0" { - return false - } - return true - } - configSpec := g.Config - var err error - var caplist []string - bounding := configSpec.Process.Capabilities.Bounding - if useNotRoot(s.User) { - configSpec.Process.Capabilities.Bounding = caplist - } - caplist, err = capabilities.MergeCapabilities(configSpec.Process.Capabilities.Bounding, s.CapAdd, s.CapDrop) - if err != nil { - return err - } - - configSpec.Process.Capabilities.Bounding = caplist - configSpec.Process.Capabilities.Permitted = caplist - configSpec.Process.Capabilities.Inheritable = caplist - configSpec.Process.Capabilities.Effective = caplist - configSpec.Process.Capabilities.Ambient = caplist - if useNotRoot(s.User) { - caplist, err = capabilities.MergeCapabilities(bounding, s.CapAdd, s.CapDrop) - if err != nil { - return err - } - } - configSpec.Process.Capabilities.Bounding = caplist - - // HANDLE SECCOMP - if s.SeccompProfilePath != "unconfined" { - seccompConfig, err := s.getSeccompConfig(configSpec, newImage) - if err != nil { - return err - } - configSpec.Linux.Seccomp = seccompConfig - } - - // Clear default Seccomp profile from Generator for privileged containers - if s.SeccompProfilePath == "unconfined" || s.Privileged { - configSpec.Linux.Seccomp = nil - } - - g.SetRootReadonly(s.ReadOnlyFilesystem) - for sysctlKey, sysctlVal := range s.Sysctl { - g.AddLinuxSysctl(sysctlKey, sysctlVal) - } - - return nil -} diff --git a/pkg/specgen/pod_validate.go b/pkg/specgen/pod_validate.go index 50309f096..9e9659fa9 100644 --- a/pkg/specgen/pod_validate.go +++ b/pkg/specgen/pod_validate.go @@ -15,7 +15,8 @@ func exclusivePodOptions(opt1, opt2 string) error { return errors.Wrapf(ErrInvalidPodSpecConfig, "%s and %s are mutually exclusive pod options", opt1, opt2) } -func (p *PodSpecGenerator) validate() error { +// Validate verifies the input is valid +func (p *PodSpecGenerator) Validate() error { // PodBasicConfig if p.NoInfra { if len(p.InfraCommand) > 0 { @@ -25,7 +26,7 @@ func (p *PodSpecGenerator) validate() error { return exclusivePodOptions("NoInfra", "InfraImage") } if len(p.SharedNamespaces) > 0 { - return exclusivePodOptions("NoInfo", "SharedNamespaces") + return exclusivePodOptions("NoInfra", "SharedNamespaces") } } diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index 2e6dd9c1d..1a05733f9 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -4,8 +4,6 @@ import ( "net" "syscall" - "github.com/containers/libpod/libpod" - "github.com/containers/image/v5/manifest" "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage" @@ -174,7 +172,7 @@ type ContainerStorageConfig struct { // These will supersede Image Volumes and VolumesFrom volumes where // there are conflicts. // Optional. - Volumes []*libpod.ContainerNamedVolume `json:"volumes,omitempty"` + Volumes []*Volumes `json:"volumes,omitempty"` // Devices are devices that will be added to the container. // Optional. Devices []spec.LinuxDevice `json:"devices,omitempty"` @@ -230,14 +228,6 @@ type ContainerSecurityConfig struct { // If SELinux is enabled and this is not specified, a label will be // automatically generated if not specified. // Optional. - SelinuxProcessLabel string `json:"selinux_process_label,omitempty"` - // SelinuxMountLabel is the mount label the container will use. - // If SELinux is enabled and this is not specified, a label will be - // automatically generated if not specified. - // Optional. - SelinuxMountLabel string `json:"selinux_mount_label,omitempty"` - // SelinuxOpts are options for configuring SELinux. - // Optional. SelinuxOpts []string `json:"selinux_opts,omitempty"` // ApparmorProfile is the name of the Apparmor profile the container // will use. @@ -403,6 +393,13 @@ type SpecGenerator struct { ContainerHealthCheckConfig } +// Volumes is a temporary struct to hold input from the User +type Volumes struct { + Name string + Dest string + Options []string +} + // NewSpecGenerator returns a SpecGenerator struct given one of two mandatory inputs func NewSpecGenerator(image string) *SpecGenerator { networkConfig := ContainerNetworkConfig{ |