containers / buildah

A tool that facilitates building OCI images.
https://buildah.io
Apache License 2.0
7.47k stars 785 forks source link

buildah commands that don't involve building containers at all run through unshare #5750

Open AdamWill opened 2 months ago

AdamWill commented 2 months ago

Description

This is yet another in the series of 'issues to do with running buildah in a locked-down container' - see https://github.com/containers/buildah/issues/4563 , https://github.com/containers/buildah/issues/1901 - but with a twist: we want to use buildah to do things that don't involve building containers at all, but those commands still run through unshare. @terinjokes also noted this in https://github.com/containers/buildah/issues/1901#issuecomment-1674648806 .

@jeremycline and I are working on improving Fedora's container registry publication. All this needs to do is take some already-built container images from a Fedora compose, create a multiarch manifest, and push the images and the manifest to some registries. The only tools we've found for doing this are buildah and podman. We have the code for this all written. But when we try to deploy it in an openshift container (which is where we want to deploy it), it blows up because unshare doesn't work. Well, sure, it doesn't, and we don't really want to mess around with the container security stuff to allow it, because we shouldn't need it to do this. Commands which just create manifests and push images to registries should not need to run through isolation; they're just writing trivial JSON and making web API calls. (podman appears to have basically the same problem as buildah).

We would happily use a different tool for this if we could find one that just does this and avoids the mess, but we can't (suggestions welcome). We really don't want to have to write one.

Steps to reproduce the issue:

  1. Create a bog-standard Fedora container in Fedora openshift, with buildah in it
  2. Try and run buildah login or buildah manifest create or buildah manifest push

Describe the results you received:

sh-5.2$ HOME=/tmp/whatever buildah login
WARN[0000] Reading allowed ID mappings: reading subuid mappings for user "1001340000" and subgid mappings for group "1001340000": no subuid ranges found for user "1001340000" in /etc/subuid 
WARN[0000] Found no UID ranges set aside for user "1001340000" in /etc/subuid. 
WARN[0000] Found no GID ranges set aside for user "1001340000" in /etc/subgid. 
Error during unshare(CLONE_NEWUSER): Function not implemented
ERRO[0000] parsing PID "": strconv.Atoi: parsing "": invalid syntax 
ERRO[0000] (Unable to determine exit status)            
sh-5.2$ 

Describe the results you expected: successful login

AdamWill commented 2 months ago

The HOME=/tmp/whatever is needed because without that buildah has a fit about ~/.config not being owned by the current user, btw.

AdamWill commented 2 months ago

Ugh. I guess https://github.com/containers/buildah/issues/3259 is a complicating factor here?

AdamWill commented 2 months ago

Here's a kinda silly 'fix' for this, I guess...

diff --git a/cmd/buildah/main.go b/cmd/buildah/main.go
index 524d87b32..df42a82f5 100644
--- a/cmd/buildah/main.go
+++ b/cmd/buildah/main.go
@@ -40,6 +40,7 @@ type globalFlags struct {
        MemoryProfile              string
        UserShortNameAliasConfPath string
        CgroupManager              string
+       NoUnshare                  bool
 }

 var rootCmd = &cobra.Command{
@@ -105,6 +106,7 @@ func init() {
        rootCmd.PersistentFlags().StringVar(&globalFlagResults.LogLevel, logLevel, "warn", `the log level to be used, one of "trace", "debug", "info", "warn", "error", "fatal", or "panic"`)
        rootCmd.PersistentFlags().StringVar(&globalFlagResults.CPUProfile, "cpu-profile", "", "`file` to write CPU profile")
        rootCmd.PersistentFlags().StringVar(&globalFlagResults.MemoryProfile, "memory-profile", "", "`file` to write memory profile")
+       rootCmd.PersistentFlags().BoolVar(&globalFlagResults.NoUnshare, "no-unshare", false, "disable user namespace re-exec when unprivileged (for running non-build commands like pull, push and manifest in locked-down containers)")

        if err := rootCmd.PersistentFlags().MarkHidden("cpu-profile"); err != nil {
                logrus.Fatalf("unable to mark cpu-profile flag as hidden: %v", err)
@@ -145,6 +147,13 @@ func before(cmd *cobra.Command) error {
        case "", "help", "version", "mount":
                return nil
        }
+       if globalFlagResults.NoUnshare {
+               switch cmd.Use {
+               case "add", "build", "commit", "copy", "from", "mkcw", "run", "umount", "unshare":
+                       return fmt.Errorf("command %s cannot be used with --no-unshare", cmd.Use)
+               }
+               return nil
+       }
        debugCapabilities()
        unshare.MaybeReexecUsingUserNamespace(false)
        if globalFlagResults.CPUProfile != "" {
rhatdan commented 2 months ago

Let's just add the ones that make no sense to unshare to the line above:

switch cmd.Use {
case "", "help", "version", "mount":
    return nil
}
github-actions[bot] commented 1 month ago

A friendly reminder that this issue had no activity for 30 days.