podman

Форк
0
/
namespaces.go 
551 строка · 16.0 Кб
1
package specgen
2

3
import (
4
	"errors"
5
	"fmt"
6
	"net"
7
	"strings"
8

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"
20
)
21

22
type NamespaceMode string
23

24
const (
25
	// Default indicates the spec generator should determine
26
	// a sane default
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)
48
	// should be used.
49
	// Only used with the network namespace, invalid otherwise.
50
	Bridge NamespaceMode = "bridge"
51
	// Slirp indicates that a slirp4netns network stack should
52
	// be used.
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"
69

70
	// DefaultKernelNamespaces is a comma-separated list of default kernel
71
	// namespaces.
72
	DefaultKernelNamespaces = "ipc,net,uts"
73
)
74

75
// Namespace describes the namespace
76
type Namespace struct {
77
	NSMode NamespaceMode `json:"nsmode,omitempty"`
78
	Value  string        `json:"value,omitempty"`
79
}
80

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 == ""
85
}
86

87
// IsHost returns a bool if the namespace is host based
88
func (n *Namespace) IsHost() bool {
89
	return n.NSMode == Host
90
}
91

92
// IsNone returns a bool if the namespace is set to none
93
func (n *Namespace) IsNone() bool {
94
	return n.NSMode == None
95
}
96

97
// IsBridge returns a bool if the namespace is a Bridge
98
func (n *Namespace) IsBridge() bool {
99
	return n.NSMode == Bridge
100
}
101

102
// IsPath indicates via bool if the namespace is based on a path
103
func (n *Namespace) IsPath() bool {
104
	return n.NSMode == Path
105
}
106

107
// IsContainer indicates via bool if the namespace is based on a container
108
func (n *Namespace) IsContainer() bool {
109
	return n.NSMode == FromContainer
110
}
111

112
// IsPod indicates via bool if the namespace is based on a pod
113
func (n *Namespace) IsPod() bool {
114
	return n.NSMode == FromPod
115
}
116

117
// IsPrivate indicates the namespace is private
118
func (n *Namespace) IsPrivate() bool {
119
	return n.NSMode == Private
120
}
121

122
// IsAuto indicates the namespace is auto
123
func (n *Namespace) IsAuto() bool {
124
	return n.NSMode == Auto
125
}
126

127
// IsKeepID indicates the namespace is KeepID
128
func (n *Namespace) IsKeepID() bool {
129
	return n.NSMode == KeepID
130
}
131

132
// IsNoMap indicates the namespace is NoMap
133
func (n *Namespace) IsNoMap() bool {
134
	return n.NSMode == NoMap
135
}
136

137
func (n *Namespace) String() string {
138
	if n.Value != "" {
139
		return fmt.Sprintf("%s:%s", n.NSMode, n.Value)
140
	}
141
	return string(n.NSMode)
142
}
143

144
func validateUserNS(n *Namespace) error {
145
	if n == nil {
146
		return nil
147
	}
148
	switch n.NSMode {
149
	case Auto, KeepID, NoMap:
150
		return nil
151
	}
152
	return n.validate()
153
}
154

155
func validateNetNS(n *Namespace) error {
156
	if n == nil {
157
		return nil
158
	}
159
	switch n.NSMode {
160
	case Slirp:
161
		break
162
	case Pasta:
163
		if rootless.IsRootless() {
164
			break
165
		}
166
		return fmt.Errorf("pasta networking is only supported for rootless mode")
167
	case "", Default, Host, Path, FromContainer, FromPod, Private, NoNetwork, Bridge:
168
		break
169
	default:
170
		return fmt.Errorf("invalid network %q", n.NSMode)
171
	}
172

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)
177
		}
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)
182
		}
183
	}
184

185
	return nil
186
}
187

188
func validateIPCNS(n *Namespace) error {
189
	if n == nil {
190
		return nil
191
	}
192
	switch n.NSMode {
193
	case Shareable, None:
194
		return nil
195
	}
196
	return n.validate()
197
}
198

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 {
202
	if n == nil {
203
		return nil
204
	}
205
	switch n.NSMode {
206
	case "", Default, Host, Path, FromContainer, FromPod, Private:
207
		// Valid, do nothing
208
	case NoNetwork, Bridge, Slirp, Pasta:
209
		return errors.New("cannot use network modes with non-network namespace")
210
	default:
211
		return fmt.Errorf("invalid namespace type %s specified", n.NSMode)
212
	}
213

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)
218
		}
