diff options
Diffstat (limited to 'pkg/specgen')
-rw-r--r-- | pkg/specgen/container_validate.go | 15 | ||||
-rw-r--r-- | pkg/specgen/generate/config_linux_cgo.go | 3 | ||||
-rw-r--r-- | pkg/specgen/generate/container.go | 106 | ||||
-rw-r--r-- | pkg/specgen/generate/container_create.go | 106 | ||||
-rw-r--r-- | pkg/specgen/generate/namespaces.go | 619 | ||||
-rw-r--r-- | pkg/specgen/generate/oci.go | 124 | ||||
-rw-r--r-- | pkg/specgen/generate/pod_create.go | 7 | ||||
-rw-r--r-- | pkg/specgen/generate/security.go | 110 | ||||
-rw-r--r-- | pkg/specgen/generate/storage.go | 961 | ||||
-rw-r--r-- | pkg/specgen/namespaces.go | 155 | ||||
-rw-r--r-- | pkg/specgen/pod_validate.go | 10 | ||||
-rw-r--r-- | pkg/specgen/specgen.go | 76 |
12 files changed, 1091 insertions, 1201 deletions
diff --git a/pkg/specgen/container_validate.go b/pkg/specgen/container_validate.go index 9152e7ee7..94e456c52 100644 --- a/pkg/specgen/container_validate.go +++ b/pkg/specgen/container_validate.go @@ -14,7 +14,7 @@ var ( // SystemDValues describes the only values that SystemD can be SystemDValues = []string{"true", "false", "always"} // ImageVolumeModeValues describes the only values that ImageVolumeMode can be - ImageVolumeModeValues = []string{"ignore", "tmpfs", "bind"} + ImageVolumeModeValues = []string{"ignore", "tmpfs", "anonymous"} ) func exclusiveOptions(opt1, opt2 string) error { @@ -34,7 +34,7 @@ func (s *SpecGenerator) Validate() error { } // Cannot set hostname and utsns if len(s.ContainerBasicConfig.Hostname) > 0 && !s.ContainerBasicConfig.UtsNS.IsPrivate() { - return errors.Wrap(ErrInvalidSpecConfig, "cannot set hostname when creating an UTS namespace") + return errors.Wrap(ErrInvalidSpecConfig, "cannot set hostname when running in the host UTS namespace") } // systemd values must be true, false, or always if len(s.ContainerBasicConfig.Systemd) > 0 && !util.StringInSlice(strings.ToLower(s.ContainerBasicConfig.Systemd), SystemDValues) { @@ -54,7 +54,7 @@ func (s *SpecGenerator) Validate() error { } // shmsize conflicts with IPC namespace if s.ContainerStorageConfig.ShmSize != nil && !s.ContainerStorageConfig.IpcNS.IsPrivate() { - return errors.New("cannot set shmsize when creating an IPC namespace") + return errors.New("cannot set shmsize when running in the host IPC Namespace") } // @@ -86,18 +86,15 @@ func (s *SpecGenerator) Validate() error { // // ContainerNetworkConfig // - if !s.NetNS.IsPrivate() && s.ConfigureNetNS { - return errors.New("can only configure network namespace when creating a network a network namespace") - } // useimageresolveconf conflicts with dnsserver, dnssearch, dnsoption if s.UseImageResolvConf { - if len(s.DNSServer) > 0 { + if len(s.DNSServers) > 0 { return exclusiveOptions("UseImageResolvConf", "DNSServer") } if len(s.DNSSearch) > 0 { return exclusiveOptions("UseImageResolvConf", "DNSSearch") } - if len(s.DNSOption) > 0 { + if len(s.DNSOptions) > 0 { return exclusiveOptions("UseImageResolvConf", "DNSOption") } } @@ -132,7 +129,7 @@ func (s *SpecGenerator) Validate() error { if err := s.CgroupNS.validate(); err != nil { return err } - if err := s.UserNS.validate(); err != nil { + if err := validateUserNS(&s.UserNS); err != nil { return err } diff --git a/pkg/specgen/generate/config_linux_cgo.go b/pkg/specgen/generate/config_linux_cgo.go index b06ef5c9a..5d629a6e6 100644 --- a/pkg/specgen/generate/config_linux_cgo.go +++ b/pkg/specgen/generate/config_linux_cgo.go @@ -24,6 +24,9 @@ func getSeccompConfig(s *specgen.SpecGenerator, configSpec *spec.Spec, img *imag } if scp == seccomp.PolicyImage { + if img == nil { + return nil, errors.New("cannot read seccomp profile without a valid image") + } labels, err := img.Labels(context.Background()) if err != nil { return nil, err diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go index 7233acb8a..b27dd1cc2 100644 --- a/pkg/specgen/generate/container.go +++ b/pkg/specgen/generate/container.go @@ -8,29 +8,49 @@ import ( envLib "github.com/containers/libpod/pkg/env" "github.com/containers/libpod/pkg/signal" "github.com/containers/libpod/pkg/specgen" - "github.com/pkg/errors" "golang.org/x/sys/unix" ) func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerator) error { + // If a rootfs is used, then there is no image data + if s.ContainerStorageConfig.Rootfs != "" { + return nil + } newImage, err := r.ImageRuntime().NewFromLocal(s.Image) if err != nil { return err } + if s.HealthConfig == nil { + s.HealthConfig, err = newImage.GetHealthCheck(ctx) + if err != nil { + return err + } + } + // Image stop signal - if s.StopSignal == nil && newImage.Config != nil { - sig, err := signal.ParseSignalNameOrNumber(newImage.Config.StopSignal) + if s.StopSignal == nil { + stopSignal, err := newImage.StopSignal(ctx) + if err != nil { + return err + } + sig, err := signal.ParseSignalNameOrNumber(stopSignal) if err != nil { return err } s.StopSignal = &sig } + // Image envs from the image if they don't exist // already - if newImage.Config != nil && len(newImage.Config.Env) > 0 { - envs, err := envLib.ParseSlice(newImage.Config.Env) + env, err := newImage.Env(ctx) + if err != nil { + return err + } + + if len(env) > 0 { + envs, err := envLib.ParseSlice(env) if err != nil { return err } @@ -41,16 +61,29 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat } } + labels, err := newImage.Labels(ctx) + if err != nil { + return err + } + // labels from the image that dont exist already - if config := newImage.Config; config != nil { - for k, v := range config.Labels { - if _, exists := s.Labels[k]; !exists { - s.Labels[k] = v - } + for k, v := range labels { + if _, exists := s.Labels[k]; !exists { + s.Labels[k] = v } } // annotations + + // Add annotations from the image + annotations, err := newImage.Annotations(ctx) + if err != nil { + return err + } + for k, v := range annotations { + annotations[k] = v + } + // in the event this container is in a pod, and the pod has an infra container // we will want to configure it as a type "container" instead defaulting to // the behavior of a "sandbox" container @@ -59,36 +92,25 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat // VM, which is the default behavior // - "container" denotes the container should join the VM of the SandboxID // (the infra container) - s.Annotations = make(map[string]string) + if len(s.Pod) > 0 { - s.Annotations[ann.SandboxID] = s.Pod - s.Annotations[ann.ContainerType] = ann.ContainerTypeContainer + annotations[ann.SandboxID] = s.Pod + annotations[ann.ContainerType] = ann.ContainerTypeContainer } - // - // Next, add annotations from the image - annotations, err := newImage.Annotations(ctx) - if err != nil { - return err - } - for k, v := range annotations { + + // now pass in the values from client + for k, v := range s.Annotations { annotations[k] = v } + s.Annotations = annotations - // entrypoint - if config := newImage.Config; config != nil { - if len(s.Entrypoint) < 1 && len(config.Entrypoint) > 0 { - s.Entrypoint = config.Entrypoint - } - if len(s.Command) < 1 && len(config.Cmd) > 0 { - s.Command = config.Cmd - } - if len(s.Command) < 1 && len(s.Entrypoint) < 1 { - return errors.Errorf("No command provided or as CMD or ENTRYPOINT in this image") - } - // workdir - if len(s.WorkDir) < 1 && len(config.WorkingDir) > 1 { - s.WorkDir = config.WorkingDir - } + // workdir + workingDir, err := newImage.WorkingDir(ctx) + if err != nil { + return err + } + if len(s.WorkDir) < 1 && len(workingDir) > 1 { + s.WorkDir = workingDir } if len(s.SeccompProfilePath) < 1 { @@ -99,15 +121,17 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat s.SeccompProfilePath = p } - if user := s.User; len(user) == 0 { - switch { + if len(s.User) == 0 { + s.User, err = newImage.User(ctx) + if err != nil { + return err + } + // TODO This should be enabled when namespaces actually work //case usernsMode.IsKeepID(): // user = fmt.Sprintf("%d:%d", rootless.GetRootlessUID(), rootless.GetRootlessGID()) - case newImage.Config == nil || (newImage.Config != nil && len(newImage.Config.User) == 0): + if len(s.User) == 0 { s.User = "0" - default: - s.User = newImage.Config.User } } if err := finishThrottleDevices(s); err != nil { @@ -116,7 +140,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 := SetLabelOpts(s, 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/container_create.go b/pkg/specgen/generate/container_create.go index 264e0ff8e..bb84f0618 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -7,6 +7,7 @@ import ( "github.com/containers/common/pkg/config" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/specgen" "github.com/containers/storage" "github.com/pkg/errors" @@ -14,40 +15,107 @@ import ( ) // MakeContainer creates a container based on the SpecGenerator -func MakeContainer(rt *libpod.Runtime, s *specgen.SpecGenerator) (*libpod.Container, error) { +func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGenerator) (*libpod.Container, error) { + rtc, err := rt.GetConfig() + if err != nil { + return nil, err + } + + // If joining a pod, retrieve the pod for use. + var pod *libpod.Pod + if s.Pod != "" { + foundPod, err := rt.LookupPod(s.Pod) + if err != nil { + return nil, errors.Wrapf(err, "error retrieving pod %s", s.Pod) + } + pod = foundPod + } + + // Set defaults for unset namespaces + if s.PidNS.IsDefault() { + defaultNS, err := GetDefaultNamespaceMode("pid", rtc, pod) + if err != nil { + return nil, err + } + s.PidNS = defaultNS + } + if s.IpcNS.IsDefault() { + defaultNS, err := GetDefaultNamespaceMode("ipc", rtc, pod) + if err != nil { + return nil, err + } + s.IpcNS = defaultNS + } + if s.UtsNS.IsDefault() { + defaultNS, err := GetDefaultNamespaceMode("uts", rtc, pod) + if err != nil { + return nil, err + } + s.UtsNS = defaultNS + } + if s.UserNS.IsDefault() { + defaultNS, err := GetDefaultNamespaceMode("user", rtc, pod) + if err != nil { + return nil, err + } + s.UserNS = defaultNS + } + if s.NetNS.IsDefault() { + defaultNS, err := GetDefaultNamespaceMode("net", rtc, pod) + if err != nil { + return nil, err + } + s.NetNS = defaultNS + } + if s.CgroupNS.IsDefault() { + defaultNS, err := GetDefaultNamespaceMode("cgroup", rtc, pod) + if err != nil { + return nil, err + } + s.CgroupNS = defaultNS + } + + options := []libpod.CtrCreateOption{} + + var newImage *image.Image + if s.Rootfs != "" { + options = append(options, libpod.WithRootFS(s.Rootfs)) + } else { + newImage, err = rt.ImageRuntime().NewFromLocal(s.Image) + if err != nil { + return nil, err + } + options = append(options, libpod.WithRootFSFromImage(newImage.ID(), s.Image, s.RawImageName)) + } if err := s.Validate(); err != nil { return nil, errors.Wrap(err, "invalid config provided") } - rtc, err := rt.GetConfig() + + finalMounts, finalVolumes, err := finalizeMounts(ctx, s, rt, rtc, newImage) if err != nil { return nil, err } - options, err := createContainerOptions(rt, s) + opts, err := createContainerOptions(rt, s, pod, finalVolumes) if err != nil { return nil, err } + options = append(options, opts...) podmanPath, err := os.Executable() if err != nil { return nil, err } options = append(options, createExitCommandOption(s, rt.StorageConfig(), rtc, podmanPath)) - newImage, err := rt.ImageRuntime().NewFromLocal(s.Image) - if err != nil { - return nil, err - } - - options = append(options, libpod.WithRootFSFromImage(newImage.ID(), s.Image, s.RawImageName)) - runtimeSpec, err := SpecGenToOCI(s, rt, newImage) + runtimeSpec, err := SpecGenToOCI(ctx, s, rt, rtc, newImage, finalMounts) if err != nil { return nil, err } - return rt.NewContainer(context.Background(), runtimeSpec, options...) + return rt.NewContainer(ctx, runtimeSpec, options...) } -func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator) ([]libpod.CtrCreateOption, error) { +func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *libpod.Pod, volumes []*specgen.NamedVolume) ([]libpod.CtrCreateOption, error) { var options []libpod.CtrCreateOption var err error @@ -74,21 +142,21 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator) ([]lib for _, mount := range s.Mounts { destinations = append(destinations, mount.Destination) } - for _, volume := range s.Volumes { + for _, volume := range volumes { destinations = append(destinations, volume.Dest) } options = append(options, libpod.WithUserVolumes(destinations)) - if len(s.Volumes) != 0 { - var volumes []*libpod.ContainerNamedVolume - for _, v := range s.Volumes { - volumes = append(volumes, &libpod.ContainerNamedVolume{ + if len(volumes) != 0 { + var vols []*libpod.ContainerNamedVolume + for _, v := range volumes { + vols = append(vols, &libpod.ContainerNamedVolume{ Name: v.Name, Dest: v.Dest, Options: v.Options, }) } - options = append(options, libpod.WithNamedVolumes(volumes)) + options = append(options, libpod.WithNamedVolumes(vols)) } if len(s.Command) != 0 { @@ -123,7 +191,7 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator) ([]lib options = append(options, libpod.WithPrivileged(s.Privileged)) // Get namespace related options - namespaceOptions, err := GenerateNamespaceContainerOpts(s, rt) + namespaceOptions, err := GenerateNamespaceOptions(s, rt, pod) if err != nil { return nil, err } diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go index cdd7d86da..a8b74b504 100644 --- a/pkg/specgen/generate/namespaces.go +++ b/pkg/specgen/generate/namespaces.go @@ -2,317 +2,408 @@ package generate import ( "os" + "strings" - "github.com/containers/common/pkg/capabilities" + "github.com/containers/common/pkg/config" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/cgroups" + "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/specgen" - "github.com/cri-o/ocicni/pkg/ocicni" + "github.com/containers/libpod/pkg/util" 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) +// Get the default namespace mode for any given namespace type. +func GetDefaultNamespaceMode(nsType string, cfg *config.Config, pod *libpod.Pod) (specgen.Namespace, error) { + // The default for most is private + toReturn := specgen.Namespace{} + toReturn.NSMode = specgen.Private - // Cgroups - switch { - case s.CgroupNS.IsPrivate(): - ns := s.CgroupNS.Value - if _, err := os.Stat(ns); err != nil { - return nil, err + // Ensure case insensitivity + nsType = strings.ToLower(nsType) + + // If the pod is not nil - check shared namespaces + if pod != nil && pod.HasInfraContainer() { + podMode := false + switch { + case nsType == "pid" && pod.SharesPID(): + podMode = true + case nsType == "ipc" && pod.SharesIPC(): + podMode = true + case nsType == "uts" && pod.SharesUTS(): + podMode = true + case nsType == "user" && pod.SharesUser(): + podMode = true + case nsType == "net" && pod.SharesNet(): + podMode = true + case nsType == "cgroup" && pod.SharesCgroup(): + podMode = true } - 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) + if podMode { + toReturn.NSMode = specgen.FromPod + return toReturn, nil } - 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 we have containers.conf and are not using cgroupns, use that. + if cfg != nil && nsType != "cgroup" { + switch nsType { + case "pid": + return specgen.ParseNamespace(cfg.Containers.PidNS) + case "ipc": + return specgen.ParseNamespace(cfg.Containers.IPCNS) + case "uts": + return specgen.ParseNamespace(cfg.Containers.UTSNS) + case "user": + return specgen.ParseUserNamespace(cfg.Containers.UserNS) + case "net": + ns, _, err := specgen.ParseNetworkNamespace(cfg.Containers.NetNS) + return ns, err + } } - if s.CgroupsMode != "" { - options = append(options, libpod.WithCgroupsMode(s.CgroupsMode)) + switch nsType { + case "pid", "ipc", "uts": + // PID, IPC, UTS both default to private, do nothing + case "user": + // User namespace always defaults to host + toReturn.NSMode = specgen.Host + case "net": + // Net defaults to Slirp on rootless, Bridge otherwise. + if rootless.IsRootless() { + toReturn.NSMode = specgen.Slirp + } else { + toReturn.NSMode = specgen.Bridge + } + case "cgroup": + // Cgroup is host for v1, private for v2. + // We can't trust c/common for this, as it only assumes private. + cgroupsv2, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + return toReturn, err + } + if !cgroupsv2 { + toReturn.NSMode = specgen.Host + } + default: + return toReturn, errors.Wrapf(define.ErrInvalidArg, "invalid namespace type %s passed", nsType) } - // ipc - switch { - case s.IpcNS.IsHost(): - options = append(options, libpod.WithShmDir("/dev/shm")) - case s.IpcNS.IsContainer(): - connectedCtr, err := rt.LookupContainer(s.IpcNS.Value) + return toReturn, nil +} + +// GenerateNamespaceOptions generates container creation options for all +// namespaces in a SpecGenerator. +// Pod is the pod the container will join. May be nil is the container is not +// joining a pod. +// TODO: Consider grouping options that are not directly attached to a namespace +// elsewhere. +func GenerateNamespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod.Pod) ([]libpod.CtrCreateOption, error) { + toReturn := []libpod.CtrCreateOption{} + + // If pod is not nil, get infra container. + var infraCtr *libpod.Container + if pod != nil { + infraID, err := pod.InfraContainerID() if err != nil { - return nil, errors.Wrapf(err, "container %q not found", s.IpcNS.Value) + // This is likely to be of the fatal kind (pod was + // removed) so hard fail + return nil, errors.Wrapf(err, "error looking up pod %s infra container", pod.ID()) + } + if infraID != "" { + ctr, err := rt.GetContainer(infraID) + if err != nil { + return nil, errors.Wrapf(err, "error retrieving pod %s infra container %s", pod.ID(), infraID) + } + infraCtr = ctr } - 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) + errNoInfra := errors.Wrapf(define.ErrInvalidArg, "cannot use pod namespace as container is not joining a pod or pod has no infra container") + + // PID + switch s.PidNS.NSMode { + case specgen.FromPod: + if pod == nil || infraCtr == nil { + return nil, errNoInfra + } + toReturn = append(toReturn, libpod.WithPIDNSFrom(infraCtr)) + case specgen.FromContainer: + pidCtr, err := rt.LookupContainer(s.PidNS.Value) if err != nil { - return nil, errors.Wrapf(err, "container %q not found", s.PidNS.Value) + return nil, errors.Wrapf(err, "error looking up container to share pid namespace with") } - options = append(options, libpod.WithPIDNSFrom(connectedCtr)) + toReturn = append(toReturn, libpod.WithPIDNSFrom(pidCtr)) } - // 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) + // IPC + switch s.IpcNS.NSMode { + case specgen.Host: + // Force use of host /dev/shm for host namespace + toReturn = append(toReturn, libpod.WithShmDir("/dev/shm")) + case specgen.FromPod: + if pod == nil || infraCtr == nil { + return nil, errNoInfra } - options = append(options, libpod.WithUTSNSFromPod(connectedPod)) - case s.UtsNS.IsContainer(): - connectedCtr, err := rt.LookupContainer(s.UtsNS.Value) + toReturn = append(toReturn, libpod.WithIPCNSFrom(infraCtr)) + case specgen.FromContainer: + ipcCtr, err := rt.LookupContainer(s.IpcNS.Value) if err != nil { - return nil, errors.Wrapf(err, "container %q not found", s.UtsNS.Value) + return nil, errors.Wrapf(err, "error looking up container to share ipc namespace with") } - - options = append(options, libpod.WithUTSNSFrom(connectedCtr)) + toReturn = append(toReturn, libpod.WithIPCNSFrom(ipcCtr)) + toReturn = append(toReturn, libpod.WithShmDir(ipcCtr.ShmDir())) } - if s.UseImageHosts { - options = append(options, libpod.WithUseImageHosts()) - } else if len(s.HostAdd) > 0 { - options = append(options, libpod.WithHosts(s.HostAdd)) + // UTS + switch s.UtsNS.NSMode { + case specgen.FromPod: + if pod == nil || infraCtr == nil { + return nil, errNoInfra + } + toReturn = append(toReturn, libpod.WithUTSNSFrom(infraCtr)) + case specgen.FromContainer: + utsCtr, err := rt.LookupContainer(s.UtsNS.Value) + if err != nil { + return nil, errors.Wrapf(err, "error looking up container to share uts namespace with") + } + toReturn = append(toReturn, libpod.WithUTSNSFrom(utsCtr)) } // 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 + switch s.UserNS.NSMode { + case specgen.KeepID: + if rootless.IsRootless() { + s.User = "" + } else { + // keep-id as root doesn't need a user namespace + s.UserNS.NSMode = specgen.Host } - if s.IDMappings != nil { - options = append(options, libpod.WithIDMappings(*s.IDMappings)) + case specgen.FromPod: + if pod == nil || infraCtr == nil { + return nil, errNoInfra } - case s.UserNS.IsContainer(): - connectedCtr, err := rt.LookupContainer(s.UserNS.Value) + toReturn = append(toReturn, libpod.WithUserNSFrom(infraCtr)) + case specgen.FromContainer: + userCtr, 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)) + return nil, errors.Wrapf(err, "error looking up container to share user namespace with") } + toReturn = append(toReturn, libpod.WithUserNSFrom(userCtr)) } - options = append(options, libpod.WithUser(s.User)) - options = append(options, libpod.WithGroups(s.Groups)) - - if len(s.PortMappings) > 0 { - portBindings = s.PortMappings + if s.IDMappings != nil { + toReturn = append(toReturn, libpod.WithIDMappings(*s.IDMappings)) + } + if s.User != "" { + toReturn = append(toReturn, libpod.WithUser(s.User)) + } + if len(s.Groups) > 0 { + toReturn = append(toReturn, libpod.WithGroups(s.Groups)) } - switch { - case s.NetNS.IsPath(): - ns := s.NetNS.Value - if ns == "" { - return nil, errors.Errorf("invalid empty user-defined network namespace") + // Cgroup + switch s.CgroupNS.NSMode { + case specgen.FromPod: + if pod == nil || infraCtr == nil { + return nil, errNoInfra } - _, err := os.Stat(ns) + toReturn = append(toReturn, libpod.WithCgroupNSFrom(infraCtr)) + case specgen.FromContainer: + cgroupCtr, err := rt.LookupContainer(s.CgroupNS.Value) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "error looking up container to share cgroup namespace with") } - case s.NetNS.IsContainer(): - connectedCtr, err := rt.LookupContainer(s.NetNS.Value) + toReturn = append(toReturn, libpod.WithCgroupNSFrom(cgroupCtr)) + } + + if s.CgroupParent != "" { + toReturn = append(toReturn, libpod.WithCgroupParent(s.CgroupParent)) + } + + if s.CgroupsMode != "" { + toReturn = append(toReturn, libpod.WithCgroupsMode(s.CgroupsMode)) + } + + // Net + // TODO image ports + // TODO validate CNINetworks, StaticIP, StaticIPv6 are only set if we + // are in bridge mode. + postConfigureNetNS := !s.UserNS.IsHost() + switch s.NetNS.NSMode { + case specgen.FromPod: + if pod == nil || infraCtr == nil { + return nil, errNoInfra + } + toReturn = append(toReturn, libpod.WithNetNSFrom(infraCtr)) + case specgen.FromContainer: + netCtr, err := rt.LookupContainer(s.NetNS.Value) if err != nil { - return nil, errors.Wrapf(err, "container %q not found", s.NetNS.Value) + return nil, errors.Wrapf(err, "error looking up container to share net namespace with") } - 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)) + toReturn = append(toReturn, libpod.WithNetNSFrom(netCtr)) + case specgen.Slirp: + toReturn = append(toReturn, libpod.WithNetNS(s.PortMappings, postConfigureNetNS, "slirp4netns", nil)) + case specgen.Bridge: + toReturn = append(toReturn, libpod.WithNetNS(s.PortMappings, postConfigureNetNS, "bridge", s.CNINetworks)) } - if len(s.DNSSearch) > 0 { - options = append(options, libpod.WithDNSSearch(s.DNSSearch)) + if s.UseImageHosts { + toReturn = append(toReturn, libpod.WithUseImageHosts()) + } else if len(s.HostAdd) > 0 { + toReturn = append(toReturn, libpod.WithHosts(s.HostAdd)) } - 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.DNSSearch) > 0 { + toReturn = append(toReturn, libpod.WithDNSSearch(s.DNSSearch)) + } + if s.UseImageResolvConf { + toReturn = append(toReturn, libpod.WithUseImageResolvConf()) + } else if len(s.DNSServers) > 0 { + var dnsServers []string + for _, d := range s.DNSServers { + dnsServers = append(dnsServers, d.String()) } + toReturn = append(toReturn, libpod.WithDNS(dnsServers)) } - if len(s.DNSOption) > 0 { - options = append(options, libpod.WithDNSOption(s.DNSOption)) + if len(s.DNSOptions) > 0 { + toReturn = append(toReturn, libpod.WithDNSOption(s.DNSOptions)) } if s.StaticIP != nil { - options = append(options, libpod.WithStaticIP(*s.StaticIP)) + toReturn = append(toReturn, libpod.WithStaticIP(*s.StaticIP)) } - if s.StaticMAC != nil { - options = append(options, libpod.WithStaticMAC(*s.StaticMAC)) + toReturn = append(toReturn, libpod.WithStaticMAC(*s.StaticMAC)) } - return options, nil + + return toReturn, 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)) +func specConfigureNamespaces(s *specgen.SpecGenerator, g *generate.Generator, rt *libpod.Runtime) error { + // PID + switch s.PidNS.NSMode { + case specgen.Path: + if _, err := os.Stat(s.PidNS.Value); err != nil { + return errors.Wrapf(err, "cannot find specified PID namespace path %q", s.PidNS.Value) + } + if err := g.AddOrReplaceLinuxNamespace(string(spec.PIDNamespace), s.PidNS.Value); err != nil { + return err + } + case specgen.Host: + if err := g.RemoveLinuxNamespace(string(spec.PIDNamespace)); err != nil { + return err + } + case specgen.Private: + if err := g.AddOrReplaceLinuxNamespace(string(spec.PIDNamespace), ""); err != nil { + return err + } } - if s.PidNS.IsContainer() { - logrus.Debugf("using container %s pidmode", s.PidNS.Value) + + // IPC + switch s.IpcNS.NSMode { + case specgen.Path: + if _, err := os.Stat(s.IpcNS.Value); err != nil { + return errors.Wrapf(err, "cannot find specified IPC namespace path %q", s.IpcNS.Value) + } + if err := g.AddOrReplaceLinuxNamespace(string(spec.IPCNamespace), s.IpcNS.Value); err != nil { + return err + } + case specgen.Host: + if err := g.RemoveLinuxNamespace(string(spec.IPCNamespace)); err != nil { + return err + } + case specgen.Private: + if err := g.AddOrReplaceLinuxNamespace(string(spec.IPCNamespace), ""); err != nil { + return err + } } - if s.PidNS.IsPod() { - logrus.Debug("using pod pidmode") + + // UTS + switch s.UtsNS.NSMode { + case specgen.Path: + if _, err := os.Stat(s.UtsNS.Value); err != nil { + return errors.Wrapf(err, "cannot find specified UTS namespace path %q", s.UtsNS.Value) + } + if err := g.AddOrReplaceLinuxNamespace(string(spec.UTSNamespace), s.UtsNS.Value); err != nil { + return err + } + case specgen.Host: + if err := g.RemoveLinuxNamespace(string(spec.UTSNamespace)); err != nil { + return err + } + case specgen.Private: + if err := g.AddOrReplaceLinuxNamespace(string(spec.UTSNamespace), ""); err != nil { + return err + } } - 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) + case s.UtsNS.NSMode == specgen.FromContainer: + utsCtr, err := rt.LookupContainer(s.UtsNS.Value) if err != nil { - return errors.Wrapf(err, "unable to retrieve hostname from dependency container %s", s.UtsNS.Value) + return errors.Wrapf(err, "error looking up container to share uts namespace with") } hostname = utsCtr.Hostname() - case s.NetNS.IsHost() || s.UtsNS.IsHost(): - hostname, err = os.Hostname() + case s.NetNS.NSMode == specgen.Host || s.UtsNS.NSMode == specgen.Host: + tmpHostname, err := os.Hostname() if err != nil { return errors.Wrap(err, "unable to retrieve hostname of the host") } + hostname = tmpHostname 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. + if s.Hostname != "" || s.UtsNS.NSMode != specgen.Host { + // Set the hostname in the OCI configuration only if specified by + // the user or if we are creating a new UTS namespace. + // TODO: Should we be doing this for pod or container shared + // namespaces? 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 + // User + switch s.UserNS.NSMode { + case specgen.Path: + if _, err := os.Stat(s.UserNS.Value); err != nil { + return errors.Wrapf(err, "cannot find specified user namespace path %s", s.UserNS.Value) } - 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 - } + case specgen.Host: + if err := g.RemoveLinuxNamespace(string(spec.UserNamespace)); err != nil { + return err + } + case specgen.KeepID: + var ( + err error + uid, gid int + ) + s.IDMappings, uid, gid, err = util.GetKeepIDMapping() + if err != nil { + return err + } + g.SetProcessUID(uint32(uid)) + g.SetProcessGID(uint32(gid)) + fallthrough + case specgen.Private: + if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), ""); err != nil { + return err + } + if s.IDMappings == nil || (len(s.IDMappings.UIDMap) == 0 && len(s.IDMappings.GIDMap) == 0) { + return errors.Errorf("must provide at least one UID or GID mapping to configure a user namespace") } for _, uidmap := range s.IDMappings.UIDMap { g.AddLinuxUIDMapping(uint32(uidmap.HostID), uint32(uidmap.ContainerID), uint32(uidmap.Size)) @@ -321,64 +412,52 @@ func userConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator) err 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 + // Cgroup + switch s.CgroupNS.NSMode { + case specgen.Path: + if _, err := os.Stat(s.CgroupNS.Value); err != nil { + return errors.Wrapf(err, "cannot find specified cgroup namespace path %s", s.CgroupNS.Value) } - 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 { + if err := g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), s.CgroupNS.Value); err != nil { + return err + } + case specgen.Host: + if err := g.RemoveLinuxNamespace(string(spec.CgroupNamespace)); err != nil { + return err + } + case specgen.Private: + if err := g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), ""); err != nil { return err } } - configSpec.Process.Capabilities.Bounding = caplist - // HANDLE SECCOMP - if s.SeccompProfilePath != "unconfined" { - seccompConfig, err := getSeccompConfig(s, configSpec, newImage) - if err != nil { + // Net + switch s.NetNS.NSMode { + case specgen.Path: + if _, err := os.Stat(s.NetNS.Value); err != nil { + return errors.Wrapf(err, "cannot find specified network namespace path %s", s.NetNS.Value) + } + if err := g.AddOrReplaceLinuxNamespace(string(spec.NetworkNamespace), s.NetNS.Value); err != nil { + return err + } + case specgen.Host: + if err := g.RemoveLinuxNamespace(string(spec.NetworkNamespace)); err != nil { + return err + } + case specgen.Private, specgen.NoNetwork: + if err := g.AddOrReplaceLinuxNamespace(string(spec.NetworkNamespace), ""); 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 + if g.Config.Annotations == nil { + g.Config.Annotations = make(map[string]string) } - - g.SetRootReadonly(s.ReadOnlyFilesystem) - for sysctlKey, sysctlVal := range s.Sysctl { - g.AddLinuxSysctl(sysctlKey, sysctlVal) + if s.PublishImagePorts { + g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseTrue + } else { + g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseFalse } return nil diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index 0ed091f9a..87262684e 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -1,8 +1,10 @@ package generate import ( + "context" "strings" + "github.com/containers/common/pkg/config" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/rootless" @@ -10,9 +12,90 @@ import ( "github.com/opencontainers/runc/libcontainer/user" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" + "github.com/pkg/errors" ) -func SpecGenToOCI(s *specgen.SpecGenerator, rt *libpod.Runtime, newImage *image.Image) (*spec.Spec, error) { +func addRlimits(s *specgen.SpecGenerator, g *generate.Generator) error { + var ( + kernelMax uint64 = 1048576 + isRootless = rootless.IsRootless() + nofileSet = false + nprocSet = false + ) + + if s.Rlimits == nil { + g.Config.Process.Rlimits = nil + return nil + } + + for _, u := range s.Rlimits { + name := "RLIMIT_" + strings.ToUpper(u.Type) + if name == "RLIMIT_NOFILE" { + nofileSet = true + } else if name == "RLIMIT_NPROC" { + nprocSet = true + } + g.AddProcessRlimits(name, u.Hard, u.Soft) + } + + // If not explicitly overridden by the user, default number of open + // files and number of processes to the maximum they can be set to + // (without overriding a sysctl) + if !nofileSet && !isRootless { + g.AddProcessRlimits("RLIMIT_NOFILE", kernelMax, kernelMax) + } + if !nprocSet && !isRootless { + g.AddProcessRlimits("RLIMIT_NPROC", kernelMax, kernelMax) + } + + return nil +} + +// Produce the final command for the container. +func makeCommand(ctx context.Context, s *specgen.SpecGenerator, img *image.Image, rtc *config.Config) ([]string, error) { + finalCommand := []string{} + + entrypoint := s.Entrypoint + if len(entrypoint) == 0 && img != nil { + newEntry, err := img.Entrypoint(ctx) + if err != nil { + return nil, err + } + entrypoint = newEntry + } + + finalCommand = append(finalCommand, entrypoint...) + + command := s.Command + if len(command) == 0 && img != nil { + newCmd, err := img.Cmd(ctx) + if err != nil { + return nil, err + } + command = newCmd + } + + finalCommand = append(finalCommand, command...) + + if len(finalCommand) == 0 { + return nil, errors.Errorf("no command or entrypoint provided, and no CMD or ENTRYPOINT from image") + } + + if s.Init { + initPath := s.InitPath + if initPath == "" && rtc != nil { + initPath = rtc.Engine.InitPath + } + if initPath == "" { + return nil, errors.Errorf("no path to init binary found but container requested an init") + } + finalCommand = append([]string{initPath, "--"}, finalCommand...) + } + + return finalCommand, nil +} + +func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, rtc *config.Config, newImage *image.Image, mounts []spec.Mount) (*spec.Spec, error) { var ( inUserNS bool ) @@ -137,7 +220,13 @@ func SpecGenToOCI(s *specgen.SpecGenerator, rt *libpod.Runtime, newImage *image. g.AddMount(cgroupMnt) } g.SetProcessCwd(s.WorkDir) - g.SetProcessArgs(s.Command) + + finalCmd, err := makeCommand(ctx, s, newImage, rtc) + if err != nil { + return nil, err + } + g.SetProcessArgs(finalCmd) + g.SetProcessTerminal(s.Terminal) for key, val := range s.Annotations { @@ -176,35 +265,12 @@ func SpecGenToOCI(s *specgen.SpecGenerator, rt *libpod.Runtime, newImage *image. g.AddProcessEnv(name, val) } - // TODO rlimits and ulimits needs further refinement by someone more - // familiar with the code. - //if err := addRlimits(config, &g); err != nil { - // return nil, err - //} - - // NAMESPACES - - if err := pidConfigureGenerator(s, &g); err != nil { - return nil, err - } - - if err := userConfigureGenerator(s, &g); err != nil { - return nil, err - } - - if err := networkConfigureGenerator(s, &g); err != nil { + if err := addRlimits(s, &g); err != nil { return nil, err } - if err := utsConfigureGenerator(s, &g, rt); err != nil { - return nil, err - } - - if err := ipcConfigureGenerator(s, &g); err != nil { - return nil, err - } - - if err := cgroupConfigureGenerator(s, &g); err != nil { + // NAMESPACES + if err := specConfigureNamespaces(s, &g, rt); err != nil { return nil, err } configSpec := g.Config @@ -214,7 +280,7 @@ func SpecGenToOCI(s *specgen.SpecGenerator, rt *libpod.Runtime, newImage *image. } // BIND MOUNTS - configSpec.Mounts = SupercedeUserMounts(s.Mounts, configSpec.Mounts) + configSpec.Mounts = SupercedeUserMounts(mounts, configSpec.Mounts) // Process mounts to ensure correct options if err := InitFSMounts(configSpec.Mounts); err != nil { return nil, err diff --git a/pkg/specgen/generate/pod_create.go b/pkg/specgen/generate/pod_create.go index 292f9b155..babfba9bc 100644 --- a/pkg/specgen/generate/pod_create.go +++ b/pkg/specgen/generate/pod_create.go @@ -46,6 +46,13 @@ func createPodOptions(p *specgen.PodSpecGenerator) ([]libpod.PodCreateOption, er if len(p.HostAdd) > 0 { options = append(options, libpod.WithPodHosts(p.HostAdd)) } + if len(p.DNSServer) > 0 { + var dnsServers []string + for _, d := range p.DNSServer { + dnsServers = append(dnsServers, d.String()) + } + options = append(options, libpod.WithPodDNS(dnsServers)) + } if len(p.DNSOption) > 0 { options = append(options, libpod.WithPodDNSOption(p.DNSOption)) } diff --git a/pkg/specgen/generate/security.go b/pkg/specgen/generate/security.go index ef4b3b47a..e2da9e976 100644 --- a/pkg/specgen/generate/security.go +++ b/pkg/specgen/generate/security.go @@ -1,15 +1,22 @@ package generate import ( + "strings" + + "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/containers/libpod/pkg/util" + "github.com/opencontainers/runtime-tools/generate" "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) -// SetLabelOpts sets the label options of the SecurityConfig according to the +// setLabelOpts sets the label options of the SecurityConfig according to the // input. -func SetLabelOpts(s *specgen.SpecGenerator, runtime *libpod.Runtime, pidConfig specgen.Namespace, ipcConfig specgen.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 @@ -48,12 +55,10 @@ func SetLabelOpts(s *specgen.SpecGenerator, runtime *libpod.Runtime, pidConfig s return nil } -// ConfigureGenerator configures the generator according to the input. -/* -func (c *SecurityConfig) ConfigureGenerator(g *generate.Generator, user *UserConfig) error { +func securityConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, newImage *image.Image) error { // HANDLE CAPABILITIES // NOTE: Must happen before SECCOMP - if c.Privileged { + if s.Privileged { g.SetupPrivileged(true) } @@ -63,56 +68,66 @@ func (c *SecurityConfig) ConfigureGenerator(g *generate.Generator, user *UserCon } return true } - configSpec := g.Config var err error - var defaultCaplist []string + var caplist []string bounding := configSpec.Process.Capabilities.Bounding - if useNotRoot(user.User) { - configSpec.Process.Capabilities.Bounding = defaultCaplist + if useNotRoot(s.User) { + configSpec.Process.Capabilities.Bounding = caplist } - defaultCaplist, err = capabilities.MergeCapabilities(configSpec.Process.Capabilities.Bounding, c.CapAdd, c.CapDrop) + caplist, err = capabilities.MergeCapabilities(configSpec.Process.Capabilities.Bounding, s.CapAdd, s.CapDrop) if err != nil { return err } + privCapsRequired := []string{} + + // If the container image specifies an label with a + // capabilities.ContainerImageLabel then split the comma separated list + // of capabilities and record them. This list indicates the only + // capabilities, required to run the container. + var capsRequiredRequested []string + for key, val := range s.Labels { + if util.StringInSlice(key, capabilities.ContainerImageLabels) { + capsRequiredRequested = strings.Split(val, ",") + } + } + if !s.Privileged && len(capsRequiredRequested) > 0 { - privCapRequired := []string{} - - if !c.Privileged && len(c.CapRequired) > 0 { - // Pass CapRequired in CapAdd field to normalize capabilities names - capRequired, err := capabilities.MergeCapabilities(nil, c.CapRequired, nil) + // Pass capRequiredRequested in CapAdd field to normalize capabilities names + capsRequired, err := capabilities.MergeCapabilities(nil, capsRequiredRequested, nil) if err != nil { - logrus.Errorf("capabilities requested by user or image are not valid: %q", strings.Join(c.CapRequired, ",")) + logrus.Errorf("capabilities requested by user or image are not valid: %q", strings.Join(capsRequired, ",")) } else { - // Verify all capRequiered are in the defaultCapList - for _, cap := range capRequired { - if !util.StringInSlice(cap, defaultCaplist) { - privCapRequired = append(privCapRequired, cap) + // Verify all capRequiered are in the capList + for _, cap := range capsRequired { + if !util.StringInSlice(cap, caplist) { + privCapsRequired = append(privCapsRequired, cap) } } } - if len(privCapRequired) == 0 { - defaultCaplist = capRequired + if len(privCapsRequired) == 0 { + caplist = capsRequired } else { - logrus.Errorf("capabilities requested by user or image are not allowed by default: %q", strings.Join(privCapRequired, ",")) + logrus.Errorf("capabilities requested by user or image are not allowed by default: %q", strings.Join(privCapsRequired, ",")) } } - configSpec.Process.Capabilities.Bounding = defaultCaplist - configSpec.Process.Capabilities.Permitted = defaultCaplist - configSpec.Process.Capabilities.Inheritable = defaultCaplist - configSpec.Process.Capabilities.Effective = defaultCaplist - configSpec.Process.Capabilities.Ambient = defaultCaplist - if useNotRoot(user.User) { - defaultCaplist, err = capabilities.MergeCapabilities(bounding, c.CapAdd, c.CapDrop) + + 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 = defaultCaplist + configSpec.Process.Capabilities.Bounding = caplist // HANDLE SECCOMP - if c.SeccompProfilePath != "unconfined" { - seccompConfig, err := getSeccompConfig(c, configSpec) + if s.SeccompProfilePath != "unconfined" { + seccompConfig, err := getSeccompConfig(s, configSpec, newImage) if err != nil { return err } @@ -120,35 +135,14 @@ func (c *SecurityConfig) ConfigureGenerator(g *generate.Generator, user *UserCon } // Clear default Seccomp profile from Generator for privileged containers - if c.SeccompProfilePath == "unconfined" || c.Privileged { + if s.SeccompProfilePath == "unconfined" || s.Privileged { configSpec.Linux.Seccomp = nil } - for _, opt := range c.SecurityOpts { - // Split on both : and = - splitOpt := strings.Split(opt, "=") - if len(splitOpt) == 1 { - splitOpt = strings.Split(opt, ":") - } - if len(splitOpt) < 2 { - continue - } - switch splitOpt[0] { - case "label": - configSpec.Annotations[libpod.InspectAnnotationLabel] = splitOpt[1] - case "seccomp": - configSpec.Annotations[libpod.InspectAnnotationSeccomp] = splitOpt[1] - case "apparmor": - configSpec.Annotations[libpod.InspectAnnotationApparmor] = splitOpt[1] - } - } - - g.SetRootReadonly(c.ReadOnlyRootfs) - for sysctlKey, sysctlVal := range c.Sysctl { + g.SetRootReadonly(s.ReadOnlyFilesystem) + for sysctlKey, sysctlVal := range s.Sysctl { g.AddLinuxSysctl(sysctlKey, sysctlVal) } return nil } - -*/ diff --git a/pkg/specgen/generate/storage.go b/pkg/specgen/generate/storage.go index c9a36ed46..241c9adeb 100644 --- a/pkg/specgen/generate/storage.go +++ b/pkg/specgen/generate/storage.go @@ -1,15 +1,16 @@ package generate -//nolint - import ( + "context" "fmt" + "os" "path" "path/filepath" "strings" - "github.com/containers/buildah/pkg/parse" + "github.com/containers/common/pkg/config" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/specgen" "github.com/containers/libpod/pkg/util" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -17,6 +18,7 @@ import ( "github.com/sirupsen/logrus" ) +// TODO unify this in one place - maybe libpod/define const ( // TypeBind is the type for mounting host dir TypeBind = "bind" @@ -27,788 +29,278 @@ const ( ) var ( - errDuplicateDest = errors.Errorf("duplicate mount destination") //nolint - optionArgError = errors.Errorf("must provide an argument for option") //nolint - noDestError = errors.Errorf("must set volume destination") //nolint + errDuplicateDest = errors.Errorf("duplicate mount destination") ) -// Parse all volume-related options in the create config into a set of mounts -// and named volumes to add to the container. -// Handles --volumes-from, --volumes, --tmpfs, --init, and --init-path flags. -// 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 parseVolumes(s *specgen.SpecGenerator, mounts, volMounts, tmpMounts []string) error { //nolint - - // TODO this needs to come from the image and erquires a runtime - - // Add image volumes. - //baseMounts, baseVolumes, err := config.getImageVolumes() - //if err != nil { - // return nil, nil, err - //} - - // Add --volumes-from. - // Overrides image volumes unconditionally. - //vFromMounts, vFromVolumes, err := config.getVolumesFrom(runtime) - //if err != nil { - // return nil, nil, err - //} - //for dest, mount := range vFromMounts { - // baseMounts[dest] = mount - //} - //for dest, volume := range vFromVolumes { - // baseVolumes[dest] = volume - //} - - // Next mounts from the --mounts flag. - // Do not override yet. - //unifiedMounts, _, err := getMounts(mounts) - //if err != nil { - // return err - //} - // - //// Next --volumes flag. - //// Do not override yet. - //volumeMounts, _ , err := getVolumeMounts(volMounts) - //if err != nil { - // return err - //} - // - //// Next --tmpfs flag. - //// Do not override yet. - //tmpfsMounts, err := getTmpfsMounts(tmpMounts) - //if err != nil { - // return err - //} - - //// Unify mounts from --mount, --volume, --tmpfs. - //// Also add mounts + volumes directly from createconfig. - //// Start with --volume. - //for dest, mount := range volumeMounts { - // if _, ok := unifiedMounts[dest]; ok { - // return nil, nil, errors.Wrapf(errDuplicateDest, dest) - // } - // unifiedMounts[dest] = mount - //} - //for dest, volume := range volumeVolumes { - // if _, ok := unifiedVolumes[dest]; ok { - // return nil, nil, errors.Wrapf(errDuplicateDest, dest) - // } - // unifiedVolumes[dest] = volume - //} - //// Now --tmpfs - //for dest, tmpfs := range tmpfsMounts { - // if _, ok := unifiedMounts[dest]; ok { - // return nil, nil, errors.Wrapf(errDuplicateDest, dest) - // } - // unifiedMounts[dest] = tmpfs - //} - //// Now spec mounts and volumes - //for _, mount := range config.Mounts { - // dest := mount.Destination - // if _, ok := unifiedMounts[dest]; ok { - // return nil, nil, errors.Wrapf(errDuplicateDest, dest) - // } - // unifiedMounts[dest] = mount - //} - //for _, volume := range config.NamedVolumes { - // dest := volume.Dest - // if _, ok := unifiedVolumes[dest]; ok { - // return nil, nil, errors.Wrapf(errDuplicateDest, dest) - // } - // unifiedVolumes[dest] = volume - //} - // - //// If requested, add container init binary - //if config.Init { - // initPath := config.InitPath - // if initPath == "" { - // rtc, err := runtime.GetConfig() - // if err != nil { - // return nil, nil, err - // } - // initPath = rtc.Engine.InitPath - // } - // initMount, err := config.addContainerInitBinary(initPath) - // if err != nil { - // return nil, nil, err - // } - // if _, ok := unifiedMounts[initMount.Destination]; ok { - // return nil, nil, errors.Wrapf(errDuplicateDest, "conflict with mount added by --init to %q", initMount.Destination) - // } - // unifiedMounts[initMount.Destination] = initMount - //} - // - //// Before superseding, we need to find volume mounts which conflict with - //// named volumes, and vice versa. - //// We'll delete the conflicts here as we supersede. - //for dest := range unifiedMounts { - // if _, ok := baseVolumes[dest]; ok { - // delete(baseVolumes, dest) - // } - //} - //for dest := range unifiedVolumes { - // if _, ok := baseMounts[dest]; ok { - // delete(baseMounts, dest) - // } - //} - // - //// Supersede volumes-from/image volumes with unified volumes from above. - //// This is an unconditional replacement. - //for dest, mount := range unifiedMounts { - // baseMounts[dest] = mount - //} - //for dest, volume := range unifiedVolumes { - // baseVolumes[dest] = volume - //} - // - //// If requested, add tmpfs filesystems for read-only containers. - //if config.Security.ReadOnlyRootfs && config.Security.ReadOnlyTmpfs { - // readonlyTmpfs := []string{"/tmp", "/var/tmp", "/run"} - // options := []string{"rw", "rprivate", "nosuid", "nodev", "tmpcopyup"} - // for _, dest := range readonlyTmpfs { - // if _, ok := baseMounts[dest]; ok { - // continue - // } - // if _, ok := baseVolumes[dest]; ok { - // continue - // } - // localOpts := options - // if dest == "/run" { - // localOpts = append(localOpts, "noexec", "size=65536k") - // } else { - // localOpts = append(localOpts, "exec") - // } - // baseMounts[dest] = spec.Mount{ - // Destination: dest, - // Type: "tmpfs", - // Source: "tmpfs", - // Options: localOpts, - // } - // } - //} - // - //// Check for conflicts between named volumes and mounts - //for dest := range baseMounts { - // if _, ok := baseVolumes[dest]; ok { - // return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) - // } - //} - //for dest := range baseVolumes { - // if _, ok := baseMounts[dest]; ok { - // return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) - // } - //} - // - //// Final step: maps to arrays - //finalMounts := make([]spec.Mount, 0, len(baseMounts)) - //for _, mount := range baseMounts { - // if mount.Type == TypeBind { - // absSrc, err := filepath.Abs(mount.Source) - // if err != nil { - // return nil, nil, errors.Wrapf(err, "error getting absolute path of %s", mount.Source) - // } - // mount.Source = absSrc - // } - // finalMounts = append(finalMounts, mount) - //} - //finalVolumes := make([]*define.ContainerNamedVolume, 0, len(baseVolumes)) - //for _, volume := range baseVolumes { - // finalVolumes = append(finalVolumes, volume) - //} - - //return finalMounts, finalVolumes, nil - return nil -} - -// Parse volumes from - a set of containers whose volumes we will mount in. -// Grab the containers, retrieve any user-created spec mounts and all named -// volumes, and return a list of them. -// Conflicts are resolved simply - the last container specified wins. -// Container names may be suffixed by mount options after a colon. -// TODO: We should clean these paths if possible -// TODO deferred baude -func getVolumesFrom() (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint - // Both of these are maps of mount destination to mount type. - // We ensure that each destination is only mounted to once in this way. - //finalMounts := make(map[string]spec.Mount) - //finalNamedVolumes := make(map[string]*define.ContainerNamedVolume) - // - //for _, vol := range config.VolumesFrom { - // var ( - // options = []string{} - // err error - // splitVol = strings.SplitN(vol, ":", 2) - // ) - // if len(splitVol) == 2 { - // splitOpts := strings.Split(splitVol[1], ",") - // for _, checkOpt := range splitOpts { - // switch checkOpt { - // case "z", "ro", "rw": - // // Do nothing, these are valid options - // default: - // return nil, nil, errors.Errorf("invalid options %q, can only specify 'ro', 'rw', and 'z'", splitVol[1]) - // } - // } - // - // if options, err = parse.ValidateVolumeOpts(splitOpts); err != nil { - // return nil, nil, err - // } - // } - // ctr, err := runtime.LookupContainer(splitVol[0]) - // if err != nil { - // return nil, nil, errors.Wrapf(err, "error looking up container %q for volumes-from", splitVol[0]) - // } - // - // logrus.Debugf("Adding volumes from container %s", ctr.ID()) - // - // // Look up the container's user volumes. This gets us the - // // destinations of all mounts the user added to the container. - // userVolumesArr := ctr.UserVolumes() - // - // // We're going to need to access them a lot, so convert to a map - // // to reduce looping. - // // We'll also use the map to indicate if we missed any volumes along the way. - // userVolumes := make(map[string]bool) - // for _, dest := range userVolumesArr { - // userVolumes[dest] = false - // } - // - // // Now we get the container's spec and loop through its volumes - // // and append them in if we can find them. - // spec := ctr.Spec() - // if spec == nil { - // return nil, nil, errors.Errorf("error retrieving container %s spec for volumes-from", ctr.ID()) - // } - // for _, mnt := range spec.Mounts { - // if mnt.Type != TypeBind { - // continue - // } - // if _, exists := userVolumes[mnt.Destination]; exists { - // userVolumes[mnt.Destination] = true - // - // if len(options) != 0 { - // mnt.Options = options - // } - // - // if _, ok := finalMounts[mnt.Destination]; ok { - // logrus.Debugf("Overriding mount to %s with new mount from container %s", mnt.Destination, ctr.ID()) - // } - // finalMounts[mnt.Destination] = mnt - // } - // } - // - // // We're done with the spec mounts. Add named volumes. - // // Add these unconditionally - none of them are automatically - // // part of the container, as some spec mounts are. - // namedVolumes := ctr.NamedVolumes() - // for _, namedVol := range namedVolumes { - // if _, exists := userVolumes[namedVol.Dest]; exists { - // userVolumes[namedVol.Dest] = true - // } - // - // if len(options) != 0 { - // namedVol.Options = options - // } - // - // if _, ok := finalMounts[namedVol.Dest]; ok { - // logrus.Debugf("Overriding named volume mount to %s with new named volume from container %s", namedVol.Dest, ctr.ID()) - // } - // finalNamedVolumes[namedVol.Dest] = namedVol - // } - // - // // Check if we missed any volumes - // for volDest, found := range userVolumes { - // if !found { - // logrus.Warnf("Unable to match volume %s from container %s for volumes-from", volDest, ctr.ID()) - // } - // } - //} - // - //return finalMounts, finalNamedVolumes, nil - return nil, nil, nil -} +// Produce final mounts and named volumes for a container +func finalizeMounts(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, rtc *config.Config, img *image.Image) ([]spec.Mount, []*specgen.NamedVolume, error) { + // Get image volumes + baseMounts, baseVolumes, err := getImageVolumes(ctx, img, s) + if err != nil { + return nil, nil, err + } -// getMounts takes user-provided input from the --mount flag and creates OCI -// spec mounts and Libpod named volumes. -// podman run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ... -// podman run --mount type=tmpfs,target=/dev/shm ... -// podman run --mount type=volume,source=test-volume, ... -func getMounts(mounts []string) (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint - finalMounts := make(map[string]spec.Mount) - finalNamedVolumes := make(map[string]*libpod.ContainerNamedVolume) + // Get volumes-from mounts + volFromMounts, volFromVolumes, err := getVolumesFrom(s.VolumesFrom, rt) + if err != nil { + return nil, nil, err + } - errInvalidSyntax := errors.Errorf("incorrect mount format: should be --mount type=<bind|tmpfs|volume>,[src=<host-dir|volume-name>,]target=<ctr-dir>[,options]") + // Supercede from --volumes-from. + for dest, mount := range volFromMounts { + baseMounts[dest] = mount + } + for dest, volume := range volFromVolumes { + baseVolumes[dest] = volume + } - // TODO(vrothberg): the manual parsing can be replaced with a regular expression - // to allow a more robust parsing of the mount format and to give - // precise errors regarding supported format versus supported options. - for _, mount := range mounts { - arr := strings.SplitN(mount, ",", 2) - if len(arr) < 2 { - return nil, nil, errors.Wrapf(errInvalidSyntax, "%q", mount) + // Need to make map forms of specgen mounts/volumes. + unifiedMounts := map[string]spec.Mount{} + unifiedVolumes := map[string]*specgen.NamedVolume{} + for _, m := range s.Mounts { + if _, ok := unifiedMounts[m.Destination]; ok { + return nil, nil, errors.Wrapf(errDuplicateDest, "conflict in specified mounts - multiple mounts at %q", m.Destination) } - kv := strings.Split(arr[0], "=") - // TODO: type is not explicitly required in Docker. - // If not specified, it defaults to "volume". - if len(kv) != 2 || kv[0] != "type" { - return nil, nil, errors.Wrapf(errInvalidSyntax, "%q", mount) + unifiedMounts[m.Destination] = m + } + for _, v := range s.Volumes { + if _, ok := unifiedVolumes[v.Dest]; ok { + return nil, nil, errors.Wrapf(errDuplicateDest, "conflict in specified volumes - multiple volumes at %q", v.Dest) } + unifiedVolumes[v.Dest] = v + } - tokens := strings.Split(arr[1], ",") - switch kv[1] { - case TypeBind: - mount, err := getBindMount(tokens) - if err != nil { - return nil, nil, err - } - if _, ok := finalMounts[mount.Destination]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination) - } - finalMounts[mount.Destination] = mount - case TypeTmpfs: - mount, err := getTmpfsMount(tokens) - if err != nil { - return nil, nil, err - } - if _, ok := finalMounts[mount.Destination]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination) - } - finalMounts[mount.Destination] = mount - case "volume": - volume, err := getNamedVolume(tokens) - if err != nil { - return nil, nil, err - } - if _, ok := finalNamedVolumes[volume.Dest]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, volume.Dest) - } - finalNamedVolumes[volume.Dest] = volume - default: - return nil, nil, errors.Errorf("invalid filesystem type %q", kv[1]) + // If requested, add container init binary + if s.Init { + initPath := s.InitPath + if initPath == "" && rtc != nil { + initPath = rtc.Engine.InitPath } + initMount, err := addContainerInitBinary(s, initPath) + if err != nil { + return nil, nil, err + } + if _, ok := unifiedMounts[initMount.Destination]; ok { + return nil, nil, errors.Wrapf(errDuplicateDest, "conflict with mount added by --init to %q", initMount.Destination) + } + unifiedMounts[initMount.Destination] = initMount } - return finalMounts, finalNamedVolumes, nil -} - -// Parse a single bind mount entry from the --mount flag. -func getBindMount(args []string) (spec.Mount, error) { //nolint - newMount := spec.Mount{ - Type: TypeBind, + // Before superseding, we need to find volume mounts which conflict with + // named volumes, and vice versa. + // We'll delete the conflicts here as we supersede. + for dest := range unifiedMounts { + if _, ok := baseVolumes[dest]; ok { + delete(baseVolumes, dest) + } } - - var setSource, setDest, setRORW, setSuid, setDev, setExec, setRelabel bool - - for _, val := range args { - kv := strings.Split(val, "=") - switch kv[0] { - case "bind-nonrecursive": - newMount.Options = append(newMount.Options, "bind") - case "ro", "rw": - if setRORW { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'ro' or 'rw' options more than once") - } - setRORW = true - // Can be formatted as one of: - // ro - // ro=[true|false] - // rw - // rw=[true|false] - switch len(kv) { - case 1: - newMount.Options = append(newMount.Options, kv[0]) - case 2: - switch strings.ToLower(kv[1]) { - case "true": - newMount.Options = append(newMount.Options, kv[0]) - case "false": - // Set the opposite only for rw - // ro's opposite is the default - if kv[0] == "rw" { - newMount.Options = append(newMount.Options, "ro") - } - default: - return newMount, errors.Wrapf(optionArgError, "%s must be set to true or false, instead received %q", kv[0], kv[1]) - } - default: - return newMount, errors.Wrapf(optionArgError, "badly formatted option %q", val) - } - case "nosuid", "suid": - if setSuid { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once") - } - setSuid = true - newMount.Options = append(newMount.Options, kv[0]) - case "nodev", "dev": - if setDev { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once") - } - setDev = true - newMount.Options = append(newMount.Options, kv[0]) - case "noexec", "exec": - if setExec { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once") - } - setExec = true - newMount.Options = append(newMount.Options, kv[0]) - case "shared", "rshared", "private", "rprivate", "slave", "rslave", "Z", "z": - newMount.Options = append(newMount.Options, kv[0]) - case "bind-propagation": - if len(kv) == 1 { - return newMount, errors.Wrapf(optionArgError, kv[0]) - } - newMount.Options = append(newMount.Options, kv[1]) - case "src", "source": - if len(kv) == 1 { - return newMount, errors.Wrapf(optionArgError, kv[0]) - } - if err := parse.ValidateVolumeHostDir(kv[1]); err != nil { - return newMount, err - } - newMount.Source = kv[1] - setSource = true - case "target", "dst", "destination": - if len(kv) == 1 { - return newMount, errors.Wrapf(optionArgError, kv[0]) - } - if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil { - return newMount, err - } - newMount.Destination = filepath.Clean(kv[1]) - setDest = true - case "relabel": - if setRelabel { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'relabel' option more than once") - } - setRelabel = true - if len(kv) != 2 { - return newMount, errors.Wrapf(util.ErrBadMntOption, "%s mount option must be 'private' or 'shared'", kv[0]) - } - switch kv[1] { - case "private": - newMount.Options = append(newMount.Options, "z") - case "shared": - newMount.Options = append(newMount.Options, "Z") - default: - return newMount, errors.Wrapf(util.ErrBadMntOption, "%s mount option must be 'private' or 'shared'", kv[0]) - } - default: - return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0]) + for dest := range unifiedVolumes { + if _, ok := baseMounts[dest]; ok { + delete(baseMounts, dest) } } - if !setDest { - return newMount, noDestError + // Supersede volumes-from/image volumes with unified volumes from above. + // This is an unconditional replacement. + for dest, mount := range unifiedMounts { + baseMounts[dest] = mount } - - if !setSource { - newMount.Source = newMount.Destination + for dest, volume := range unifiedVolumes { + baseVolumes[dest] = volume } - options, err := parse.ValidateVolumeOpts(newMount.Options) - if err != nil { - return newMount, err - } - newMount.Options = options - return newMount, nil -} + // TODO: Investigate moving readonlyTmpfs into here. Would be more + // correct. -// Parse a single tmpfs mount entry from the --mount flag -func getTmpfsMount(args []string) (spec.Mount, error) { //nolint - newMount := spec.Mount{ - Type: TypeTmpfs, - Source: TypeTmpfs, + // Check for conflicts between named volumes and mounts + for dest := range baseMounts { + if _, ok := baseVolumes[dest]; ok { + return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) + } } - - var setDest, setRORW, setSuid, setDev, setExec, setTmpcopyup bool - - for _, val := range args { - kv := strings.Split(val, "=") - switch kv[0] { - case "tmpcopyup", "notmpcopyup": - if setTmpcopyup { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'tmpcopyup' and 'notmpcopyup' options more than once") - } - setTmpcopyup = true - newMount.Options = append(newMount.Options, kv[0]) - case "ro", "rw": - if setRORW { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'ro' and 'rw' options more than once") - } - setRORW = true - newMount.Options = append(newMount.Options, kv[0]) - case "nosuid", "suid": - if setSuid { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once") - } - setSuid = true - newMount.Options = append(newMount.Options, kv[0]) - case "nodev", "dev": - if setDev { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once") - } - setDev = true - newMount.Options = append(newMount.Options, kv[0]) - case "noexec", "exec": - if setExec { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once") - } - setExec = true - newMount.Options = append(newMount.Options, kv[0]) - case "tmpfs-mode": - if len(kv) == 1 { - return newMount, errors.Wrapf(optionArgError, kv[0]) - } - newMount.Options = append(newMount.Options, fmt.Sprintf("mode=%s", kv[1])) - case "tmpfs-size": - if len(kv) == 1 { - return newMount, errors.Wrapf(optionArgError, kv[0]) - } - newMount.Options = append(newMount.Options, fmt.Sprintf("size=%s", kv[1])) - case "src", "source": - return newMount, errors.Errorf("source is not supported with tmpfs mounts") - case "target", "dst", "destination": - if len(kv) == 1 { - return newMount, errors.Wrapf(optionArgError, kv[0]) - } - if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil { - return newMount, err + for dest := range baseVolumes { + if _, ok := baseMounts[dest]; ok { + return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) + } + } + // Final step: maps to arrays + finalMounts := make([]spec.Mount, 0, len(baseMounts)) + for _, mount := range baseMounts { + if mount.Type == TypeBind { + absSrc, err := filepath.Abs(mount.Source) + if err != nil { + return nil, nil, errors.Wrapf(err, "error getting absolute path of %s", mount.Source) } - newMount.Destination = filepath.Clean(kv[1]) - setDest = true - default: - return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0]) + mount.Source = absSrc } + finalMounts = append(finalMounts, mount) } - - if !setDest { - return newMount, noDestError + finalVolumes := make([]*specgen.NamedVolume, 0, len(baseVolumes)) + for _, volume := range baseVolumes { + finalVolumes = append(finalVolumes, volume) } - return newMount, nil + return finalMounts, finalVolumes, nil } -// Parse a single volume mount entry from the --mount flag. -// Note that the volume-label option for named volumes is currently NOT supported. -// TODO: add support for --volume-label -func getNamedVolume(args []string) (*libpod.ContainerNamedVolume, error) { //nolint - newVolume := new(libpod.ContainerNamedVolume) +// Get image volumes from the given image +func getImageVolumes(ctx context.Context, img *image.Image, s *specgen.SpecGenerator) (map[string]spec.Mount, map[string]*specgen.NamedVolume, error) { + mounts := make(map[string]spec.Mount) + volumes := make(map[string]*specgen.NamedVolume) - var setSource, setDest, setRORW, setSuid, setDev, setExec bool + mode := strings.ToLower(s.ImageVolumeMode) - for _, val := range args { - kv := strings.Split(val, "=") - switch kv[0] { - case "ro", "rw": - if setRORW { - return nil, errors.Wrapf(optionArgError, "cannot pass 'ro' and 'rw' options more than once") - } - setRORW = true - newVolume.Options = append(newVolume.Options, kv[0]) - case "nosuid", "suid": - if setSuid { - return nil, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once") - } - setSuid = true - newVolume.Options = append(newVolume.Options, kv[0]) - case "nodev", "dev": - if setDev { - return nil, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once") - } - setDev = true - newVolume.Options = append(newVolume.Options, kv[0]) - case "noexec", "exec": - if setExec { - return nil, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once") - } - setExec = true - newVolume.Options = append(newVolume.Options, kv[0]) - case "volume-label": - return nil, errors.Errorf("the --volume-label option is not presently implemented") - case "src", "source": - if len(kv) == 1 { - return nil, errors.Wrapf(optionArgError, kv[0]) - } - newVolume.Name = kv[1] - setSource = true - case "target", "dst", "destination": - if len(kv) == 1 { - return nil, errors.Wrapf(optionArgError, kv[0]) - } - if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil { - return nil, err - } - newVolume.Dest = filepath.Clean(kv[1]) - setDest = true - default: - return nil, errors.Wrapf(util.ErrBadMntOption, kv[0]) - } + // Image may be nil (rootfs in use), or image volume mode may be ignore. + if img == nil || mode == "ignore" { + return mounts, volumes, nil } - if !setSource { - return nil, errors.Errorf("must set source volume") + inspect, err := img.InspectNoSize(ctx) + if err != nil { + return nil, nil, errors.Wrapf(err, "error inspecting image to get image volumes") } - if !setDest { - return nil, noDestError + for volume := range inspect.Config.Volumes { + logrus.Debugf("Image has volume at %q", volume) + cleanDest := filepath.Clean(volume) + switch mode { + case "", "anonymous": + // Anonymous volumes have no name. + newVol := new(specgen.NamedVolume) + newVol.Dest = cleanDest + newVol.Options = []string{"rprivate", "rw", "nodev", "exec"} + volumes[cleanDest] = newVol + logrus.Debugf("Adding anonymous image volume at %q", cleanDest) + case "tmpfs": + mount := spec.Mount{ + Destination: cleanDest, + Source: TypeTmpfs, + Type: TypeTmpfs, + Options: []string{"rprivate", "rw", "nodev", "exec"}, + } + mounts[cleanDest] = mount + logrus.Debugf("Adding tmpfs image volume at %q", cleanDest) + } } - return newVolume, nil + return mounts, volumes, nil } -func getVolumeMounts(vols []string) (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint - mounts := make(map[string]spec.Mount) - volumes := make(map[string]*libpod.ContainerNamedVolume) - - volumeFormatErr := errors.Errorf("incorrect volume format, should be [host-dir:]ctr-dir[:option]") +func getVolumesFrom(volumesFrom []string, runtime *libpod.Runtime) (map[string]spec.Mount, map[string]*specgen.NamedVolume, error) { + finalMounts := make(map[string]spec.Mount) + finalNamedVolumes := make(map[string]*specgen.NamedVolume) - for _, vol := range vols { - var ( - options []string - src string - dest string - err error - ) + for _, volume := range volumesFrom { + var options []string - splitVol := strings.Split(vol, ":") - if len(splitVol) > 3 { - return nil, nil, errors.Wrapf(volumeFormatErr, vol) + splitVol := strings.SplitN(volume, ":", 2) + if len(splitVol) == 2 { + splitOpts := strings.Split(splitVol[1], ",") + for _, opt := range splitOpts { + setRORW := false + setZ := false + switch opt { + case "z": + if setZ { + return nil, nil, errors.Errorf("cannot set :z more than once in mount options") + } + setZ = true + case "ro", "rw": + if setRORW { + return nil, nil, errors.Errorf("cannot set ro or rw options more than once") + } + setRORW = true + default: + return nil, nil, errors.Errorf("invalid option %q specified - volumes from another container can only use z,ro,rw options", opt) + } + } + options = splitOpts } - src = splitVol[0] - if len(splitVol) == 1 { - // This is an anonymous named volume. Only thing given - // is destination. - // Name/source will be blank, and populated by libpod. - src = "" - dest = splitVol[0] - } else if len(splitVol) > 1 { - dest = splitVol[1] - } - if len(splitVol) > 2 { - if options, err = parse.ValidateVolumeOpts(strings.Split(splitVol[2], ",")); err != nil { - return nil, nil, err - } + ctr, err := runtime.LookupContainer(splitVol[0]) + if err != nil { + return nil, nil, errors.Wrapf(err, "error looking up container %q for volumes-from", splitVol[0]) } - // Do not check source dir for anonymous volumes - if len(splitVol) > 1 { - if err := parse.ValidateVolumeHostDir(src); err != nil { - return nil, nil, err - } + logrus.Debugf("Adding volumes from container %s", ctr.ID()) + + // Look up the container's user volumes. This gets us the + // destinations of all mounts the user added to the container. + userVolumesArr := ctr.UserVolumes() + + // We're going to need to access them a lot, so convert to a map + // to reduce looping. + // We'll also use the map to indicate if we missed any volumes along the way. + userVolumes := make(map[string]bool) + for _, dest := range userVolumesArr { + userVolumes[dest] = false } - if err := parse.ValidateVolumeCtrDir(dest); err != nil { - return nil, nil, err + + // Now we get the container's spec and loop through its volumes + // and append them in if we can find them. + spec := ctr.Spec() + if spec == nil { + return nil, nil, errors.Errorf("error retrieving container %s spec for volumes-from", ctr.ID()) } + for _, mnt := range spec.Mounts { + if mnt.Type != TypeBind { + continue + } + if _, exists := userVolumes[mnt.Destination]; exists { + userVolumes[mnt.Destination] = true - cleanDest := filepath.Clean(dest) + if len(options) != 0 { + mnt.Options = options + } - if strings.HasPrefix(src, "/") || strings.HasPrefix(src, ".") { - // This is not a named volume - newMount := spec.Mount{ - Destination: cleanDest, - Type: string(TypeBind), - Source: src, - Options: options, - } - if _, ok := mounts[newMount.Destination]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, newMount.Destination) - } - mounts[newMount.Destination] = newMount - } else { - // This is a named volume - newNamedVol := new(libpod.ContainerNamedVolume) - newNamedVol.Name = src - newNamedVol.Dest = cleanDest - newNamedVol.Options = options - - if _, ok := volumes[newNamedVol.Dest]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, newNamedVol.Dest) + if _, ok := finalMounts[mnt.Destination]; ok { + logrus.Debugf("Overriding mount to %s with new mount from container %s", mnt.Destination, ctr.ID()) + } + finalMounts[mnt.Destination] = mnt } - volumes[newNamedVol.Dest] = newNamedVol } - logrus.Debugf("User mount %s:%s options %v", src, dest, options) - } + // We're done with the spec mounts. Add named volumes. + // Add these unconditionally - none of them are automatically + // part of the container, as some spec mounts are. + namedVolumes := ctr.NamedVolumes() + for _, namedVol := range namedVolumes { + if _, exists := userVolumes[namedVol.Dest]; exists { + userVolumes[namedVol.Dest] = true + } - return mounts, volumes, nil -} + if len(options) != 0 { + namedVol.Options = options + } -// Get mounts for container's image volumes -// TODO deferred baude -func getImageVolumes() (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint - //mounts := make(map[string]spec.Mount) - //volumes := make(map[string]*define.ContainerNamedVolume) - // - //if config.ImageVolumeType == "ignore" { - // return mounts, volumes, nil - //} - // - //for vol := range config.BuiltinImgVolumes { - // cleanDest := filepath.Clean(vol) - // logrus.Debugf("Adding image volume at %s", cleanDest) - // if config.ImageVolumeType == "tmpfs" { - // // Tmpfs image volumes are handled as mounts - // mount := spec.Mount{ - // Destination: cleanDest, - // Source: TypeTmpfs, - // Type: TypeTmpfs, - // Options: []string{"rprivate", "rw", "nodev", "exec"}, - // } - // mounts[cleanDest] = mount - // } else { - // // Anonymous volumes have no name. - // namedVolume := new(define.ContainerNamedVolume) - // namedVolume.Options = []string{"rprivate", "rw", "nodev", "exec"} - // namedVolume.Dest = cleanDest - // volumes[cleanDest] = namedVolume - // } - //} - // - //return mounts, volumes, nil - return nil, nil, nil -} + if _, ok := finalMounts[namedVol.Dest]; ok { + logrus.Debugf("Overriding named volume mount to %s with new named volume from container %s", namedVol.Dest, ctr.ID()) + } -// GetTmpfsMounts creates spec.Mount structs for user-requested tmpfs mounts -func getTmpfsMounts(mounts []string) (map[string]spec.Mount, error) { //nolint - m := make(map[string]spec.Mount) - for _, i := range mounts { - // Default options if nothing passed - var options []string - spliti := strings.Split(i, ":") - destPath := spliti[0] - if err := parse.ValidateVolumeCtrDir(spliti[0]); err != nil { - return nil, err - } - if len(spliti) > 1 { - options = strings.Split(spliti[1], ",") - } + newVol := new(specgen.NamedVolume) + newVol.Dest = namedVol.Dest + newVol.Options = namedVol.Options + newVol.Name = namedVol.Name - if _, ok := m[destPath]; ok { - return nil, errors.Wrapf(errDuplicateDest, destPath) + finalNamedVolumes[namedVol.Dest] = newVol } - mount := spec.Mount{ - Destination: filepath.Clean(destPath), - Type: string(TypeTmpfs), - Options: options, - Source: string(TypeTmpfs), + // Check if we missed any volumes + for volDest, found := range userVolumes { + if !found { + logrus.Warnf("Unable to match volume %s from container %s for volumes-from", volDest, ctr.ID()) + } } - m[destPath] = mount } - return m, nil + + return finalMounts, finalNamedVolumes, nil } // AddContainerInitBinary adds the init binary specified by path iff the // container will run in a private PID namespace that is not shared with the // host or another pre-existing container, where an init-like process is // already running. -// -// Note that AddContainerInitBinary prepends "/dev/init" "--" to the command -// to execute the bind-mounted binary as PID 1. -// TODO this needs to be worked on to work in new env -func addContainerInitBinary(path string) (spec.Mount, error) { //nolint +// This does *NOT* modify the container command - that must be done elsewhere. +func addContainerInitBinary(s *specgen.SpecGenerator, path string) (spec.Mount, error) { mount := spec.Mount{ Destination: "/dev/init", Type: TypeBind, @@ -816,19 +308,18 @@ func addContainerInitBinary(path string) (spec.Mount, error) { //nolint Options: []string{TypeBind, "ro"}, } - //if path == "" { - // return mount, fmt.Errorf("please specify a path to the container-init binary") - //} - //if !config.Pid.PidMode.IsPrivate() { - // return mount, fmt.Errorf("cannot add init binary as PID 1 (PID namespace isn't private)") - //} - //if config.Systemd { - // return mount, fmt.Errorf("cannot use container-init binary with systemd") - //} - //if _, err := os.Stat(path); os.IsNotExist(err) { - // return mount, errors.Wrap(err, "container-init binary not found on the host") - //} - //config.Command = append([]string{"/dev/init", "--"}, config.Command...) + if path == "" { + return mount, fmt.Errorf("please specify a path to the container-init binary") + } + if !s.PidNS.IsPrivate() { + return mount, fmt.Errorf("cannot add init binary as PID 1 (PID namespace isn't private)") + } + if s.Systemd == "true" || s.Systemd == "always" { + return mount, fmt.Errorf("cannot use container-init binary with systemd") + } + if _, err := os.Stat(path); os.IsNotExist(err) { + return mount, errors.Wrap(err, "container-init binary not found on the host") + } return mount, nil } diff --git a/pkg/specgen/namespaces.go b/pkg/specgen/namespaces.go index 2e7f80fe8..f0161a793 100644 --- a/pkg/specgen/namespaces.go +++ b/pkg/specgen/namespaces.go @@ -1,6 +1,8 @@ package specgen import ( + "strings" + "github.com/pkg/errors" ) @@ -31,6 +33,11 @@ const ( // Slirp indicates that a slirp4netns network stack should // be used Slirp NamespaceMode = "slirp4netns" + // KeepId indicates a user namespace to keep the owner uid inside + // of the namespace itself + KeepID NamespaceMode = "keep-id" + // KeepId indicates to automatically create a user namespace + Auto NamespaceMode = "auto" ) // Namespace describes the namespace @@ -39,6 +46,12 @@ type Namespace struct { Value string `json:"string,omitempty"` } +// IsDefault returns whether the namespace is set to the default setting (which +// also includes the empty string). +func (n *Namespace) IsDefault() bool { + return n.NSMode == Default || n.NSMode == "" +} + // IsHost returns a bool if the namespace is host based func (n *Namespace) IsHost() bool { return n.NSMode == Host @@ -64,16 +77,50 @@ func (n *Namespace) IsPrivate() bool { return n.NSMode == Private } +// IsAuto indicates the namespace is auto +func (n *Namespace) IsAuto() bool { + return n.NSMode == Auto +} + +// IsKeepID indicates the namespace is KeepID +func (n *Namespace) IsKeepID() bool { + return n.NSMode == KeepID +} + +func validateUserNS(n *Namespace) error { + if n == nil { + return nil + } + switch n.NSMode { + case Auto, KeepID: + return nil + } + return n.validate() +} + func validateNetNS(n *Namespace) error { if n == nil { return nil } switch n.NSMode { - case Host, Path, FromContainer, FromPod, Private, NoNetwork, Bridge, Slirp: + case "", Default, Host, Path, FromContainer, FromPod, Private, NoNetwork, Bridge, Slirp: break default: return errors.Errorf("invalid network %q", n.NSMode) } + + // Path and From Container MUST have a string value set + if n.NSMode == Path || n.NSMode == FromContainer { + if len(n.Value) < 1 { + return errors.Errorf("namespace mode %s requires a value", n.NSMode) + } + } else { + // All others must NOT set a string value + if len(n.Value) > 0 { + return errors.Errorf("namespace value %s cannot be provided with namespace mode %s", n.Value, n.NSMode) + } + } + return nil } @@ -83,6 +130,15 @@ func (n *Namespace) validate() error { if n == nil { return nil } + switch n.NSMode { + case "", Default, Host, Path, FromContainer, FromPod, Private: + // Valid, do nothing + case NoNetwork, Bridge, Slirp: + return errors.Errorf("cannot use network modes with non-network namespace") + default: + return errors.Errorf("invalid namespace type %s specified", n.NSMode) + } + // Path and From Container MUST have a string value set if n.NSMode == Path || n.NSMode == FromContainer { if len(n.Value) < 1 { @@ -96,3 +152,100 @@ func (n *Namespace) validate() error { } return nil } + +// ParseNamespace parses a namespace in string form. +// This is not intended for the network namespace, which has a separate +// function. +func ParseNamespace(ns string) (Namespace, error) { + toReturn := Namespace{} + switch { + case ns == "pod": + toReturn.NSMode = FromPod + case ns == "host": + toReturn.NSMode = Host + case ns == "private": + toReturn.NSMode = Private + case strings.HasPrefix(ns, "ns:"): + split := strings.SplitN(ns, ":", 2) + if len(split) != 2 { + return toReturn, errors.Errorf("must provide a path to a namespace when specifying ns:") + } + toReturn.NSMode = Path + toReturn.Value = split[1] + case strings.HasPrefix(ns, "container:"): + split := strings.SplitN(ns, ":", 2) + if len(split) != 2 { + return toReturn, errors.Errorf("must provide name or ID or a container when specifying container:") + } + toReturn.NSMode = FromContainer + toReturn.Value = split[1] + default: + return toReturn, errors.Errorf("unrecognized namespace mode %s passed", ns) + } + + return toReturn, nil +} + +// ParseUserNamespace parses a user namespace specification in string +// form. +func ParseUserNamespace(ns string) (Namespace, error) { + toReturn := Namespace{} + switch { + case ns == "auto": + toReturn.NSMode = Auto + return toReturn, nil + case strings.HasPrefix(ns, "auto:"): + split := strings.SplitN(ns, ":", 2) + if len(split) != 2 { + return toReturn, errors.Errorf("invalid setting for auto: mode") + } + toReturn.NSMode = Auto + toReturn.Value = split[1] + return toReturn, nil + case ns == "keep-id": + toReturn.NSMode = KeepID + return toReturn, nil + } + return ParseNamespace(ns) +} + +// ParseNetworkNamespace parses a network namespace specification in string +// form. +// Returns a namespace and (optionally) a list of CNI networks to join. +func ParseNetworkNamespace(ns string) (Namespace, []string, error) { + toReturn := Namespace{} + var cniNetworks []string + switch { + case ns == "pod": + toReturn.NSMode = FromPod + case ns == "bridge": + toReturn.NSMode = Bridge + case ns == "none": + toReturn.NSMode = NoNetwork + case ns == "host": + toReturn.NSMode = Host + case ns == "private": + toReturn.NSMode = Private + case strings.HasPrefix(ns, "ns:"): + split := strings.SplitN(ns, ":", 2) + if len(split) != 2 { + return toReturn, nil, errors.Errorf("must provide a path to a namespace when specifying ns:") + } + toReturn.NSMode = Path + toReturn.Value = split[1] + case strings.HasPrefix(ns, "container:"): + split := strings.SplitN(ns, ":", 2) + if len(split) != 2 { + return toReturn, nil, errors.Errorf("must provide name or ID or a container when specifying container:") + } + toReturn.NSMode = FromContainer + toReturn.Value = split[1] + default: + // Assume we have been given a list of CNI networks. + // Which only works in bridge mode, so set that. + cniNetworks = strings.Split(ns, ",") + toReturn.NSMode = Bridge + } + + return toReturn, cniNetworks, nil +} diff --git a/pkg/specgen/pod_validate.go b/pkg/specgen/pod_validate.go index 9e9659fa9..98d59549e 100644 --- a/pkg/specgen/pod_validate.go +++ b/pkg/specgen/pod_validate.go @@ -1,14 +1,16 @@ package specgen import ( - "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" ) var ( // ErrInvalidPodSpecConfig describes an error given when the podspecgenerator is invalid ErrInvalidPodSpecConfig error = errors.New("invalid pod spec") + // containerConfig has the default configurations defined in containers.conf + containerConfig = util.DefaultContainerConfig() ) func exclusivePodOptions(opt1, opt2 string) error { @@ -60,7 +62,7 @@ func (p *PodSpecGenerator) Validate() error { return exclusivePodOptions("NoInfra", "NoManageResolvConf") } } - if p.NetNS.NSMode != Bridge { + if p.NetNS.NSMode != "" && p.NetNS.NSMode != Bridge && p.NetNS.NSMode != Default { if len(p.PortMappings) > 0 { return errors.New("PortMappings can only be used with Bridge mode networking") } @@ -96,10 +98,10 @@ func (p *PodSpecGenerator) Validate() error { } } if len(p.InfraImage) < 1 { - p.InfraImage = define.DefaultInfraImage + p.InfraImage = containerConfig.Engine.InfraImage } if len(p.InfraCommand) < 1 { - p.InfraCommand = []string{define.DefaultInfraCommand} + p.InfraCommand = []string{containerConfig.Engine.InfraCommand} } return nil } diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index 1a05733f9..20c8f8800 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -5,7 +5,6 @@ import ( "syscall" "github.com/containers/image/v5/manifest" - "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage" "github.com/cri-o/ocicni/pkg/ocicni" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -155,14 +154,23 @@ type ContainerStorageConfig struct { // ImageVolumeMode indicates how image volumes will be created. // Supported modes are "ignore" (do not create), "tmpfs" (create as // tmpfs), and "anonymous" (create as anonymous volumes). - // The default is anonymous. + // The default if unset is anonymous. // Optional. ImageVolumeMode string `json:"image_volume_mode,omitempty"` - // VolumesFrom is a list of containers whose volumes will be added to - // this container. Supported mount options may be added after the - // container name with a : and include "ro" and "rw". - // Optional. + // VolumesFrom is a set of containers whose volumes will be added to + // this container. The name or ID of the container must be provided, and + // may optionally be followed by a : and then one or more + // comma-separated options. Valid options are 'ro', 'rw', and 'z'. + // Options will be used for all volumes sourced from the container. VolumesFrom []string `json:"volumes_from,omitempty"` + // Init specifies that an init binary will be mounted into the + // container, and will be used as PID1. + Init bool `json:"init,omitempty"` + // InitPath specifies the path to the init binary that will be added if + // Init is specified above. If not specified, the default set in the + // Libpod config will be used. Ignored if Init above is not set. + // Optional. + InitPath string `json:"init_path,omitempty"` // Mounts are mounts that will be added to the container. // These will supersede Image Volumes and VolumesFrom volumes where // there are conflicts. @@ -172,7 +180,7 @@ type ContainerStorageConfig struct { // These will supersede Image Volumes and VolumesFrom volumes where // there are conflicts. // Optional. - Volumes []*Volumes `json:"volumes,omitempty"` + Volumes []*NamedVolume `json:"volumes,omitempty"` // Devices are devices that will be added to the container. // Optional. Devices []spec.LinuxDevice `json:"devices,omitempty"` @@ -283,25 +291,20 @@ type ContainerNetworkConfig struct { // namespace. // Mandatory. NetNS Namespace `json:"netns,omitempty"` - // ConfigureNetNS is whether Libpod will configure the container's - // network namespace to send and receive traffic. - // Only available is NetNS is private - conflicts with other NetNS - // modes. - ConfigureNetNS bool `json:"configure_netns,omitempty"` // StaticIP is the a IPv4 address of the container. - // Only available if ConfigureNetNS is true. + // Only available if NetNS is set to Bridge. // Optional. StaticIP *net.IP `json:"static_ip,omitempty"` // StaticIPv6 is a static IPv6 address to set in the container. - // Only available if ConfigureNetNS is true. + // Only available if NetNS is set to Bridge. // Optional. StaticIPv6 *net.IP `json:"static_ipv6,omitempty"` // StaticMAC is a static MAC address to set in the container. - // Only available if ConfigureNetNS is true. + // Only available if NetNS is set to bridge. // Optional. StaticMAC *net.HardwareAddr `json:"static_mac,omitempty"` // PortBindings is a set of ports to map into the container. - // Only available if ConfigureNetNS is true. + // Only available if NetNS is set to bridge or slirp. // Optional. PortMappings []ocicni.PortMapping `json:"portmappings,omitempty"` // PublishImagePorts will publish ports specified in the image to random @@ -312,31 +315,31 @@ type ContainerNetworkConfig struct { // If this list is empty, the default CNI network will be joined // instead. If at least one entry is present, we will not join the // default network (unless it is part of this list). - // Only available if ConfigureNetNS is true. + // Only available if NetNS is set to bridge. // Optional. CNINetworks []string `json:"cni_networks,omitempty"` // UseImageResolvConf indicates that resolv.conf should not be managed // by Podman, but instead sourced from the image. // Conflicts with DNSServer, DNSSearch, DNSOption. UseImageResolvConf bool `json:"use_image_resolve_conf,omitempty"` - // DNSServer is a set of DNS servers that will be used in the + // DNSServers is a set of DNS servers that will be used in the // container's resolv.conf, replacing the host's DNS Servers which are // used by default. // Conflicts with UseImageResolvConf. // Optional. - DNSServer []net.IP `json:"dns_server,omitempty"` + DNSServers []net.IP `json:"dns_server,omitempty"` // DNSSearch is a set of DNS search domains that will be used in the // container's resolv.conf, replacing the host's DNS search domains // which are used by default. // Conflicts with UseImageResolvConf. // Optional. DNSSearch []string `json:"dns_search,omitempty"` - // DNSOption is a set of DNS options that will be used in the + // DNSOptions is a set of DNS options that will be used in the // container's resolv.conf, replacing the host's DNS options which are // used by default. // Conflicts with UseImageResolvConf. // Optional. - DNSOption []string `json:"dns_option,omitempty"` + DNSOptions []string `json:"dns_option,omitempty"` // UseImageHosts indicates that /etc/hosts should not be managed by // Podman, and instead sourced from the image. // Conflicts with HostAdd. @@ -393,27 +396,30 @@ type SpecGenerator struct { ContainerHealthCheckConfig } -// Volumes is a temporary struct to hold input from the User -type Volumes struct { - Name string - Dest string +// NamedVolume holds information about a named volume that will be mounted into +// the container. +type NamedVolume struct { + // Name is the name of the named volume to be mounted. May be empty. + // If empty, a new named volume with a pseudorandomly generated name + // will be mounted at the given destination. + Name string + // Destination to mount the named volume within the container. Must be + // an absolute path. Path will be created if it does not exist. + Dest string + // Options are options that the named volume will be mounted with. Options []string } // NewSpecGenerator returns a SpecGenerator struct given one of two mandatory inputs -func NewSpecGenerator(image string) *SpecGenerator { - networkConfig := ContainerNetworkConfig{ - NetNS: Namespace{ - NSMode: Bridge, - }, - } - csc := ContainerStorageConfig{Image: image} - if rootless.IsRootless() { - networkConfig.NetNS.NSMode = Slirp +func NewSpecGenerator(arg string, rootfs bool) *SpecGenerator { + csc := ContainerStorageConfig{} + if rootfs { + csc.Rootfs = arg + } else { + csc.Image = arg } return &SpecGenerator{ ContainerStorageConfig: csc, - ContainerNetworkConfig: networkConfig, } } |