9
"github.com/containers/common/libnetwork/types"
10
"github.com/containers/common/pkg/cgroups"
11
"github.com/containers/podman/v5/libpod/define"
12
"github.com/containers/podman/v5/pkg/namespaces"
13
"github.com/containers/podman/v5/pkg/rootless"
14
"github.com/containers/podman/v5/pkg/util"
15
"github.com/containers/storage/pkg/fileutils"
16
storageTypes "github.com/containers/storage/types"
17
spec "github.com/opencontainers/runtime-spec/specs-go"
18
"github.com/opencontainers/runtime-tools/generate"
19
"golang.org/x/exp/slices"
22
type NamespaceMode string
25
// Default indicates the spec generator should determine
27
Default NamespaceMode = "default"
28
// Host means the namespace is derived from the host
29
Host NamespaceMode = "host"
30
// Path is the path to a namespace
31
Path NamespaceMode = "path"
32
// FromContainer means namespace is derived from a
33
// different container
34
FromContainer NamespaceMode = "container"
35
// FromPod indicates the namespace is derived from a pod
36
FromPod NamespaceMode = "pod"
37
// Private indicates the namespace is private
38
Private NamespaceMode = "private"
39
// Shareable indicates the namespace is shareable
40
Shareable NamespaceMode = "shareable"
41
// None indicates the IPC namespace is created without mounting /dev/shm
42
None NamespaceMode = "none"
43
// NoNetwork indicates no network namespace should
44
// be joined. loopback should still exist.
45
// Only used with the network namespace, invalid otherwise.
46
NoNetwork NamespaceMode = "none"
47
// Bridge indicates that the network backend (CNI/netavark)
49
// Only used with the network namespace, invalid otherwise.
50
Bridge NamespaceMode = "bridge"
51
// Slirp indicates that a slirp4netns network stack should
53
// Only used with the network namespace, invalid otherwise.
54
Slirp NamespaceMode = "slirp4netns"
55
// Pasta indicates that a pasta network stack should be used.
56
// Only used with the network namespace, invalid otherwise.
57
Pasta NamespaceMode = "pasta"
58
// KeepId indicates a user namespace to keep the owner uid inside
59
// of the namespace itself.
60
// Only used with the user namespace, invalid otherwise.
61
KeepID NamespaceMode = "keep-id"
62
// NoMap indicates a user namespace to keep the owner uid out
63
// of the namespace itself.
64
// Only used with the user namespace, invalid otherwise.
65
NoMap NamespaceMode = "no-map"
66
// Auto indicates to automatically create a user namespace.
67
// Only used with the user namespace, invalid otherwise.
68
Auto NamespaceMode = "auto"
70
// DefaultKernelNamespaces is a comma-separated list of default kernel
72
DefaultKernelNamespaces = "ipc,net,uts"
75
// Namespace describes the namespace
76
type Namespace struct {
77
NSMode NamespaceMode `json:"nsmode,omitempty"`
78
Value string `json:"value,omitempty"`
81
// IsDefault returns whether the namespace is set to the default setting (which
82
// also includes the empty string).
83
func (n *Namespace) IsDefault() bool {
84
return n.NSMode == Default || n.NSMode == ""
87
// IsHost returns a bool if the namespace is host based
88
func (n *Namespace) IsHost() bool {
89
return n.NSMode == Host
92
// IsNone returns a bool if the namespace is set to none
93
func (n *Namespace) IsNone() bool {
94
return n.NSMode == None
97
// IsBridge returns a bool if the namespace is a Bridge
98
func (n *Namespace) IsBridge() bool {
99
return n.NSMode == Bridge
102
// IsPath indicates via bool if the namespace is based on a path
103
func (n *Namespace) IsPath() bool {
104
return n.NSMode == Path
107
// IsContainer indicates via bool if the namespace is based on a container
108
func (n *Namespace) IsContainer() bool {
109
return n.NSMode == FromContainer
112
// IsPod indicates via bool if the namespace is based on a pod
113
func (n *Namespace) IsPod() bool {
114
return n.NSMode == FromPod
117
// IsPrivate indicates the namespace is private
118
func (n *Namespace) IsPrivate() bool {
119
return n.NSMode == Private
122
// IsAuto indicates the namespace is auto
123
func (n *Namespace) IsAuto() bool {
124
return n.NSMode == Auto
127
// IsKeepID indicates the namespace is KeepID
128
func (n *Namespace) IsKeepID() bool {
129
return n.NSMode == KeepID
132
// IsNoMap indicates the namespace is NoMap
133
func (n *Namespace) IsNoMap() bool {
134
return n.NSMode == NoMap
137
func (n *Namespace) String() string {
139
return fmt.Sprintf("%s:%s", n.NSMode, n.Value)
141
return string(n.NSMode)
144
func validateUserNS(n *Namespace) error {
149
case Auto, KeepID, NoMap:
155
func validateNetNS(n *Namespace) error {
163
if rootless.IsRootless() {
166
return fmt.Errorf("pasta networking is only supported for rootless mode")
167
case "", Default, Host, Path, FromContainer, FromPod, Private, NoNetwork, Bridge:
170
return fmt.Errorf("invalid network %q", n.NSMode)
173
// Path and From Container MUST have a string value set
174
if n.NSMode == Path || n.NSMode == FromContainer {
175
if len(n.Value) < 1 {
176
return fmt.Errorf("namespace mode %s requires a value", n.NSMode)
178
} else if n.NSMode != Slirp {
179
// All others except must NOT set a string value
180
if len(n.Value) > 0 {
181
return fmt.Errorf("namespace value %s cannot be provided with namespace mode %s", n.Value, n.NSMode)
188
func validateIPCNS(n *Namespace) error {
193
case Shareable, None:
199
// Validate perform simple validation on the namespace to make sure it is not
200
// invalid from the get-go
201
func (n *Namespace) validate() error {
206
case "", Default, Host, Path, FromContainer, FromPod, Private:
208
case NoNetwork, Bridge, Slirp, Pasta:
209
return errors.New("cannot use network modes with non-network namespace")
211
return fmt.Errorf("invalid namespace type %s specified", n.NSMode)
214
// Path and From Container MUST have a string value set
215
if n.NSMode == Path || n.NSMode == FromContainer {
216
if len(n.Value) < 1 {
217
return fmt.Errorf("namespace mode %s requires a value", n.NSMode)
220
// All others must NOT set a string value
221
if len(n.Value) > 0 {
222
return fmt.Errorf("namespace value %s cannot be provided with namespace mode %s", n.Value, n.NSMode)
228
// ParseNamespace parses a namespace in string form.
229
// This is not intended for the network namespace, which has a separate
231
func ParseNamespace(ns string) (Namespace, error) {
232
toReturn := Namespace{}
235
toReturn.NSMode = FromPod
237
toReturn.NSMode = Host
239
toReturn.NSMode = Private
241
if value, ok := strings.CutPrefix(ns, "ns:"); ok {
242
toReturn.NSMode = Path
243
toReturn.Value = value
244
} else if value, ok := strings.CutPrefix(ns, "container:"); ok {
245
toReturn.NSMode = FromContainer
246
toReturn.Value = value
248
return toReturn, fmt.Errorf("unrecognized namespace mode %s passed", ns)
255
// ParseCgroupNamespace parses a cgroup namespace specification in string
257
func ParseCgroupNamespace(ns string) (Namespace, error) {
258
toReturn := Namespace{}
259
// Cgroup is host for v1, private for v2.
260
// We can't trust c/common for this, as it only assumes private.
261
cgroupsv2, err := cgroups.IsCgroup2UnifiedMode()
268
toReturn.NSMode = Host
270
toReturn.NSMode = Private
272
return toReturn, fmt.Errorf("unrecognized cgroup namespace mode %s passed", ns)
275
toReturn.NSMode = Host
280
// ParseIPCNamespace parses an ipc namespace specification in string
282
func ParseIPCNamespace(ns string) (Namespace, error) {
283
toReturn := Namespace{}
285
case ns == "shareable", ns == "":
286
toReturn.NSMode = Shareable
289
toReturn.NSMode = None
292
return ParseNamespace(ns)
295
// ParseUserNamespace parses a user namespace specification in string
297
func ParseUserNamespace(ns string) (Namespace, error) {
298
toReturn := Namespace{}
301
toReturn.NSMode = Auto
304
toReturn.NSMode = KeepID
307
toReturn.NSMode = NoMap
310
toReturn.NSMode = Host
313
if value, ok := strings.CutPrefix(ns, "auto:"); ok {
314
toReturn.NSMode = Auto
315
toReturn.Value = value
317
} else if value, ok := strings.CutPrefix(ns, "keep-id:"); ok {
318
toReturn.NSMode = KeepID
319
toReturn.Value = value
322
return ParseNamespace(ns)
327
// ParseNetworkFlag parses a network string slice into the network options
328
// If the input is nil or empty it will use the default setting from containers.conf
329
func ParseNetworkFlag(networks []string) (Namespace, map[string]types.PerNetworkOptions, map[string][]string, error) {
330
var networkOptions map[string][]string
331
// by default we try to use the containers.conf setting
332
// if we get at least one value use this instead
333
ns := containerConfig.Containers.NetNS
334
if len(networks) > 0 {
338
toReturn := Namespace{}
339
podmanNetworks := make(map[string]types.PerNetworkOptions)
342
case ns == string(Slirp), strings.HasPrefix(ns, string(Slirp)+":"):
343
key, options, hasOptions := strings.Cut(ns, ":")
345
networkOptions = make(map[string][]string)
346
networkOptions[key] = strings.Split(options, ",")
348
toReturn.NSMode = Slirp
349
case ns == string(FromPod):
350
toReturn.NSMode = FromPod
351
case ns == "" || ns == string(Default) || ns == string(Private):
352
toReturn.NSMode = Private
353
case ns == string(Bridge), strings.HasPrefix(ns, string(Bridge)+":"):
354
toReturn.NSMode = Bridge
355
_, options, hasOptions := strings.Cut(ns, ":")
356
netOpts := types.PerNetworkOptions{}
359
netOpts, err = parseBridgeNetworkOptions(options)
361
return toReturn, nil, nil, err
364
// we have to set the special default network name here
365
podmanNetworks["default"] = netOpts
367
case ns == string(NoNetwork):
368
toReturn.NSMode = NoNetwork
369
case ns == string(Host):
370
toReturn.NSMode = Host
371
case strings.HasPrefix(ns, "ns:"):
372
_, value, _ := strings.Cut(ns, ":")
373
toReturn.NSMode = Path
374
toReturn.Value = value
375
case strings.HasPrefix(ns, string(FromContainer)+":"):
376
_, value, _ := strings.Cut(ns, ":")
377
toReturn.NSMode = FromContainer
378
toReturn.Value = value
379
case ns == string(Pasta), strings.HasPrefix(ns, string(Pasta)+":"):
380
key, options, hasOptions := strings.Cut(ns, ":")
382
networkOptions = make(map[string][]string)
383
networkOptions[key] = strings.Split(options, ",")
385
toReturn.NSMode = Pasta
387
// we should have a normal network
388
name, options, hasOptions := strings.Cut(ns, ":")
391
return toReturn, nil, nil, errors.New("network name cannot be empty")
393
netOpts, err := parseBridgeNetworkOptions(options)
395
return toReturn, nil, nil, fmt.Errorf("invalid option for network %s: %w", name, err)
397
podmanNetworks[name] = netOpts
399
// Assume we have been given a comma separated list of networks for backwards compat.
400
networkList := strings.Split(ns, ",")
401
for _, net := range networkList {
402
podmanNetworks[net] = types.PerNetworkOptions{}
406
// networks need bridge mode
407
toReturn.NSMode = Bridge
410
if len(networks) > 1 {
411
if !toReturn.IsBridge() {
412
return toReturn, nil, nil, fmt.Errorf("cannot set multiple networks without bridge network mode, selected mode %s: %w", toReturn.NSMode, define.ErrInvalidArg)
415
for _, network := range networks[1:] {
416
name, options, hasOptions := strings.Cut(network, ":")
418
return toReturn, nil, nil, fmt.Errorf("network name cannot be empty: %w", define.ErrInvalidArg)
420
if slices.Contains([]string{string(Bridge), string(Slirp), string(Pasta), string(FromPod), string(NoNetwork),
421
string(Default), string(Private), string(Path), string(FromContainer), string(Host)}, name) {
422
return toReturn, nil, nil, fmt.Errorf("can only set extra network names, selected mode %s conflicts with bridge: %w", name, define.ErrInvalidArg)
424
netOpts := types.PerNetworkOptions{}
427
netOpts, err = parseBridgeNetworkOptions(options)
429
return toReturn, nil, nil, fmt.Errorf("invalid option for network %s: %w", name, err)
432
podmanNetworks[name] = netOpts
436
return toReturn, podmanNetworks, networkOptions, nil
439
func parseBridgeNetworkOptions(opts string) (types.PerNetworkOptions, error) {
440
netOpts := types.PerNetworkOptions{}
444
allopts := strings.Split(opts, ",")
445
for _, opt := range allopts {
446
name, value, _ := strings.Cut(opt, "=")
449
ip := net.ParseIP(value)
451
return netOpts, fmt.Errorf("invalid ip address %q", value)
453
netOpts.StaticIPs = append(netOpts.StaticIPs, ip)
456
mac, err := net.ParseMAC(value)
460
netOpts.StaticMAC = types.HardwareAddr(mac)
464
return netOpts, errors.New("alias cannot be empty")
466
netOpts.Aliases = append(netOpts.Aliases, value)
468
case "interface_name":
470
return netOpts, errors.New("interface_name cannot be empty")
472
netOpts.InterfaceName = value
475
return netOpts, fmt.Errorf("unknown bridge network option: %s", name)
481
func SetupUserNS(idmappings *storageTypes.IDMappingOptions, userns Namespace, g *generate.Generator) (string, error) {
484
switch userns.NSMode {
486
if err := fileutils.Exists(userns.Value); err != nil {
487
return user, fmt.Errorf("cannot find specified user namespace path: %w", err)
489
if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), userns.Value); err != nil {
492
// runc complains if no mapping is specified, even if we join another ns. So provide a dummy mapping
493
g.AddLinuxUIDMapping(uint32(0), uint32(0), uint32(1))
494
g.AddLinuxGIDMapping(uint32(0), uint32(0), uint32(1))
496
if err := g.RemoveLinuxNamespace(string(spec.UserNamespace)); err != nil {
500
opts, err := namespaces.UsernsMode(userns.String()).GetKeepIDOptions()
504
mappings, uid, gid, err := util.GetKeepIDMapping(opts)
508
idmappings = mappings
509
g.SetProcessUID(uint32(uid))
510
g.SetProcessGID(uint32(gid))
511
g.AddProcessAdditionalGid(uint32(gid))
512
user = fmt.Sprintf("%d:%d", uid, gid)
513
if err := privateUserNamespace(idmappings, g); err != nil {
517
mappings, uid, gid, err := util.GetNoMapMapping()
521
idmappings = mappings
522
g.SetProcessUID(uint32(uid))
523
g.SetProcessGID(uint32(gid))
524
g.AddProcessAdditionalGid(uint32(gid))
525
user = fmt.Sprintf("%d:%d", uid, gid)
526
if err := privateUserNamespace(idmappings, g); err != nil {
530
if err := privateUserNamespace(idmappings, g); err != nil {
537
func privateUserNamespace(idmappings *storageTypes.IDMappingOptions, g *generate.Generator) error {
538
if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), ""); err != nil {
541
if idmappings == nil || (len(idmappings.UIDMap) == 0 && len(idmappings.GIDMap) == 0) {
542
return errors.New("must provide at least one UID or GID mapping to configure a user namespace")
544
for _, uidmap := range idmappings.UIDMap {
545
g.AddLinuxUIDMapping(uint32(uidmap.HostID), uint32(uidmap.ContainerID), uint32(uidmap.Size))
547
for _, gidmap := range idmappings.GIDMap {
548
g.AddLinuxGIDMapping(uint32(gidmap.HostID), uint32(gidmap.ContainerID), uint32(gidmap.Size))