219
	} else {
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)
223
		}
224
	}
225
	return nil
226
}
227

228
// ParseNamespace parses a namespace in string form.
229
// This is not intended for the network namespace, which has a separate
230
// function.
231
func ParseNamespace(ns string) (Namespace, error) {
232
	toReturn := Namespace{}
233
	switch ns {
234
	case "pod":
235
		toReturn.NSMode = FromPod
236
	case "host":
237
		toReturn.NSMode = Host
238
	case "private", "":
239
		toReturn.NSMode = Private
240
	default:
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
247
		} else {
248
			return toReturn, fmt.Errorf("unrecognized namespace mode %s passed", ns)
249
		}
250
	}
251

252
	return toReturn, nil
253
}
254

255
// ParseCgroupNamespace parses a cgroup namespace specification in string
256
// form.
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()
262
	if err != nil {
263
		return toReturn, err
264
	}
265
	if cgroupsv2 {
266
		switch ns {
267
		case "host":
268
			toReturn.NSMode = Host
269
		case "private", "":
270
			toReturn.NSMode = Private
271
		default:
272
			return toReturn, fmt.Errorf("unrecognized cgroup namespace mode %s passed", ns)
273
		}
274
	} else {
275
		toReturn.NSMode = Host
276
	}
277
	return toReturn, nil
278
}
279

280
// ParseIPCNamespace parses an ipc namespace specification in string
281
// form.
282
func ParseIPCNamespace(ns string) (Namespace, error) {
283
	toReturn := Namespace{}
284
	switch {
285
	case ns == "shareable", ns == "":
286
		toReturn.NSMode = Shareable
287
		return toReturn, nil
288
	case ns == "none":
289
		toReturn.NSMode = None
290
		return toReturn, nil
291
	}
292
	return ParseNamespace(ns)
293
}
294

295
// ParseUserNamespace parses a user namespace specification in string
296
// form.
297
func ParseUserNamespace(ns string) (Namespace, error) {
298
	toReturn := Namespace{}
299
	switch ns {
300
	case "auto":
301
		toReturn.NSMode = Auto
302
		return toReturn, nil
303
	case "keep-id":
304
		toReturn.NSMode = KeepID
305
		return toReturn, nil
306
	case "nomap":
307
		toReturn.NSMode = NoMap
308
		return toReturn, nil
309
	case "":
310
		toReturn.NSMode = Host
311
		return toReturn, nil
312
	default:
313
		if value, ok := strings.CutPrefix(ns, "auto:"); ok {
314
			toReturn.NSMode = Auto
315
			toReturn.Value = value
316
			return toReturn, nil
317
		} else if value, ok := strings.CutPrefix(ns, "keep-id:"); ok {
318
			toReturn.NSMode = KeepID
319
			toReturn.Value = value
320
			return toReturn, nil
321
		} else {
322
			return ParseNamespace(ns)
323
		}
324
	}
325
}
326

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 {
335
		ns = networks[0]
336
	}
337

338
	toReturn := Namespace{}
339
	podmanNetworks := make(map[string]types.PerNetworkOptions)
340

341
	switch {
342
	case ns == string(Slirp), strings.HasPrefix(ns, string(Slirp)+":"):
343
		key, options, hasOptions := strings.Cut(ns, ":")
344
		if hasOptions {
345
			networkOptions = make(map[string][]string)
346
			networkOptions[key] = strings.Split(options, ",")
347
		}
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{}
357
		if hasOptions {
358
			var err error
359
			netOpts, err = parseBridgeNetworkOptions(options)
360
			if err != nil {
361
				return toReturn, nil, nil, err
362
			}
363
		}
364
		// we have to set the special default network name here
365
		podmanNetworks["default"] = netOpts
366

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, ":")
381
		if hasOptions {
382
			networkOptions = make(map[string][]string)
383
			networkOptions[key] = strings.Split(options, ",")
384
		}
385
		toReturn.NSMode = Pasta
386
	default:
387
		// we should have a normal network
388
		name, options, hasOptions := strings.Cut(ns, ":")
389
		if hasOptions {
390
			if name == "" {
391
				return toReturn, nil, nil, errors.New("network name cannot be empty")
392
			}
393
			netOpts, err := parseBridgeNetworkOptions(options)
394
			if err != nil {
395
				return toReturn, nil, nil, fmt.Errorf("invalid option for network %s: %w", name, err)
396
			}
397
			podmanNetworks[name] = netOpts
398
		} else {
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{}
403
			}
