From 8428d2ac763407329136b3961af1b16393e03067 Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Wed, 18 Dec 2024 10:47:40 -0500 Subject: [PATCH] Add support for --security-opt mask and unmask Fixes: https://github.com/containers/buildah/issues/5881 Signed-off-by: Daniel J Walsh --- define/build.go | 4 ++++ docs/buildah-build.1.md | 6 ++++++ internal/mkcw/embed/entrypoint_amd64.gz | Bin 393 -> 375 bytes pkg/parse/parse.go | 12 ++++++++++++ run_linux.go | 12 +++++++++--- tests/bud.bats | 13 +++++++++++++ tests/bud/masks/Containerfile | 4 ++++ tests/bud/masks/test.sh | 15 +++++++++++++++ 8 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 tests/bud/masks/Containerfile create mode 100644 tests/bud/masks/test.sh diff --git a/define/build.go b/define/build.go index 359eec7d16e..256197f3f7c 100644 --- a/define/build.go +++ b/define/build.go @@ -62,6 +62,8 @@ type CommonBuildOptions struct { // LabelOpts is a slice of the fields of an SELinux context, given in "field:pair" format, or "disable". // Recognized field names are "role", "type", and "level". LabelOpts []string + // Paths to mask + Masks []string // MemorySwap limits the amount of memory and swap together. MemorySwap int64 // NoHostname tells the builder not to create /etc/hostname content when running @@ -109,6 +111,8 @@ type CommonBuildOptions struct { SSHSources []string // OCIHooksDir is the location of OCI hooks for the build containers OCIHooksDir []string + // Paths to unmask + Unmasks []string } // BuildOptions can be used to alter how an image is built. diff --git a/docs/buildah-build.1.md b/docs/buildah-build.1.md index 6fe68e7ed40..c27fb29fd36 100644 --- a/docs/buildah-build.1.md +++ b/docs/buildah-build.1.md @@ -935,11 +935,17 @@ Security Options "label=type:TYPE" : Set the label type for the container "label=level:LEVEL" : Set the label level for the container "label=disable" : Turn off label confinement 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 : JSON configuration for a seccomp filter + "unmask=_ALL_ or _/path/1:/path/2_, or shell expanded paths (/proc/*): Paths to unmask separated by a colon. If set to **ALL**, it unmasks 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**, **/sys/devices/virtual/powercap**. The default paths that are read-only are **/proc/asound**, **/proc/bus**, **/proc/fs**, **/proc/irq**, **/proc/sys**, **/proc/sysrq-trigger**, **/sys/fs/cgroup**. + **--shm-size**="" Size of `/dev/shm`. The format is ``. `number` must be greater than `0`. diff --git a/internal/mkcw/embed/entrypoint_amd64.gz b/internal/mkcw/embed/entrypoint_amd64.gz index 947bed9b688479e757c0a11db7076b8774310d7e..953670818fe136267f5a1624e4298358495fb743 100755 GIT binary patch literal 375 zcmV--0f_z|iwFP!000021MQkYO2a@DhF@Z%MHki^%)+G%!IcY&$+@sW+c zvPFuH9Z{g~0X0l+K+8lc%3HLeWVNbmR0Fpzi&k`O!%kgYV@+LX!%mx4uV*U$N!^vM z+obQ)A^-pY000000O0S@o=!X?Q{TvYsDFst)3*!lb^|?oJfHQ9$M*sN000000002~ zBUMkF(Y%L$etAnCd6O#UP6YThf744#mOh9?GChc8nI@TxnaWvLvSO4mQ8K~q^6Wm1 zWu{UwV2^pxALJrtqcY7BHb}dDoyfO|c-bpep+uLsl23|wmMh7VY{Z8}K9q%eCSK&R zP~zA7+_8ID^z!Zb2G_9XwS2GU_w==8zK3OTeqb(PEx%dw`*zJk>qB?$z}9!Zc>c0( Ve6p{xtv^`v{vQyN#K-6%007vKwlx3% literal 393 zcmV;40e1c$iwFP!000021MQm6O2a@9#wRtcMGy7~WDg!?DV{utsy0(vY3IijB?bf^-&ndNGr}esWJU1T_G&Ho~uvO>VV`b;D z@`8pAZMxIG)th$}{ig0(*Y#j?+&1`jX~o`LT;1vQdTZaV+q|EqM-T)-5ClOG1VNBZ z80F=c`6$mjrMwj%(xd$KQoFsbzI$TsdZowxLJ$N&5ClOG1VNDh$n8XAL_D&X6MYf! zOvDL~h>~Q?NxDb~%LpU)GIx@F<&7V<^7}2n52lA+_2{er4~)=O ne!Jg(U}HxvQ2mEsB>bknu3KNME41~8KaBqYCdfACxFG-lYni%Z diff --git a/pkg/parse/parse.go b/pkg/parse/parse.go index 695b9c334d1..15b520cfa11 100644 --- a/pkg/parse/parse.go +++ b/pkg/parse/parse.go @@ -249,6 +249,18 @@ func parseSecurityOpts(securityOpts []string, commonOpts *define.CommonBuildOpti commonOpts.ApparmorProfile = con[1] case "seccomp": commonOpts.SeccompProfilePath = con[1] + case "mask": + commonOpts.Masks = append(commonOpts.Masks, strings.Split(con[1], ":")...) + case "unmask": + unmasks := strings.Split(con[1], ":") + for _, unmask := range unmasks { + matches, _ := filepath.Glob(unmask) + if len(matches) > 0 { + commonOpts.Unmasks = append(commonOpts.Unmasks, matches...) + continue + } + commonOpts.Unmasks = append(commonOpts.Unmasks, unmask) + } default: return fmt.Errorf("invalid --security-opt 2: %q", opt) } diff --git a/run_linux.go b/run_linux.go index 0acee7ac84e..20f1aad3e44 100644 --- a/run_linux.go +++ b/run_linux.go @@ -328,7 +328,7 @@ func (b *Builder) Run(command []string, options RunOptions) error { } } - setupMaskedPaths(g) + setupMaskedPaths(g, b.CommonBuildOpts) setupReadOnlyPaths(g) setupTerminal(g, options.Terminal, options.TerminalSize) @@ -1199,8 +1199,14 @@ func (b *Builder) runSetupVolumeMounts(mountLabel string, volumeMounts []string, return mounts, nil } -func setupMaskedPaths(g *generate.Generator) { - for _, mp := range config.DefaultMaskedPaths { +func setupMaskedPaths(g *generate.Generator, opts *define.CommonBuildOptions) { + if slices.Contains(opts.Unmasks, "all") { + return + } + for _, mp := range append(config.DefaultMaskedPaths, opts.Masks...) { + if slices.Contains(opts.Unmasks, mp) { + continue + } g.AddLinuxMaskedPaths(mp) } } diff --git a/tests/bud.bats b/tests/bud.bats index 8eae1d30672..36aa3be79c6 100644 --- a/tests/bud.bats +++ b/tests/bud.bats @@ -7123,3 +7123,16 @@ EOF echo RUN --mount=type=tmpfs,target=tmpfssubdir test '`stat -f -c %i .`' '!=' '`stat -f -c %i tmpfssubdir`' >> ${TEST_SCRATCH_DIR}/Containerfile run_buildah build --security-opt label=disable ${TEST_SCRATCH_DIR} } + +@test "build-security-opt-mask" { + base=busybox + _prefetch $base + run_buildah build bud/masks + expect_output --substring "masked" "Everything should be masked" + run_buildah build --security-opt unmask=all bud/masks + expect_output --substring "unmasked" "Everything should be masked" + run_buildah build --security-opt unmask=/proc/* bud/masks + expect_output --substring "unmasked" "Everything should be masked" + run_buildah build --security-opt unmask=/proc/acpi bud/masks + expect_output --substring "unmasked" "Everything should be masked" +} diff --git a/tests/bud/masks/Containerfile b/tests/bud/masks/Containerfile new file mode 100644 index 00000000000..8d7cfd0be6a --- /dev/null +++ b/tests/bud/masks/Containerfile @@ -0,0 +1,4 @@ +FROM alpine +COPY --chmod=700 test.sh / +RUN /test.sh + diff --git a/tests/bud/masks/test.sh b/tests/bud/masks/test.sh new file mode 100644 index 00000000000..9a88dac9bd2 --- /dev/null +++ b/tests/bud/masks/test.sh @@ -0,0 +1,15 @@ +# !/bin/sh + +function output { + for mask in /proc/acpi /proc/kcore /proc/keys /proc/latency_stats /proc/sched_debug /proc/scsi /proc/timer_list /proc/timer_stats /sys/dev/block /sys/devices/virtual/powercap /sys/firmware /sys/fs/selinux; do + test -e $mask || continue + test -f $mask && cat $mask 2> /dev/null + test -d $mask && ls $mask + done +} +output=$(output | wc -c ) +if [ $output == 0 ]; then + echo "masked" +else + echo "unmasked" +fi