Add filepath glob support to --security-opt unmask

Want to allow users to specify --security-opt unmask=/proc/*.
This allows us to run podman within podman more securely, then
specifing umask=all, also gives the user more flexibilty.

Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
This commit is contained in:
Daniel J Walsh
2021-05-01 05:50:31 -04:00
parent 7f2c27d43f
commit 4fd1965ab4
6 changed files with 81 additions and 30 deletions
cmd/podman/common
docs/source/markdown
pkg/specgen/generate
test/e2e

@ -540,7 +540,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
return fmt.Errorf("invalid systempaths option %q, only `unconfined` is supported", con[1]) return fmt.Errorf("invalid systempaths option %q, only `unconfined` is supported", con[1])
} }
case "unmask": case "unmask":
s.ContainerSecurityConfig.Unmask = append(s.ContainerSecurityConfig.Unmask, strings.Split(con[1], ":")...) s.ContainerSecurityConfig.Unmask = append(s.ContainerSecurityConfig.Unmask, con[1:]...)
default: default:
return fmt.Errorf("invalid --security-opt 2: %q", opt) return fmt.Errorf("invalid --security-opt 2: %q", opt)
} }

@ -882,8 +882,7 @@ Note: Labeling can be disabled for all containers by setting label=false in the
- `proc-opts=OPTIONS` : Comma-separated list of options to use for the /proc mount. More details for the - `proc-opts=OPTIONS` : Comma-separated list of options to use for the /proc mount. More details for the
possible mount options are specified in the **proc(5)** man page. possible mount options are specified in the **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_ or _/path/1:/path/2_, or shell expanded paths (/proc/*): Paths to unmask separated by a colon. If set to **ALL**, it will unmask all the paths that are masked or made read only by default.
unmask all the paths that are masked or made read only 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.** The default paths that are read only are **/proc/asound, /proc/bus, /proc/fs, /proc/irq, /proc/sys, /proc/sysrq-trigger, /sys/fs/cgroup**. 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.** The default paths that are read only are **/proc/asound, /proc/bus, /proc/fs, /proc/irq, /proc/sys, /proc/sysrq-trigger, /sys/fs/cgroup**.
Note: Labeling can be disabled for all containers by setting label=false in the **containers.conf** (`/etc/containers/containers.conf` or `$HOME/.config/containers/containers.conf`) file. Note: Labeling can be disabled for all containers by setting label=false in the **containers.conf** (`/etc/containers/containers.conf` or `$HOME/.config/containers/containers.conf`) file.

