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 }