github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/specgen/namespaces.go (about)

     1  package specgen
     2  
     3  import (
     4  	"fmt"
     5  	"net"
     6  	"os"
     7  	"strings"
     8  
     9  	"github.com/containers/common/libnetwork/types"
    10  	"github.com/containers/common/pkg/cgroups"
    11  	cutil "github.com/containers/common/pkg/util"
    12  	"github.com/hanks177/podman/v4/libpod/define"
    13  	"github.com/hanks177/podman/v4/pkg/util"
    14  	"github.com/containers/storage"
    15  	spec "github.com/opencontainers/runtime-spec/specs-go"
    16  	"github.com/opencontainers/runtime-tools/generate"
    17  	"github.com/pkg/errors"
    18  )
    19  
    20  type NamespaceMode string
    21  
    22  const (
    23  	// Default indicates the spec generator should determine
    24  	// a sane default
    25  	Default NamespaceMode = "default"
    26  	// Host means the the namespace is derived from
    27  	// the host
    28  	Host NamespaceMode = "host"
    29  	// Path is the path to a namespace
    30  	Path NamespaceMode = "path"
    31  	// FromContainer means namespace is derived from a
    32  	// different container
    33  	FromContainer NamespaceMode = "container"
    34  	// FromPod indicates the namespace is derived from a pod
    35  	FromPod NamespaceMode = "pod"
    36  	// Private indicates the namespace is private
    37  	Private NamespaceMode = "private"
    38  	// Shareable indicates the namespace is shareable
    39  	Shareable NamespaceMode = "shareable"
    40  	// None indicates the IPC namespace is created without mounting /dev/shm
    41  	None NamespaceMode = "none"
    42  	// NoNetwork indicates no network namespace should
    43  	// be joined.  loopback should still exists.
    44  	// Only used with the network namespace, invalid otherwise.
    45  	NoNetwork NamespaceMode = "none"
    46  	// Bridge indicates that a CNI network stack
    47  	// should be used.
    48  	// Only used with the network namespace, invalid otherwise.
    49  	Bridge NamespaceMode = "bridge"
    50  	// Slirp indicates that a slirp4netns network stack should
    51  	// be used.
    52  	// Only used with the network namespace, invalid otherwise.
    53  	Slirp NamespaceMode = "slirp4netns"
    54  	// KeepId indicates a user namespace to keep the owner uid inside
    55  	// of the namespace itself.
    56  	// Only used with the user namespace, invalid otherwise.
    57  	KeepID NamespaceMode = "keep-id"
    58  	// NoMap indicates a user namespace to keep the owner uid out
    59  	// of the namespace itself.
    60  	// Only used with the user namespace, invalid otherwise.
    61  	NoMap NamespaceMode = "no-map"
    62  	// Auto indicates to automatically create a user namespace.
    63  	// Only used with the user namespace, invalid otherwise.
    64  	Auto NamespaceMode = "auto"
    65  
    66  	// DefaultKernelNamespaces is a comma-separated list of default kernel
    67  	// namespaces.
    68  	DefaultKernelNamespaces = "ipc,net,uts"
    69  )
    70  
    71  // Namespace describes the namespace
    72  type Namespace struct {
    73  	NSMode NamespaceMode `json:"nsmode,omitempty"`
    74  	Value  string        `json:"value,omitempty"`
    75  }
    76  
    77  // IsDefault returns whether the namespace is set to the default setting (which
    78  // also includes the empty string).
    79  func (n *Namespace) IsDefault() bool {
    80  	return n.NSMode == Default || n.NSMode == ""
    81  }
    82  
    83  // IsHost returns a bool if the namespace is host based
    84  func (n *Namespace) IsHost() bool {
    85  	return n.NSMode == Host
    86  }
    87  
    88  // IsNone returns a bool if the namespace is set to none
    89  func (n *Namespace) IsNone() bool {
    90  	return n.NSMode == None
    91  }
    92  
    93  // IsBridge returns a bool if the namespace is a Bridge
    94  func (n *Namespace) IsBridge() bool {
    95  	return n.NSMode == Bridge
    96  }
    97  
    98  // IsPath indicates via bool if the namespace is based on a path
    99  func (n *Namespace) IsPath() bool {
   100  	return n.NSMode == Path
   101  }
   102  
   103  // IsContainer indicates via bool if the namespace is based on a container
   104  func (n *Namespace) IsContainer() bool {
   105  	return n.NSMode == FromContainer
   106  }
   107  
   108  // IsPod indicates via bool if the namespace is based on a pod
   109  func (n *Namespace) IsPod() bool {
   110  	return n.NSMode == FromPod
   111  }
   112  
   113  // IsPrivate indicates the namespace is private
   114  func (n *Namespace) IsPrivate() bool {
   115  	return n.NSMode == Private
   116  }
   117  
   118  // IsAuto indicates the namespace is auto
   119  func (n *Namespace) IsAuto() bool {
   120  	return n.NSMode == Auto
   121  }
   122  
   123  // IsKeepID indicates the namespace is KeepID
   124  func (n *Namespace) IsKeepID() bool {
   125  	return n.NSMode == KeepID
   126  }
   127  
   128  // IsNoMap indicates the namespace is NoMap
   129  func (n *Namespace) IsNoMap() bool {
   130  	return n.NSMode == NoMap
   131  }
   132  
   133  func (n *Namespace) String() string {
   134  	if n.Value != "" {
   135  		return fmt.Sprintf("%s:%s", n.NSMode, n.Value)
   136  	}
   137  	return string(n.NSMode)
   138  }
   139  
   140  func validateUserNS(n *Namespace) error {
   141  	if n == nil {
   142  		return nil
   143  	}
   144  	switch n.NSMode {
   145  	case Auto, KeepID, NoMap:
   146  		return nil
   147  	}
   148  	return n.validate()
   149  }
   150  
   151  func validateNetNS(n *Namespace) error {
   152  	if n == nil {
   153  		return nil
   154  	}
   155  	switch n.NSMode {
   156  	case Slirp:
   157  		break
   158  	case "", Default, Host, Path, FromContainer, FromPod, Private, NoNetwork, Bridge:
   159  		break
   160  	default:
   161  		return errors.Errorf("invalid network %q", n.NSMode)
   162  	}
   163  
   164  	// Path and From Container MUST have a string value set
   165  	if n.NSMode == Path || n.NSMode == FromContainer {
   166  		if len(n.Value) < 1 {
   167  			return errors.Errorf("namespace mode %s requires a value", n.NSMode)
   168  		}
   169  	} else if n.NSMode != Slirp {
   170  		// All others except must NOT set a string value
   171  		if len(n.Value) > 0 {
   172  			return errors.Errorf("namespace value %s cannot be provided with namespace mode %s", n.Value, n.NSMode)
   173  		}
   174  	}
   175  
   176  	return nil
   177  }
   178  
   179  func validateIPCNS(n *Namespace) error {
   180  	if n == nil {
   181  		return nil
   182  	}
   183  	switch n.NSMode {
   184  	case Shareable, None:
   185  		return nil
   186  	}
   187  	return n.validate()
   188  }
   189  
   190  // Validate perform simple validation on the namespace to make sure it is not
   191  // invalid from the get-go
   192  func (n *Namespace) validate() error {
   193  	if n == nil {
   194  		return nil
   195  	}
   196  	switch n.NSMode {
   197  	case "", Default, Host, Path, FromContainer, FromPod, Private:
   198  		// Valid, do nothing
   199  	case NoNetwork, Bridge, Slirp:
   200  		return errors.Errorf("cannot use network modes with non-network namespace")
   201  	default:
   202  		return errors.Errorf("invalid namespace type %s specified", n.NSMode)
   203  	}
   204  
   205  	// Path and From Container MUST have a string value set
   206  	if n.NSMode == Path || n.NSMode == FromContainer {
   207  		if len(n.Value) < 1 {
   208  			return errors.Errorf("namespace mode %s requires a value", n.NSMode)
   209  		}
   210  	} else {
   211  		// All others must NOT set a string value
   212  		if len(n.Value) > 0 {
   213  			return errors.Errorf("namespace value %s cannot be provided with namespace mode %s", n.Value, n.NSMode)
   214  		}
   215  	}
   216  	return nil
   217  }
   218  
   219  // ParseNamespace parses a namespace in string form.
   220  // This is not intended for the network namespace, which has a separate
   221  // function.
   222  func ParseNamespace(ns string) (Namespace, error) {
   223  	toReturn := Namespace{}
   224  	switch {
   225  	case ns == "pod":
   226  		toReturn.NSMode = FromPod
   227  	case ns == "host":
   228  		toReturn.NSMode = Host
   229  	case ns == "private", ns == "":
   230  		toReturn.NSMode = Private
   231  	case strings.HasPrefix(ns, "ns:"):
   232  		split := strings.SplitN(ns, ":", 2)
   233  		if len(split) != 2 {
   234  			return toReturn, errors.Errorf("must provide a path to a namespace when specifying \"ns:\"")
   235  		}
   236  		toReturn.NSMode = Path
   237  		toReturn.Value = split[1]
   238  	case strings.HasPrefix(ns, "container:"):
   239  		split := strings.SplitN(ns, ":", 2)
   240  		if len(split) != 2 {
   241  			return toReturn, errors.Errorf("must provide name or ID or a container when specifying \"container:\"")
   242  		}
   243  		toReturn.NSMode = FromContainer
   244  		toReturn.Value = split[1]
   245  	default:
   246  		return toReturn, errors.Errorf("unrecognized namespace mode %s passed", ns)
   247  	}
   248  
   249  	return toReturn, nil
   250  }
   251  
   252  // ParseCgroupNamespace parses a cgroup namespace specification in string
   253  // form.
   254  func ParseCgroupNamespace(ns string) (Namespace, error) {
   255  	toReturn := Namespace{}
   256  	// Cgroup is host for v1, private for v2.
   257  	// We can't trust c/common for this, as it only assumes private.
   258  	cgroupsv2, err := cgroups.IsCgroup2UnifiedMode()
   259  	if err != nil {
   260  		return toReturn, err
   261  	}
   262  	if cgroupsv2 {
   263  		switch ns {
   264  		case "host":
   265  			toReturn.NSMode = Host
   266  		case "private", "":
   267  			toReturn.NSMode = Private
   268  		default:
   269  			return toReturn, errors.Errorf("unrecognized cgroup namespace mode %s passed", ns)
   270  		}
   271  	} else {
   272  		toReturn.NSMode = Host
   273  	}
   274  	return toReturn, nil
   275  }
   276  
   277  // ParseIPCNamespace parses a ipc namespace specification in string
   278  // form.
   279  func ParseIPCNamespace(ns string) (Namespace, error) {
   280  	toReturn := Namespace{}
   281  	switch {
   282  	case ns == "shareable", ns == "":
   283  		toReturn.NSMode = Shareable
   284  		return toReturn, nil
   285  	case ns == "none":
   286  		toReturn.NSMode = None
   287  		return toReturn, nil
   288  	}
   289  	return ParseNamespace(ns)
   290  }
   291  
   292  // ParseUserNamespace parses a user namespace specification in string
   293  // form.
   294  func ParseUserNamespace(ns string) (Namespace, error) {
   295  	toReturn := Namespace{}
   296  	switch {
   297  	case ns == "auto":
   298  		toReturn.NSMode = Auto
   299  		return toReturn, nil
   300  	case strings.HasPrefix(ns, "auto:"):
   301  		split := strings.SplitN(ns, ":", 2)
   302  		if len(split) != 2 {
   303  			return toReturn, errors.Errorf("invalid setting for auto: mode")
   304  		}
   305  		toReturn.NSMode = Auto
   306  		toReturn.Value = split[1]
   307  		return toReturn, nil
   308  	case ns == "keep-id":
   309  		toReturn.NSMode = KeepID
   310  		return toReturn, nil
   311  	case ns == "nomap":
   312  		toReturn.NSMode = NoMap
   313  		return toReturn, nil
   314  	case ns == "":
   315  		toReturn.NSMode = Host
   316  		return toReturn, nil
   317  	}
   318  	return ParseNamespace(ns)
   319  }
   320  
   321  // ParseNetworkFlag parses a network string slice into the network options
   322  // If the input is nil or empty it will use the default setting from containers.conf
   323  func ParseNetworkFlag(networks []string) (Namespace, map[string]types.PerNetworkOptions, map[string][]string, error) {
   324  	var networkOptions map[string][]string
   325  	// by default we try to use the containers.conf setting
   326  	// if we get at least one value use this instead
   327  	ns := containerConfig.Containers.NetNS
   328  	if len(networks) > 0 {
   329  		ns = networks[0]
   330  	}
   331  
   332  	toReturn := Namespace{}
   333  	podmanNetworks := make(map[string]types.PerNetworkOptions)
   334  
   335  	switch {
   336  	case ns == string(Slirp), strings.HasPrefix(ns, string(Slirp)+":"):
   337  		parts := strings.SplitN(ns, ":", 2)
   338  		if len(parts) > 1 {
   339  			networkOptions = make(map[string][]string)
   340  			networkOptions[parts[0]] = strings.Split(parts[1], ",")
   341  		}
   342  		toReturn.NSMode = Slirp
   343  	case ns == string(FromPod):
   344  		toReturn.NSMode = FromPod
   345  	case ns == "" || ns == string(Default) || ns == string(Private):
   346  		toReturn.NSMode = Private
   347  	case ns == string(Bridge), strings.HasPrefix(ns, string(Bridge)+":"):
   348  		toReturn.NSMode = Bridge
   349  		parts := strings.SplitN(ns, ":", 2)
   350  		netOpts := types.PerNetworkOptions{}
   351  		if len(parts) > 1 {
   352  			var err error
   353  			netOpts, err = parseBridgeNetworkOptions(parts[1])
   354  			if err != nil {
   355  				return toReturn, nil, nil, err
   356  			}
   357  		}
   358  		// we have to set the special default network name here
   359  		podmanNetworks["default"] = netOpts
   360  
   361  	case ns == string(NoNetwork):
   362  		toReturn.NSMode = NoNetwork
   363  	case ns == string(Host):
   364  		toReturn.NSMode = Host
   365  	case strings.HasPrefix(ns, "ns:"):
   366  		split := strings.SplitN(ns, ":", 2)
   367  		if len(split) != 2 {
   368  			return toReturn, nil, nil, errors.Errorf("must provide a path to a namespace when specifying \"ns:\"")
   369  		}
   370  		toReturn.NSMode = Path
   371  		toReturn.Value = split[1]
   372  	case strings.HasPrefix(ns, string(FromContainer)+":"):
   373  		split := strings.SplitN(ns, ":", 2)
   374  		if len(split) != 2 {
   375  			return toReturn, nil, nil, errors.Errorf("must provide name or ID or a container when specifying \"container:\"")
   376  		}
   377  		toReturn.NSMode = FromContainer
   378  		toReturn.Value = split[1]
   379  	default:
   380  		// we should have a normal network
   381  		parts := strings.SplitN(ns, ":", 2)
   382  		if len(parts) == 1 {
   383  			// Assume we have been given a comma separated list of networks for backwards compat.
   384  			networkList := strings.Split(ns, ",")
   385  			for _, net := range networkList {
   386  				podmanNetworks[net] = types.PerNetworkOptions{}
   387  			}
   388  		} else {
   389  			if parts[0] == "" {
   390  				return toReturn, nil, nil, errors.New("network name cannot be empty")
   391  			}
   392  			netOpts, err := parseBridgeNetworkOptions(parts[1])
   393  			if err != nil {
   394  				return toReturn, nil, nil, errors.Wrapf(err, "invalid option for network %s", parts[0])
   395  			}
   396  			podmanNetworks[parts[0]] = netOpts
   397  		}
   398  
   399  		// networks need bridge mode
   400  		toReturn.NSMode = Bridge
   401  	}
   402  
   403  	if len(networks) > 1 {
   404  		if !toReturn.IsBridge() {
   405  			return toReturn, nil, nil, errors.Wrapf(define.ErrInvalidArg, "cannot set multiple networks without bridge network mode, selected mode %s", toReturn.NSMode)
   406  		}
   407  
   408  		for _, network := range networks[1:] {
   409  			parts := strings.SplitN(network, ":", 2)
   410  			if parts[0] == "" {
   411  				return toReturn, nil, nil, errors.Wrapf(define.ErrInvalidArg, "network name cannot be empty")
   412  			}
   413  			if cutil.StringInSlice(parts[0], []string{string(Bridge), string(Slirp), string(FromPod), string(NoNetwork),
   414  				string(Default), string(Private), string(Path), string(FromContainer), string(Host)}) {
   415  				return toReturn, nil, nil, errors.Wrapf(define.ErrInvalidArg, "can only set extra network names, selected mode %s conflicts with bridge", parts[0])
   416  			}
   417  			netOpts := types.PerNetworkOptions{}
   418  			if len(parts) > 1 {
   419  				var err error
   420  				netOpts, err = parseBridgeNetworkOptions(parts[1])
   421  				if err != nil {
   422  					return toReturn, nil, nil, errors.Wrapf(err, "invalid option for network %s", parts[0])
   423  				}
   424  			}
   425  			podmanNetworks[parts[0]] = netOpts
   426  		}
   427  	}
   428  
   429  	return toReturn, podmanNetworks, networkOptions, nil
   430  }
   431  
   432  func parseBridgeNetworkOptions(opts string) (types.PerNetworkOptions, error) {
   433  	netOpts := types.PerNetworkOptions{}
   434  	if len(opts) == 0 {
   435  		return netOpts, nil
   436  	}
   437  	allopts := strings.Split(opts, ",")
   438  	for _, opt := range allopts {
   439  		split := strings.SplitN(opt, "=", 2)
   440  		switch split[0] {
   441  		case "ip", "ip6":
   442  			ip := net.ParseIP(split[1])
   443  			if ip == nil {
   444  				return netOpts, errors.Errorf("invalid ip address %q", split[1])
   445  			}
   446  			netOpts.StaticIPs = append(netOpts.StaticIPs, ip)
   447  
   448  		case "mac":
   449  			mac, err := net.ParseMAC(split[1])
   450  			if err != nil {
   451  				return netOpts, err
   452  			}
   453  			netOpts.StaticMAC = types.HardwareAddr(mac)
   454  
   455  		case "alias":
   456  			if split[1] == "" {
   457  				return netOpts, errors.New("alias cannot be empty")
   458  			}
   459  			netOpts.Aliases = append(netOpts.Aliases, split[1])
   460  
   461  		case "interface_name":
   462  			if split[1] == "" {
   463  				return netOpts, errors.New("interface_name cannot be empty")
   464  			}
   465  			netOpts.InterfaceName = split[1]
   466  
   467  		default:
   468  			return netOpts, errors.Errorf("unknown bridge network option: %s", split[0])
   469  		}
   470  	}
   471  	return netOpts, nil
   472  }
   473  
   474  func SetupUserNS(idmappings *storage.IDMappingOptions, userns Namespace, g *generate.Generator) (string, error) {
   475  	// User
   476  	var user string
   477  	switch userns.NSMode {
   478  	case Path:
   479  		if _, err := os.Stat(userns.Value); err != nil {
   480  			return user, errors.Wrap(err, "cannot find specified user namespace path")
   481  		}
   482  		if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), userns.Value); err != nil {
   483  			return user, err
   484  		}
   485  		// runc complains if no mapping is specified, even if we join another ns.  So provide a dummy mapping
   486  		g.AddLinuxUIDMapping(uint32(0), uint32(0), uint32(1))
   487  		g.AddLinuxGIDMapping(uint32(0), uint32(0), uint32(1))
   488  	case Host:
   489  		if err := g.RemoveLinuxNamespace(string(spec.UserNamespace)); err != nil {
   490  			return user, err
   491  		}
   492  	case KeepID:
   493  		mappings, uid, gid, err := util.GetKeepIDMapping()
   494  		if err != nil {
   495  			return user, err
   496  		}
   497  		idmappings = mappings
   498  		g.SetProcessUID(uint32(uid))
   499  		g.SetProcessGID(uint32(gid))
   500  		user = fmt.Sprintf("%d:%d", uid, gid)
   501  		if err := privateUserNamespace(idmappings, g); err != nil {
   502  			return user, err
   503  		}
   504  	case NoMap:
   505  		mappings, uid, gid, err := util.GetNoMapMapping()
   506  		if err != nil {
   507  			return user, err
   508  		}
   509  		idmappings = mappings
   510  		g.SetProcessUID(uint32(uid))
   511  		g.SetProcessGID(uint32(gid))
   512  		user = fmt.Sprintf("%d:%d", uid, gid)
   513  		if err := privateUserNamespace(idmappings, g); err != nil {
   514  			return user, err
   515  		}
   516  	case Private:
   517  		if err := privateUserNamespace(idmappings, g); err != nil {
   518  			return user, err
   519  		}
   520  	}
   521  	return user, nil
   522  }
   523  
   524  func privateUserNamespace(idmappings *storage.IDMappingOptions, g *generate.Generator) error {
   525  	if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), ""); err != nil {
   526  		return err
   527  	}
   528  	if idmappings == nil || (len(idmappings.UIDMap) == 0 && len(idmappings.GIDMap) == 0) {
   529  		return errors.Errorf("must provide at least one UID or GID mapping to configure a user namespace")
   530  	}
   531  	for _, uidmap := range idmappings.UIDMap {
   532  		g.AddLinuxUIDMapping(uint32(uidmap.HostID), uint32(uidmap.ContainerID), uint32(uidmap.Size))
   533  	}
   534  	for _, gidmap := range idmappings.GIDMap {
   535  		g.AddLinuxGIDMapping(uint32(gidmap.HostID), uint32(gidmap.ContainerID), uint32(gidmap.Size))
   536  	}
   537  	return nil
   538  }