diff options
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/adapter/containers.go | 24 | ||||
-rw-r--r-- | pkg/adapter/pods.go | 108 | ||||
-rw-r--r-- | pkg/adapter/runtime.go | 2 | ||||
-rw-r--r-- | pkg/cgroups/cpu.go | 4 | ||||
-rw-r--r-- | pkg/namespaces/namespaces.go | 5 | ||||
-rw-r--r-- | pkg/rootless/rootless_linux.c | 14 | ||||
-rw-r--r-- | pkg/spec/config_linux_cgo.go | 2 | ||||
-rw-r--r-- | pkg/spec/config_linux_nocgo.go | 2 | ||||
-rw-r--r-- | pkg/spec/config_unsupported.go | 2 | ||||
-rw-r--r-- | pkg/spec/createconfig.go | 412 | ||||
-rw-r--r-- | pkg/spec/namespaces.go | 433 | ||||
-rw-r--r-- | pkg/spec/security.go | 172 | ||||
-rw-r--r-- | pkg/spec/spec.go | 302 | ||||
-rw-r--r-- | pkg/spec/spec_test.go | 6 | ||||
-rw-r--r-- | pkg/spec/storage.go | 12 | ||||
-rw-r--r-- | pkg/util/mountOpts.go | 31 |
16 files changed, 920 insertions, 611 deletions
diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go index 64550f545..287bd8474 100644 --- a/pkg/adapter/containers.go +++ b/pkg/adapter/containers.go @@ -400,17 +400,8 @@ func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode } } - config, err := r.Runtime.GetConfig() - if err != nil { - return exitCode, err - } - detachKeys := c.String("detach-keys") - if detachKeys == "" { - detachKeys = config.DetachKeys - } - // if the container was created as part of a pod, also start its dependencies, if any. - if err := StartAttachCtr(ctx, ctr, outputStream, errorStream, inputStream, detachKeys, c.Bool("sig-proxy"), true, c.IsSet("pod")); err != nil { + if err := StartAttachCtr(ctx, ctr, outputStream, errorStream, inputStream, c.String("detach-keys"), c.Bool("sig-proxy"), true, c.IsSet("pod")); err != nil { // We've manually detached from the container // Do not perform cleanup, or wait for container exit code // Just exit immediately @@ -547,12 +538,13 @@ func (r *LocalRuntime) Restore(ctx context.Context, c *cliconfig.RestoreValues) ) options := libpod.ContainerCheckpointOptions{ - Keep: c.Keep, - TCPEstablished: c.TcpEstablished, - TargetFile: c.Import, - Name: c.Name, - IgnoreRootfs: c.IgnoreRootfs, - IgnoreStaticIP: c.IgnoreStaticIP, + Keep: c.Keep, + TCPEstablished: c.TcpEstablished, + TargetFile: c.Import, + Name: c.Name, + IgnoreRootfs: c.IgnoreRootfs, + IgnoreStaticIP: c.IgnoreStaticIP, + IgnoreStaticMAC: c.IgnoreStaticMAC, } filterFuncs = append(filterFuncs, func(c *libpod.Container) bool { diff --git a/pkg/adapter/pods.go b/pkg/adapter/pods.go index d8d5b884f..85f93ed3e 100644 --- a/pkg/adapter/pods.go +++ b/pkg/adapter/pods.go @@ -666,10 +666,69 @@ func getPodPorts(containers []v1.Container) []ocicni.PortMapping { return infraPorts } +func setupSecurityContext(securityConfig *createconfig.SecurityConfig, userConfig *createconfig.UserConfig, containerYAML v1.Container) { + if containerYAML.SecurityContext == nil { + return + } + if containerYAML.SecurityContext.ReadOnlyRootFilesystem != nil { + securityConfig.ReadOnlyRootfs = *containerYAML.SecurityContext.ReadOnlyRootFilesystem + } + if containerYAML.SecurityContext.Privileged != nil { + securityConfig.Privileged = *containerYAML.SecurityContext.Privileged + } + + if containerYAML.SecurityContext.AllowPrivilegeEscalation != nil { + securityConfig.NoNewPrivs = !*containerYAML.SecurityContext.AllowPrivilegeEscalation + } + + if seopt := containerYAML.SecurityContext.SELinuxOptions; seopt != nil { + if seopt.User != "" { + securityConfig.SecurityOpts = append(securityConfig.SecurityOpts, fmt.Sprintf("label=user:%s", seopt.User)) + securityConfig.LabelOpts = append(securityConfig.LabelOpts, fmt.Sprintf("user:%s", seopt.User)) + } + if seopt.Role != "" { + securityConfig.SecurityOpts = append(securityConfig.SecurityOpts, fmt.Sprintf("label=role:%s", seopt.Role)) + securityConfig.LabelOpts = append(securityConfig.LabelOpts, fmt.Sprintf("role:%s", seopt.Role)) + } + if seopt.Type != "" { + securityConfig.SecurityOpts = append(securityConfig.SecurityOpts, fmt.Sprintf("label=type:%s", seopt.Type)) + securityConfig.LabelOpts = append(securityConfig.LabelOpts, fmt.Sprintf("type:%s", seopt.Type)) + } + if seopt.Level != "" { + securityConfig.SecurityOpts = append(securityConfig.SecurityOpts, fmt.Sprintf("label=level:%s", seopt.Level)) + securityConfig.LabelOpts = append(securityConfig.LabelOpts, fmt.Sprintf("level:%s", seopt.Level)) + } + } + if caps := containerYAML.SecurityContext.Capabilities; caps != nil { + for _, capability := range caps.Add { + securityConfig.CapAdd = append(securityConfig.CapAdd, string(capability)) + } + for _, capability := range caps.Drop { + securityConfig.CapDrop = append(securityConfig.CapDrop, string(capability)) + } + } + if containerYAML.SecurityContext.RunAsUser != nil { + userConfig.User = fmt.Sprintf("%d", *containerYAML.SecurityContext.RunAsUser) + } + if containerYAML.SecurityContext.RunAsGroup != nil { + if userConfig.User == "" { + userConfig.User = "0" + } + userConfig.User = fmt.Sprintf("%s:%d", userConfig.User, *containerYAML.SecurityContext.RunAsGroup) + } +} + // kubeContainerToCreateConfig takes a v1.Container and returns a createconfig describing a container func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container, runtime *libpod.Runtime, newImage *image.Image, namespaces map[string]string, volumes map[string]string, podID string) (*createconfig.CreateConfig, error) { var ( containerConfig createconfig.CreateConfig + pidConfig createconfig.PidConfig + networkConfig createconfig.NetworkConfig + cgroupConfig createconfig.CgroupConfig + utsConfig createconfig.UtsConfig + ipcConfig createconfig.IpcConfig + userConfig createconfig.UserConfig + securityConfig createconfig.SecurityConfig ) // The default for MemorySwappiness is -1, not 0 @@ -685,36 +744,15 @@ func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container imageData, _ := newImage.Inspect(ctx) - containerConfig.User = "0" + userConfig.User = "0" if imageData != nil { - containerConfig.User = imageData.Config.User + userConfig.User = imageData.Config.User } - if containerYAML.SecurityContext != nil { - if containerConfig.SecurityOpts != nil { - if containerYAML.SecurityContext.ReadOnlyRootFilesystem != nil { - containerConfig.ReadOnlyRootfs = *containerYAML.SecurityContext.ReadOnlyRootFilesystem - } - if containerYAML.SecurityContext.Privileged != nil { - containerConfig.Privileged = *containerYAML.SecurityContext.Privileged - } - - if containerYAML.SecurityContext.AllowPrivilegeEscalation != nil { - containerConfig.NoNewPrivs = !*containerYAML.SecurityContext.AllowPrivilegeEscalation - } + setupSecurityContext(&securityConfig, &userConfig, containerYAML) - } - if caps := containerYAML.SecurityContext.Capabilities; caps != nil { - for _, capability := range caps.Add { - containerConfig.CapAdd = append(containerConfig.CapAdd, string(capability)) - } - for _, capability := range caps.Drop { - containerConfig.CapDrop = append(containerConfig.CapDrop, string(capability)) - } - } - } var err error - containerConfig.SeccompProfilePath, err = libpod.DefaultSeccompPath() + containerConfig.Security.SeccompProfilePath, err = libpod.DefaultSeccompPath() if err != nil { return nil, err } @@ -737,20 +775,28 @@ func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container containerConfig.StopSignal = 15 // If the user does not pass in ID mappings, just set to basics - if containerConfig.IDMappings == nil { - containerConfig.IDMappings = &storage.IDMappingOptions{} + if userConfig.IDMappings == nil { + userConfig.IDMappings = &storage.IDMappingOptions{} } - containerConfig.NetMode = ns.NetworkMode(namespaces["net"]) - containerConfig.IpcMode = ns.IpcMode(namespaces["ipc"]) - containerConfig.UtsMode = ns.UTSMode(namespaces["uts"]) + networkConfig.NetMode = ns.NetworkMode(namespaces["net"]) + ipcConfig.IpcMode = ns.IpcMode(namespaces["ipc"]) + utsConfig.UtsMode = ns.UTSMode(namespaces["uts"]) // disabled in code review per mheon //containerConfig.PidMode = ns.PidMode(namespaces["pid"]) - containerConfig.UsernsMode = ns.UsernsMode(namespaces["user"]) + userConfig.UsernsMode = ns.UsernsMode(namespaces["user"]) if len(containerConfig.WorkDir) == 0 { containerConfig.WorkDir = "/" } + containerConfig.Pid = pidConfig + containerConfig.Network = networkConfig + containerConfig.Uts = utsConfig + containerConfig.Ipc = ipcConfig + containerConfig.Cgroup = cgroupConfig + containerConfig.User = userConfig + containerConfig.Security = securityConfig + // Set default environment variables and incorporate data from image, if necessary envs := shared.EnvVariablesFromData(imageData) diff --git a/pkg/adapter/runtime.go b/pkg/adapter/runtime.go index 4f70e90f9..81a43853c 100644 --- a/pkg/adapter/runtime.go +++ b/pkg/adapter/runtime.go @@ -338,7 +338,7 @@ func (r *LocalRuntime) SaveImage(ctx context.Context, c *cliconfig.SaveValues) e return newImage.Save(ctx, source, c.Format, c.Output, additionalTags, c.Quiet, c.Compress) } -// LoadImage is a wrapper function for libpod PruneVolumes +// LoadImage is a wrapper function for libpod LoadImage func (r *LocalRuntime) LoadImage(ctx context.Context, name string, cli *cliconfig.LoadValues) (string, error) { var ( writer io.Writer diff --git a/pkg/cgroups/cpu.go b/pkg/cgroups/cpu.go index 03677f1ef..a43a76b22 100644 --- a/pkg/cgroups/cpu.go +++ b/pkg/cgroups/cpu.go @@ -81,14 +81,14 @@ func (c *cpuHandler) Stat(ctr *CgroupControl, m *Metrics) error { return err } if val, found := values["usage_usec"]; found { - usage.Kernel, err = strconv.ParseUint(cleanString(val[0]), 10, 0) + usage.Total, err = strconv.ParseUint(cleanString(val[0]), 10, 0) if err != nil { return err } usage.Kernel *= 1000 } if val, found := values["system_usec"]; found { - usage.Total, err = strconv.ParseUint(cleanString(val[0]), 10, 0) + usage.Kernel, err = strconv.ParseUint(cleanString(val[0]), 10, 0) if err != nil { return err } diff --git a/pkg/namespaces/namespaces.go b/pkg/namespaces/namespaces.go index 9d1033b93..78b55bb2a 100644 --- a/pkg/namespaces/namespaces.go +++ b/pkg/namespaces/namespaces.go @@ -25,6 +25,11 @@ func (n CgroupMode) IsHost() bool { return n == hostType } +// IsDefaultValue indicates whether the cgroup namespace has the default value. +func (n CgroupMode) IsDefaultValue() bool { + return n == "" +} + // IsNS indicates a cgroup namespace passed in by path (ns:<path>) func (n CgroupMode) IsNS() bool { return strings.HasPrefix(string(n), nsType) diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c index 94933ddd0..9604de638 100644 --- a/pkg/rootless/rootless_linux.c +++ b/pkg/rootless/rootless_linux.c @@ -24,12 +24,16 @@ int renameat2 (int olddirfd, const char *oldpath, int newdirfd, const char *newpath, unsigned int flags) { -# ifdef __NR_renameat2 - return (int) syscall (__NR_renameat2, olddirfd, oldpath, newdirfd, newpath, flags); +# ifdef SYS_renameat2 + return (int) syscall (SYS_renameat2, olddirfd, oldpath, newdirfd, newpath, flags); # else - /* no way to implement it atomically. */ - errno = ENOSYS; - return -1; + /* This might be an issue if another process is trying to read the file while it is empty. */ + int fd = open (newpath, O_EXCL|O_CREAT, 0700); + if (fd < 0) + return fd; + close (fd); + /* We are sure we created the file, let's overwrite it. */ + return rename (oldpath, newpath); # endif } #endif diff --git a/pkg/spec/config_linux_cgo.go b/pkg/spec/config_linux_cgo.go index a1527752a..c47156456 100644 --- a/pkg/spec/config_linux_cgo.go +++ b/pkg/spec/config_linux_cgo.go @@ -10,7 +10,7 @@ import ( seccomp "github.com/seccomp/containers-golang" ) -func getSeccompConfig(config *CreateConfig, configSpec *spec.Spec) (*spec.LinuxSeccomp, error) { +func getSeccompConfig(config *SecurityConfig, configSpec *spec.Spec) (*spec.LinuxSeccomp, error) { var seccompConfig *spec.LinuxSeccomp var err error diff --git a/pkg/spec/config_linux_nocgo.go b/pkg/spec/config_linux_nocgo.go index 10329ff3b..8d720b6d4 100644 --- a/pkg/spec/config_linux_nocgo.go +++ b/pkg/spec/config_linux_nocgo.go @@ -6,6 +6,6 @@ import ( spec "github.com/opencontainers/runtime-spec/specs-go" ) -func getSeccompConfig(config *CreateConfig, configSpec *spec.Spec) (*spec.LinuxSeccomp, error) { +func getSeccompConfig(config *SecurityConfig, configSpec *spec.Spec) (*spec.LinuxSeccomp, error) { return nil, nil } diff --git a/pkg/spec/config_unsupported.go b/pkg/spec/config_unsupported.go index 160414878..a2c7f4416 100644 --- a/pkg/spec/config_unsupported.go +++ b/pkg/spec/config_unsupported.go @@ -8,7 +8,7 @@ import ( "github.com/pkg/errors" ) -func getSeccompConfig(config *CreateConfig, configSpec *spec.Spec) (*spec.LinuxSeccomp, error) { +func getSeccompConfig(config *SecurityConfig, configSpec *spec.Spec) (*spec.LinuxSeccomp, error) { return nil, errors.New("function not supported on non-linux OS's") } func addDevice(g *generate.Generator, device string) error { diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index 2a8fe7332..244a8d1cd 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -1,7 +1,6 @@ package createconfig import ( - "net" "os" "strconv" "strings" @@ -12,7 +11,6 @@ import ( "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/namespaces" "github.com/containers/storage" - "github.com/cri-o/ocicni/pkg/ocicni" "github.com/docker/go-connections/nat" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" @@ -55,89 +53,126 @@ type CreateResourceConfig struct { Ulimit []string //ulimit } -// CreateConfig is a pre OCI spec structure. It represents user input from varlink or the CLI -type CreateConfig struct { - Annotations map[string]string - Args []string +// PidConfig configures the pid namespace for the container +type PidConfig struct { + PidMode namespaces.PidMode //pid +} + +// IpcConfig configures the ipc namespace for the container +type IpcConfig struct { + IpcMode namespaces.IpcMode //ipc +} + +// CgroupConfig configures the cgroup namespace for the container +type CgroupConfig struct { + Cgroups string + Cgroupns string + CgroupParent string // cgroup-parent + CgroupMode namespaces.CgroupMode //cgroup +} + +// UserConfig configures the user namespace for the container +type UserConfig struct { + GroupAdd []string // group-add + IDMappings *storage.IDMappingOptions + UsernsMode namespaces.UsernsMode //userns + User string //user +} + +// UtsConfig configures the uts namespace for the container +type UtsConfig struct { + UtsMode namespaces.UTSMode //uts + NoHosts bool + HostAdd []string //add-host + Hostname string +} + +// NetworkConfig configures the network namespace for the container +type NetworkConfig struct { + DNSOpt []string //dns-opt + DNSSearch []string //dns-search + DNSServers []string //dns + ExposedPorts map[nat.Port]struct{} + HTTPProxy bool + IP6Address string //ipv6 + IPAddress string //ip + LinkLocalIP []string // link-local-ip + MacAddress string //mac-address + NetMode namespaces.NetworkMode //net + Network string //network + NetworkAlias []string //network-alias + PortBindings nat.PortMap + Publish []string //publish + PublishAll bool //publish-all +} + +// SecurityConfig configures the security features for the container +type SecurityConfig struct { CapAdd []string // cap-add CapDrop []string // cap-drop - CidFile string - ConmonPidFile string - Cgroupns string - Cgroups string - CgroupParent string // cgroup-parent - Command []string // Full command that will be used - UserCommand []string // User-entered command (or image CMD) - Detach bool // detach - Devices []string // device - DNSOpt []string //dns-opt - DNSSearch []string //dns-search - DNSServers []string //dns - Entrypoint []string //entrypoint - Env map[string]string //env - ExposedPorts map[nat.Port]struct{} - GroupAdd []string // group-add - HealthCheck *manifest.Schema2HealthConfig - NoHosts bool - HostAdd []string //add-host - Hostname string //hostname - HTTPProxy bool - Init bool // init - InitPath string //init-path - Image string - ImageID string - BuiltinImgVolumes map[string]struct{} // volumes defined in the image config - IDMappings *storage.IDMappingOptions - ImageVolumeType string // how to handle the image volume, either bind, tmpfs, or ignore - Interactive bool //interactive - IpcMode namespaces.IpcMode //ipc - IP6Address string //ipv6 - IPAddress string //ip - Labels map[string]string //label - LinkLocalIP []string // link-local-ip - LogDriver string // log-driver - LogDriverOpt []string // log-opt - MacAddress string //mac-address - Name string //name - NetMode namespaces.NetworkMode //net - Network string //network - NetworkAlias []string //network-alias - PidMode namespaces.PidMode //pid - Pod string //pod - PodmanPath string - CgroupMode namespaces.CgroupMode //cgroup - PortBindings nat.PortMap - Privileged bool //privileged - Publish []string //publish - PublishAll bool //publish-all - Quiet bool //quiet - ReadOnlyRootfs bool //read-only - ReadOnlyTmpfs bool //read-only-tmpfs - Resources CreateResourceConfig - RestartPolicy string - Rm bool //rm - StopSignal syscall.Signal // stop-signal - StopTimeout uint // stop-timeout - Sysctl map[string]string //sysctl - Systemd bool - Tmpfs []string // tmpfs - Tty bool //tty - UsernsMode namespaces.UsernsMode //userns - User string //user - UtsMode namespaces.UTSMode //uts - Mounts []spec.Mount - MountsFlag []string // mounts - NamedVolumes []*libpod.ContainerNamedVolume - Volumes []string //volume - VolumesFrom []string - WorkDir string //workdir LabelOpts []string //SecurityOpts NoNewPrivs bool //SecurityOpts ApparmorProfile string //SecurityOpts SeccompProfilePath string //SecurityOpts SecurityOpts []string - Rootfs string - Syslog bool // Whether to enable syslog on exit commands + Privileged bool //privileged + ReadOnlyRootfs bool //read-only + ReadOnlyTmpfs bool //read-only-tmpfs + Sysctl map[string]string //sysctl +} + +// CreateConfig is a pre OCI spec structure. It represents user input from varlink or the CLI +type CreateConfig struct { + Annotations map[string]string + Args []string + CidFile string + ConmonPidFile string + Command []string // Full command that will be used + UserCommand []string // User-entered command (or image CMD) + Detach bool // detach + Devices []string // device + Entrypoint []string //entrypoint + Env map[string]string //env + HealthCheck *manifest.Schema2HealthConfig + Init bool // init + InitPath string //init-path + Image string + ImageID string + BuiltinImgVolumes map[string]struct{} // volumes defined in the image config + ImageVolumeType string // how to handle the image volume, either bind, tmpfs, or ignore + Interactive bool //interactive + Labels map[string]string //label + LogDriver string // log-driver + LogDriverOpt []string // log-opt + Name string //name + PodmanPath string + Pod string //pod + Quiet bool //quiet + Resources CreateResourceConfig + RestartPolicy string + Rm bool //rm + StopSignal syscall.Signal // stop-signal + StopTimeout uint // stop-timeout + Systemd bool + Tmpfs []string // tmpfs + Tty bool //tty + Mounts []spec.Mount + MountsFlag []string // mounts + NamedVolumes []*libpod.ContainerNamedVolume + Volumes []string //volume + VolumesFrom []string + WorkDir string //workdir + Rootfs string + Security SecurityConfig + Syslog bool // Whether to enable syslog on exit commands + + // Namespaces + Pid PidConfig + Ipc IpcConfig + Cgroup CgroupConfig + User UserConfig + Uts UtsConfig + Network NetworkConfig } func u32Ptr(i int64) *uint32 { u := uint32(i); return &u } @@ -199,7 +234,6 @@ func (c *CreateConfig) createExitCommand(runtime *libpod.Runtime) ([]string, err // GetContainerCreateOptions takes a CreateConfig and returns a slice of CtrCreateOptions func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *libpod.Pod, mounts []spec.Mount, namedVolumes []*libpod.ContainerNamedVolume) ([]libpod.CtrCreateOption, error) { var options []libpod.CtrCreateOption - var portBindings []ocicni.PortMapping var err error if c.Interactive { @@ -216,15 +250,6 @@ func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *l logrus.Debugf("adding container to pod %s", c.Pod) options = append(options, runtime.WithPod(pod)) } - if c.Cgroups == "disabled" { - options = append(options, libpod.WithNoCgroups()) - } - if len(c.PortBindings) > 0 { - portBindings, err = c.CreatePortBindings() - if err != nil { - return nil, errors.Wrapf(err, "unable to create port bindings") - } - } if len(mounts) != 0 || len(namedVolumes) != 0 { destinations := []string{} @@ -253,179 +278,72 @@ func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *l // does not have one options = append(options, libpod.WithEntrypoint(c.Entrypoint)) - networks := make([]string, 0) - userNetworks := c.NetMode.UserDefined() - if IsPod(userNetworks) { - userNetworks = "" - } - if userNetworks != "" { - for _, netName := range strings.Split(userNetworks, ",") { - if netName == "" { - return nil, errors.Wrapf(err, "container networks %q invalid", networks) - } - networks = append(networks, netName) - } - } - - if c.NetMode.IsNS() { - ns := c.NetMode.NS() - if ns == "" { - return nil, errors.Errorf("invalid empty user-defined network namespace") - } - _, err := os.Stat(ns) - if err != nil { - return nil, err - } - } else if c.NetMode.IsContainer() { - connectedCtr, err := runtime.LookupContainer(c.NetMode.Container()) - if err != nil { - return nil, errors.Wrapf(err, "container %q not found", c.NetMode.Container()) - } - options = append(options, libpod.WithNetNSFrom(connectedCtr)) - } else if !c.NetMode.IsHost() && !c.NetMode.IsNone() { - hasUserns := c.UsernsMode.IsContainer() || c.UsernsMode.IsNS() || len(c.IDMappings.UIDMap) > 0 || len(c.IDMappings.GIDMap) > 0 - postConfigureNetNS := hasUserns && !c.UsernsMode.IsHost() - options = append(options, libpod.WithNetNS(portBindings, postConfigureNetNS, string(c.NetMode), networks)) - } + // TODO: MNT, USER, CGROUP + options = append(options, libpod.WithStopSignal(c.StopSignal)) + options = append(options, libpod.WithStopTimeout(c.StopTimeout)) - if c.CgroupMode.IsNS() { - ns := c.CgroupMode.NS() - if ns == "" { - return nil, errors.Errorf("invalid empty user-defined network namespace") - } - _, err := os.Stat(ns) - if err != nil { - return nil, err - } - } else if c.CgroupMode.IsContainer() { - connectedCtr, err := runtime.LookupContainer(c.CgroupMode.Container()) - if err != nil { - return nil, errors.Wrapf(err, "container %q not found", c.CgroupMode.Container()) - } - options = append(options, libpod.WithCgroupNSFrom(connectedCtr)) + logPath := getLoggingPath(c.LogDriverOpt) + if logPath != "" { + options = append(options, libpod.WithLogPath(logPath)) } - if c.UsernsMode.IsNS() { - ns := c.UsernsMode.NS() - if ns == "" { - return nil, errors.Errorf("invalid empty user-defined user namespace") - } - _, err := os.Stat(ns) - if err != nil { - return nil, err - } - options = append(options, libpod.WithIDMappings(*c.IDMappings)) - } else if c.UsernsMode.IsContainer() { - connectedCtr, err := runtime.LookupContainer(c.UsernsMode.Container()) - if err != nil { - return nil, errors.Wrapf(err, "container %q not found", c.UsernsMode.Container()) - } - options = append(options, libpod.WithUserNSFrom(connectedCtr)) - } else { - options = append(options, libpod.WithIDMappings(*c.IDMappings)) + if c.LogDriver != "" { + options = append(options, libpod.WithLogDriver(c.LogDriver)) } - if c.PidMode.IsContainer() { - connectedCtr, err := runtime.LookupContainer(c.PidMode.Container()) - if err != nil { - return nil, errors.Wrapf(err, "container %q not found", c.PidMode.Container()) - } - - options = append(options, libpod.WithPIDNSFrom(connectedCtr)) + secOpts, err := c.Security.ToCreateOptions() + if err != nil { + return nil, err } + options = append(options, secOpts...) - if c.IpcMode.IsContainer() { - connectedCtr, err := runtime.LookupContainer(c.IpcMode.Container()) - if err != nil { - return nil, errors.Wrapf(err, "container %q not found", c.IpcMode.Container()) - } - - options = append(options, libpod.WithIPCNSFrom(connectedCtr)) + nsOpts, err := c.Cgroup.ToCreateOptions(runtime) + if err != nil { + return nil, err } + options = append(options, nsOpts...) - if IsPod(string(c.UtsMode)) { - options = append(options, libpod.WithUTSNSFromPod(pod)) + nsOpts, err = c.Ipc.ToCreateOptions(runtime) + if err != nil { + return nil, err } - if c.UtsMode.IsContainer() { - connectedCtr, err := runtime.LookupContainer(c.UtsMode.Container()) - if err != nil { - return nil, errors.Wrapf(err, "container %q not found", c.UtsMode.Container()) - } + options = append(options, nsOpts...) - options = append(options, libpod.WithUTSNSFrom(connectedCtr)) + nsOpts, err = c.Pid.ToCreateOptions(runtime) + if err != nil { + return nil, err } + options = append(options, nsOpts...) - // TODO: MNT, USER, CGROUP - options = append(options, libpod.WithStopSignal(c.StopSignal)) - options = append(options, libpod.WithStopTimeout(c.StopTimeout)) - if len(c.DNSSearch) > 0 { - options = append(options, libpod.WithDNSSearch(c.DNSSearch)) - } - if len(c.DNSServers) > 0 { - if len(c.DNSServers) == 1 && strings.ToLower(c.DNSServers[0]) == "none" { - options = append(options, libpod.WithUseImageResolvConf()) - } else { - options = append(options, libpod.WithDNS(c.DNSServers)) - } - } - if len(c.DNSOpt) > 0 { - options = append(options, libpod.WithDNSOption(c.DNSOpt)) - } - if c.NoHosts { - options = append(options, libpod.WithUseImageHosts()) - } - if len(c.HostAdd) > 0 && !c.NoHosts { - options = append(options, libpod.WithHosts(c.HostAdd)) - } - logPath := getLoggingPath(c.LogDriverOpt) - if logPath != "" { - options = append(options, libpod.WithLogPath(logPath)) + nsOpts, err = c.Network.ToCreateOptions(runtime, &c.User) + if err != nil { + return nil, err } + options = append(options, nsOpts...) - if c.LogDriver != "" { - options = append(options, libpod.WithLogDriver(c.LogDriver)) + nsOpts, err = c.Uts.ToCreateOptions(runtime, pod) + if err != nil { + return nil, err } + options = append(options, nsOpts...) - if c.IPAddress != "" { - ip := net.ParseIP(c.IPAddress) - if ip == nil { - return nil, errors.Wrapf(define.ErrInvalidArg, "cannot parse %s as IP address", c.IPAddress) - } else if ip.To4() == nil { - return nil, errors.Wrapf(define.ErrInvalidArg, "%s is not an IPv4 address", c.IPAddress) - } - options = append(options, libpod.WithStaticIP(ip)) + nsOpts, err = c.User.ToCreateOptions(runtime) + if err != nil { + return nil, err } - - options = append(options, libpod.WithPrivileged(c.Privileged)) + options = append(options, nsOpts...) useImageVolumes := c.ImageVolumeType == TypeBind // Gather up the options for NewContainer which consist of With... funcs options = append(options, libpod.WithRootFSFromImage(c.ImageID, c.Image, useImageVolumes)) - options = append(options, libpod.WithSecLabels(c.LabelOpts)) options = append(options, libpod.WithConmonPidFile(c.ConmonPidFile)) options = append(options, libpod.WithLabels(c.Labels)) - options = append(options, libpod.WithUser(c.User)) - if c.IpcMode.IsHost() { - options = append(options, libpod.WithShmDir("/dev/shm")) - - } else if c.IpcMode.IsContainer() { - ctr, err := runtime.LookupContainer(c.IpcMode.Container()) - if err != nil { - return nil, errors.Wrapf(err, "container %q not found", c.IpcMode.Container()) - } - options = append(options, libpod.WithShmDir(ctr.ShmDir())) - } options = append(options, libpod.WithShmSize(c.Resources.ShmSize)) - options = append(options, libpod.WithGroups(c.GroupAdd)) if c.Rootfs != "" { options = append(options, libpod.WithRootFS(c.Rootfs)) } // Default used if not overridden on command line - if c.CgroupParent != "" { - options = append(options, libpod.WithCgroupParent(c.CgroupParent)) - } - if c.RestartPolicy != "" { if c.RestartPolicy == "unless-stopped" { return nil, errors.Wrapf(define.ErrInvalidArg, "the unless-stopped restart policy is not supported") @@ -459,38 +377,6 @@ func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *l return options, nil } -// CreatePortBindings iterates ports mappings and exposed ports into a format CNI understands -func (c *CreateConfig) CreatePortBindings() ([]ocicni.PortMapping, error) { - return NatToOCIPortBindings(c.PortBindings) -} - -// NatToOCIPortBindings iterates a nat.portmap slice and creates []ocicni portmapping slice -func NatToOCIPortBindings(ports nat.PortMap) ([]ocicni.PortMapping, error) { - var portBindings []ocicni.PortMapping - for containerPb, hostPb := range ports { - var pm ocicni.PortMapping - pm.ContainerPort = int32(containerPb.Int()) - for _, i := range hostPb { - var hostPort int - var err error - pm.HostIP = i.HostIP - if i.HostPort == "" { - hostPort = containerPb.Int() - } else { - hostPort, err = strconv.Atoi(i.HostPort) - if err != nil { - return nil, errors.Wrapf(err, "unable to convert host port to integer") - } - } - - pm.HostPort = int32(hostPort) - pm.Protocol = containerPb.Proto() - portBindings = append(portBindings, pm) - } - } - return portBindings, nil -} - // AddPrivilegedDevices iterates through host devices and adds all // host devices to the spec func (c *CreateConfig) AddPrivilegedDevices(g *generate.Generator) error { diff --git a/pkg/spec/namespaces.go b/pkg/spec/namespaces.go new file mode 100644 index 000000000..a45137416 --- /dev/null +++ b/pkg/spec/namespaces.go @@ -0,0 +1,433 @@ +package createconfig + +import ( + "net" + "os" + "strconv" + "strings" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/cgroups" + "github.com/cri-o/ocicni/pkg/ocicni" + "github.com/docker/go-connections/nat" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-tools/generate" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +func (c *NetworkConfig) ToCreateOptions(runtime *libpod.Runtime, userns *UserConfig) ([]libpod.CtrCreateOption, error) { + var portBindings []ocicni.PortMapping + var err error + if len(c.PortBindings) > 0 { + portBindings, err = NatToOCIPortBindings(c.PortBindings) + if err != nil { + return nil, errors.Wrapf(err, "unable to create port bindings") + } + } + + options := make([]libpod.CtrCreateOption, 0) + userNetworks := c.NetMode.UserDefined() + networks := make([]string, 0) + + if IsPod(userNetworks) { + userNetworks = "" + } + if userNetworks != "" { + for _, netName := range strings.Split(userNetworks, ",") { + if netName == "" { + return nil, errors.Errorf("container networks %q invalid", userNetworks) + } + networks = append(networks, netName) + } + } + + if c.NetMode.IsNS() { + ns := c.NetMode.NS() + if ns == "" { + return nil, errors.Errorf("invalid empty user-defined network namespace") + } + _, err := os.Stat(ns) + if err != nil { + return nil, err + } + } else if c.NetMode.IsContainer() { + connectedCtr, err := runtime.LookupContainer(c.NetMode.Container()) + if err != nil { + return nil, errors.Wrapf(err, "container %q not found", c.NetMode.Container()) + } + options = append(options, libpod.WithNetNSFrom(connectedCtr)) + } else if !c.NetMode.IsHost() && !c.NetMode.IsNone() { + postConfigureNetNS := userns.getPostConfigureNetNS() + options = append(options, libpod.WithNetNS(portBindings, postConfigureNetNS, string(c.NetMode), networks)) + } + + if len(c.DNSSearch) > 0 { + options = append(options, libpod.WithDNSSearch(c.DNSSearch)) + } + if len(c.DNSServers) > 0 { + if len(c.DNSServers) == 1 && strings.ToLower(c.DNSServers[0]) == "none" { + options = append(options, libpod.WithUseImageResolvConf()) + } else { + options = append(options, libpod.WithDNS(c.DNSServers)) + } + } + if len(c.DNSOpt) > 0 { + options = append(options, libpod.WithDNSOption(c.DNSOpt)) + } + if c.IPAddress != "" { + ip := net.ParseIP(c.IPAddress) + if ip == nil { + return nil, errors.Wrapf(define.ErrInvalidArg, "cannot parse %s as IP address", c.IPAddress) + } else if ip.To4() == nil { + return nil, errors.Wrapf(define.ErrInvalidArg, "%s is not an IPv4 address", c.IPAddress) + } + options = append(options, libpod.WithStaticIP(ip)) + } + + if c.MacAddress != "" { + mac, err := net.ParseMAC(c.MacAddress) + if err != nil { + return nil, errors.Wrapf(define.ErrInvalidArg, "cannot parse %s as MAC address: %v", c.MacAddress, err) + } + options = append(options, libpod.WithStaticMAC(mac)) + } + + return options, nil +} + +func (c *NetworkConfig) ConfigureGenerator(g *generate.Generator) error { + netMode := c.NetMode + if netMode.IsHost() { + logrus.Debug("Using host netmode") + if err := g.RemoveLinuxNamespace(string(spec.NetworkNamespace)); err != nil { + return err + } + } else if netMode.IsNone() { + logrus.Debug("Using none netmode") + } else if netMode.IsBridge() { + logrus.Debug("Using bridge netmode") + } else if netCtr := netMode.Container(); netCtr != "" { + logrus.Debugf("using container %s netmode", netCtr) + } else if IsNS(string(netMode)) { + logrus.Debug("Using ns netmode") + if err := g.AddOrReplaceLinuxNamespace(string(spec.NetworkNamespace), NS(string(netMode))); err != nil { + return err + } + } else if IsPod(string(netMode)) { + logrus.Debug("Using pod netmode, unless pod is not sharing") + } else if netMode.IsSlirp4netns() { + logrus.Debug("Using slirp4netns netmode") + } else if netMode.IsUserDefined() { + logrus.Debug("Using user defined netmode") + } else { + return errors.Errorf("unknown network mode") + } + + if c.HTTPProxy { + for _, envSpec := range []string{ + "http_proxy", + "HTTP_PROXY", + "https_proxy", + "HTTPS_PROXY", + "ftp_proxy", + "FTP_PROXY", + "no_proxy", + "NO_PROXY", + } { + envVal := os.Getenv(envSpec) + if envVal != "" { + g.AddProcessEnv(envSpec, envVal) + } + } + } + + if g.Config.Annotations == nil { + g.Config.Annotations = make(map[string]string) + } + + if c.PublishAll { + g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseTrue + } else { + g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseFalse + } + + return nil +} + +// NatToOCIPortBindings iterates a nat.portmap slice and creates []ocicni portmapping slice +func NatToOCIPortBindings(ports nat.PortMap) ([]ocicni.PortMapping, error) { + var portBindings []ocicni.PortMapping + for containerPb, hostPb := range ports { + var pm ocicni.PortMapping + pm.ContainerPort = int32(containerPb.Int()) + for _, i := range hostPb { + var hostPort int + var err error + pm.HostIP = i.HostIP + if i.HostPort == "" { + hostPort = containerPb.Int() + } else { + hostPort, err = strconv.Atoi(i.HostPort) + if err != nil { + return nil, errors.Wrapf(err, "unable to convert host port to integer") + } + } + + pm.HostPort = int32(hostPort) + pm.Protocol = containerPb.Proto() + portBindings = append(portBindings, pm) + } + } + return portBindings, nil +} + +func (c *CgroupConfig) ToCreateOptions(runtime *libpod.Runtime) ([]libpod.CtrCreateOption, error) { + options := make([]libpod.CtrCreateOption, 0) + if c.CgroupMode.IsNS() { + ns := c.CgroupMode.NS() + if ns == "" { + return nil, errors.Errorf("invalid empty user-defined network namespace") + } + _, err := os.Stat(ns) + if err != nil { + return nil, err + } + } else if c.CgroupMode.IsContainer() { + connectedCtr, err := runtime.LookupContainer(c.CgroupMode.Container()) + if err != nil { + return nil, errors.Wrapf(err, "container %q not found", c.CgroupMode.Container()) + } + options = append(options, libpod.WithCgroupNSFrom(connectedCtr)) + } + + if c.CgroupParent != "" { + options = append(options, libpod.WithCgroupParent(c.CgroupParent)) + } + + if c.Cgroups == "disabled" { + options = append(options, libpod.WithNoCgroups()) + } + + return options, nil +} + +func (c *UserConfig) ToCreateOptions(runtime *libpod.Runtime) ([]libpod.CtrCreateOption, error) { + options := make([]libpod.CtrCreateOption, 0) + if c.UsernsMode.IsNS() { + ns := c.UsernsMode.NS() + if ns == "" { + return nil, errors.Errorf("invalid empty user-defined user namespace") + } + _, err := os.Stat(ns) + if err != nil { + return nil, err + } + options = append(options, libpod.WithIDMappings(*c.IDMappings)) + } else if c.UsernsMode.IsContainer() { + connectedCtr, err := runtime.LookupContainer(c.UsernsMode.Container()) + if err != nil { + return nil, errors.Wrapf(err, "container %q not found", c.UsernsMode.Container()) + } + options = append(options, libpod.WithUserNSFrom(connectedCtr)) + } else { + options = append(options, libpod.WithIDMappings(*c.IDMappings)) + } + + options = append(options, libpod.WithUser(c.User)) + options = append(options, libpod.WithGroups(c.GroupAdd)) + + return options, nil +} + +func (c *UserConfig) ConfigureGenerator(g *generate.Generator) error { + if IsNS(string(c.UsernsMode)) { + if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), NS(string(c.UsernsMode))); 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 (len(c.IDMappings.UIDMap) > 0 || len(c.IDMappings.GIDMap) > 0) && !c.UsernsMode.IsHost() { + if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), ""); err != nil { + return err + } + } + for _, uidmap := range c.IDMappings.UIDMap { + g.AddLinuxUIDMapping(uint32(uidmap.HostID), uint32(uidmap.ContainerID), uint32(uidmap.Size)) + } + for _, gidmap := range c.IDMappings.GIDMap { + g.AddLinuxGIDMapping(uint32(gidmap.HostID), uint32(gidmap.ContainerID), uint32(gidmap.Size)) + } + return nil +} + +func (c *UserConfig) getPostConfigureNetNS() bool { + hasUserns := c.UsernsMode.IsContainer() || c.UsernsMode.IsNS() || len(c.IDMappings.UIDMap) > 0 || len(c.IDMappings.GIDMap) > 0 + postConfigureNetNS := hasUserns && !c.UsernsMode.IsHost() + return postConfigureNetNS +} + +func (c *UserConfig) InNS(isRootless bool) bool { + hasUserns := c.UsernsMode.IsContainer() || c.UsernsMode.IsNS() || len(c.IDMappings.UIDMap) > 0 || len(c.IDMappings.GIDMap) > 0 + return isRootless || (hasUserns && !c.UsernsMode.IsHost()) +} + +func (c *IpcConfig) ToCreateOptions(runtime *libpod.Runtime) ([]libpod.CtrCreateOption, error) { + options := make([]libpod.CtrCreateOption, 0) + if c.IpcMode.IsHost() { + options = append(options, libpod.WithShmDir("/dev/shm")) + } else if c.IpcMode.IsContainer() { + connectedCtr, err := runtime.LookupContainer(c.IpcMode.Container()) + if err != nil { + return nil, errors.Wrapf(err, "container %q not found", c.IpcMode.Container()) + } + + options = append(options, libpod.WithIPCNSFrom(connectedCtr)) + options = append(options, libpod.WithShmDir(connectedCtr.ShmDir())) + } + + return options, nil +} + +func (c *IpcConfig) ConfigureGenerator(g *generate.Generator) error { + ipcMode := c.IpcMode + if IsNS(string(ipcMode)) { + return g.AddOrReplaceLinuxNamespace(string(spec.IPCNamespace), NS(string(ipcMode))) + } + if ipcMode.IsHost() { + return g.RemoveLinuxNamespace(string(spec.IPCNamespace)) + } + if ipcCtr := ipcMode.Container(); ipcCtr != "" { + logrus.Debugf("Using container %s ipcmode", ipcCtr) + } + + return nil +} + +func (c *CgroupConfig) ConfigureGenerator(g *generate.Generator) error { + cgroupMode := c.CgroupMode + if cgroupMode.IsDefaultValue() { + // If the value is not specified, default to "private" on cgroups v2 and "host" on cgroups v1. + unified, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + return err + } + if unified { + cgroupMode = "private" + } else { + cgroupMode = "host" + } + } + if cgroupMode.IsNS() { + return g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), NS(string(cgroupMode))) + } + if cgroupMode.IsHost() { + return g.RemoveLinuxNamespace(string(spec.CgroupNamespace)) + } + if cgroupMode.IsPrivate() { + return g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), "") + } + if cgCtr := cgroupMode.Container(); cgCtr != "" { + logrus.Debugf("Using container %s cgroup mode", cgCtr) + } + return nil +} + +func (c *PidConfig) ToCreateOptions(runtime *libpod.Runtime) ([]libpod.CtrCreateOption, error) { + options := make([]libpod.CtrCreateOption, 0) + if c.PidMode.IsContainer() { + connectedCtr, err := runtime.LookupContainer(c.PidMode.Container()) + if err != nil { + return nil, errors.Wrapf(err, "container %q not found", c.PidMode.Container()) + } + + options = append(options, libpod.WithPIDNSFrom(connectedCtr)) + } + + return options, nil +} + +func (c *PidConfig) ConfigureGenerator(g *generate.Generator) error { + pidMode := c.PidMode + if IsNS(string(pidMode)) { + return g.AddOrReplaceLinuxNamespace(string(spec.PIDNamespace), NS(string(pidMode))) + } + if pidMode.IsHost() { + return g.RemoveLinuxNamespace(string(spec.PIDNamespace)) + } + if pidCtr := pidMode.Container(); pidCtr != "" { + logrus.Debugf("using container %s pidmode", pidCtr) + } + if IsPod(string(pidMode)) { + logrus.Debug("using pod pidmode") + } + return nil +} + +func (c *UtsConfig) ToCreateOptions(runtime *libpod.Runtime, pod *libpod.Pod) ([]libpod.CtrCreateOption, error) { + options := make([]libpod.CtrCreateOption, 0) + if IsPod(string(c.UtsMode)) { + options = append(options, libpod.WithUTSNSFromPod(pod)) + } + if c.UtsMode.IsContainer() { + connectedCtr, err := runtime.LookupContainer(c.UtsMode.Container()) + if err != nil { + return nil, errors.Wrapf(err, "container %q not found", c.UtsMode.Container()) + } + + options = append(options, libpod.WithUTSNSFrom(connectedCtr)) + } + if c.NoHosts { + options = append(options, libpod.WithUseImageHosts()) + } + if len(c.HostAdd) > 0 && !c.NoHosts { + options = append(options, libpod.WithHosts(c.HostAdd)) + } + + return options, nil +} + +func (c *UtsConfig) ConfigureGenerator(g *generate.Generator, net *NetworkConfig, runtime *libpod.Runtime) error { + hostname := c.Hostname + var err error + if hostname == "" { + if utsCtrID := c.UtsMode.Container(); utsCtrID != "" { + utsCtr, err := runtime.GetContainer(utsCtrID) + if err != nil { + return errors.Wrapf(err, "unable to retrieve hostname from dependency container %s", utsCtrID) + } + hostname = utsCtr.Hostname() + } else if net.NetMode.IsHost() || c.UtsMode.IsHost() { + hostname, err = os.Hostname() + if err != nil { + return errors.Wrap(err, "unable to retrieve hostname of the host") + } + } else { + logrus.Debug("No hostname set; container's hostname will default to runtime default") + } + } + g.RemoveHostname() + if c.Hostname != "" || !c.UtsMode.IsHost() { + // Set the hostname in the OCI configuration only + // if specified by the user or if we are creating + // a new UTS namespace. + g.SetHostname(hostname) + } + g.AddProcessEnv("HOSTNAME", hostname) + + utsMode := c.UtsMode + if IsNS(string(utsMode)) { + return g.AddOrReplaceLinuxNamespace(string(spec.UTSNamespace), NS(string(utsMode))) + } + if utsMode.IsHost() { + return g.RemoveLinuxNamespace(string(spec.UTSNamespace)) + } + if utsCtr := utsMode.Container(); utsCtr != "" { + logrus.Debugf("using container %s utsmode", utsCtr) + } + return nil +} diff --git a/pkg/spec/security.go b/pkg/spec/security.go new file mode 100644 index 000000000..05ed94e66 --- /dev/null +++ b/pkg/spec/security.go @@ -0,0 +1,172 @@ +package createconfig + +import ( + "fmt" + "strings" + + "github.com/containers/libpod/libpod" + "github.com/docker/docker/oci/caps" + "github.com/opencontainers/runtime-tools/generate" + "github.com/opencontainers/selinux/go-selinux/label" + "github.com/pkg/errors" +) + +func (c *SecurityConfig) ToCreateOptions() ([]libpod.CtrCreateOption, error) { + options := make([]libpod.CtrCreateOption, 0) + options = append(options, libpod.WithSecLabels(c.LabelOpts)) + options = append(options, libpod.WithPrivileged(c.Privileged)) + return options, nil +} + +func (c *SecurityConfig) SetLabelOpts(runtime *libpod.Runtime, pidConfig *PidConfig, ipcConfig *IpcConfig) error { + if c.Privileged { + c.LabelOpts = label.DisableSecOpt() + return nil + } + + var labelOpts []string + if pidConfig.PidMode.IsHost() { + labelOpts = append(labelOpts, label.DisableSecOpt()...) + } else if pidConfig.PidMode.IsContainer() { + ctr, err := runtime.LookupContainer(pidConfig.PidMode.Container()) + if err != nil { + return errors.Wrapf(err, "container %q not found", pidConfig.PidMode.Container()) + } + secopts, err := label.DupSecOpt(ctr.ProcessLabel()) + if err != nil { + return errors.Wrapf(err, "failed to duplicate label %q ", ctr.ProcessLabel()) + } + labelOpts = append(labelOpts, secopts...) + } + + if ipcConfig.IpcMode.IsHost() { + labelOpts = append(labelOpts, label.DisableSecOpt()...) + } else if ipcConfig.IpcMode.IsContainer() { + ctr, err := runtime.LookupContainer(ipcConfig.IpcMode.Container()) + if err != nil { + return errors.Wrapf(err, "container %q not found", ipcConfig.IpcMode.Container()) + } + secopts, err := label.DupSecOpt(ctr.ProcessLabel()) + if err != nil { + return errors.Wrapf(err, "failed to duplicate label %q ", ctr.ProcessLabel()) + } + labelOpts = append(labelOpts, secopts...) + } + + c.LabelOpts = append(c.LabelOpts, labelOpts...) + return nil +} + +func (c *SecurityConfig) SetSecurityOpts(runtime *libpod.Runtime, securityOpts []string) error { + for _, opt := range securityOpts { + if opt == "no-new-privileges" { + c.NoNewPrivs = true + } else { + con := strings.SplitN(opt, "=", 2) + if len(con) != 2 { + return fmt.Errorf("invalid --security-opt 1: %q", opt) + } + + switch con[0] { + case "label": + c.LabelOpts = append(c.LabelOpts, con[1]) + case "apparmor": + c.ApparmorProfile = con[1] + case "seccomp": + c.SeccompProfilePath = con[1] + default: + return fmt.Errorf("invalid --security-opt 2: %q", opt) + } + } + } + + if c.SeccompProfilePath == "" { + var err error + c.SeccompProfilePath, err = libpod.DefaultSeccompPath() + if err != nil { + return err + } + } + c.SecurityOpts = securityOpts + return nil +} + +func (c *SecurityConfig) ConfigureGenerator(g *generate.Generator, user *UserConfig) error { + // HANDLE CAPABILITIES + // NOTE: Must happen before SECCOMP + if c.Privileged { + g.SetupPrivileged(true) + } + + useNotRoot := func(user string) bool { + if user == "" || user == "root" || user == "0" { + return false + } + return true + } + + configSpec := g.Config + var err error + var caplist []string + bounding := configSpec.Process.Capabilities.Bounding + if useNotRoot(user.User) { + configSpec.Process.Capabilities.Bounding = caplist + } + caplist, err = caps.TweakCapabilities(configSpec.Process.Capabilities.Bounding, c.CapAdd, c.CapDrop, nil, false) + 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(user.User) { + caplist, err = caps.TweakCapabilities(bounding, c.CapAdd, c.CapDrop, nil, false) + if err != nil { + return err + } + } + configSpec.Process.Capabilities.Bounding = caplist + + // HANDLE SECCOMP + if c.SeccompProfilePath != "unconfined" { + seccompConfig, err := getSeccompConfig(c, configSpec) + if err != nil { + return err + } + configSpec.Linux.Seccomp = seccompConfig + } + + // Clear default Seccomp profile from Generator for privileged containers + if c.SeccompProfilePath == "unconfined" || c.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.AddLinuxSysctl(sysctlKey, sysctlVal) + } + + return nil +} diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index 86d701f7e..7a220012f 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -1,7 +1,6 @@ package createconfig import ( - "os" "strings" "github.com/containers/libpod/libpod" @@ -10,13 +9,11 @@ import ( "github.com/containers/libpod/pkg/cgroups" "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/sysinfo" - "github.com/docker/docker/oci/caps" "github.com/docker/go-units" "github.com/opencontainers/runc/libcontainer/user" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" "github.com/pkg/errors" - "github.com/sirupsen/logrus" ) const cpuPeriod = 100000 @@ -47,14 +44,13 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM canMountSys := true isRootless := rootless.IsRootless() - hasUserns := config.UsernsMode.IsContainer() || config.UsernsMode.IsNS() || len(config.IDMappings.UIDMap) > 0 || len(config.IDMappings.GIDMap) > 0 - inUserNS := isRootless || (hasUserns && !config.UsernsMode.IsHost()) + inUserNS := config.User.InNS(isRootless) - if inUserNS && config.NetMode.IsHost() { + if inUserNS && config.Network.NetMode.IsHost() { canMountSys = false } - if config.Privileged && canMountSys { + if config.Security.Privileged && canMountSys { cgroupPerm = "rw" g.RemoveMount("/sys") sysMnt := spec.Mount{ @@ -68,7 +64,7 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM addCgroup = false g.RemoveMount("/sys") r := "ro" - if config.Privileged { + if config.Security.Privileged { r = "rw" } sysMnt := spec.Mount{ @@ -78,7 +74,7 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM Options: []string{"rprivate", "nosuid", "noexec", "nodev", r, "rbind"}, } g.AddMount(sysMnt) - if !config.Privileged && isRootless { + if !config.Security.Privileged && isRootless { g.AddLinuxMaskedPaths("/sys/kernel") } } @@ -92,9 +88,9 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM } // When using a different user namespace, check that the GID 5 is mapped inside // the container. - if gid5Available && len(config.IDMappings.GIDMap) > 0 { + if gid5Available && len(config.User.IDMappings.GIDMap) > 0 { mappingFound := false - for _, r := range config.IDMappings.GIDMap { + for _, r := range config.User.IDMappings.GIDMap { if r.ContainerID <= 5 && 5 < r.ContainerID+r.Size { mappingFound = true break @@ -117,7 +113,7 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM g.AddMount(devPts) } - if inUserNS && config.IpcMode.IsHost() { + if inUserNS && config.Ipc.IpcMode.IsHost() { g.RemoveMount("/dev/mqueue") devMqueue := spec.Mount{ Destination: "/dev/mqueue", @@ -127,7 +123,7 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM } g.AddMount(devMqueue) } - if inUserNS && config.PidMode.IsHost() { + if inUserNS && config.Pid.PidMode.IsHost() { g.RemoveMount("/proc") procMount := spec.Mount{ Destination: "/proc", @@ -154,55 +150,6 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM for key, val := range config.Annotations { g.AddAnnotation(key, val) } - g.SetRootReadonly(config.ReadOnlyRootfs) - - if config.HTTPProxy { - for _, envSpec := range []string{ - "http_proxy", - "HTTP_PROXY", - "https_proxy", - "HTTPS_PROXY", - "ftp_proxy", - "FTP_PROXY", - "no_proxy", - "NO_PROXY", - } { - envVal := os.Getenv(envSpec) - if envVal != "" { - g.AddProcessEnv(envSpec, envVal) - } - } - } - - hostname := config.Hostname - if hostname == "" { - if utsCtrID := config.UtsMode.Container(); utsCtrID != "" { - utsCtr, err := runtime.GetContainer(utsCtrID) - if err != nil { - return nil, errors.Wrapf(err, "unable to retrieve hostname from dependency container %s", utsCtrID) - } - hostname = utsCtr.Hostname() - } else if config.NetMode.IsHost() || config.UtsMode.IsHost() { - hostname, err = os.Hostname() - if err != nil { - return nil, errors.Wrap(err, "unable to retrieve hostname of the host") - } - } else { - logrus.Debug("No hostname set; container's hostname will default to runtime default") - } - } - g.RemoveHostname() - if config.Hostname != "" || !config.UtsMode.IsHost() { - // Set the hostname in the OCI configuration only - // if specified by the user or if we are creating - // a new UTS namespace. - g.SetHostname(hostname) - } - g.AddProcessEnv("HOSTNAME", hostname) - - for sysctlKey, sysctlVal := range config.Sysctl { - g.AddLinuxSysctl(sysctlKey, sysctlVal) - } g.AddProcessEnv("container", "podman") addedResources := false @@ -272,7 +219,7 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM } // Devices - if config.Privileged { + if config.Security.Privileged { // If privileged, we need to add all the host devices to the // spec. We do not add the user provided ones because we are // already adding them all. @@ -287,17 +234,11 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM } } - for _, uidmap := range config.IDMappings.UIDMap { - g.AddLinuxUIDMapping(uint32(uidmap.HostID), uint32(uidmap.ContainerID), uint32(uidmap.Size)) - } - for _, gidmap := range config.IDMappings.GIDMap { - g.AddLinuxGIDMapping(uint32(gidmap.HostID), uint32(gidmap.ContainerID), uint32(gidmap.Size)) - } // SECURITY OPTS - g.SetProcessNoNewPrivileges(config.NoNewPrivs) + g.SetProcessNoNewPrivileges(config.Security.NoNewPrivs) - if !config.Privileged { - g.SetProcessApparmorProfile(config.ApparmorProfile) + if !config.Security.Privileged { + g.SetProcessApparmorProfile(config.Security.ApparmorProfile) } blockAccessToKernelFilesystems(config, &g) @@ -341,54 +282,35 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM return nil, err } - if err := addPidNS(config, &g); err != nil { + // NAMESPACES + + if err := config.Pid.ConfigureGenerator(&g); err != nil { return nil, err } - if err := addUserNS(config, &g); err != nil { + if err := config.User.ConfigureGenerator(&g); err != nil { return nil, err } - if err := addNetNS(config, &g); err != nil { + if err := config.Network.ConfigureGenerator(&g); err != nil { return nil, err } - if err := addUTSNS(config, &g); err != nil { + if err := config.Uts.ConfigureGenerator(&g, &config.Network, runtime); err != nil { return nil, err } - if err := addIpcNS(config, &g); err != nil { + if err := config.Ipc.ConfigureGenerator(&g); err != nil { return nil, err } - if err := addCgroupNS(config, &g); err != nil { + if err := config.Cgroup.ConfigureGenerator(&g); err != nil { return nil, err } configSpec := g.Config - // HANDLE CAPABILITIES - // NOTE: Must happen before SECCOMP - if !config.Privileged { - if err := setupCapabilities(config, configSpec); err != nil { - return nil, err - } - } else { - g.SetupPrivileged(true) - } - - // HANDLE SECCOMP - - if config.SeccompProfilePath != "unconfined" { - seccompConfig, err := getSeccompConfig(config, configSpec) - if err != nil { - return nil, err - } - configSpec.Linux.Seccomp = seccompConfig - } - - // Clear default Seccomp profile from Generator for privileged containers - if config.SeccompProfilePath == "unconfined" || config.Privileged { - configSpec.Linux.Seccomp = nil + if err := config.Security.ConfigureGenerator(&g, &config.User); err != nil { + return nil, err } // BIND MOUNTS @@ -430,7 +352,7 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM } } - switch config.Cgroups { + switch config.Cgroup.Cgroups { case "disabled": if addedResources { return nil, errors.New("cannot specify resource limits when cgroups are disabled is specified") @@ -461,48 +383,23 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM configSpec.Annotations[libpod.InspectAnnotationVolumesFrom] = strings.Join(config.VolumesFrom, ",") } - if config.Privileged { + if config.Security.Privileged { configSpec.Annotations[libpod.InspectAnnotationPrivileged] = libpod.InspectResponseTrue } else { configSpec.Annotations[libpod.InspectAnnotationPrivileged] = libpod.InspectResponseFalse } - if config.PublishAll { - configSpec.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseTrue - } else { - configSpec.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseFalse - } - if config.Init { configSpec.Annotations[libpod.InspectAnnotationInit] = libpod.InspectResponseTrue } else { configSpec.Annotations[libpod.InspectAnnotationInit] = libpod.InspectResponseFalse } - for _, opt := range config.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] - } - } - return configSpec, nil } func blockAccessToKernelFilesystems(config *CreateConfig, g *generate.Generator) { - if !config.Privileged { + if !config.Security.Privileged { for _, mp := range []string{ "/proc/acpi", "/proc/kcore", @@ -518,7 +415,7 @@ func blockAccessToKernelFilesystems(config *CreateConfig, g *generate.Generator) g.AddLinuxMaskedPaths(mp) } - if config.PidMode.IsHost() && rootless.IsRootless() { + if config.Pid.PidMode.IsHost() && rootless.IsRootless() { return } @@ -535,117 +432,6 @@ func blockAccessToKernelFilesystems(config *CreateConfig, g *generate.Generator) } } -func addPidNS(config *CreateConfig, g *generate.Generator) error { - pidMode := config.PidMode - if IsNS(string(pidMode)) { - return g.AddOrReplaceLinuxNamespace(string(spec.PIDNamespace), NS(string(pidMode))) - } - if pidMode.IsHost() { - return g.RemoveLinuxNamespace(string(spec.PIDNamespace)) - } - if pidCtr := pidMode.Container(); pidCtr != "" { - logrus.Debugf("using container %s pidmode", pidCtr) - } - if IsPod(string(pidMode)) { - logrus.Debug("using pod pidmode") - } - return nil -} - -func addUserNS(config *CreateConfig, g *generate.Generator) error { - if IsNS(string(config.UsernsMode)) { - if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), NS(string(config.UsernsMode))); 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 (len(config.IDMappings.UIDMap) > 0 || len(config.IDMappings.GIDMap) > 0) && !config.UsernsMode.IsHost() { - if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), ""); err != nil { - return err - } - } - return nil -} - -func addNetNS(config *CreateConfig, g *generate.Generator) error { - netMode := config.NetMode - if netMode.IsHost() { - logrus.Debug("Using host netmode") - return g.RemoveLinuxNamespace(string(spec.NetworkNamespace)) - } else if netMode.IsNone() { - logrus.Debug("Using none netmode") - return nil - } else if netMode.IsBridge() { - logrus.Debug("Using bridge netmode") - return nil - } else if netCtr := netMode.Container(); netCtr != "" { - logrus.Debugf("using container %s netmode", netCtr) - return nil - } else if IsNS(string(netMode)) { - logrus.Debug("Using ns netmode") - return g.AddOrReplaceLinuxNamespace(string(spec.NetworkNamespace), NS(string(netMode))) - } else if IsPod(string(netMode)) { - logrus.Debug("Using pod netmode, unless pod is not sharing") - return nil - } else if netMode.IsSlirp4netns() { - logrus.Debug("Using slirp4netns netmode") - return nil - } else if netMode.IsUserDefined() { - logrus.Debug("Using user defined netmode") - return nil - } - return errors.Errorf("unknown network mode") -} - -func addUTSNS(config *CreateConfig, g *generate.Generator) error { - utsMode := config.UtsMode - if IsNS(string(utsMode)) { - return g.AddOrReplaceLinuxNamespace(string(spec.UTSNamespace), NS(string(utsMode))) - } - if utsMode.IsHost() { - return g.RemoveLinuxNamespace(string(spec.UTSNamespace)) - } - if utsCtr := utsMode.Container(); utsCtr != "" { - logrus.Debugf("using container %s utsmode", utsCtr) - } - return nil -} - -func addIpcNS(config *CreateConfig, g *generate.Generator) error { - ipcMode := config.IpcMode - if IsNS(string(ipcMode)) { - return g.AddOrReplaceLinuxNamespace(string(spec.IPCNamespace), NS(string(ipcMode))) - } - if ipcMode.IsHost() { - return g.RemoveLinuxNamespace(string(spec.IPCNamespace)) - } - if ipcCtr := ipcMode.Container(); ipcCtr != "" { - logrus.Debugf("Using container %s ipcmode", ipcCtr) - } - - return nil -} - -func addCgroupNS(config *CreateConfig, g *generate.Generator) error { - cgroupMode := config.CgroupMode - if cgroupMode.IsNS() { - return g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), NS(string(cgroupMode))) - } - if cgroupMode.IsHost() { - return g.RemoveLinuxNamespace(string(spec.CgroupNamespace)) - } - if cgroupMode.IsPrivate() { - return g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), "") - } - if cgCtr := cgroupMode.Container(); cgCtr != "" { - logrus.Debugf("Using container %s cgroup mode", cgCtr) - } - return nil -} - func addRlimits(config *CreateConfig, g *generate.Generator) error { var ( kernelMax uint64 = 1048576 @@ -689,37 +475,3 @@ func addRlimits(config *CreateConfig, g *generate.Generator) error { return nil } - -func setupCapabilities(config *CreateConfig, configSpec *spec.Spec) error { - useNotRoot := func(user string) bool { - if user == "" || user == "root" || user == "0" { - return false - } - return true - } - - var err error - var caplist []string - bounding := configSpec.Process.Capabilities.Bounding - if useNotRoot(config.User) { - configSpec.Process.Capabilities.Bounding = caplist - } - caplist, err = caps.TweakCapabilities(configSpec.Process.Capabilities.Bounding, config.CapAdd, config.CapDrop, nil, false) - 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(config.User) { - caplist, err = caps.TweakCapabilities(bounding, config.CapAdd, config.CapDrop, nil, false) - if err != nil { - return err - } - } - configSpec.Process.Capabilities.Bounding = caplist - return nil -} diff --git a/pkg/spec/spec_test.go b/pkg/spec/spec_test.go index 2f91e1b21..0f63b2bbc 100644 --- a/pkg/spec/spec_test.go +++ b/pkg/spec/spec_test.go @@ -21,9 +21,9 @@ var ( func makeTestCreateConfig() *CreateConfig { cc := new(CreateConfig) cc.Resources = CreateResourceConfig{} - cc.IDMappings = new(storage.IDMappingOptions) - cc.IDMappings.UIDMap = []idtools.IDMap{} - cc.IDMappings.GIDMap = []idtools.IDMap{} + cc.User.IDMappings = new(storage.IDMappingOptions) + cc.User.IDMappings.UIDMap = []idtools.IDMap{} + cc.User.IDMappings.GIDMap = []idtools.IDMap{} return cc } diff --git a/pkg/spec/storage.go b/pkg/spec/storage.go index 095534589..79c065b5d 100644 --- a/pkg/spec/storage.go +++ b/pkg/spec/storage.go @@ -160,7 +160,7 @@ func (config *CreateConfig) parseVolumes(runtime *libpod.Runtime) ([]spec.Mount, } // If requested, add tmpfs filesystems for read-only containers. - if config.ReadOnlyRootfs && config.ReadOnlyTmpfs { + if config.Security.ReadOnlyRootfs && config.Security.ReadOnlyTmpfs { readonlyTmpfs := []string{"/tmp", "/var/tmp", "/run"} options := []string{"rw", "rprivate", "nosuid", "nodev", "tmpcopyup"} for _, dest := range readonlyTmpfs { @@ -514,11 +514,17 @@ func getTmpfsMount(args []string) (spec.Mount, error) { Source: TypeTmpfs, } - var setDest, setRORW, setSuid, setDev, setExec bool + 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") @@ -801,7 +807,7 @@ func (config *CreateConfig) addContainerInitBinary(path string) (spec.Mount, err if path == "" { return mount, fmt.Errorf("please specify a path to the container-init binary") } - if !config.PidMode.IsPrivate() { + if !config.Pid.PidMode.IsPrivate() { return mount, fmt.Errorf("cannot add init binary as PID 1 (PID namespace isn't private)") } if config.Systemd { diff --git a/pkg/util/mountOpts.go b/pkg/util/mountOpts.go index 670daeaf9..d21800bc3 100644 --- a/pkg/util/mountOpts.go +++ b/pkg/util/mountOpts.go @@ -30,6 +30,8 @@ func ProcessOptions(options []string, isTmpfs bool, defaults *DefaultMountOption foundWrite, foundSize, foundProp, foundMode, foundExec, foundSuid, foundDev, foundCopyUp, foundBind, foundZ bool ) + var newOptions []string + for _, opt := range options { // Some options have parameters - size, mode splitOpt := strings.SplitN(opt, "=", 2) @@ -80,9 +82,19 @@ func ProcessOptions(options []string, isTmpfs bool, defaults *DefaultMountOption return nil, errors.Wrapf(ErrBadMntOption, "the 'tmpcopyup' option is only allowed with tmpfs mounts") } if foundCopyUp { - return nil, errors.Wrapf(ErrDupeMntOption, "the 'tmpcopyup' option can only be set once") + return nil, errors.Wrapf(ErrDupeMntOption, "the 'tmpcopyup' or 'notmpcopyup' option can only be set once") + } + foundCopyUp = true + case "notmpcopyup": + if !isTmpfs { + return nil, errors.Wrapf(ErrBadMntOption, "the 'notmpcopyup' option is only allowed with tmpfs mounts") + } + if foundCopyUp { + return nil, errors.Wrapf(ErrDupeMntOption, "the 'tmpcopyup' or 'notmpcopyup' option can only be set once") } foundCopyUp = true + // do not propagate notmpcopyup to the OCI runtime + continue case "bind", "rbind": if isTmpfs { return nil, errors.Wrapf(ErrBadMntOption, "the 'bind' and 'rbind' options are not allowed with tmpfs mounts") @@ -101,29 +113,30 @@ func ProcessOptions(options []string, isTmpfs bool, defaults *DefaultMountOption default: return nil, errors.Wrapf(ErrBadMntOption, "unknown mount option %q", opt) } + newOptions = append(newOptions, opt) } if !foundWrite { - options = append(options, "rw") + newOptions = append(newOptions, "rw") } if !foundProp { - options = append(options, "rprivate") + newOptions = append(newOptions, "rprivate") } if !foundExec && (defaults == nil || defaults.Noexec) { - options = append(options, "noexec") + newOptions = append(newOptions, "noexec") } if !foundSuid && (defaults == nil || defaults.Nosuid) { - options = append(options, "nosuid") + newOptions = append(newOptions, "nosuid") } if !foundDev && (defaults == nil || defaults.Nodev) { - options = append(options, "nodev") + newOptions = append(newOptions, "nodev") } if isTmpfs && !foundCopyUp { - options = append(options, "tmpcopyup") + newOptions = append(newOptions, "tmpcopyup") } if !isTmpfs && !foundBind { - options = append(options, "rbind") + newOptions = append(newOptions, "rbind") } - return options, nil + return newOptions, nil } |