diff options
Diffstat (limited to 'pkg/specgen')
-rw-r--r-- | pkg/specgen/generate/container_create.go | 8 | ||||
-rw-r--r-- | pkg/specgen/generate/namespaces.go | 73 | ||||
-rw-r--r-- | pkg/specgen/generate/oci.go | 9 | ||||
-rw-r--r-- | pkg/specgen/generate/pod_create.go | 6 | ||||
-rw-r--r-- | pkg/specgen/generate/ports.go | 333 | ||||
-rw-r--r-- | pkg/specgen/generate/security.go | 92 | ||||
-rw-r--r-- | pkg/specgen/namespaces.go | 39 | ||||
-rw-r--r-- | pkg/specgen/podspecgen.go | 4 | ||||
-rw-r--r-- | pkg/specgen/specgen.go | 52 |
9 files changed, 502 insertions, 114 deletions
diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 14836035d..be54b60d2 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -96,7 +96,7 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener return nil, err } - opts, err := createContainerOptions(rt, s, pod, finalVolumes) + opts, err := createContainerOptions(ctx, rt, s, pod, finalVolumes, newImage) if err != nil { return nil, err } @@ -115,7 +115,7 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener return rt.NewContainer(ctx, runtimeSpec, options...) } -func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *libpod.Pod, volumes []*specgen.NamedVolume) ([]libpod.CtrCreateOption, error) { +func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGenerator, pod *libpod.Pod, volumes []*specgen.NamedVolume, img *image.Image) ([]libpod.CtrCreateOption, error) { var options []libpod.CtrCreateOption var err error @@ -134,7 +134,7 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l options = append(options, rt.WithPod(pod)) } destinations := []string{} - // // Take all mount and named volume destinations. + // Take all mount and named volume destinations. for _, mount := range s.Mounts { destinations = append(destinations, mount.Destination) } @@ -188,7 +188,7 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l options = append(options, libpod.WithPrivileged(s.Privileged)) // Get namespace related options - namespaceOptions, err := GenerateNamespaceOptions(s, rt, pod) + namespaceOptions, err := GenerateNamespaceOptions(ctx, s, rt, pod, img) if err != nil { return nil, err } diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go index a8b74b504..96c65b551 100644 --- a/pkg/specgen/generate/namespaces.go +++ b/pkg/specgen/generate/namespaces.go @@ -1,13 +1,14 @@ package generate import ( + "context" "os" "strings" "github.com/containers/common/pkg/config" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/pkg/cgroups" + "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/specgen" "github.com/containers/libpod/pkg/util" @@ -49,51 +50,26 @@ func GetDefaultNamespaceMode(nsType string, cfg *config.Config, pod *libpod.Pod) } } - // If we have containers.conf and are not using cgroupns, use that. - if cfg != nil && nsType != "cgroup" { - switch nsType { - case "pid": - return specgen.ParseNamespace(cfg.Containers.PidNS) - case "ipc": - return specgen.ParseNamespace(cfg.Containers.IPCNS) - case "uts": - return specgen.ParseNamespace(cfg.Containers.UTSNS) - case "user": - return specgen.ParseUserNamespace(cfg.Containers.UserNS) - case "net": - ns, _, err := specgen.ParseNetworkNamespace(cfg.Containers.NetNS) - return ns, err - } + if cfg == nil { + cfg = &config.Config{} } - switch nsType { - case "pid", "ipc", "uts": - // PID, IPC, UTS both default to private, do nothing + case "pid": + return specgen.ParseNamespace(cfg.Containers.PidNS) + case "ipc": + return specgen.ParseNamespace(cfg.Containers.IPCNS) + case "uts": + return specgen.ParseNamespace(cfg.Containers.UTSNS) case "user": - // User namespace always defaults to host - toReturn.NSMode = specgen.Host - case "net": - // Net defaults to Slirp on rootless, Bridge otherwise. - if rootless.IsRootless() { - toReturn.NSMode = specgen.Slirp - } else { - toReturn.NSMode = specgen.Bridge - } + return specgen.ParseUserNamespace(cfg.Containers.UserNS) case "cgroup": - // Cgroup is host for v1, private for v2. - // We can't trust c/common for this, as it only assumes private. - cgroupsv2, err := cgroups.IsCgroup2UnifiedMode() - if err != nil { - return toReturn, err - } - if !cgroupsv2 { - toReturn.NSMode = specgen.Host - } - default: - return toReturn, errors.Wrapf(define.ErrInvalidArg, "invalid namespace type %s passed", nsType) + return specgen.ParseCgroupNamespace(cfg.Containers.CgroupNS) + case "net": + ns, _, err := specgen.ParseNetworkNamespace(cfg.Containers.NetNS) + return ns, err } - return toReturn, nil + return toReturn, errors.Wrapf(define.ErrInvalidArg, "invalid namespace type %q passed", nsType) } // GenerateNamespaceOptions generates container creation options for all @@ -102,7 +78,7 @@ func GetDefaultNamespaceMode(nsType string, cfg *config.Config, pod *libpod.Pod) // joining a pod. // TODO: Consider grouping options that are not directly attached to a namespace // elsewhere. -func GenerateNamespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod.Pod) ([]libpod.CtrCreateOption, error) { +func GenerateNamespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod.Pod, img *image.Image) ([]libpod.CtrCreateOption, error) { toReturn := []libpod.CtrCreateOption{} // If pod is not nil, get infra container. @@ -230,7 +206,6 @@ func GenerateNamespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod } // Net - // TODO image ports // TODO validate CNINetworks, StaticIP, StaticIPv6 are only set if we // are in bridge mode. postConfigureNetNS := !s.UserNS.IsHost() @@ -247,9 +222,17 @@ func GenerateNamespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod } toReturn = append(toReturn, libpod.WithNetNSFrom(netCtr)) case specgen.Slirp: - toReturn = append(toReturn, libpod.WithNetNS(s.PortMappings, postConfigureNetNS, "slirp4netns", nil)) + portMappings, err := createPortMappings(ctx, s, img) + if err != nil { + return nil, err + } + toReturn = append(toReturn, libpod.WithNetNS(portMappings, postConfigureNetNS, "slirp4netns", nil)) case specgen.Bridge: - toReturn = append(toReturn, libpod.WithNetNS(s.PortMappings, postConfigureNetNS, "bridge", s.CNINetworks)) + portMappings, err := createPortMappings(ctx, s, img) + if err != nil { + return nil, err + } + toReturn = append(toReturn, libpod.WithNetNS(portMappings, postConfigureNetNS, "bridge", s.CNINetworks)) } if s.UseImageHosts { @@ -454,7 +437,7 @@ func specConfigureNamespaces(s *specgen.SpecGenerator, g *generate.Generator, rt if g.Config.Annotations == nil { g.Config.Annotations = make(map[string]string) } - if s.PublishImagePorts { + if s.PublishExposedPorts { g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseTrue } else { g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseFalse diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index 7993777fb..8136c0993 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -267,6 +267,13 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt return nil, err } } else { + // add default devices from containers.conf + for _, device := range rtc.Containers.Devices { + if err := DevicesFromPath(&g, device); err != nil { + return nil, err + } + } + // add default devices specified by caller for _, device := range s.Devices { if err := DevicesFromPath(&g, device.Path); err != nil { return nil, err @@ -297,7 +304,7 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt } configSpec := g.Config - if err := securityConfigureGenerator(s, &g, newImage); err != nil { + if err := securityConfigureGenerator(s, &g, newImage, rtc); err != nil { return nil, err } diff --git a/pkg/specgen/generate/pod_create.go b/pkg/specgen/generate/pod_create.go index babfba9bc..df5775f8b 100644 --- a/pkg/specgen/generate/pod_create.go +++ b/pkg/specgen/generate/pod_create.go @@ -83,7 +83,11 @@ func createPodOptions(p *specgen.PodSpecGenerator) ([]libpod.PodCreateOption, er options = append(options, libpod.WithPodUseImageHosts()) } if len(p.PortMappings) > 0 { - options = append(options, libpod.WithInfraContainerPorts(p.PortMappings)) + ports, _, _, err := parsePortMapping(p.PortMappings) + if err != nil { + return nil, err + } + options = append(options, libpod.WithInfraContainerPorts(ports)) } options = append(options, libpod.WithPodCgroups()) return options, nil diff --git a/pkg/specgen/generate/ports.go b/pkg/specgen/generate/ports.go new file mode 100644 index 000000000..91c8e68d1 --- /dev/null +++ b/pkg/specgen/generate/ports.go @@ -0,0 +1,333 @@ +package generate + +import ( + "context" + "net" + "strconv" + "strings" + + "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/specgen" + "github.com/cri-o/ocicni/pkg/ocicni" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +const ( + protoTCP = "tcp" + protoUDP = "udp" + protoSCTP = "sctp" +) + +// Parse port maps to OCICNI port mappings. +// Returns a set of OCICNI port mappings, and maps of utilized container and +// host ports. +func parsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping, map[string]map[string]map[uint16]uint16, map[string]map[string]map[uint16]uint16, error) { + // First, we need to validate the ports passed in the specgen, and then + // convert them into CNI port mappings. + finalMappings := []ocicni.PortMapping{} + + // To validate, we need two maps: one for host ports, one for container + // ports. + // Each is a map of protocol to map of IP address to map of port to + // port (for hostPortValidate, it's host port to container port; + // for containerPortValidate, container port to host port. + // These will ensure no collisions. + hostPortValidate := make(map[string]map[string]map[uint16]uint16) + containerPortValidate := make(map[string]map[string]map[uint16]uint16) + + // Initialize the first level of maps (we can't really guess keys for + // the rest). + for _, proto := range []string{protoTCP, protoUDP, protoSCTP} { + hostPortValidate[proto] = make(map[string]map[uint16]uint16) + containerPortValidate[proto] = make(map[string]map[uint16]uint16) + } + + // Iterate through all port mappings, generating OCICNI PortMapping + // structs and validating there is no overlap. + for _, port := range portMappings { + // First, check proto + protocols, err := checkProtocol(port.Protocol, true) + if err != nil { + return nil, nil, nil, err + } + + // Validate host IP + hostIP := port.HostIP + if hostIP == "" { + hostIP = "0.0.0.0" + } + if ip := net.ParseIP(hostIP); ip == nil { + return nil, nil, nil, errors.Errorf("invalid IP address %s in port mapping", port.HostIP) + } + + // Validate port numbers and range. + len := port.Range + if len == 0 { + len = 1 + } + containerPort := port.ContainerPort + if containerPort == 0 { + return nil, nil, nil, errors.Errorf("container port number must be non-0") + } + hostPort := port.HostPort + if hostPort == 0 { + hostPort = containerPort + } + if uint32(len-1)+uint32(containerPort) > 65535 { + return nil, nil, nil, errors.Errorf("container port range exceeds maximum allowable port number") + } + if uint32(len-1)+uint32(hostPort) > 65536 { + return nil, nil, nil, errors.Errorf("host port range exceeds maximum allowable port number") + } + + // Iterate through ports, populating maps to check for conflicts + // and generating CNI port mappings. + for _, p := range protocols { + hostIPMap := hostPortValidate[p] + ctrIPMap := containerPortValidate[p] + + hostPortMap, ok := hostIPMap[hostIP] + if !ok { + hostPortMap = make(map[uint16]uint16) + hostIPMap[hostIP] = hostPortMap + } + ctrPortMap, ok := ctrIPMap[hostIP] + if !ok { + ctrPortMap = make(map[uint16]uint16) + ctrIPMap[hostIP] = ctrPortMap + } + + // Iterate through all port numbers in the requested + // range. + var index uint16 + for index = 0; index < len; index++ { + cPort := containerPort + index + hPort := hostPort + index + + if cPort == 0 || hPort == 0 { + return nil, nil, nil, errors.Errorf("host and container ports cannot be 0") + } + + testCPort := ctrPortMap[cPort] + if testCPort != 0 && testCPort != hPort { + // This is an attempt to redefine a port + return nil, nil, nil, errors.Errorf("conflicting port mappings for container port %d (protocol %s)", cPort, p) + } + ctrPortMap[cPort] = hPort + + testHPort := hostPortMap[hPort] + if testHPort != 0 && testHPort != cPort { + return nil, nil, nil, errors.Errorf("conflicting port mappings for host port %d (protocol %s)", hPort, p) + } + hostPortMap[hPort] = cPort + + // If we have an exact duplicate, just continue + if testCPort == hPort && testHPort == cPort { + continue + } + + // We appear to be clear. Make an OCICNI port + // struct. + // Don't use hostIP - we want to preserve the + // empty string hostIP by default for compat. + cniPort := ocicni.PortMapping{ + HostPort: int32(hPort), + ContainerPort: int32(cPort), + Protocol: p, + HostIP: port.HostIP, + } + finalMappings = append(finalMappings, cniPort) + } + } + } + + return finalMappings, containerPortValidate, hostPortValidate, nil +} + +// Make final port mappings for the container +func createPortMappings(ctx context.Context, s *specgen.SpecGenerator, img *image.Image) ([]ocicni.PortMapping, error) { + finalMappings, containerPortValidate, hostPortValidate, err := parsePortMapping(s.PortMappings) + if err != nil { + return nil, err + } + + // If not publishing exposed ports, or if we are publishing and there is + // nothing to publish - then just return the port mappings we've made so + // far. + if !s.PublishExposedPorts || (len(s.Expose) == 0 && img == nil) { + return finalMappings, nil + } + + logrus.Debugf("Adding exposed ports") + + // We need to merge s.Expose into image exposed ports + expose := make(map[uint16]string) + for k, v := range s.Expose { + expose[k] = v + } + if img != nil { + inspect, err := img.InspectNoSize(ctx) + if err != nil { + return nil, errors.Wrapf(err, "error inspecting image to get exposed ports") + } + for imgExpose := range inspect.Config.ExposedPorts { + // Expose format is portNumber[/protocol] + splitExpose := strings.SplitN(imgExpose, "/", 2) + num, err := strconv.Atoi(splitExpose[0]) + if err != nil { + return nil, errors.Wrapf(err, "unable to convert image EXPOSE statement %q to port number", imgExpose) + } + if num > 65535 || num < 1 { + return nil, errors.Errorf("%d from image EXPOSE statement %q is not a valid port number", num, imgExpose) + } + // No need to validate protocol, we'll do it below. + if len(splitExpose) == 1 { + expose[uint16(num)] = "tcp" + } else { + expose[uint16(num)] = splitExpose[1] + } + } + } + + // There's been a request to expose some ports. Let's do that. + // Start by figuring out what needs to be exposed. + // This is a map of container port number to protocols to expose. + toExpose := make(map[uint16][]string) + for port, proto := range expose { + // Validate protocol first + protocols, err := checkProtocol(proto, false) + if err != nil { + return nil, errors.Wrapf(err, "error validating protocols for exposed port %d", port) + } + + if port == 0 { + return nil, errors.Errorf("cannot expose 0 as it is not a valid port number") + } + + // Check to see if the port is already present in existing + // mappings. + for _, p := range protocols { + ctrPortMap, ok := containerPortValidate[p]["0.0.0.0"] + if !ok { + ctrPortMap = make(map[uint16]uint16) + containerPortValidate[p]["0.0.0.0"] = ctrPortMap + } + + if portNum := ctrPortMap[port]; portNum == 0 { + // We want to expose this port for this protocol + exposeProto, ok := toExpose[port] + if !ok { + exposeProto = []string{} + } + exposeProto = append(exposeProto, p) + toExpose[port] = exposeProto + } + } + } + + // We now have a final list of ports that we want exposed. + // Let's find empty, unallocated host ports for them. + for port, protocols := range toExpose { + for _, p := range protocols { + // Find an open port on the host. + // I see a faint possibility that this will infinite + // loop trying to find a valid open port, so I've + // included a max-tries counter. + hostPort := 0 + tries := 15 + for hostPort == 0 && tries > 0 { + // We can't select a specific protocol, which is + // unfortunate for the UDP case. + candidate, err := getRandomPort() + if err != nil { + return nil, err + } + + // Check if the host port is already bound + hostPortMap, ok := hostPortValidate[p]["0.0.0.0"] + if !ok { + hostPortMap = make(map[uint16]uint16) + hostPortValidate[p]["0.0.0.0"] = hostPortMap + } + + if checkPort := hostPortMap[uint16(candidate)]; checkPort != 0 { + // Host port is already allocated, try again + tries-- + continue + } + + hostPortMap[uint16(candidate)] = port + hostPort = candidate + logrus.Debugf("Mapping exposed port %d/%s to host port %d", port, p, hostPort) + + // Make a CNI port mapping + cniPort := ocicni.PortMapping{ + HostPort: int32(candidate), + ContainerPort: int32(port), + Protocol: p, + HostIP: "", + } + finalMappings = append(finalMappings, cniPort) + } + if tries == 0 && hostPort == 0 { + // We failed to find an open port. + return nil, errors.Errorf("failed to find an open port to expose container port %d on the host", port) + } + } + } + + return finalMappings, nil +} + +// Check a string to ensure it is a comma-separated set of valid protocols +func checkProtocol(protocol string, allowSCTP bool) ([]string, error) { + protocols := make(map[string]struct{}) + splitProto := strings.Split(protocol, ",") + // Don't error on duplicates - just deduplicate + for _, p := range splitProto { + switch p { + case protoTCP, "": + protocols[protoTCP] = struct{}{} + case protoUDP: + protocols[protoUDP] = struct{}{} + case protoSCTP: + if !allowSCTP { + return nil, errors.Errorf("protocol SCTP is not allowed for exposed ports") + } + protocols[protoSCTP] = struct{}{} + default: + return nil, errors.Errorf("unrecognized protocol %q in port mapping", p) + } + } + + finalProto := []string{} + for p := range protocols { + finalProto = append(finalProto, p) + } + + // This shouldn't be possible, but check anyways + if len(finalProto) == 0 { + return nil, errors.Errorf("no valid protocols specified for port mapping") + } + + return finalProto, nil +} + +// Find a random, open port on the host +func getRandomPort() (int, error) { + l, err := net.Listen("tcp", ":0") + if err != nil { + return 0, errors.Wrapf(err, "unable to get free TCP port") + } + defer l.Close() + _, randomPort, err := net.SplitHostPort(l.Addr().String()) + if err != nil { + return 0, errors.Wrapf(err, "unable to determine free port") + } + rp, err := strconv.Atoi(randomPort) + if err != nil { + return 0, errors.Wrapf(err, "unable to convert random port to int") + } + return rp, nil +} diff --git a/pkg/specgen/generate/security.go b/pkg/specgen/generate/security.go index e2da9e976..d2229b06f 100644 --- a/pkg/specgen/generate/security.go +++ b/pkg/specgen/generate/security.go @@ -4,6 +4,7 @@ import ( "strings" "github.com/containers/common/pkg/capabilities" + "github.com/containers/common/pkg/config" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/specgen" @@ -55,76 +56,61 @@ func setLabelOpts(s *specgen.SpecGenerator, runtime *libpod.Runtime, pidConfig s return nil } -func securityConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, newImage *image.Image) error { +func securityConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, newImage *image.Image, rtc *config.Config) error { + var ( + caplist []string + err error + ) // HANDLE CAPABILITIES // NOTE: Must happen before SECCOMP if s.Privileged { g.SetupPrivileged(true) - } - - useNotRoot := func(user string) bool { - if user == "" || user == "root" || user == "0" { - return false + caplist = capabilities.AllCapabilities() + } else { + caplist, err = rtc.Capabilities(s.User, s.CapAdd, s.CapDrop) + if err != nil { + return err } - return true - } - configSpec := g.Config - var err error - var caplist []string - bounding := configSpec.Process.Capabilities.Bounding - if useNotRoot(s.User) { - configSpec.Process.Capabilities.Bounding = caplist - } - caplist, err = capabilities.MergeCapabilities(configSpec.Process.Capabilities.Bounding, s.CapAdd, s.CapDrop) - if err != nil { - return err - } - privCapsRequired := []string{} - // If the container image specifies an label with a - // capabilities.ContainerImageLabel then split the comma separated list - // of capabilities and record them. This list indicates the only - // capabilities, required to run the container. - var capsRequiredRequested []string - for key, val := range s.Labels { - if util.StringInSlice(key, capabilities.ContainerImageLabels) { - capsRequiredRequested = strings.Split(val, ",") + privCapsRequired := []string{} + + // If the container image specifies an label with a + // capabilities.ContainerImageLabel then split the comma separated list + // of capabilities and record them. This list indicates the only + // capabilities, required to run the container. + var capsRequiredRequested []string + for key, val := range s.Labels { + if util.StringInSlice(key, capabilities.ContainerImageLabels) { + capsRequiredRequested = strings.Split(val, ",") + } } - } - if !s.Privileged && len(capsRequiredRequested) > 0 { + if !s.Privileged && len(capsRequiredRequested) > 0 { - // Pass capRequiredRequested in CapAdd field to normalize capabilities names - capsRequired, err := capabilities.MergeCapabilities(nil, capsRequiredRequested, nil) - if err != nil { - logrus.Errorf("capabilities requested by user or image are not valid: %q", strings.Join(capsRequired, ",")) - } else { - // Verify all capRequiered are in the capList - for _, cap := range capsRequired { - if !util.StringInSlice(cap, caplist) { - privCapsRequired = append(privCapsRequired, cap) + // Pass capRequiredRequested in CapAdd field to normalize capabilities names + capsRequired, err := capabilities.MergeCapabilities(nil, capsRequiredRequested, nil) + if err != nil { + logrus.Errorf("capabilities requested by user or image are not valid: %q", strings.Join(capsRequired, ",")) + } else { + // Verify all capRequiered are in the capList + for _, cap := range capsRequired { + if !util.StringInSlice(cap, caplist) { + privCapsRequired = append(privCapsRequired, cap) + } } } - } - if len(privCapsRequired) == 0 { - caplist = capsRequired - } else { - logrus.Errorf("capabilities requested by user or image are not allowed by default: %q", strings.Join(privCapsRequired, ",")) + if len(privCapsRequired) == 0 { + caplist = capsRequired + } else { + logrus.Errorf("capabilities requested by user or image are not allowed by default: %q", strings.Join(privCapsRequired, ",")) + } } } - + configSpec := g.Config configSpec.Process.Capabilities.Bounding = caplist configSpec.Process.Capabilities.Permitted = caplist configSpec.Process.Capabilities.Inheritable = caplist configSpec.Process.Capabilities.Effective = caplist configSpec.Process.Capabilities.Ambient = caplist - if useNotRoot(s.User) { - caplist, err = capabilities.MergeCapabilities(bounding, s.CapAdd, s.CapDrop) - if err != nil { - return err - } - } - configSpec.Process.Capabilities.Bounding = caplist - // HANDLE SECCOMP if s.SeccompProfilePath != "unconfined" { seccompConfig, err := getSeccompConfig(s, configSpec, newImage) diff --git a/pkg/specgen/namespaces.go b/pkg/specgen/namespaces.go index 396563267..11dee1986 100644 --- a/pkg/specgen/namespaces.go +++ b/pkg/specgen/namespaces.go @@ -3,6 +3,8 @@ package specgen import ( "strings" + "github.com/containers/libpod/pkg/cgroups" + "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" ) @@ -163,7 +165,7 @@ func ParseNamespace(ns string) (Namespace, error) { toReturn.NSMode = FromPod case ns == "host": toReturn.NSMode = Host - case ns == "private": + case ns == "private", ns == "": toReturn.NSMode = Private case strings.HasPrefix(ns, "ns:"): split := strings.SplitN(ns, ":", 2) @@ -186,6 +188,31 @@ func ParseNamespace(ns string) (Namespace, error) { return toReturn, nil } +// ParseCgroupNamespace parses a cgroup namespace specification in string +// form. +func ParseCgroupNamespace(ns string) (Namespace, error) { + toReturn := Namespace{} + // Cgroup is host for v1, private for v2. + // We can't trust c/common for this, as it only assumes private. + cgroupsv2, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + return toReturn, err + } + if cgroupsv2 { + switch ns { + case "host": + toReturn.NSMode = Host + case "private", "": + toReturn.NSMode = Private + default: + return toReturn, errors.Errorf("unrecognized namespace mode %s passed", ns) + } + } else { + toReturn.NSMode = Host + } + return toReturn, nil +} + // ParseUserNamespace parses a user namespace specification in string // form. func ParseUserNamespace(ns string) (Namespace, error) { @@ -205,6 +232,9 @@ func ParseUserNamespace(ns string) (Namespace, error) { case ns == "keep-id": toReturn.NSMode = KeepID return toReturn, nil + case ns == "": + toReturn.NSMode = Host + return toReturn, nil } return ParseNamespace(ns) } @@ -215,11 +245,18 @@ func ParseUserNamespace(ns string) (Namespace, error) { func ParseNetworkNamespace(ns string) (Namespace, []string, error) { toReturn := Namespace{} var cniNetworks []string + // Net defaults to Slirp on rootless switch { case ns == "slirp4netns": toReturn.NSMode = Slirp case ns == "pod": toReturn.NSMode = FromPod + case ns == "": + if rootless.IsRootless() { + toReturn.NSMode = Slirp + } else { + toReturn.NSMode = Bridge + } case ns == "bridge": toReturn.NSMode = Bridge case ns == "none": diff --git a/pkg/specgen/podspecgen.go b/pkg/specgen/podspecgen.go index 3f830014d..682f3f215 100644 --- a/pkg/specgen/podspecgen.go +++ b/pkg/specgen/podspecgen.go @@ -2,8 +2,6 @@ package specgen import ( "net" - - "github.com/cri-o/ocicni/pkg/ocicni" ) // PodBasicConfig contains basic configuration options for pods. @@ -79,7 +77,7 @@ type PodNetworkConfig struct { // container, this will forward the ports to the entire pod. // Only available if NetNS is set to Bridge or Slirp. // Optional. - PortMappings []ocicni.PortMapping `json:"portmappings,omitempty"` + PortMappings []PortMapping `json:"portmappings,omitempty"` // CNINetworks is a list of CNI networks that the infra container will // join. As, by default, containers share their network with the infra // container, these networks will effectively be joined by the diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index 20c8f8800..4f1c4fde1 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -6,7 +6,6 @@ import ( "github.com/containers/image/v5/manifest" "github.com/containers/storage" - "github.com/cri-o/ocicni/pkg/ocicni" spec "github.com/opencontainers/runtime-spec/specs-go" ) @@ -306,11 +305,23 @@ type ContainerNetworkConfig struct { // PortBindings is a set of ports to map into the container. // Only available if NetNS is set to bridge or slirp. // Optional. - PortMappings []ocicni.PortMapping `json:"portmappings,omitempty"` - // PublishImagePorts will publish ports specified in the image to random - // ports outside. - // Requires Image to be set. - PublishImagePorts bool `json:"publish_image_ports,omitempty"` + PortMappings []PortMapping `json:"portmappings,omitempty"` + // PublishExposedPorts will publish ports specified in the image to + // random unused ports (guaranteed to be above 1024) on the host. + // This is based on ports set in Expose below, and any ports specified + // by the Image (if one is given). + // Only available if NetNS is set to Bridge or Slirp. + PublishExposedPorts bool `json:"publish_image_ports,omitempty"` + // Expose is a number of ports that will be forwarded to the container + // if PublishExposedPorts is set. + // Expose is a map of uint16 (port number) to a string representing + // protocol. Allowed protocols are "tcp", "udp", and "sctp", or some + // combination of the three separated by commas. + // If protocol is set to "" we will assume TCP. + // Only available if NetNS is set to Bridge or Slirp, and + // PublishExposedPorts is set. + // Optional. + Expose map[uint16]string `json:"expose,omitempty"` // CNINetworks is a list of CNI networks to join the container to. // If this list is empty, the default CNI network will be joined // instead. If at least one entry is present, we will not join the @@ -410,6 +421,35 @@ type NamedVolume struct { Options []string } +// PortMapping is one or more ports that will be mapped into the container. +type PortMapping struct { + // HostIP is the IP that we will bind to on the host. + // If unset, assumed to be 0.0.0.0 (all interfaces). + HostIP string `json:"host_ip,omitempty"` + // ContainerPort is the port number that will be exposed from the + // container. + // Mandatory. + ContainerPort uint16 `json:"container_port"` + // HostPort is the port number that will be forwarded from the host into + // the container. + // If omitted, will be assumed to be identical to + HostPort uint16 `json:"host_port,omitempty"` + // Range is the number of ports that will be forwarded, starting at + // HostPort and ContainerPort and counting up. + // This is 1-indexed, so 1 is assumed to be a single port (only the + // Hostport:Containerport mapping will be added), 2 is two ports (both + // Hostport:Containerport and Hostport+1:Containerport+1), etc. + // If unset, assumed to be 1 (a single port). + // Both hostport + range and containerport + range must be less than + // 65536. + Range uint16 `json:"range,omitempty"` + // Protocol is the protocol forward. + // Must be either "tcp", "udp", and "sctp", or some combination of these + // separated by commas. + // If unset, assumed to be TCP. + Protocol string `json:"protocol,omitempty"` +} + // NewSpecGenerator returns a SpecGenerator struct given one of two mandatory inputs func NewSpecGenerator(arg string, rootfs bool) *SpecGenerator { csc := ContainerStorageConfig{} |