From dcf3c742b1ac4d641d66810113f3d17441a412f4 Mon Sep 17 00:00:00 2001 From: Peter Hunt Date: Wed, 11 Sep 2019 16:50:02 -0400 Subject: Split up create config handling of namespaces and security As it stands, createconfig is a huge struct. This works fine when the only caller is when we create a container with a fully created config. However, if we wish to share code for security and namespace configuration, a single large struct becomes unweildy, as well as difficult to configure with the single createConfigToOCISpec function. This PR breaks up namespace and security configuration into their own structs, with the eventual goal of allowing the namespace/security fields to be configured by the pod create cli, and allow the infra container to share this with the pod's containers. Signed-off-by: Peter Hunt --- pkg/spec/config_linux_cgo.go | 2 +- pkg/spec/config_linux_nocgo.go | 2 +- pkg/spec/config_unsupported.go | 2 +- pkg/spec/createconfig.go | 420 ++++++++++++++------------------------- pkg/spec/namespaces.go | 433 +++++++++++++++++++++++++++++++++++++++++ pkg/spec/security.go | 172 ++++++++++++++++ pkg/spec/spec.go | 315 +++--------------------------- pkg/spec/spec_test.go | 6 +- pkg/spec/storage.go | 4 +- 9 files changed, 789 insertions(+), 567 deletions(-) create mode 100644 pkg/spec/namespaces.go create mode 100644 pkg/spec/security.go (limited to 'pkg/spec') 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 e054b3b13..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,187 +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)) - } - - 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)) - } + // TODO: MNT, USER, CGROUP + options = append(options, libpod.WithStopSignal(c.StopSignal)) + options = append(options, libpod.WithStopTimeout(c.StopTimeout)) - 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)) + logPath := getLoggingPath(c.LogDriverOpt) + if logPath != "" { + options = append(options, libpod.WithLogPath(logPath)) } - 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)) + if c.LogDriver != "" { + options = append(options, libpod.WithLogDriver(c.LogDriver)) } - 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)) + secOpts, err := c.Security.ToCreateOptions() + if err != nil { + return nil, err } + options = append(options, secOpts...) - if IsPod(string(c.UtsMode)) { - options = append(options, libpod.WithUTSNSFromPod(pod)) + nsOpts, err := c.Cgroup.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.Ipc.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.Pid.ToCreateOptions(runtime) + if err != nil { + return nil, err } + options = append(options, nsOpts...) - if c.LogDriver != "" { - options = append(options, libpod.WithLogDriver(c.LogDriver)) + nsOpts, err = c.Network.ToCreateOptions(runtime, &c.User) + 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.Uts.ToCreateOptions(runtime, pod) + if err != nil { + return nil, err } + options = append(options, nsOpts...) - 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)) + 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") @@ -467,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 33e9ec076..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,130 +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.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 addRlimits(config *CreateConfig, g *generate.Generator) error { var ( kernelMax uint64 = 1048576 @@ -702,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 e30bdfc67..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 { @@ -807,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 { -- cgit v1.2.3-54-g00ecf