github.com/containers/podman/v4@v4.9.4/pkg/specgen/generate/namespaces.go (about)

     1  //go:build !remote
     2  // +build !remote
     3  
     4  package generate
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/containers/common/libimage"
    11  	"github.com/containers/common/libnetwork/types"
    12  	"github.com/containers/common/pkg/config"
    13  	"github.com/containers/podman/v4/libpod"
    14  	"github.com/containers/podman/v4/libpod/define"
    15  	"github.com/containers/podman/v4/pkg/namespaces"
    16  	"github.com/containers/podman/v4/pkg/rootless"
    17  	"github.com/containers/podman/v4/pkg/specgen"
    18  	"github.com/containers/podman/v4/pkg/util"
    19  	spec "github.com/opencontainers/runtime-spec/specs-go"
    20  	"github.com/sirupsen/logrus"
    21  )
    22  
    23  const host = "host"
    24  
    25  // Get the default namespace mode for any given namespace type.
    26  func GetDefaultNamespaceMode(nsType string, cfg *config.Config, pod *libpod.Pod) (specgen.Namespace, error) {
    27  	// The default for most is private
    28  	toReturn := specgen.Namespace{}
    29  	toReturn.NSMode = specgen.Private
    30  
    31  	// Ensure case insensitivity
    32  	nsType = strings.ToLower(nsType)
    33  
    34  	// If the pod is not nil - check shared namespaces
    35  	if pod != nil && pod.HasInfraContainer() {
    36  		podMode := false
    37  		switch {
    38  		case nsType == "pid" && pod.SharesPID():
    39  			if pod.NamespaceMode(spec.PIDNamespace) == host {
    40  				toReturn.NSMode = specgen.Host
    41  				return toReturn, nil
    42  			}
    43  			podMode = true
    44  		case nsType == "ipc" && pod.SharesIPC():
    45  			if pod.NamespaceMode(spec.IPCNamespace) == host {
    46  				toReturn.NSMode = specgen.Host
    47  				return toReturn, nil
    48  			}
    49  			podMode = true
    50  		case nsType == "uts" && pod.SharesUTS():
    51  			if pod.NamespaceMode(spec.UTSNamespace) == host {
    52  				toReturn.NSMode = specgen.Host
    53  				return toReturn, nil
    54  			}
    55  			podMode = true
    56  		case nsType == "user" && pod.SharesUser():
    57  			// user does not need a special check for host, this is already validated on pod creation
    58  			// if --userns=host then pod.SharesUser == false
    59  			podMode = true
    60  		case nsType == "net" && pod.SharesNet():
    61  			if pod.NetworkMode() == host {
    62  				toReturn.NSMode = specgen.Host
    63  				return toReturn, nil
    64  			}
    65  			podMode = true
    66  		case nsType == "cgroup" && pod.SharesCgroup():
    67  			if pod.NamespaceMode(spec.CgroupNamespace) == host {
    68  				toReturn.NSMode = specgen.Host
    69  				return toReturn, nil
    70  			}
    71  			podMode = true
    72  		}
    73  		if podMode {
    74  			toReturn.NSMode = specgen.FromPod
    75  			return toReturn, nil
    76  		}
    77  	}
    78  
    79  	if cfg == nil {
    80  		cfg = &config.Config{}
    81  	}
    82  	switch nsType {
    83  	case "pid":
    84  		return specgen.ParseNamespace(cfg.Containers.PidNS)
    85  	case "ipc":
    86  		return specgen.ParseIPCNamespace(cfg.Containers.IPCNS)
    87  	case "uts":
    88  		return specgen.ParseNamespace(cfg.Containers.UTSNS)
    89  	case "user":
    90  		return specgen.ParseUserNamespace(cfg.Containers.UserNS)
    91  	case "cgroup":
    92  		return specgen.ParseCgroupNamespace(cfg.Containers.CgroupNS)
    93  	case "net":
    94  		ns, _, _, err := specgen.ParseNetworkFlag(nil, false)
    95  		return ns, err
    96  	}
    97  
    98  	return toReturn, fmt.Errorf("invalid namespace type %q passed: %w", nsType, define.ErrInvalidArg)
    99  }
   100  
   101  // namespaceOptions generates container creation options for all
   102  // namespaces in a SpecGenerator.
   103  // Pod is the pod the container will join. May be nil is the container is not
   104  // joining a pod.
   105  // TODO: Consider grouping options that are not directly attached to a namespace
   106  // elsewhere.
   107  func namespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod.Pod, imageData *libimage.ImageData) ([]libpod.CtrCreateOption, error) {
   108  	toReturn := []libpod.CtrCreateOption{}
   109  
   110  	// If pod is not nil, get infra container.
   111  	var infraCtr *libpod.Container
   112  	if pod != nil {
   113  		infraID, err := pod.InfraContainerID()
   114  		if err != nil {
   115  			// This is likely to be of the fatal kind (pod was
   116  			// removed) so hard fail
   117  			return nil, fmt.Errorf("looking up pod %s infra container: %w", pod.ID(), err)
   118  		}
   119  		if infraID != "" {
   120  			ctr, err := rt.GetContainer(infraID)
   121  			if err != nil {
   122  				return nil, fmt.Errorf("retrieving pod %s infra container %s: %w", pod.ID(), infraID, err)
   123  			}
   124  			infraCtr = ctr
   125  		}
   126  	}
   127  
   128  	errNoInfra := fmt.Errorf("cannot use pod namespace as container is not joining a pod or pod has no infra container: %w", define.ErrInvalidArg)
   129  
   130  	// PID
   131  	switch s.PidNS.NSMode {
   132  	case specgen.FromPod:
   133  		if pod == nil || infraCtr == nil {
   134  			return nil, errNoInfra
   135  		}
   136  		toReturn = append(toReturn, libpod.WithPIDNSFrom(infraCtr))
   137  	case specgen.FromContainer:
   138  		pidCtr, err := rt.LookupContainer(s.PidNS.Value)
   139  		if err != nil {
   140  			return nil, fmt.Errorf("looking up container to share pid namespace with: %w", err)
   141  		}
   142  		if rootless.IsRootless() && pidCtr.NamespaceMode(spec.PIDNamespace, pidCtr.ConfigNoCopy().Spec) == host {
   143  			// Treat this the same as host, the problem is the runtime tries to do a
   144  			// setns call and this will fail when it is the host ns as rootless user.
   145  			s.PidNS.NSMode = specgen.Host
   146  		} else {
   147  			toReturn = append(toReturn, libpod.WithPIDNSFrom(pidCtr))
   148  		}
   149  	}
   150  
   151  	// IPC
   152  	switch s.IpcNS.NSMode {
   153  	case specgen.Host:
   154  		// Force use of host /dev/shm for host namespace
   155  		toReturn = append(toReturn, libpod.WithShmDir("/dev/shm"))
   156  	case specgen.FromPod:
   157  		if pod == nil || infraCtr == nil {
   158  			return nil, errNoInfra
   159  		}
   160  		toReturn = append(toReturn, libpod.WithIPCNSFrom(infraCtr))
   161  		toReturn = append(toReturn, libpod.WithShmDir(infraCtr.ShmDir()))
   162  	case specgen.FromContainer:
   163  		ipcCtr, err := rt.LookupContainer(s.IpcNS.Value)
   164  		if err != nil {
   165  			return nil, fmt.Errorf("looking up container to share ipc namespace with: %w", err)
   166  		}
   167  		if ipcCtr.ConfigNoCopy().NoShmShare {
   168  			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())
   169  		}
   170  		if rootless.IsRootless() && ipcCtr.NamespaceMode(spec.IPCNamespace, ipcCtr.ConfigNoCopy().Spec) == host {
   171  			// Treat this the same as host, the problem is the runtime tries to do a
   172  			// setns call and this will fail when it is the host ns as rootless user.
   173  			s.IpcNS.NSMode = specgen.Host
   174  			toReturn = append(toReturn, libpod.WithShmDir("/dev/shm"))
   175  		} else {
   176  			toReturn = append(toReturn, libpod.WithIPCNSFrom(ipcCtr))
   177  			if !ipcCtr.ConfigNoCopy().NoShm {
   178  				toReturn = append(toReturn, libpod.WithShmDir(ipcCtr.ShmDir()))
   179  			}
   180  		}
   181  	case specgen.None:
   182  		toReturn = append(toReturn, libpod.WithNoShm(true))
   183  	case specgen.Private:
   184  		toReturn = append(toReturn, libpod.WithNoShmShare(true))
   185  	}
   186  
   187  	// UTS
   188  	switch s.UtsNS.NSMode {
   189  	case specgen.FromPod:
   190  		if pod == nil || infraCtr == nil {
   191  			return nil, errNoInfra
   192  		}
   193  		if pod.NamespaceMode(spec.UTSNamespace) == host {
   194  			// adding infra as a nsCtr is not what we want to do when uts == host
   195  			// this leads the new ctr to try to add an ns path which is should not in this mode
   196  			logrus.Debug("pod has host uts, not adding infra as a nsCtr")
   197  			s.UtsNS = specgen.Namespace{NSMode: specgen.Host}
   198  		} else {
   199  			toReturn = append(toReturn, libpod.WithUTSNSFrom(infraCtr))
   200  		}
   201  	case specgen.FromContainer:
   202  		utsCtr, err := rt.LookupContainer(s.UtsNS.Value)
   203  		if err != nil {
   204  			return nil, fmt.Errorf("looking up container to share uts namespace with: %w", err)
   205  		}
   206  		if rootless.IsRootless() && utsCtr.NamespaceMode(spec.UTSNamespace, utsCtr.ConfigNoCopy().Spec) == host {
   207  			// Treat this the same as host, the problem is the runtime tries to do a
   208  			// setns call and this will fail when it is the host ns as rootless user.
   209  			s.UtsNS.NSMode = specgen.Host
   210  		} else {
   211  			toReturn = append(toReturn, libpod.WithUTSNSFrom(utsCtr))
   212  		}
   213  	}
   214  
   215  	// User
   216  	switch s.UserNS.NSMode {
   217  	case specgen.KeepID:
   218  		opts, err := namespaces.UsernsMode(s.UserNS.String()).GetKeepIDOptions()
   219  		if err != nil {
   220  			return nil, err
   221  		}
   222  		if opts.UID == nil && opts.GID == nil {
   223  			toReturn = append(toReturn, libpod.WithAddCurrentUserPasswdEntry())
   224  		}
   225  
   226  		// If user is not overridden, set user in the container
   227  		// to user running Podman.
   228  		if s.User == "" {
   229  			_, uid, gid, err := util.GetKeepIDMapping(opts)
   230  			if err != nil {
   231  				return nil, err
   232  			}
   233  			toReturn = append(toReturn, libpod.WithUser(fmt.Sprintf("%d:%d", uid, gid)))
   234  		}
   235  	case specgen.FromPod:
   236  		if pod == nil || infraCtr == nil {
   237  			return nil, errNoInfra
   238  		}
   239  		// Inherit the user from the infra container if it is set and --user has not
   240  		// been set explicitly
   241  		if infraCtr.User() != "" && s.User == "" {
   242  			toReturn = append(toReturn, libpod.WithUser(infraCtr.User()))
   243  		}
   244  		toReturn = append(toReturn, libpod.WithUserNSFrom(infraCtr))
   245  	case specgen.FromContainer:
   246  		userCtr, err := rt.LookupContainer(s.UserNS.Value)
   247  		if err != nil {
   248  			return nil, fmt.Errorf("looking up container to share user namespace with: %w", err)
   249  		}
   250  		toReturn = append(toReturn, libpod.WithUserNSFrom(userCtr))
   251  	}
   252  
   253  	// This wipes the UserNS settings that get set from the infra container
   254  	// when we are inheriting from the pod. So only apply this if the container
   255  	// is not being created in a pod.
   256  	if s.IDMappings != nil {
   257  		if pod == nil {
   258  			toReturn = append(toReturn, libpod.WithIDMappings(*s.IDMappings))
   259  		} else if pod.HasInfraContainer() && (len(s.IDMappings.UIDMap) > 0 || len(s.IDMappings.GIDMap) > 0) {
   260  			return nil, fmt.Errorf("cannot specify a new uid/gid map when entering a pod with an infra container: %w", define.ErrInvalidArg)
   261  		}
   262  	}
   263  	if s.User != "" {
   264  		toReturn = append(toReturn, libpod.WithUser(s.User))
   265  	}
   266  	if len(s.Groups) > 0 {
   267  		toReturn = append(toReturn, libpod.WithGroups(s.Groups))
   268  	}
   269  
   270  	// Cgroup
   271  	switch s.CgroupNS.NSMode {
   272  	case specgen.FromPod:
   273  		if pod == nil || infraCtr == nil {
   274  			return nil, errNoInfra
   275  		}
   276  		toReturn = append(toReturn, libpod.WithCgroupNSFrom(infraCtr))
   277  	case specgen.FromContainer:
   278  		cgroupCtr, err := rt.LookupContainer(s.CgroupNS.Value)
   279  		if err != nil {
   280  			return nil, fmt.Errorf("looking up container to share cgroup namespace with: %w", err)
   281  		}
   282  		if rootless.IsRootless() && cgroupCtr.NamespaceMode(spec.CgroupNamespace, cgroupCtr.ConfigNoCopy().Spec) == host {
   283  			// Treat this the same as host, the problem is the runtime tries to do a
   284  			// setns call and this will fail when it is the host ns as rootless user.
   285  			s.CgroupNS.NSMode = specgen.Host
   286  		} else {
   287  			toReturn = append(toReturn, libpod.WithCgroupNSFrom(cgroupCtr))
   288  		}
   289  	}
   290  
   291  	if s.CgroupParent != "" {
   292  		toReturn = append(toReturn, libpod.WithCgroupParent(s.CgroupParent))
   293  	}
   294  
   295  	if s.CgroupsMode != "" {
   296  		toReturn = append(toReturn, libpod.WithCgroupsMode(s.CgroupsMode))
   297  	}
   298  
   299  	postConfigureNetNS := !s.UserNS.IsHost()
   300  
   301  	switch s.NetNS.NSMode {
   302  	case specgen.FromPod:
   303  		if pod == nil || infraCtr == nil {
   304  			return nil, errNoInfra
   305  		}
   306  		toReturn = append(toReturn, libpod.WithNetNSFrom(infraCtr))
   307  	case specgen.FromContainer:
   308  		netCtr, err := rt.LookupContainer(s.NetNS.Value)
   309  		if err != nil {
   310  			return nil, fmt.Errorf("looking up container to share net namespace with: %w", err)
   311  		}
   312  		if rootless.IsRootless() && netCtr.NamespaceMode(spec.NetworkNamespace, netCtr.ConfigNoCopy().Spec) == host {
   313  			// Treat this the same as host, the problem is the runtime tries to do a
   314  			// setns call and this will fail when it is the host ns as rootless user.
   315  			s.NetNS.NSMode = specgen.Host
   316  		} else {
   317  			toReturn = append(toReturn, libpod.WithNetNSFrom(netCtr))
   318  		}
   319  	case specgen.Slirp:
   320  		portMappings, expose, err := createPortMappings(s, imageData)
   321  		if err != nil {
   322  			return nil, err
   323  		}
   324  		val := "slirp4netns"
   325  		if s.NetNS.Value != "" {
   326  			val = fmt.Sprintf("slirp4netns:%s", s.NetNS.Value)
   327  		}
   328  		toReturn = append(toReturn, libpod.WithNetNS(portMappings, expose, postConfigureNetNS, val, nil))
   329  	case specgen.Pasta:
   330  		portMappings, expose, err := createPortMappings(s, imageData)
   331  		if err != nil {
   332  			return nil, err
   333  		}
   334  		val := "pasta"
   335  		toReturn = append(toReturn, libpod.WithNetNS(portMappings, expose, postConfigureNetNS, val, nil))
   336  	case specgen.Bridge, specgen.Private, specgen.Default:
   337  		portMappings, expose, err := createPortMappings(s, imageData)
   338  		if err != nil {
   339  			return nil, err
   340  		}
   341  
   342  		rtConfig, err := rt.GetConfigNoCopy()
   343  		if err != nil {
   344  			return nil, err
   345  		}
   346  		// if no network was specified use add the default
   347  		if len(s.Networks) == 0 {
   348  			// backwards config still allow the old cni networks list and convert to new format
   349  			if len(s.CNINetworks) > 0 {
   350  				logrus.Warn(`specgen "cni_networks" option is deprecated use the "networks" map instead`)
   351  				networks := make(map[string]types.PerNetworkOptions, len(s.CNINetworks))
   352  				for _, net := range s.CNINetworks {
   353  					networks[net] = types.PerNetworkOptions{}
   354  				}
   355  				s.Networks = networks
   356  			} else {
   357  				// no networks given but bridge is set so use default network
   358  				s.Networks = map[string]types.PerNetworkOptions{
   359  					rtConfig.Network.DefaultNetwork: {},
   360  				}
   361  			}
   362  		}
   363  		// rename the "default" network to the correct default name
   364  		if opts, ok := s.Networks["default"]; ok {
   365  			s.Networks[rtConfig.Network.DefaultNetwork] = opts
   366  			delete(s.Networks, "default")
   367  		}
   368  		toReturn = append(toReturn, libpod.WithNetNS(portMappings, expose, postConfigureNetNS, "bridge", s.Networks))
   369  	}
   370  
   371  	if s.UseImageHosts {
   372  		toReturn = append(toReturn, libpod.WithUseImageHosts())
   373  	} else if len(s.HostAdd) > 0 {
   374  		toReturn = append(toReturn, libpod.WithHosts(s.HostAdd))
   375  	}
   376  	if len(s.DNSSearch) > 0 {
   377  		toReturn = append(toReturn, libpod.WithDNSSearch(s.DNSSearch))
   378  	}
   379  	if s.UseImageResolvConf {
   380  		toReturn = append(toReturn, libpod.WithUseImageResolvConf())
   381  	} else if len(s.DNSServers) > 0 {
   382  		var dnsServers []string
   383  		for _, d := range s.DNSServers {
   384  			dnsServers = append(dnsServers, d.String())
   385  		}
   386  		toReturn = append(toReturn, libpod.WithDNS(dnsServers))
   387  	}
   388  	if len(s.DNSOptions) > 0 {
   389  		toReturn = append(toReturn, libpod.WithDNSOption(s.DNSOptions))
   390  	}
   391  	if s.NetworkOptions != nil {
   392  		toReturn = append(toReturn, libpod.WithNetworkOptions(s.NetworkOptions))
   393  	}
   394  
   395  	return toReturn, nil
   396  }
   397  
   398  // GetNamespaceOptions transforms a slice of kernel namespaces
   399  // into a slice of pod create options. Currently, not all
   400  // kernel namespaces are supported, and they will be returned in an error
   401  func GetNamespaceOptions(ns []string, netnsIsHost bool) ([]libpod.PodCreateOption, error) {
   402  	var options []libpod.PodCreateOption
   403  	var erroredOptions []libpod.PodCreateOption
   404  	if ns == nil {
   405  		// set the default namespaces
   406  		ns = strings.Split(specgen.DefaultKernelNamespaces, ",")
   407  	}
   408  	for _, toShare := range ns {
   409  		switch toShare {
   410  		case "cgroup":
   411  			options = append(options, libpod.WithPodCgroup())
   412  		case "net":
   413  			options = append(options, libpod.WithPodNet())
   414  		case "mnt":
   415  			return erroredOptions, fmt.Errorf("mount sharing functionality not supported on pod level")
   416  		case "pid":
   417  			options = append(options, libpod.WithPodPID())
   418  		case "user":
   419  			continue
   420  		case "ipc":
   421  			options = append(options, libpod.WithPodIPC())
   422  		case "uts":
   423  			options = append(options, libpod.WithPodUTS())
   424  		case "":
   425  		case "none":
   426  			return erroredOptions, nil
   427  		default:
   428  			return erroredOptions, fmt.Errorf("invalid kernel namespace to share: %s. Options are: cgroup, ipc, net, pid, uts or none", toShare)
   429  		}
   430  	}
   431  	return options, nil
   432  }