diff options
-rw-r--r-- | cmd/podman/common/specgen.go | 14 | ||||
-rw-r--r-- | docs/source/markdown/podman-run.1.md | 25 | ||||
-rw-r--r-- | pkg/specgen/generate/config_linux.go | 53 | ||||
-rw-r--r-- | pkg/specgen/generate/oci.go | 2 | ||||
-rw-r--r-- | pkg/specgen/specgen.go | 7 | ||||
-rw-r--r-- | test/e2e/run_test.go | 33 | ||||
-rw-r--r-- | test/system/120-load.bats | 30 | ||||
-rw-r--r-- | test/system/400-unprivileged-access.bats | 2 |
8 files changed, 131 insertions, 35 deletions
diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go index 0bb6e79e5..e0da142ad 100644 --- a/cmd/podman/common/specgen.go +++ b/cmd/podman/common/specgen.go @@ -517,18 +517,22 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string } switch con[0] { - case "proc-opts": - s.ProcOpts = strings.Split(con[1], ",") + case "apparmor": + s.ContainerSecurityConfig.ApparmorProfile = con[1] + s.Annotations[define.InspectAnnotationApparmor] = con[1] case "label": // TODO selinux opts and label opts are the same thing s.ContainerSecurityConfig.SelinuxOpts = append(s.ContainerSecurityConfig.SelinuxOpts, con[1]) s.Annotations[define.InspectAnnotationLabel] = strings.Join(s.ContainerSecurityConfig.SelinuxOpts, ",label=") - case "apparmor": - s.ContainerSecurityConfig.ApparmorProfile = con[1] - s.Annotations[define.InspectAnnotationApparmor] = con[1] + case "mask": + s.ContainerSecurityConfig.Mask = append(s.ContainerSecurityConfig.Mask, strings.Split(con[1], ":")...) + case "proc-opts": + s.ProcOpts = strings.Split(con[1], ",") case "seccomp": s.SeccompProfilePath = con[1] s.Annotations[define.InspectAnnotationSeccomp] = con[1] + case "unmask": + s.ContainerSecurityConfig.Unmask = append(s.ContainerSecurityConfig.Unmask, strings.Split(con[1], ":")...) default: return fmt.Errorf("invalid --security-opt 2: %q", opt) } diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index 83aaa33e8..1038906c0 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -885,11 +885,16 @@ Security Options - **label=level:**_LEVEL_: Set the label level for the container processes - **label=filetype:**TYPE_: Set the label file type for the container files - **label=disable**: Turn off label separation for the container +- **mask**=_/path/1:/path/2_: The paths to mask separated by a colon. A masked path + cannot be accessed inside the container. - **no-new-privileges**: Disable container processes from gaining additional privileges - **seccomp=unconfined**: Turn off seccomp confinement for the container - **seccomp**=_profile.json_: Allowed syscall list seccomp JSON file to be used as a seccomp filter - **proc-opts**=_OPTIONS_ : Comma separated list of options to use for the /proc mount. More details for the possible mount options are specified at **proc(5)** man page. +- **unmask**=_ALL_ or _/path/1:/path/2_: Paths to unmask separated by a colon. If set to **ALL**, it will + unmask all the paths that are masked by default. + The default masked paths are **/proc/acpi, /proc/kcore, /proc/keys, /proc/latency_stats, /proc/sched_debug, /proc/scsi, /proc/timer_list, /proc/timer_stats, /sys/firmware, and /sys/fs/selinux.** Note: Labeling can be disabled for all containers by setting **label=false** in the **containers.conf**(5) file. @@ -1479,6 +1484,26 @@ $ podman run --security-opt label=type:svirt_apache_t -i -t centos bash Note you would have to write policy defining a **svirt_apache_t** type. +To mask additional specific paths in the container, specify the paths +separated by a colon using the **mask** option with the **--security-opt** +flag. + +``` +$ podman run --security-opt mask=/foo/bar:/second/path fedora bash +``` + +To unmask all the paths that are masked by default, set the **unmask** option to +**ALL**. Or to only unmask specific paths, specify the paths as shown above with +the **mask** option. + +``` +$ podman run --security-opt unmask=ALL fedora bash +``` + +``` +$ podman run --security-opt unmask=/foo/bar:/sys/firmware fedora bash +``` + ### Setting device weight If you want to set _/dev/sda_ device weight to **200**, you can specify the device diff --git a/pkg/specgen/generate/config_linux.go b/pkg/specgen/generate/config_linux.go index 2d40dba8f..1808f99b8 100644 --- a/pkg/specgen/generate/config_linux.go +++ b/pkg/specgen/generate/config_linux.go @@ -4,13 +4,16 @@ import ( "fmt" "io/ioutil" "os" + "path" "path/filepath" "strings" "github.com/containers/podman/v2/pkg/rootless" + "github.com/containers/podman/v2/pkg/util" 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" ) @@ -137,22 +140,33 @@ func DevicesFromPath(g *generate.Generator, devicePath string) error { return addDevice(g, strings.Join(append([]string{resolvedDevicePath}, devs[1:]...), ":")) } -func BlockAccessToKernelFilesystems(privileged, pidModeIsHost bool, g *generate.Generator) { +func BlockAccessToKernelFilesystems(privileged, pidModeIsHost bool, mask, unmask []string, g *generate.Generator) { + defaultMaskPaths := []string{"/proc/acpi", + "/proc/kcore", + "/proc/keys", + "/proc/latency_stats", + "/proc/timer_list", + "/proc/timer_stats", + "/proc/sched_debug", + "/proc/scsi", + "/sys/firmware", + "/sys/fs/selinux", + "/sys/dev/block", + } + + unmaskAll := false + if unmask != nil && unmask[0] == "ALL" { + unmaskAll = true + } + if !privileged { - for _, mp := range []string{ - "/proc/acpi", - "/proc/kcore", - "/proc/keys", - "/proc/latency_stats", - "/proc/timer_list", - "/proc/timer_stats", - "/proc/sched_debug", - "/proc/scsi", - "/sys/firmware", - "/sys/fs/selinux", - "/sys/dev", - } { - g.AddLinuxMaskedPaths(mp) + if !unmaskAll { + for _, mp := range defaultMaskPaths { + // check that the path to mask is not in the list of paths to unmask + if !util.StringInSlice(mp, unmask) { + g.AddLinuxMaskedPaths(mp) + } + } } if pidModeIsHost && rootless.IsRootless() { @@ -170,6 +184,15 @@ func BlockAccessToKernelFilesystems(privileged, pidModeIsHost bool, g *generate. g.AddLinuxReadonlyPaths(rp) } } + + // mask the paths provided by the user + for _, mp := range mask { + if !path.IsAbs(mp) && mp != "" { + logrus.Errorf("Path %q is not an absolute path, skipping...", mp) + continue + } + g.AddLinuxMaskedPaths(mp) + } } // based on getDevices from runc (libcontainer/devices/devices.go) diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index 8454458a8..0368ab205 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -298,7 +298,7 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt } } - BlockAccessToKernelFilesystems(s.Privileged, s.PidNS.IsHost(), &g) + BlockAccessToKernelFilesystems(s.Privileged, s.PidNS.IsHost(), s.Mask, s.Unmask, &g) for name, val := range s.Env { g.AddProcessEnv(name, val) diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index fad2406e5..964b89fa4 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -307,6 +307,13 @@ type ContainerSecurityConfig struct { Umask string `json:"umask,omitempty"` // ProcOpts are the options used for the proc mount. ProcOpts []string `json:"procfs_opts,omitempty"` + // Mask is the path we want to mask in the container. This masks the paths + // given in addition to the default list. + // Optional + Mask []string `json:"mask,omitempty"` + // Unmask is the path we want to unmask in the container. To override + // all the default paths that are masked, set unmask=ALL. + Unmask []string `json:"unmask,omitempty"` } // ContainerCgroupConfig contains configuration information about a container's diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 0d65a3e59..efc125d2b 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -233,6 +233,39 @@ var _ = Describe("Podman run", func() { return jsonFile } + It("podman run mask and unmask path test", func() { + session := podmanTest.Podman([]string{"run", "-d", "--name=maskCtr1", "--security-opt", "unmask=ALL", "--security-opt", "mask=/proc/acpi", ALPINE, "sleep", "200"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + session = podmanTest.Podman([]string{"exec", "maskCtr1", "ls", "/sys/firmware"}) + session.WaitWithDefaultTimeout() + Expect(session.OutputToString()).To(Not(BeEmpty())) + Expect(session.ExitCode()).To(Equal(0)) + session = podmanTest.Podman([]string{"exec", "maskCtr1", "ls", "/proc/acpi"}) + session.WaitWithDefaultTimeout() + Expect(session.OutputToString()).To(BeEmpty()) + + session = podmanTest.Podman([]string{"run", "-d", "--name=maskCtr2", "--security-opt", "unmask=/proc/acpi:/sys/firmware", ALPINE, "sleep", "200"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + session = podmanTest.Podman([]string{"exec", "maskCtr2", "ls", "/sys/firmware"}) + session.WaitWithDefaultTimeout() + Expect(session.OutputToString()).To(Not(BeEmpty())) + Expect(session.ExitCode()).To(Equal(0)) + session = podmanTest.Podman([]string{"exec", "maskCtr2", "ls", "/proc/acpi"}) + session.WaitWithDefaultTimeout() + Expect(session.OutputToString()).To(Not(BeEmpty())) + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"run", "-d", "--name=maskCtr3", "--security-opt", "mask=/sys/power/disk", ALPINE, "sleep", "200"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + session = podmanTest.Podman([]string{"exec", "maskCtr3", "cat", "/sys/power/disk"}) + session.WaitWithDefaultTimeout() + Expect(session.OutputToString()).To(BeEmpty()) + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman run seccomp test", func() { session := podmanTest.Podman([]string{"run", "-it", "--security-opt", strings.Join([]string{"seccomp=", forbidGetCWDSeccompProfile()}, ""), ALPINE, "pwd"}) session.WaitWithDefaultTimeout() diff --git a/test/system/120-load.bats b/test/system/120-load.bats index 8ea9b1c69..272e2ae93 100644 --- a/test/system/120-load.bats +++ b/test/system/120-load.bats @@ -28,12 +28,15 @@ verify_iid_and_name() { @test "podman save to pipe and load" { # Generate a random name and tag (must be lower-case) - local random_name=x$(random_string 12 | tr A-Z a-z) - local random_tag=t$(random_string 7 | tr A-Z a-z) + local random_name=x0$(random_string 12 | tr A-Z a-z) + local random_tag=t0$(random_string 7 | tr A-Z a-z) local fqin=localhost/$random_name:$random_tag run_podman tag $IMAGE $fqin - archive=$PODMAN_TMPDIR/myimage-$(random_string 8).tar + # Believe it or not, 'podman load' would barf if any path element + # included a capital letter + archive=$PODMAN_TMPDIR/MySubDirWithCaps/MyImage-$(random_string 8).tar + mkdir -p $(dirname $archive) # We can't use run_podman because that uses the BATS 'run' function # which redirects stdout and stderr. Here we need to guarantee @@ -51,19 +54,20 @@ verify_iid_and_name() { run_podman images $fqin --format '{{.Repository}}:{{.Tag}}' is "$output" "$fqin" "image preserves name across save/load" - # FIXME: when/if 7337 gets fixed, load with a new tag - if false; then - local new_name=x$(random_string 14 | tr A-Z a-z) - local new_tag=t$(random_string 6 | tr A-Z a-z) + # Load with a new tag + local new_name=x1$(random_string 14 | tr A-Z a-z) + local new_tag=t1$(random_string 6 | tr A-Z a-z) run_podman rmi $fqin - fqin=localhost/$new_name:$new_tag - run_podman load -i $archive $fqin - run_podman images $fqin --format '{{.Repository}}:{{.Tag}}' - is "$output" "$fqin" "image can be loaded with new name:tag" - fi + + new_fqin=localhost/$new_name:$new_tag + run_podman load -i $archive $new_fqin + run_podman images --format '{{.Repository}}:{{.Tag}}' --sort tag + is "${lines[0]}" "$IMAGE" "image is preserved" + is "${lines[1]}" "$fqin" "image is reloaded with old fqin" + is "${lines[2]}" "$new_fqin" "image is reloaded with new fqin too" # Clean up - run_podman rmi $fqin + run_podman rmi $fqin $new_fqin } diff --git a/test/system/400-unprivileged-access.bats b/test/system/400-unprivileged-access.bats index 142d7dcd9..20fdd068f 100644 --- a/test/system/400-unprivileged-access.bats +++ b/test/system/400-unprivileged-access.bats @@ -118,7 +118,7 @@ EOF /proc/scsi /sys/firmware /sys/fs/selinux - /sys/dev + /sys/dev/block ) # Some of the above may not exist on our host. Find only the ones that do. |