Add support for ramfs as well as tmpfs in volume mounts

Users want to mount a tmpfs file system with secrets, and make
sure the secret is never saved into swap. They can do this either
by using a ramfs tmpfs mount or by passing `noswap` option to
a tmpfs mount.

Fixes: https://github.com/containers/podman/issues/19659

Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
This commit is contained in:
Daniel J Walsh 2023-08-17 10:48:36 -04:00
parent 84447c0855
commit 45ce4834af
No known key found for this signature in database
GPG Key ID: A2DF901DABE2C028
5 changed files with 68 additions and 24 deletions

View File

@ -6,25 +6,28 @@
Attach a filesystem mount to the container
Current supported mount TYPEs are **bind**, **devpts**, **glob**, **image**, **tmpfs** and **volume**. <sup>[[1]](#Footnote1)</sup>
Current supported mount TYPEs are **bind**, **devpts**, **glob**, **image**, **ramfs**, **tmpfs** and **volume**. <sup>[[1]](#Footnote1)</sup>
e.g.
type=bind,source=/path/on/host,destination=/path/in/container
type=bind,src=/path/on/host,dst=/path/in/container,relabel=shared
type=bind,src=/path/on/host,dst=/path/in/container,relabel=shared,U=true
type=devpts,destination=/dev/pts
type=glob,src=/usr/lib/libfoo*,destination=/usr/lib,ro=true
type=volume,source=vol1,destination=/path/in/container,ro=true
type=tmpfs,tmpfs-size=512M,destination=/path/in/container
type=image,source=fedora,destination=/fedora-image,rw=true
type=devpts,destination=/dev/pts
type=ramfs,tmpfs-size=512M,destination=/path/in/container
type=tmpfs,tmpfs-size=512M,destination=/path/in/container
type=tmpfs,destination=/path/in/container,noswap
type=volume,source=vol1,destination=/path/in/container,ro=true
Common Options:
@ -72,17 +75,17 @@ Current supported mount TYPEs are **bind**, **devpts**, **glob**, **image**, **t
. U, chown: true or false (default). Change recursively the owner and group of the source volume based on the UID and GID of the container.
Options specific to tmpfs:
Options specific to tmpfs and ramfs:
· ro, readonly: true or false (default).
· tmpfs-size: Size of the tmpfs mount in bytes. Unlimited by default in Linux.
· tmpfs-size: Size of the tmpfs/ramfs mount in bytes. Unlimited by default in Linux.
· tmpfs-mode: File mode of the tmpfs in octal. (e.g. 700 or 0700.) Defaults to 1777 in Linux.
· tmpfs-mode: File mode of the tmpfs/ramfs in octal. (e.g. 700 or 0700.) Defaults to 1777 in Linux.
· tmpcopyup: Enable copyup from the image directory at the same location to the tmpfs. Used by default.
· tmpcopyup: Enable copyup from the image directory at the same location to the tmpfs/ramfs. Used by default.
· notmpcopyup: Disable copying files from the image to the tmpfs.
· notmpcopyup: Disable copying files from the image to the tmpfs/ramfs.
. U, chown: true or false (default). Change recursively the owner and group of the source volume based on the UID and GID of the container.

View File

@ -1,10 +1,12 @@
package define
const (
// TypeVolume is the type for named volumes
TypeVolume = "volume"
// TypeTmpfs is the type for mounting tmpfs
TypeTmpfs = "tmpfs"
// TypeDevpts is the type for creating a devpts
TypeDevpts = "devpts"
// TypeTmpfs is the type for mounting tmpfs
TypeTmpfs = "tmpfs"
// TypeRamfs is the type for mounting ramfs
TypeRamfs = "ramfs"
// TypeVolume is the type for named volumes
TypeVolume = "volume"
)

View File

@ -11,6 +11,7 @@ import (
"github.com/containers/common/pkg/config"
"github.com/containers/common/pkg/parse"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/rootless"
"github.com/containers/podman/v4/pkg/specgen"
"github.com/containers/podman/v4/pkg/util"
spec "github.com/opencontainers/runtime-spec/specs-go"
@ -211,8 +212,8 @@ func Mounts(mountFlag []string, configMounts []string) (map[string]spec.Mount, m
}
finalMounts[mount.Destination] = mount
}
case define.TypeTmpfs:
mount, err := getTmpfsMount(tokens)
case define.TypeTmpfs, define.TypeRamfs:
mount, err := parseMemoryMount(tokens, mountType)
if err != nil {
return err
}
@ -282,7 +283,7 @@ func Mounts(mountFlag []string, configMounts []string) (map[string]spec.Mount, m
}
func parseMountOptions(mountType string, args []string) (*spec.Mount, error) {
var setTmpcopyup, setRORW, setSuid, setDev, setExec, setRelabel, setOwnership bool
var setTmpcopyup, setRORW, setSuid, setDev, setExec, setRelabel, setOwnership, setSwap bool
mnt := spec.Mount{}
for _, val := range args {
@ -359,6 +360,15 @@ func parseMountOptions(mountType string, args []string) (*spec.Mount, error) {
}
setSuid = true
mnt.Options = append(mnt.Options, kv[0])
case "noswap":
if setSwap {
return nil, fmt.Errorf("cannot pass 'noswap' mnt.Options more than once: %w", errOptionArg)
}
if rootless.IsRootless() {
return nil, fmt.Errorf("the 'noswap' option is only allowed with rootful tmpfs mounts: %w", errOptionArg)
}
setSwap = true
mnt.Options = append(mnt.Options, kv[0])
case "relabel":
if setRelabel {
return nil, fmt.Errorf("cannot pass 'relabel' option more than once: %w", errOptionArg)
@ -525,11 +535,11 @@ func getBindMount(args []string) (spec.Mount, error) {
return newMount, nil
}
// Parse a single tmpfs mount entry from the --mount flag
func getTmpfsMount(args []string) (spec.Mount, error) {
// Parse a single tmpfs/ramfs mount entry from the --mount flag
func parseMemoryMount(args []string, mountType string) (spec.Mount, error) {
newMount := spec.Mount{
Type: define.TypeTmpfs,
Source: define.TypeTmpfs,
Type: mountType,
Source: mountType,
}
var err error

View File

@ -6,6 +6,7 @@ import (
"strings"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/rootless"
)
var (
@ -27,7 +28,7 @@ type defaultMountOptions struct {
// The sourcePath variable, if not empty, contains a bind mount source.
func ProcessOptions(options []string, isTmpfs bool, sourcePath string) ([]string, error) {
var (
foundWrite, foundSize, foundProp, foundMode, foundExec, foundSuid, foundDev, foundCopyUp, foundBind, foundZ, foundU, foundOverlay, foundIdmap, foundCopy bool
foundWrite, foundSize, foundProp, foundMode, foundExec, foundSuid, foundDev, foundCopyUp, foundBind, foundZ, foundU, foundOverlay, foundIdmap, foundCopy, foundNoSwap bool
)
newOptions := make([]string, 0, len(options))
@ -133,6 +134,20 @@ func ProcessOptions(options []string, isTmpfs bool, sourcePath string) ([]string
foundCopyUp = true
// do not propagate notmpcopyup to the OCI runtime
continue
case "noswap":
if !isTmpfs {
return nil, fmt.Errorf("the 'noswap' option is only allowed with tmpfs mounts: %w", ErrBadMntOption)
}
if rootless.IsRootless() {
return nil, fmt.Errorf("the 'noswap' option is only allowed with rootful tmpfs mounts: %w", ErrBadMntOption)
}
if foundNoSwap {
return nil, fmt.Errorf("the 'tmpswap' option can only be set once: %w", ErrDupeMntOption)
}
foundNoSwap = true
newOptions = append(newOptions, opt)
continue
case define.TypeBind, "rbind":
if isTmpfs {
return nil, fmt.Errorf("the 'bind' and 'rbind' options are not allowed with tmpfs mounts: %w", ErrBadMntOption)

View File

@ -294,4 +294,18 @@ EOF
is "$output" "bar1.*bar2.*bar3" "Should match multiple source files on single destination directory"
}
@test "podman mount noswap memory mounts" {
# if volumes source and dest match then pass
run_podman run --rm --mount type=ramfs,destination=${PODMAN_TMPDIR} $IMAGE stat -f -c "%T" ${PODMAN_TMPDIR}
is "$output" "ramfs" "ramfs mounted"
if is_rootless; then
run_podman 125 run --rm --mount type=tmpfs,destination=${PODMAN_TMPDIR},noswap $IMAGE stat -f -c "%T" ${PODMAN_TMPDIR}
is "$output" "Error: the 'noswap' option is only allowed with rootful tmpfs mounts: must provide an argument for option" "noswap not supported in rootless mode"
else
run_podman run --rm --mount type=tmpfs,destination=${PODMAN_TMPDIR},noswap $IMAGE sh -c "mount| grep ${PODMAN_TMPDIR}"
is "$output" ".*noswap" "tmpfs noswap mounted"
fi
}
# vim: filetype=sh