package generate import ( "errors" "fmt" "os" "strings" "github.com/containers/common/libimage" "github.com/containers/common/libnetwork/types" "github.com/containers/common/pkg/config" "github.com/containers/podman/v4/libpod" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/rootless" "github.com/containers/podman/v4/pkg/specgen" "github.com/containers/podman/v4/pkg/util" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" "github.com/sirupsen/logrus" ) const host = "host" // Get the default namespace mode for any given namespace type. func GetDefaultNamespaceMode(nsType string, cfg *config.Config, pod *libpod.Pod) (specgen.Namespace, error) { // The default for most is private toReturn := specgen.Namespace{} toReturn.NSMode = specgen.Private // Ensure case insensitivity nsType = strings.ToLower(nsType) // If the pod is not nil - check shared namespaces if pod != nil && pod.HasInfraContainer() { podMode := false switch { case nsType == "pid" && pod.SharesPID(): if pod.NamespaceMode(spec.PIDNamespace) == host { toReturn.NSMode = specgen.Host return toReturn, nil } podMode = true case nsType == "ipc" && pod.SharesIPC(): if pod.NamespaceMode(spec.IPCNamespace) == host { toReturn.NSMode = specgen.Host return toReturn, nil } podMode = true case nsType == "uts" && pod.SharesUTS(): if pod.NamespaceMode(spec.UTSNamespace) == host { toReturn.NSMode = specgen.Host return toReturn, nil } podMode = true case nsType == "user" && pod.SharesUser(): // user does not need a special check for host, this is already validated on pod creation // if --userns=host then pod.SharesUser == false podMode = true case nsType == "net" && pod.SharesNet(): if pod.NetworkMode() == host { toReturn.NSMode = specgen.Host return toReturn, nil } podMode = true case nsType == "cgroup" && pod.SharesCgroup(): if pod.NamespaceMode(spec.CgroupNamespace) == host { toReturn.NSMode = specgen.Host return toReturn, nil } podMode = true } if podMode { toReturn.NSMode = specgen.FromPod return toReturn, nil } } if cfg == nil { cfg = &config.Config{} } switch nsType { case "pid": return specgen.ParseNamespace(cfg.Containers.PidNS) case "ipc": return specgen.ParseIPCNamespace(cfg.Containers.IPCNS) case "uts": return specgen.ParseNamespace(cfg.Containers.UTSNS) case "user": return specgen.ParseUserNamespace(cfg.Containers.UserNS) case "cgroup": return specgen.ParseCgroupNamespace(cfg.Containers.CgroupNS) case "net": ns, _, _, err := specgen.ParseNetworkFlag(nil) return ns, err } return toReturn, fmt.Errorf("invalid namespace type %q passed: %w", nsType, define.ErrInvalidArg) } // namespaceOptions generates container creation options for all // namespaces in a SpecGenerator. // Pod is the pod the container will join. May be nil is the container is not // joining a pod. // TODO: Consider grouping options that are not directly attached to a namespace // elsewhere. func namespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod.Pod, imageData *libimage.ImageData) ([]libpod.CtrCreateOption, error) { toReturn := []libpod.CtrCreateOption{} // If pod is not nil, get infra container. var infraCtr *libpod.Container if pod != nil { infraID, err := pod.InfraContainerID() if err != nil { // This is likely to be of the fatal kind (pod was // removed) so hard fail return nil, fmt.Errorf("error looking up pod %s infra container: %w", pod.ID(), err) } if infraID != "" { ctr, err := rt.GetContainer(infraID) if err != nil { return nil, fmt.Errorf("error retrieving pod %s infra container %s: %w", pod.ID(), infraID, err) } infraCtr = ctr } } errNoInfra := fmt.Errorf("cannot use pod namespace as container is not joining a pod or pod has no infra container: %w", define.ErrInvalidArg) // PID switch s.PidNS.NSMode { case specgen.FromPod: if pod == nil || infraCtr == nil { return nil, errNoInfra } toReturn = append(toReturn, libpod.WithPIDNSFrom(infraCtr)) case specgen.FromContainer: pidCtr, err := rt.LookupContainer(s.PidNS.Value) if err != nil { return nil, fmt.Errorf("error looking up container to share pid namespace with: %w", err) } toReturn = append(toReturn, libpod.WithPIDNSFrom(pidCtr)) } // IPC switch s.IpcNS.NSMode { case specgen.Host: // Force use of host /dev/shm for host namespace toReturn = append(toReturn, libpod.WithShmDir("/dev/shm")) case specgen.FromPod: if pod == nil || infraCtr == nil { return nil, errNoInfra } toReturn = append(toReturn, libpod.WithIPCNSFrom(infraCtr)) toReturn = append(toReturn, libpod.WithShmDir(infraCtr.ShmDir())) case specgen.FromContainer: ipcCtr, err := rt.LookupContainer(s.IpcNS.Value) if err != nil { return nil, fmt.Errorf("error looking up container to share ipc namespace with: %w", err) } if ipcCtr.ConfigNoCopy().NoShmShare { return nil, fmt.Errorf("joining IPC of container %s is not allowed: non-shareable IPC (hint: use IpcMode:shareable for the donor container)", ipcCtr.ID()) } toReturn = append(toReturn, libpod.WithIPCNSFrom(ipcCtr)) if !ipcCtr.ConfigNoCopy().NoShm { toReturn = append(toReturn, libpod.WithShmDir(ipcCtr.ShmDir())) } case specgen.None: toReturn = append(toReturn, libpod.WithNoShm(true)) case specgen.Private: toReturn = append(toReturn, libpod.WithNoShmShare(true)) } // UTS switch s.UtsNS.NSMode { case specgen.FromPod: if pod == nil || infraCtr == nil { return nil, errNoInfra } if pod.NamespaceMode(spec.UTSNamespace) == host { // adding infra as a nsCtr is not what we want to do when uts == host // this leads the new ctr to try to add an ns path which is should not in this mode logrus.Debug("pod has host uts, not adding infra as a nsCtr") s.UtsNS = specgen.Namespace{NSMode: specgen.Host} } else { toReturn = append(toReturn, libpod.WithUTSNSFrom(infraCtr)) } case specgen.FromContainer: utsCtr, err := rt.LookupContainer(s.UtsNS.Value) if err != nil { return nil, fmt.Errorf("error looking up container to share uts namespace with: %w", err) } toReturn = append(toReturn, libpod.WithUTSNSFrom(utsCtr)) } // User switch s.UserNS.NSMode { case specgen.KeepID: if !rootless.IsRootless() { return nil, errors.New("keep-id is only supported in rootless mode") } toReturn = append(toReturn, libpod.WithAddCurrentUserPasswdEntry()) // If user is not overridden, set user in the container // to user running Podman. if s.User == "" { _, uid, gid, err := util.GetKeepIDMapping() if err != nil { return nil, err } toReturn = append(toReturn, libpod.WithUser(fmt.Sprintf("%d:%d", uid, gid))) } case specgen.FromPod: if pod == nil || infraCtr == nil { return nil, errNoInfra } // Inherit the user from the infra container if it is set and --user has not // been set explicitly if infraCtr.User() != "" && s.User == "" { toReturn = append(toReturn, libpod.WithUser(infraCtr.User())) } toReturn = append(toReturn, libpod.WithUserNSFrom(infraCtr)) case specgen.FromContainer: userCtr, err := rt.LookupContainer(s.UserNS.Value) if err != nil { return nil, fmt.Errorf("error looking up container to share user namespace with: %w", err) } toReturn = append(toReturn, libpod.WithUserNSFrom(userCtr)) } // This wipes the UserNS settings that get set from the infra container // when we are inheritting from the pod. So only apply this if the container // is not being created in a pod. if s.IDMappings != nil { if pod == nil { toReturn = append(toReturn, libpod.WithIDMappings(*s.IDMappings)) } else if pod.HasInfraContainer() && (len(s.IDMappings.UIDMap) > 0 || len(s.IDMappings.GIDMap) > 0) { return nil, fmt.Errorf("cannot specify a new uid/gid map when entering a pod with an infra container: %w", define.ErrInvalidArg) } } if s.User != "" { toReturn = append(toReturn, libpod.WithUser(s.User)) } if len(s.Groups) > 0 { toReturn = append(toReturn, libpod.WithGroups(s.Groups)) } // Cgroup switch s.CgroupNS.NSMode { case specgen.FromPod: if pod == nil || infraCtr == nil { return nil, errNoInfra } toReturn = append(toReturn, libpod.WithCgroupNSFrom(infraCtr)) case specgen.FromContainer: cgroupCtr, err := rt.LookupContainer(s.CgroupNS.Value) if err != nil { return nil, fmt.Errorf("error looking up container to share cgroup namespace with: %w", err) } toReturn = append(toReturn, libpod.WithCgroupNSFrom(cgroupCtr)) } if s.CgroupParent != "" { toReturn = append(toReturn, libpod.WithCgroupParent(s.CgroupParent)) } if s.CgroupsMode != "" { toReturn = append(toReturn, libpod.WithCgroupsMode(s.CgroupsMode)) } postConfigureNetNS := !s.UserNS.IsHost() // when we are rootless we default to slirp4netns if rootless.IsRootless() && (s.NetNS.IsPrivate() || s.NetNS.IsDefault()) { s.NetNS.NSMode = specgen.Slirp } switch s.NetNS.NSMode { case specgen.FromPod: if pod == nil || infraCtr == nil { return nil, errNoInfra } toReturn = append(toReturn, libpod.WithNetNSFrom(infraCtr)) case specgen.FromContainer: netCtr, err := rt.LookupContainer(s.NetNS.Value) if err != nil { return nil, fmt.Errorf("error looking up container to share net namespace with: %w", err) } toReturn = append(toReturn, libpod.WithNetNSFrom(netCtr)) case specgen.Slirp: portMappings, expose, err := createPortMappings(s, imageData) if err != nil { return nil, err } val := "slirp4netns" if s.NetNS.Value != "" { val = fmt.Sprintf("slirp4netns:%s", s.NetNS.Value) } toReturn = append(toReturn, libpod.WithNetNS(portMappings, expose, postConfigureNetNS, val, nil)) case specgen.Bridge, specgen.Private, specgen.Default: portMappings, expose, err := createPortMappings(s, imageData) if err != nil { return nil, err } rtConfig, err := rt.GetConfigNoCopy() if err != nil { return nil, err } // if no network was specified use add the default if len(s.Networks) == 0 { // backwards config still allow the old cni networks list and convert to new format if len(s.CNINetworks) > 0 { logrus.Warn(`specgen "cni_networks" option is deprecated use the "networks" map instead`) networks := make(map[string]types.PerNetworkOptions, len(s.CNINetworks)) for _, net := range s.CNINetworks { networks[net] = types.PerNetworkOptions{} } s.Networks = networks } else { // no networks given but bridge is set so use default network s.Networks = map[string]types.PerNetworkOptions{ rtConfig.Network.DefaultNetwork: {}, } } } // rename the "default" network to the correct default name if opts, ok := s.Networks["default"]; ok { s.Networks[rtConfig.Network.DefaultNetwork] = opts delete(s.Networks, "default") } toReturn = append(toReturn, libpod.WithNetNS(portMappings, expose, postConfigureNetNS, "bridge", s.Networks)) } if s.UseImageHosts { toReturn = append(toReturn, libpod.WithUseImageHosts()) } else if len(s.HostAdd) > 0 { toReturn = append(toReturn, libpod.WithHosts(s.HostAdd)) } if len(s.DNSSearch) > 0 { toReturn = append(toReturn, libpod.WithDNSSearch(s.DNSSearch)) } if s.UseImageResolvConf { toReturn = append(toReturn, libpod.WithUseImageResolvConf()) } else if len(s.DNSServers) > 0 { var dnsServers []string for _, d := range s.DNSServers { dnsServers = append(dnsServers, d.String()) } toReturn = append(toReturn, libpod.WithDNS(dnsServers)) } if len(s.DNSOptions) > 0 { toReturn = append(toReturn, libpod.WithDNSOption(s.DNSOptions)) } if s.NetworkOptions != nil { toReturn = append(toReturn, libpod.WithNetworkOptions(s.NetworkOptions)) } return toReturn, nil } func specConfigureNamespaces(s *specgen.SpecGenerator, g *generate.Generator, rt *libpod.Runtime, pod *libpod.Pod) error { // PID switch s.PidNS.NSMode { case specgen.Path: if _, err := os.Stat(s.PidNS.Value); err != nil { return fmt.Errorf("cannot find specified PID namespace path: %w", err) } if err := g.AddOrReplaceLinuxNamespace(string(spec.PIDNamespace), s.PidNS.Value); err != nil { return err } case specgen.Host: if err := g.RemoveLinuxNamespace(string(spec.PIDNamespace)); err != nil { return err } case specgen.Private: if err := g.AddOrReplaceLinuxNamespace(string(spec.PIDNamespace), ""); err != nil { return err } } // IPC switch s.IpcNS.NSMode { case specgen.Path: if _, err := os.Stat(s.IpcNS.Value); err != nil { return fmt.Errorf("cannot find specified IPC namespace path: %w", err) } if err := g.AddOrReplaceLinuxNamespace(string(spec.IPCNamespace), s.IpcNS.Value); err != nil { return err } case specgen.Host: if err := g.RemoveLinuxNamespace(string(spec.IPCNamespace)); err != nil { return err } case specgen.Private: if err := g.AddOrReplaceLinuxNamespace(string(spec.IPCNamespace), ""); err != nil { return err } } // UTS switch s.UtsNS.NSMode { case specgen.Path: if _, err := os.Stat(s.UtsNS.Value); err != nil { return fmt.Errorf("cannot find specified UTS namespace path: %w", err) } if err := g.AddOrReplaceLinuxNamespace(string(spec.UTSNamespace), s.UtsNS.Value); err != nil { return err } case specgen.Host: if err := g.RemoveLinuxNamespace(string(spec.UTSNamespace)); err != nil { return err } case specgen.Private: if err := g.AddOrReplaceLinuxNamespace(string(spec.UTSNamespace), ""); err != nil { return err } } hostname := s.Hostname if hostname == "" { switch { case s.UtsNS.NSMode == specgen.FromPod: hostname = pod.Hostname() case s.UtsNS.NSMode == specgen.FromContainer: utsCtr, err := rt.LookupContainer(s.UtsNS.Value) if err != nil { return fmt.Errorf("error looking up container to share uts namespace with: %w", err) } hostname = utsCtr.Hostname() case (s.NetNS.NSMode == specgen.Host && hostname == "") || s.UtsNS.NSMode == specgen.Host: tmpHostname, err := os.Hostname() if err != nil { return fmt.Errorf("unable to retrieve hostname of the host: %w", err) } hostname = tmpHostname default: logrus.Debug("No hostname set; container's hostname will default to runtime default") } } g.RemoveHostname() if s.Hostname != "" || s.UtsNS.NSMode != specgen.Host { // Set the hostname in the OCI configuration only if specified by // the user or if we are creating a new UTS namespace. // TODO: Should we be doing this for pod or container shared // namespaces? g.SetHostname(hostname) } if _, ok := s.Env["HOSTNAME"]; !ok && s.Hostname != "" { g.AddProcessEnv("HOSTNAME", hostname) } // User if _, err := specgen.SetupUserNS(s.IDMappings, s.UserNS, g); err != nil { return err } // Cgroup switch s.CgroupNS.NSMode { case specgen.Path: if _, err := os.Stat(s.CgroupNS.Value); err != nil { return fmt.Errorf("cannot find specified cgroup namespace path: %w", err) } if err := g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), s.CgroupNS.Value); err != nil { return err } case specgen.Host: if err := g.RemoveLinuxNamespace(string(spec.CgroupNamespace)); err != nil { return err } case specgen.Private: if err := g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), ""); err != nil { return err } } // Net switch s.NetNS.NSMode { case specgen.Path: if _, err := os.Stat(s.NetNS.Value); err != nil { return fmt.Errorf("cannot find specified network namespace path: %w", err) } if err := g.AddOrReplaceLinuxNamespace(string(spec.NetworkNamespace), s.NetNS.Value); err != nil { return err } case specgen.Host: if err := g.RemoveLinuxNamespace(string(spec.NetworkNamespace)); err != nil { return err } case specgen.Private, specgen.NoNetwork: if err := g.AddOrReplaceLinuxNamespace(string(spec.NetworkNamespace), ""); err != nil { return err } } if g.Config.Annotations == nil { g.Config.Annotations = make(map[string]string) } if s.PublishExposedPorts { g.Config.Annotations[define.InspectAnnotationPublishAll] = define.InspectResponseTrue } else { g.Config.Annotations[define.InspectAnnotationPublishAll] = define.InspectResponseFalse } return nil } // GetNamespaceOptions transforms a slice of kernel namespaces // into a slice of pod create options. Currently, not all // kernel namespaces are supported, and they will be returned in an error func GetNamespaceOptions(ns []string, netnsIsHost bool) ([]libpod.PodCreateOption, error) { var options []libpod.PodCreateOption var erroredOptions []libpod.PodCreateOption if ns == nil { // set the default namespaces ns = strings.Split(specgen.DefaultKernelNamespaces, ",") } for _, toShare := range ns { switch toShare { case "cgroup": options = append(options, libpod.WithPodCgroup()) case "net": options = append(options, libpod.WithPodNet()) case "mnt": return erroredOptions, fmt.Errorf("mount sharing functionality not supported on pod level") case "pid": options = append(options, libpod.WithPodPID()) case "user": continue case "ipc": options = append(options, libpod.WithPodIPC()) case "uts": options = append(options, libpod.WithPodUTS()) case "": case "none": return erroredOptions, nil default: return erroredOptions, fmt.Errorf("invalid kernel namespace to share: %s. Options are: cgroup, ipc, net, pid, uts or none", toShare) } } return options, nil }