404
		}
405

406
		// networks need bridge mode
407
		toReturn.NSMode = Bridge
408
	}
409

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)
413
		}
414

415
		for _, network := range networks[1:] {
416
			name, options, hasOptions := strings.Cut(network, ":")
417
			if name == "" {
418
				return toReturn, nil, nil, fmt.Errorf("network name cannot be empty: %w", define.ErrInvalidArg)
419
			}
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)
423
			}
424
			netOpts := types.PerNetworkOptions{}
425
			if hasOptions {
426
				var err error
427
				netOpts, err = parseBridgeNetworkOptions(options)
428
				if err != nil {
429
					return toReturn, nil, nil, fmt.Errorf("invalid option for network %s: %w", name, err)
430
				}
431
			}
432
			podmanNetworks[name] = netOpts
433
		}
434
	}
435

436
	return toReturn, podmanNetworks, networkOptions, nil
437
}
438

439
func parseBridgeNetworkOptions(opts string) (types.PerNetworkOptions, error) {
440
	netOpts := types.PerNetworkOptions{}
441
	if len(opts) == 0 {
442
		return netOpts, nil
443
	}
444
	allopts := strings.Split(opts, ",")
445
	for _, opt := range allopts {
446
		name, value, _ := strings.Cut(opt, "=")
447
		switch name {
448
		case "ip", "ip6":
449
			ip := net.ParseIP(value)
450
			if ip == nil {
451
				return netOpts, fmt.Errorf("invalid ip address %q", value)
452
			}
453
			netOpts.StaticIPs = append(netOpts.StaticIPs, ip)
454

455
		case "mac":
456
			mac, err := net.ParseMAC(value)
457
			if err != nil {
458
				return netOpts, err
459
			}
460
			netOpts.StaticMAC = types.HardwareAddr(mac)
461

462
		case "alias":
463
			if value == "" {
464
				return netOpts, errors.New("alias cannot be empty")
465
			}
466
			netOpts.Aliases = append(netOpts.Aliases, value)
467

468
		case "interface_name":
469
			if value == "" {
470
				return netOpts, errors.New("interface_name cannot be empty")
471
			}
472
			netOpts.InterfaceName = value
473

474
		default:
475
			return netOpts, fmt.Errorf("unknown bridge network option: %s", name)
476
		}
477
	}
478
	return netOpts, nil
479
}
480

481
func SetupUserNS(idmappings *storageTypes.IDMappingOptions, userns Namespace, g *generate.Generator) (string, error) {
482
	// User
483
	var user string
484
	switch userns.NSMode {
485
	case Path:
486
		if err := fileutils.Exists(userns.Value); err != nil {
487
			return user, fmt.Errorf("cannot find specified user namespace path: %w", err)
488
		}
489
		if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), userns.Value); err != nil {
490
			return user, err
491
		}
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))
495
	case Host:
496
		if err := g.RemoveLinuxNamespace(string(spec.UserNamespace)); err != nil {
497
			return user, err
498
		}
499
	case KeepID:
500
		opts, err := namespaces.UsernsMode(userns.String()).GetKeepIDOptions()
501
		if err != nil {
502
			return user, err
503
		}
504
		mappings, uid, gid, err := util.GetKeepIDMapping(opts)
505
		if err != nil {
506
			return user, err
507
		}
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 {
514
			return user, err
515
		}
516
	case NoMap:
517
		mappings, uid, gid, err := util.GetNoMapMapping()
518
		if err != nil {
519
			return user, err
520
		}
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 {
527
			return user, err
528
		}
529
	case Private:
530
		if err := privateUserNamespace(idmappings, g); err != nil {
531
			return user, err
532
		}
533
	}
534
	return user, nil
535
}
536

537
func privateUserNamespace(idmappings *storageTypes.IDMappingOptions, g *generate.Generator) error {
538
	if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), ""); err != nil {
539
		return err
540
	}
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")
543
	}
544
	for _, uidmap := range idmappings.UIDMap {
545
		g.AddLinuxUIDMapping(uint32(uidmap.HostID), uint32(uidmap.ContainerID), uint32(uidmap.Size))
546
	}
547
	for _, gidmap := range idmappings.GIDMap {
548
		g.AddLinuxGIDMapping(uint32(gidmap.HostID), uint32(gidmap.ContainerID), uint32(gidmap.Size))
549
	}
550
	return nil
551
}
552

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.