I ran into the `--local` option not working at least in my setup with the current container image (which for some reason hasn't been updated, and predates the latest tip commit which talks about btrfs) On this current test system (MacOS + podman 5 + default podman-machine) things do work with the tip commit. However...I don't quite understand the need to *both* try to run `systemd-detect-virt` *and* parse `/proc/self/mountinfo`. (BTW, the logic for `insideContainer` was really confusing me because it only returned `true` if we were *not* in a container...I wonder if that was really intentional?) Anyways, I think the goal here is just a friendly direct error message if it doesn't look like the mount is there, which we can do by just checking for the file path. If it isn't mounted from the host then nothing will be there. Signed-off-by: Colin Walters <walters@verbum.org>
117 lines
4.1 KiB
Go
117 lines
4.1 KiB
Go
package setup
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"golang.org/x/sys/unix"
|
|
|
|
"github.com/osbuild/bootc-image-builder/bib/internal/podmanutil"
|
|
"github.com/osbuild/bootc-image-builder/bib/internal/util"
|
|
)
|
|
|
|
// EnsureEnvironment mutates external filesystem state as necessary
|
|
// to run in a container environment. This function is idempotent.
|
|
func EnsureEnvironment(storePath string) error {
|
|
osbuildPath := "/usr/bin/osbuild"
|
|
if util.IsMountpoint(osbuildPath) {
|
|
return nil
|
|
}
|
|
|
|
// Forcibly label the store to ensure we're not grabbing container labels
|
|
rootType := "system_u:object_r:root_t:s0"
|
|
// This papers over the lack of ensuring correct labels for the /ostree root
|
|
// in the existing pipeline
|
|
if err := util.RunCmdSync("chcon", rootType, storePath); err != nil {
|
|
return err
|
|
}
|
|
|
|
// A hardcoded security label from Fedora derivatives for osbuild
|
|
// TODO: Avoid hardcoding this by using either host policy lookup
|
|
// Or eventually depend on privileged containers just having this capability.
|
|
//
|
|
// We need this in order to get `install_t` that has `CAP_MAC_ADMIN` for creating SELinux
|
|
// labels unknown to the host.
|
|
//
|
|
// Note that the transition to `install_t` must happen at this point. Osbuild stages run in `bwrap` that creates
|
|
// a nosuid, no_new_privs environment. In such an environment, we cannot transition from `unconfined_t` to `install_t`,
|
|
// because we would get more privileges.
|
|
installType := "system_u:object_r:install_exec_t:s0"
|
|
// Where we dump temporary files; this must be an overlayfs as we cannot
|
|
// write security contexts on overlayfs.
|
|
runTmp := "/run/osbuild/"
|
|
|
|
if err := os.MkdirAll(runTmp, 0o755); err != nil {
|
|
return err
|
|
}
|
|
if !util.IsMountpoint(runTmp) {
|
|
if err := util.RunCmdSync("mount", "-t", "tmpfs", "tmpfs", runTmp); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
destPath := filepath.Join(runTmp, "osbuild")
|
|
if err := util.RunCmdSync("cp", "-p", "/usr/bin/osbuild", destPath); err != nil {
|
|
return err
|
|
}
|
|
if err := util.RunCmdSync("chcon", installType, destPath); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Ensure we have devfs inside the container to get dynamic loop
|
|
// loop devices inside the container.
|
|
if err := util.RunCmdSync("mount", "-t", "devtmpfs", "devtmpfs", "/dev"); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Create a bind mount into our target location; we can't copy it because
|
|
// again we have to perserve the SELinux label.
|
|
if err := util.RunCmdSync("mount", "--bind", destPath, osbuildPath); err != nil {
|
|
return err
|
|
}
|
|
// NOTE: Don't add new code here, do it before the bind mount which acts as the final success indicator
|
|
|
|
return nil
|
|
}
|
|
|
|
// Validate checks that the environment is supported (e.g. caller set up the
|
|
// container correctly)
|
|
func Validate() error {
|
|
isRootless, err := podmanutil.IsRootless()
|
|
if err != nil {
|
|
return fmt.Errorf("checking rootless: %w", err)
|
|
}
|
|
if isRootless {
|
|
return fmt.Errorf("this command must be run in rootful (not rootless) podman")
|
|
}
|
|
|
|
// Having /sys be writable is an easy to check proxy for privileges; more effective
|
|
// is really looking for CAP_SYS_ADMIN, but that involves more Go libraries.
|
|
var stvfsbuf unix.Statfs_t
|
|
if err := unix.Statfs("/sys", &stvfsbuf); err != nil {
|
|
return fmt.Errorf("failed to stat /sys: %w", err)
|
|
}
|
|
if (stvfsbuf.Flags & unix.ST_RDONLY) > 0 {
|
|
return fmt.Errorf("this command requires a privileged container")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ValidateHasContainerStorageMounted checks that the hostcontainer storage
|
|
// is mounted inside the container
|
|
func ValidateHasContainerStorageMounted() error {
|
|
// Just look for the overlay backend, which we expect by default.
|
|
// In theory, one could be using a different backend, but we don't
|
|
// really need to worry about this right now. If it turns out
|
|
// we do need to care, then we can probably handle this by
|
|
// just trying to query the image.
|
|
overlayPath := "/var/lib/containers/storage/overlay"
|
|
if _, err := os.Stat(overlayPath); err != nil {
|
|
if os.IsNotExist(err) {
|
|
return fmt.Errorf("cannot find %q (missing -v /var/lib/containers/storage:/var/lib/containers/storage mount?)", overlayPath)
|
|
}
|
|
return fmt.Errorf("failed to stat %q: %w", overlayPath, err)
|
|
}
|
|
return nil
|
|
}
|