@ -934,8 +934,7 @@ Note: Labeling can be disabled for all containers by setting label=false in the
- **proc-opts**=_OPTIONS_ : Comma-separated list of options to use for the /proc mount. More details - **proc-opts**=_OPTIONS_ : Comma-separated list of options to use for the /proc mount. More details
for the possible mount options are specified in the **proc(5)** man page. for the possible mount options are specified in the **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_ or _/path/1:/path/2_, or shell expanded paths (/proc/*): Paths to unmask separated by a colon. If set to **ALL**, it will unmask all the paths that are masked or made read only by default.
unmask all the paths that are masked or made read only 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.**. The default paths that are read only are **/proc/asound**, **/proc/bus**, **/proc/fs**, **/proc/irq**, **/proc/sys**, **/proc/sysrq-trigger**, **/sys/fs/cgroup**. 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.**. The default paths that are read only are **/proc/asound**, **/proc/bus**, **/proc/fs**, **/proc/irq**, **/proc/sys**, **/proc/sysrq-trigger**, **/sys/fs/cgroup**.
Note: Labeling can be disabled for all containers by setting **label=false** in the **containers.conf**(5) file. Note: Labeling can be disabled for all containers by setting **label=false** in the **containers.conf**(5) file.
@ -1644,6 +1643,13 @@ the **mask** option.
$ podman run --security-opt unmask=ALL fedora bash $ podman run --security-opt unmask=ALL fedora bash
``` ```
To unmask all the paths that start with /proc, set the **unmask** option to
**/proc/***.
```
$ podman run --security-opt unmask=/proc/* fedora bash
```
``` ```
$ podman run --security-opt unmask=/foo/bar:/sys/firmware fedora bash $ podman run --security-opt unmask=/foo/bar:/sys/firmware fedora bash
``` ```

@ -10,7 +10,6 @@ import (
"github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/pkg/rootless" "github.com/containers/podman/v3/pkg/rootless"
"github.com/containers/podman/v3/pkg/util"
spec "github.com/opencontainers/runtime-spec/specs-go" spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate" "github.com/opencontainers/runtime-tools/generate"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -151,16 +150,10 @@ func BlockAccessToKernelFilesystems(privileged, pidModeIsHost bool, mask, unmask
"/sys/dev/block", "/sys/dev/block",
} }
unmaskAll := false
if unmask != nil && unmask[0] == "ALL" {
unmaskAll = true
}
if !privileged { if !privileged {
if !unmaskAll {
for _, mp := range defaultMaskPaths { for _, mp := range defaultMaskPaths {
// check that the path to mask is not in the list of paths to unmask // check that the path to mask is not in the list of paths to unmask
if !util.StringInSlice(mp, unmask) { if shouldMask(mp, unmask) {
g.AddLinuxMaskedPaths(mp) g.AddLinuxMaskedPaths(mp)
} }
} }
@ -172,11 +165,10 @@ func BlockAccessToKernelFilesystems(privileged, pidModeIsHost bool, mask, unmask
"/proc/sys", "/proc/sys",
"/proc/sysrq-trigger", "/proc/sysrq-trigger",
} { } {
if !util.StringInSlice(rp, unmask) { if shouldMask(rp, unmask) {
g.AddLinuxReadonlyPaths(rp) g.AddLinuxReadonlyPaths(rp)
} }
} }
}
if pidModeIsHost && rootless.IsRootless() { if pidModeIsHost && rootless.IsRootless() {
return return
@ -376,3 +368,21 @@ func supportAmbientCapabilities() bool {
err := unix.Prctl(unix.PR_CAP_AMBIENT, unix.PR_CAP_AMBIENT_IS_SET, 0, 0, 0) err := unix.Prctl(unix.PR_CAP_AMBIENT, unix.PR_CAP_AMBIENT_IS_SET, 0, 0, 0)
return err == nil return err == nil
} }
func shouldMask(mask string, unmask []string) bool {
for _, m := range unmask {
if strings.ToLower(m) == "all" {
return false
}
for _, m1 := range strings.Split(m, ":") {
match, err := filepath.Match(m1, mask)
if err != nil {
logrus.Errorf(err.Error())
}
if match {
return false
}
}
}
return true
}

@ -0,0 +1,28 @@
package generate
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestShouldMask(t *testing.T) {
tests := []struct {
mask string
unmask []string
shouldMask bool
}{
{"/proc/foo", []string{"all"}, false},
{"/proc/foo", []string{"ALL"}, false},
{"/proc/foo", []string{"/proc/foo"}, false},
{"/proc/foo", []string{"/proc/*"}, false},
{"/proc/foo", []string{"/proc/bar", "all"}, false},
{"/proc/foo", []string{"/proc/f*"}, false},
{"/proc/foo", []string{"/proc/b*"}, true},
{"/proc/foo", []string{}, true},
}
for _, test := range tests {
val := shouldMask(test.mask, test.unmask)
assert.Equal(t, val, test.shouldMask)
}
}

@ -299,9 +299,17 @@ var _ = Describe("Podman run", func() {
session = podmanTest.Podman([]string{"run", "-d", "--name=maskCtr5", "--security-opt", "systempaths=unconfined", ALPINE, "grep", "/proc", "/proc/self/mounts"}) session = podmanTest.Podman([]string{"run", "-d", "--name=maskCtr5", "--security-opt", "systempaths=unconfined", ALPINE, "grep", "/proc", "/proc/self/mounts"})
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0)) Expect(session.ExitCode()).To(Equal(0))
stdoutLines := session.OutputToStringArray() Expect(session.OutputToStringArray()).Should(HaveLen(1))
Expect(stdoutLines).Should(HaveLen(1))
session = podmanTest.Podman([]string{"run", "-d", "--security-opt", "unmask=/proc/*", ALPINE, "grep", "/proc", "/proc/self/mounts"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(session.OutputToStringArray()).Should(HaveLen(1))
session = podmanTest.Podman([]string{"run", "--security-opt", "unmask=/proc/a*", ALPINE, "ls", "/proc/acpi"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(session.OutputToString()).To(Not(BeEmpty()))
}) })
It("podman run security-opt unmask on /sys/fs/cgroup", func() { It("podman run security-opt unmask on /sys/fs/cgroup", func() {