diff options
-rw-r--r-- | .cirrus.yml | 147 | ||||
-rw-r--r-- | contrib/cirrus/CIModes.md | 129 | ||||
-rw-r--r-- | docs/tutorials/basic_networking.md | 16 | ||||
-rw-r--r-- | libpod/container_config.go | 3 | ||||
-rw-r--r-- | libpod/container_internal_linux.go | 8 | ||||
-rw-r--r-- | libpod/options.go | 14 | ||||
-rw-r--r-- | pkg/specgen/generate/config_linux.go | 139 | ||||
-rw-r--r-- | pkg/specgen/generate/container_create.go | 14 | ||||
-rw-r--r-- | pkg/specgen/generate/oci.go | 10 | ||||
-rw-r--r-- | pkg/util/utils_linux.go | 142 | ||||
-rw-r--r-- | test/e2e/pod_create_test.go | 22 | ||||
-rw-r--r-- | test/e2e/run_privileged_test.go | 24 |
12 files changed, 469 insertions, 199 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index fe00974b3..21100388f 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -59,6 +59,7 @@ env: curl --fail --location -O --url https://api.cirrus-ci.com/v1/artifact/build/${CIRRUS_BUILD_ID} + # Default timeout for each task timeout_in: 60m @@ -71,7 +72,9 @@ gcp_credentials: ENCRYPTED[a28959877b2c9c36f151781b0a05407218cda646c7d047fc556e4 ext_svc_check_task: alias: 'ext_svc_check' # int. ref. name - required for depends_on reference name: "Ext. services" # Displayed Title - has no other significance - skip: &tags "$CIRRUS_TAG != ''" # Don't run on tags + # Don't create this task for new tags so release process is more reliable + # Docs: ./contrib/cirrus/CIModes.md + only_if: $CIRRUS_TAG == '' # Default/small container image to execute tasks with container: &smallcontainer image: ${CTR_FQIN} @@ -117,7 +120,9 @@ ext_svc_check_task: automation_task: alias: 'automation' name: "Check Automation" - skip: &branches_and_tags "$CIRRUS_PR == '' || $CIRRUS_TAG != ''" # Don't run on branches/tags + # This task is not needed for branches, tags, or cron runs. + # Docs: ./contrib/cirrus/CIModes.md + only_if: &is_pr "$CIRRUS_PR != ''" container: *smallcontainer env: TEST_FLAVOR: automation @@ -138,6 +143,9 @@ automation_task: build_task: alias: 'build' name: 'Build for $DISTRO_NV' + # Multiarch doesn't depend on buildability in this automation context + # Docs: ./contrib/cirrus/CIModes.md + only_if: "$CIRRUS_CRON != 'multiarch'" gce_instance: &standardvm image_project: libpod-218412 zone: "us-central1-a" @@ -189,7 +197,8 @@ validate_task: # automation reliability/speed in those contexts. Any missed errors due # to nonsequential PR merging practices, will be caught on a future PR, # build or test task failures. - skip: *branches_and_tags + # Docs: ./contrib/cirrus/CIModes.md + only_if: *is_pr depends_on: - ext_svc_check - automation @@ -218,9 +227,12 @@ validate_task: bindings_task: name: "Test Bindings" alias: bindings - # Don't run for [CI:DOCS] or [CI:BUILD] - only_if: ¬_build $CIRRUS_CHANGE_TITLE !=~ '.*CI:DOCS.*' && $CIRRUS_CHANGE_TITLE !=~ '.*CI:BUILD.*' - skip: *branches_and_tags + # Don't create task for PRs using [CI:DOCS] or [CI:BUILD] + # Docs: ./contrib/cirrus/CIModes.md + only_if: >- + $CIRRUS_PR != '' && + $CIRRUS_CHANGE_TITLE !=~ '.*CI:DOCS.*' && + $CIRRUS_CHANGE_TITLE !=~ '.*CI:BUILD.*' depends_on: - build gce_instance: *standardvm @@ -249,6 +261,11 @@ bindings_task: swagger_task: name: "Test Swagger" alias: swagger + # Don't create task for [CI:BUILD] or multiarch builds + # Docs: ./contrib/cirrus/CIModes.md + only_if: >- + $CIRRUS_CHANGE_TITLE !=~ '.*CI:BUILD.*' && + $CIRRUS_CRON != 'multiarch' depends_on: - build gce_instance: *standardvm @@ -276,7 +293,8 @@ swagger_task: consistency_task: name: "Test Code Consistency" alias: consistency - skip: *tags + # Docs: ./contrib/cirrus/CIModes.md + only_if: *is_pr depends_on: - build container: *smallcontainer @@ -297,8 +315,11 @@ consistency_task: alt_build_task: name: "$ALT_NAME" alias: alt_build - # Don't run for [CI:DOCS]; DO run for [CI:BUILD] - only_if: ¬_docs $CIRRUS_CHANGE_TITLE !=~ '.*CI:DOCS.*' + # Don't create task for [CI:DOCS] or multiarch builds + # Docs: ./contrib/cirrus/CIModes.md + only_if: ¬_docs_multiarch >- + $CIRRUS_CHANGE_TITLE !=~ '.*CI:DOCS.*' && + $CIRRUS_CRON != 'multiarch' depends_on: - build env: @@ -330,7 +351,8 @@ alt_build_task: osx_alt_build_task: name: "OSX Cross" alias: osx_alt_build - only_if: *not_docs + # Docs: ./contrib/cirrus/CIModes.md + only_if: *not_docs_multiarch depends_on: - build env: @@ -359,8 +381,14 @@ osx_alt_build_task: docker-py_test_task: name: Docker-py Compat. alias: docker-py_test - skip: *tags - only_if: *not_build + # Don't create task for tags, branches, or PRs w/ [CI:DOCS] or [CI:BUILD] + # N/B: for PRs $CIRRUS_BRANCH == 'pull/<number>' + # Docs: ./contrib/cirrus/CIModes.md + only_if: ¬_tag_branch_build_docs >- + $CIRRUS_PR != '' && + $CIRRUS_CHANGE_TITLE !=~ '.*CI:DOCS.*' && + $CIRRUS_CHANGE_TITLE !=~ '.*CI:BUILD.*' + depends_on: - build gce_instance: *standardvm @@ -379,8 +407,8 @@ docker-py_test_task: unit_test_task: name: "Unit tests on $DISTRO_NV" alias: unit_test - skip: *tags - only_if: *not_build + # Docs: ./contrib/cirrus/CIModes.md + only_if: *not_tag_branch_build_docs depends_on: - validate matrix: @@ -404,8 +432,8 @@ unit_test_task: apiv2_test_task: name: "APIv2 test on $DISTRO_NV" alias: apiv2_test - only_if: *not_build - skip: *tags + # Docs: ./contrib/cirrus/CIModes.md + only_if: *not_tag_branch_build_docs depends_on: - validate gce_instance: *standardvm @@ -424,8 +452,8 @@ apiv2_test_task: compose_test_task: name: "$TEST_FLAVOR test on $DISTRO_NV ($PRIV_NAME)" alias: compose_test - only_if: *not_build - skip: *tags + # Docs: ./contrib/cirrus/CIModes.md + only_if: *not_tag_branch_build_docs depends_on: - validate gce_instance: *standardvm @@ -457,8 +485,8 @@ local_integration_test_task: &local_integration_test_task # <int.|sys.> <podman|remote> <Distro NV> <root|rootless> name: &std_name_fmt "$TEST_FLAVOR $PODBIN_NAME $DISTRO_NV $PRIV_NAME $TEST_ENVIRON" alias: local_integration_test - only_if: *not_build - skip: *branches_and_tags + # Docs: ./contrib/cirrus/CIModes.md + only_if: *not_tag_branch_build_docs depends_on: - unit_test matrix: *platform_axis @@ -492,8 +520,8 @@ remote_integration_test_task: container_integration_test_task: name: *std_name_fmt alias: container_integration_test - only_if: *not_build - skip: *branches_and_tags + # Docs: ./contrib/cirrus/CIModes.md + only_if: *not_tag_branch_build_docs depends_on: - unit_test matrix: &fedora_vm_axis @@ -522,8 +550,8 @@ container_integration_test_task: rootless_integration_test_task: name: *std_name_fmt alias: rootless_integration_test - only_if: *not_build - skip: *branches_and_tags + # Docs: ./contrib/cirrus/CIModes.md + only_if: *not_tag_branch_build_docs depends_on: - unit_test matrix: *platform_axis @@ -545,8 +573,13 @@ rootless_integration_test_task: local_system_test_task: &local_system_test_task name: *std_name_fmt alias: local_system_test - skip: *tags - only_if: *not_build + # Don't create task for tags, or if using [CI:DOCS], [CI:BUILD], multiarch + # Docs: ./contrib/cirrus/CIModes.md + only_if: ¬_tag_build_docs_multiarch >- + $CIRRUS_TAG == '' && + $CIRRUS_CHANGE_TITLE !=~ '.*CI:DOCS.*' && + $CIRRUS_CHANGE_TITLE !=~ '.*CI:BUILD.*' && + $CIRRUS_CRON != 'multiarch' depends_on: - local_integration_test matrix: *platform_axis @@ -590,11 +623,29 @@ rootless_remote_system_test_task: PRIV_NAME: rootless +rootless_system_test_task: + name: *std_name_fmt + alias: rootless_system_test + # Docs: ./contrib/cirrus/CIModes.md + only_if: *not_tag_build_docs_multiarch + depends_on: + - rootless_integration_test + matrix: *platform_axis + gce_instance: *standardvm + env: + TEST_FLAVOR: sys + PRIV_NAME: rootless + clone_script: *get_gosrc + setup_script: *setup + main_script: *main + always: *logs_artifacts + + buildah_bud_test_task: name: *std_name_fmt alias: buildah_bud_test - skip: *tags - only_if: *not_build + # Docs: ./contrib/cirrus/CIModes.md + only_if: *not_tag_branch_build_docs depends_on: - local_integration_test env: @@ -618,29 +669,11 @@ buildah_bud_test_task: always: *int_logs_artifacts -rootless_system_test_task: - name: *std_name_fmt - alias: rootless_system_test - skip: *tags - only_if: *not_build - depends_on: - - rootless_integration_test - matrix: *platform_axis - gce_instance: *standardvm - env: - TEST_FLAVOR: sys - PRIV_NAME: rootless - clone_script: *get_gosrc - setup_script: *setup - main_script: *main - always: *logs_artifacts - - rootless_gitlab_test_task: name: *std_name_fmt alias: rootless_gitlab_test - skip: *tags - only_if: *not_build + # Docs: ./contrib/cirrus/CIModes.md + only_if: *not_tag_branch_build_docs # Community-maintained downstream test may fail unexpectedly. # Ref. repository: https://gitlab.com/gitlab-org/gitlab-runner # If necessary, uncomment the next line and file issue(s) with details. @@ -666,8 +699,8 @@ rootless_gitlab_test_task: upgrade_test_task: name: "Upgrade test: from $PODMAN_UPGRADE_FROM" alias: upgrade_test - skip: *tags - only_if: *not_build + # Docs: ./contrib/cirrus/CIModes.md + only_if: *not_tag_branch_build_docs depends_on: - local_system_test matrix: @@ -695,9 +728,10 @@ image_build_task: &image-build alias: image_build # Some of these container images take > 1h to build, limit # this task to a specific Cirrus-Cron entry with this name. + # Docs: ./contrib/cirrus/CIModes.md only_if: $CIRRUS_CRON == 'multiarch' depends_on: - - build + - ext_svc_check timeout_in: 120m # emulation is sssllllooooowwww gce_instance: <<: *standardvm @@ -728,6 +762,7 @@ test_image_build_task: <<: *image-build alias: test_image_build # Allow this to run inside a PR w/ [CI:BUILD] only. + # Docs: ./contrib/cirrus/CIModes.md only_if: $CIRRUS_PR != '' && $CIRRUS_CHANGE_TITLE =~ '.*CI:BUILD.*' # This takes a LONG time, only run when requested. N/B: Any task # made to depend on this one will block FOREVER unless triggered. @@ -793,9 +828,9 @@ success_task: - remote_system_test - rootless_system_test - rootless_remote_system_test + - buildah_bud_test - rootless_gitlab_test - upgrade_test - - buildah_bud_test - image_build - meta container: *smallcontainer @@ -809,7 +844,8 @@ success_task: artifacts_task: name: "Artifacts" alias: artifacts - only_if: *not_docs + # Docs: ./contrib/cirrus/CIModes.md + only_if: *not_docs_multiarch depends_on: - success # This task is a secondary/convenience for downstream consumers, don't @@ -862,7 +898,9 @@ artifacts_task: release_task: name: "Verify Release" alias: release - only_if: *tags + # This should _only_ run for new tags + # Docs: ./contrib/cirrus/CIModes.md + only_if: $CIRRUS_TAG != '' depends_on: - success gce_instance: *standardvm @@ -884,6 +922,7 @@ release_test_task: name: "Optional Release Test" alias: release_test # Release-PRs always include "release" or "Bump" in the title + # Docs: ./contrib/cirrus/CIModes.md only_if: $CIRRUS_CHANGE_TITLE =~ '.*((release)|(bump)).*' # Allow running manually only as part of release-related builds # see RELEASE_PROCESS.md diff --git a/contrib/cirrus/CIModes.md b/contrib/cirrus/CIModes.md new file mode 100644 index 000000000..8b1e33bb1 --- /dev/null +++ b/contrib/cirrus/CIModes.md @@ -0,0 +1,129 @@ +The following is a list (incomplete) of the primary contexts and runtime +"modes" supported by podman CI. Note that there may be additional checks +done regarding "skipping work" in the `runner.sh` script. This document +only details the controls at the `.cirrus.yml` level. + +## Visualization + +The relationship between tasks can be incredibly hard to understand by +staring at the YAML. +[A tool exists](https://github.com/containers/automation/tree/main/cirrus-task-map) +for producing a graph (flow-chart) of the `.cirrus.yml` file. A (possibly +outdated) example of it's output can be seen below: + +![cirrus-task-map output](https://github.com/containers/podman/wiki/cirrus-map.svg) + +## Implementation notes + ++ The `skip` conditional should never be used for tasks. + While it's arguably easier to read that `only_if`, it leads to a cluttered + status output that's harder to page through when reviewing PRs. As opposed + to `only_if` which will bypass creation of the task (at runtime) completely. + Also, by sticking to one conditional style, it's easer to re-use the YAML + statements across multiple tasks. + ++ The only variables which can be used as part of conditions are defined by + Cirrus-CI. + [The list is documented](https://cirrus-ci.org/guide/writing-tasks/#environment-variables). Reference to any variables defined in YAML will **not** behave how + you expect, don't use them! + +* Somme Cirrus-CI defined variables contain non-empty values outside their + obvious context. For example, when running for a PR a task will have + `$CIRRUS_BRANCH` set to `pull/<number>`. + +* Conditions which use positive or negative regular-expressions have several + "flags" set: "Multi-line" and "Case-insensitive". + +## Testing + +Executing most of the modes can be mocked by forcing values for (otherwise) +Cirrus-CI defined variables. For example `$CIRRUS_TAG`. As of the publishing +of this document, it's not possible to override the behavior of `$CIRRUS_PR`. + +## Cirrus Task contexts and runtime modes + +### Intended general PR Tasks (*italic*: matrix) ++ ext_svc_check ++ automation ++ *build* ++ validate ++ bindings ++ swagger ++ consistency ++ *alt_build* ++ osx_alt_build ++ docker-py_test ++ *unit_test* ++ apiv2_test ++ *compose_test* ++ *local_integration_test* ++ *remote_integration_test* ++ *container_integration_test* ++ *rootless_integration_test* ++ *local_system_test* ++ *remote_system_test* ++ *rootless_remote_system_test* ++ *buildah_bud_test* ++ *rootless_system_test* ++ rootless_gitlab_test ++ *upgrade_test* ++ meta ++ success ++ artifacts + +### Intended for PR w/ "release" or "bump" in title: ++ (All the general PR tasks above) ++ release_test + +### Intended `[CI:DOCS]` PR Tasks: ++ ext_svc_check ++ automation ++ *build* ++ validate ++ swagger ++ consistency ++ meta ++ success + +### Intend `[CI:BUILD]` PR Tasks: ++ ext_svc_check ++ automation ++ *build* ++ validate ++ consistency ++ *alt_build* ++ osx_alt_build ++ test_image_build ++ meta ++ success ++ artifacts + +### Intended Branch tasks (and Cirrus-cron jobs, except "multiarch"): ++ ext_svc_check ++ *build* ++ swagger ++ *alt_build* ++ osx_alt_build ++ *local_system_test* ++ *remote_system_test* ++ *rootless_remote_system_test* ++ *rootless_system_test* ++ meta ++ success ++ artifacts + +### Intended for "multiarch" Cirrus-Cron (always a branch): ++ ext_svc_check ++ image_build ++ meta ++ success + +### Intended for new Tag tasks: ++ *build* ++ swagger ++ *alt_build* ++ osx_alt_build ++ meta ++ success ++ artifacts ++ release diff --git a/docs/tutorials/basic_networking.md b/docs/tutorials/basic_networking.md index 396994596..b6f53175b 100644 --- a/docs/tutorials/basic_networking.md +++ b/docs/tutorials/basic_networking.md @@ -93,6 +93,22 @@ When rootless containers are run, network operations will be executed inside an extra network namespace. To join this namespace, use `podman unshare --rootless-netns`. +#### Default Network + +The default network `podman` with netavark is memory-only. It does not support dns resolution because of backwards compatibility with Docker. To change settings, export the in-memory network and change the file. + +For the default rootful network use +``` +podman network inspect podman | jq .[] > /etc/containers/networks/podman.json +``` + +And for the rootless network use + +``` +podman network inspect podman | jq .[] > ~/.local/share/containers/storage/networks/podman.json +``` + + #### Example By default, rootful containers use the netavark for its default network if diff --git a/libpod/container_config.go b/libpod/container_config.go index 30b84adcf..6558f3c89 100644 --- a/libpod/container_config.go +++ b/libpod/container_config.go @@ -412,6 +412,9 @@ type ContainerMiscConfig struct { InitContainerType string `json:"init_container_type,omitempty"` // PasswdEntry specifies arbitrary data to append to a file. PasswdEntry string `json:"passwd_entry,omitempty"` + // MountAllDevices is an option to indicate whether a privileged container + // will mount all the host's devices + MountAllDevices bool `json:"mountAllDevices"` } // InfraInherit contains the compatible options inheritable from the infra container diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 0056b8e86..b298014df 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -438,6 +438,14 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { //nolint:staticcheck g := generate.NewFromSpec(c.config.Spec) + // If the flag to mount all devices is set for a privileged container, add + // all the devices from the host's machine into the container + if c.config.MountAllDevices { + if err := util.AddPrivilegedDevices(&g); err != nil { + return nil, err + } + } + // If network namespace was requested, add it now if c.config.CreateNetNS { if c.config.PostConfigureNetNS { diff --git a/libpod/options.go b/libpod/options.go index 4b6803c3f..8b3b07efa 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -2174,3 +2174,17 @@ func WithPasswdEntry(passwdEntry string) CtrCreateOption { return nil } } + +// WithMountAllDevices sets the option to mount all of a privileged container's +// host devices +func WithMountAllDevices() CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.MountAllDevices = true + + return nil + } +} diff --git a/pkg/specgen/generate/config_linux.go b/pkg/specgen/generate/config_linux.go index ed2e5408d..4c3748e67 100644 --- a/pkg/specgen/generate/config_linux.go +++ b/pkg/specgen/generate/config_linux.go @@ -3,7 +3,6 @@ package generate import ( "fmt" "io/fs" - "io/ioutil" "os" "path" "path/filepath" @@ -11,6 +10,7 @@ import ( "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/rootless" + "github.com/containers/podman/v4/pkg/util" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" "github.com/pkg/errors" @@ -18,56 +18,6 @@ import ( "golang.org/x/sys/unix" ) -var ( - errNotADevice = errors.New("not a device node") -) - -func addPrivilegedDevices(g *generate.Generator) error { - hostDevices, err := getDevices("/dev") - if err != nil { - return err - } - g.ClearLinuxDevices() - - if rootless.IsRootless() { - mounts := make(map[string]interface{}) - for _, m := range g.Mounts() { - mounts[m.Destination] = true - } - newMounts := []spec.Mount{} - for _, d := range hostDevices { - devMnt := spec.Mount{ - Destination: d.Path, - Type: define.TypeBind, - Source: d.Path, - Options: []string{"slave", "nosuid", "noexec", "rw", "rbind"}, - } - if d.Path == "/dev/ptmx" || strings.HasPrefix(d.Path, "/dev/tty") { - continue - } - if _, found := mounts[d.Path]; found { - continue - } - newMounts = append(newMounts, devMnt) - } - g.Config.Mounts = append(newMounts, g.Config.Mounts...) - if g.Config.Linux.Resources != nil { - g.Config.Linux.Resources.Devices = nil - } - } else { - for _, d := range hostDevices { - g.AddDevice(d) - } - // Add resources device - need to clear the existing one first. - if g.Config.Linux.Resources != nil { - g.Config.Linux.Resources.Devices = nil - } - g.AddLinuxResourcesDevice(true, "", nil, nil, "rwm") - } - - return nil -} - // DevicesFromPath computes a list of devices func DevicesFromPath(g *generate.Generator, devicePath string) error { devs := strings.Split(devicePath, ":") @@ -174,60 +124,12 @@ func BlockAccessToKernelFilesystems(privileged, pidModeIsHost bool, mask, unmask } } -// based on getDevices from runc (libcontainer/devices/devices.go) -func getDevices(path string) ([]spec.LinuxDevice, error) { - files, err := ioutil.ReadDir(path) - if err != nil { - if rootless.IsRootless() && os.IsPermission(err) { - return nil, nil - } - return nil, err - } - out := []spec.LinuxDevice{} - for _, f := range files { - switch { - case f.IsDir(): - switch f.Name() { - // ".lxc" & ".lxd-mounts" added to address https://github.com/lxc/lxd/issues/2825 - case "pts", "shm", "fd", "mqueue", ".lxc", ".lxd-mounts": - continue - default: - sub, err := getDevices(filepath.Join(path, f.Name())) - if err != nil { - return nil, err - } - if sub != nil { - out = append(out, sub...) - } - continue - } - case f.Name() == "console": - continue - case f.Mode()&os.ModeSymlink != 0: - continue - } - - device, err := deviceFromPath(filepath.Join(path, f.Name())) - if err != nil { - if err == errNotADevice { - continue - } - if os.IsNotExist(err) { - continue - } - return nil, err - } - out = append(out, *device) - } - return out, nil -} - func addDevice(g *generate.Generator, device string) error { src, dst, permissions, err := ParseDevice(device) if err != nil { return err } - dev, err := deviceFromPath(src) + dev, err := util.DeviceFromPath(src) if err != nil { return errors.Wrapf(err, "%s is not a valid device", src) } @@ -316,43 +218,6 @@ func IsValidDeviceMode(mode string) bool { return true } -// Copied from github.com/opencontainers/runc/libcontainer/devices -// Given the path to a device look up the information about a linux device -func deviceFromPath(path string) (*spec.LinuxDevice, error) { - var stat unix.Stat_t - err := unix.Lstat(path, &stat) - if err != nil { - return nil, err - } - var ( - devType string - mode = stat.Mode - devNumber = uint64(stat.Rdev) // nolint: unconvert - m = os.FileMode(mode) - ) - - switch { - case mode&unix.S_IFBLK == unix.S_IFBLK: - devType = "b" - case mode&unix.S_IFCHR == unix.S_IFCHR: - devType = "c" - case mode&unix.S_IFIFO == unix.S_IFIFO: - devType = "p" - default: - return nil, errNotADevice - } - - return &spec.LinuxDevice{ - Type: devType, - Path: path, - FileMode: &m, - UID: &stat.Uid, - GID: &stat.Gid, - Major: int64(unix.Major(devNumber)), - Minor: int64(unix.Minor(devNumber)), - }, nil -} - func supportAmbientCapabilities() bool { err := unix.Prctl(unix.PR_CAP_AMBIENT, unix.PR_CAP_AMBIENT_IS_SET, 0, 0, 0) return err == nil diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 04e24d625..7faf13465 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -278,6 +278,10 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l options = append(options, libpod.WithPasswdEntry(s.PasswdEntry)) } + if s.Privileged { + options = append(options, libpod.WithMountAllDevices()) + } + useSystemd := false switch s.Systemd { case "always": @@ -542,6 +546,16 @@ func Inherit(infra libpod.Container, s *specgen.SpecGenerator, rt *libpod.Runtim infraConf := infra.Config() infraSpec := infraConf.Spec + // need to set compatOptions to the currently filled specgenOptions so we do not overwrite + compatibleOptions.CapAdd = append(compatibleOptions.CapAdd, s.CapAdd...) + compatibleOptions.CapDrop = append(compatibleOptions.CapDrop, s.CapDrop...) + compatibleOptions.HostDeviceList = append(compatibleOptions.HostDeviceList, s.HostDeviceList...) + compatibleOptions.ImageVolumes = append(compatibleOptions.ImageVolumes, s.ImageVolumes...) + compatibleOptions.Mounts = append(compatibleOptions.Mounts, s.Mounts...) + compatibleOptions.OverlayVolumes = append(compatibleOptions.OverlayVolumes, s.OverlayVolumes...) + compatibleOptions.SelinuxOpts = append(compatibleOptions.SelinuxOpts, s.SelinuxOpts...) + compatibleOptions.Volumes = append(compatibleOptions.Volumes, s.Volumes...) + compatByte, err := json.Marshal(compatibleOptions) if err != nil { return nil, nil, nil, err diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index dda2de6e4..716960024 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -337,14 +337,8 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt } var userDevices []spec.LinuxDevice - if s.Privileged { - // If privileged, we need to add all the host devices to the - // spec. We do not add the user provided ones because we are - // already adding them all. - if err := addPrivilegedDevices(&g); err != nil { - return nil, err - } - } else { + + if !s.Privileged { // add default devices from containers.conf for _, device := range rtc.Containers.Devices { if err = DevicesFromPath(&g, device); err != nil { diff --git a/pkg/util/utils_linux.go b/pkg/util/utils_linux.go index 0b21bf3c5..871303f64 100644 --- a/pkg/util/utils_linux.go +++ b/pkg/util/utils_linux.go @@ -3,13 +3,24 @@ package util import ( "fmt" "io/fs" + "io/ioutil" "os" "path/filepath" + "strings" "syscall" + "github.com/containers/podman/v4/libpod/define" + "github.com/containers/podman/v4/pkg/rootless" "github.com/containers/psgo" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-tools/generate" "github.com/pkg/errors" "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +var ( + errNotADevice = errors.New("not a device node") ) // GetContainerPidInformationDescriptors returns a string slice of all supported @@ -59,3 +70,134 @@ func FindDeviceNodes() (map[string]string, error) { return nodes, nil } + +func AddPrivilegedDevices(g *generate.Generator) error { + hostDevices, err := getDevices("/dev") + if err != nil { + return err + } + g.ClearLinuxDevices() + + if rootless.IsRootless() { + mounts := make(map[string]interface{}) + for _, m := range g.Mounts() { + mounts[m.Destination] = true + } + newMounts := []spec.Mount{} + for _, d := range hostDevices { + devMnt := spec.Mount{ + Destination: d.Path, + Type: define.TypeBind, + Source: d.Path, + Options: []string{"slave", "nosuid", "noexec", "rw", "rbind"}, + } + if d.Path == "/dev/ptmx" || strings.HasPrefix(d.Path, "/dev/tty") { + continue + } + if _, found := mounts[d.Path]; found { + continue + } + newMounts = append(newMounts, devMnt) + } + g.Config.Mounts = append(newMounts, g.Config.Mounts...) + if g.Config.Linux.Resources != nil { + g.Config.Linux.Resources.Devices = nil + } + } else { + for _, d := range hostDevices { + g.AddDevice(d) + } + // Add resources device - need to clear the existing one first. + if g.Config.Linux.Resources != nil { + g.Config.Linux.Resources.Devices = nil + } + g.AddLinuxResourcesDevice(true, "", nil, nil, "rwm") + } + + return nil +} + +// based on getDevices from runc (libcontainer/devices/devices.go) +func getDevices(path string) ([]spec.LinuxDevice, error) { + files, err := ioutil.ReadDir(path) + if err != nil { + if rootless.IsRootless() && os.IsPermission(err) { + return nil, nil + } + return nil, err + } + out := []spec.LinuxDevice{} + for _, f := range files { + switch { + case f.IsDir(): + switch f.Name() { + // ".lxc" & ".lxd-mounts" added to address https://github.com/lxc/lxd/issues/2825 + case "pts", "shm", "fd", "mqueue", ".lxc", ".lxd-mounts": + continue + default: + sub, err := getDevices(filepath.Join(path, f.Name())) + if err != nil { + return nil, err + } + if sub != nil { + out = append(out, sub...) + } + continue + } + case f.Name() == "console": + continue + case f.Mode()&os.ModeSymlink != 0: + continue + } + + device, err := DeviceFromPath(filepath.Join(path, f.Name())) + if err != nil { + if err == errNotADevice { + continue + } + if os.IsNotExist(err) { + continue + } + return nil, err + } + out = append(out, *device) + } + return out, nil +} + +// Copied from github.com/opencontainers/runc/libcontainer/devices +// Given the path to a device look up the information about a linux device +func DeviceFromPath(path string) (*spec.LinuxDevice, error) { + var stat unix.Stat_t + err := unix.Lstat(path, &stat) + if err != nil { + return nil, err + } + var ( + devType string + mode = stat.Mode + devNumber = uint64(stat.Rdev) // nolint: unconvert + m = os.FileMode(mode) + ) + + switch { + case mode&unix.S_IFBLK == unix.S_IFBLK: + devType = "b" + case mode&unix.S_IFCHR == unix.S_IFCHR: + devType = "c" + case mode&unix.S_IFIFO == unix.S_IFIFO: + devType = "p" + default: + return nil, errNotADevice + } + + return &spec.LinuxDevice{ + Type: devType, + Path: path, + FileMode: &m, + UID: &stat.Uid, + GID: &stat.Gid, + Major: int64(unix.Major(devNumber)), + Minor: int64(unix.Minor(devNumber)), + }, nil +} diff --git a/test/e2e/pod_create_test.go b/test/e2e/pod_create_test.go index dedb1caeb..4919cc670 100644 --- a/test/e2e/pod_create_test.go +++ b/test/e2e/pod_create_test.go @@ -1112,4 +1112,26 @@ ENTRYPOINT ["sleep","99999"] }) + It("podman pod create infra inheritance test", func() { + volName := "testVol1" + volCreate := podmanTest.Podman([]string{"volume", "create", volName}) + volCreate.WaitWithDefaultTimeout() + Expect(volCreate).Should(Exit(0)) + + session := podmanTest.Podman([]string{"pod", "create", "-v", volName + ":/vol1"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + volName2 := "testVol2" + volCreate = podmanTest.Podman([]string{"volume", "create", volName2}) + volCreate.WaitWithDefaultTimeout() + Expect(volCreate).Should(Exit(0)) + + session = podmanTest.Podman([]string{"run", "--pod", session.OutputToString(), "-v", volName2 + ":/vol2", ALPINE, "mount"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(ContainSubstring("/vol1")) + Expect(session.OutputToString()).Should(ContainSubstring("/vol2")) + }) + }) diff --git a/test/e2e/run_privileged_test.go b/test/e2e/run_privileged_test.go index 4f0b512c6..dfaff7e67 100644 --- a/test/e2e/run_privileged_test.go +++ b/test/e2e/run_privileged_test.go @@ -131,6 +131,30 @@ var _ = Describe("Podman privileged container tests", func() { Expect(len(session.OutputToStringArray())).To(BeNumerically(">", 20)) }) + It("podman privileged should restart after host devices change", func() { + containerName := "privileged-restart-test" + SkipIfRootless("Cannot create devices in /dev in rootless mode") + Expect(os.MkdirAll("/dev/foodevdir", os.ModePerm)).To(BeNil()) + + mknod := SystemExec("mknod", []string{"/dev/foodevdir/null", "c", "1", "3"}) + mknod.WaitWithDefaultTimeout() + Expect(mknod).Should(Exit(0)) + + session := podmanTest.Podman([]string{"run", "--name=" + containerName, "--privileged", "-it", fedoraMinimal, "ls", "/dev"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + deviceFiles := session.OutputToStringArray() + + os.RemoveAll("/dev/foodevdir") + session = podmanTest.Podman([]string{"start", "--attach", containerName}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + deviceFilesAfterRemoval := session.OutputToStringArray() + Expect(deviceFiles).To(Not(Equal(deviceFilesAfterRemoval))) + }) + It("run no-new-privileges test", func() { // Check if our kernel is new enough k, err := IsKernelNewerThan("4.14") |