summaryrefslogtreecommitdiff
path: root/pkg/specgen
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/specgen')
-rw-r--r--pkg/specgen/generate/config_linux.go323
-rw-r--r--pkg/specgen/generate/container.go2
-rw-r--r--pkg/specgen/generate/oci.go28
-rw-r--r--pkg/specgen/generate/security.go (renamed from pkg/specgen/security.go)5
4 files changed, 347 insertions, 11 deletions
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/generate/container.go b/pkg/specgen/generate/container.go
index edd54847d..7233acb8a 100644
--- a/pkg/specgen/generate/container.go
+++ b/pkg/specgen/generate/container.go
@@ -116,7 +116,7 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat
// 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 := s.SetLabelOpts(r, s.PidNS, s.IpcNS); err != nil {
+ if err := SetLabelOpts(s, r, s.PidNS, s.IpcNS); err != nil {
return err
}
}
diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go
index 4bc4d2327..0ed091f9a 100644
--- a/pkg/specgen/generate/oci.go
+++ b/pkg/specgen/generate/oci.go
@@ -6,8 +6,8 @@ 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"
)
@@ -73,7 +73,7 @@ func SpecGenToOCI(s *specgen.SpecGenerator, rt *libpod.Runtime, newImage *image.
}
gid5Available := true
if isRootless {
- nGids, err := createconfig.GetAvailableGids()
+ nGids, err := GetAvailableGids()
if err != nil {
return nil, err
}
@@ -120,7 +120,7 @@ func SpecGenToOCI(s *specgen.SpecGenerator, rt *libpod.Runtime, newImage *image.
g.RemoveMount("/proc")
procMount := spec.Mount{
Destination: "/proc",
- Type: createconfig.TypeBind,
+ Type: TypeBind,
Source: "/proc",
Options: []string{"rbind", "nosuid", "noexec", "nodev"},
}
@@ -152,12 +152,12 @@ func SpecGenToOCI(s *specgen.SpecGenerator, rt *libpod.Runtime, newImage *image.
// 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
}
}
@@ -170,7 +170,7 @@ func SpecGenToOCI(s *specgen.SpecGenerator, rt *libpod.Runtime, newImage *image.
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)
@@ -214,9 +214,9 @@ func SpecGenToOCI(s *specgen.SpecGenerator, rt *libpod.Runtime, newImage *image.
}
// 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
}
@@ -257,3 +257,15 @@ func SpecGenToOCI(s *specgen.SpecGenerator, rt *libpod.Runtime, newImage *image.
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/security.go b/pkg/specgen/generate/security.go
index 6f835eae4..ef4b3b47a 100644
--- a/pkg/specgen/security.go
+++ b/pkg/specgen/generate/security.go
@@ -1,14 +1,15 @@
-package specgen
+package generate
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 (s *SpecGenerator) SetLabelOpts(runtime *libpod.Runtime, pidConfig Namespace, ipcConfig Namespace) error {
+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