diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aa589a05..daec024f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,7 +29,6 @@ jobs: TAR_FILE_NAME="buildg-${RELEASE_TAG}-linux-${{ matrix.arch }}.tar.gz" SHA256SUM_FILE_NAME="${TAR_FILE_NAME}.sha256sum" PREFIX=$(pwd)/out GOARCH=${{ matrix.arch }} make - cp ./extras/buildg.sh ./out/ tar -C ./out/ -zcvf "${OUTPUT_DIR}/${TAR_FILE_NAME}" . cat "${OUTPUT_DIR}/${TAR_FILE_NAME}" | tar -zv --list ( cd ${OUTPUT_DIR}; sha256sum ${TAR_FILE_NAME} ) > "${OUTPUT_DIR}/${SHA256SUM_FILE_NAME}" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0f688674..f7bdf8d9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -48,7 +48,7 @@ jobs: sudo go test -v ./... elif [ "${{ matrix.mode }}" == "rootless" ] ; then mkdir -p ${GITHUB_WORKSPACE}/tmp - TEST_BUILDG_PATH=buildg.sh TEST_BUILDG_TMP_DIR=${GITHUB_WORKSPACE}/tmp go test -v ./... + TEST_BUILDG_TMP_DIR=${GITHUB_WORKSPACE}/tmp go test -v ./... else echo "unknown mode ${{ matrix.mode }}" exit 1 diff --git a/README.md b/README.md index 62abd1b7..dd26384c 100644 --- a/README.md +++ b/README.md @@ -402,7 +402,7 @@ Usage: `help [COMMAND]` - `--oci-worker-net value`: Worker network type: "auto", "cni", "host" (default: "auto") - `--oci-cni-config-path value`: Path to CNI config file (default: "/etc/buildkit/cni.json") - `--oci-cni-binary-path value`: Path to CNI plugin binary dir (default: "/opt/cni/bin") -- `--rootless`: Enable rootless configuration +- `--rootlesskit-args`: Change arguments for rootlesskit in JSON format [`BUILDG_ROOTLESSKIT_ARGS`] # Additional documents diff --git a/main.go b/main.go index e694a85b..7dc38176 100644 --- a/main.go +++ b/main.go @@ -2,10 +2,13 @@ package main import ( "context" + "encoding/json" "fmt" "io" + "io/fs" "net" "os" + "os/exec" "os/signal" "path/filepath" "strings" @@ -33,17 +36,6 @@ func main() { app := cli.NewApp() app.Usage = "Interactive debugger for Dockerfile" var flags []cli.Flag - if userns.RunningInUserNS() { - flags = append(flags, cli.BoolTFlag{ - Name: "rootless", - Usage: "Enable rootless configuration (default:true)", - }) - } else { - flags = append(flags, cli.BoolFlag{ - Name: "rootless", - Usage: "Enable rootless configuration", - }) - } app.Flags = append([]cli.Flag{ cli.BoolFlag{ Name: "debug", @@ -73,6 +65,12 @@ func main() { Usage: "Path to CNI plugin binary dir", Value: "/opt/cni/bin", }, + cli.StringFlag{ + Name: "rootlesskit-args", + Usage: "Change arguments for rootlesskit in JSON format", + EnvVar: "BUILDG_ROOTLESSKIT_ARGS", + Value: "", + }, }, flags...) app.Commands = []cli.Command{ newDebugCommand(), @@ -86,6 +84,20 @@ func main() { if context.GlobalBool("debug") { logrus.SetLevel(logrus.DebugLevel) } + if os.Geteuid() != 0 { + // Running by nonroot user. Enter to the rootless mode. + if err := reexecRootless(context); err != nil { + fmt.Fprintf(os.Stderr, "failed to run by non-root user: %v\n", err) + } + os.Exit(1) // shouldn't reach here if reexec succeeds + } + if userns.RunningInUserNS() && os.Getenv("ROOTLESSKIT_STATE_DIR") != "" && os.Getenv("_BUILDG_ROOTLESSKIT_ENABLED") != "" { + // Running in the rootlesskit user namespace created for buildg. Do preparation for this environment. + if err := prepareRootlessChild(); err != nil { + fmt.Fprintf(os.Stderr, "failed to prepare rootless child: %v\n", err) + os.Exit(1) + } + } return nil } if err := app.Run(os.Args); err != nil { @@ -94,6 +106,55 @@ func main() { } } +func reexecRootless(context *cli.Context) error { + arg0, err := exec.LookPath("rootlesskit") + if err != nil { + return err + } + var args []string + if argsStr := context.String("rootlesskit-args"); argsStr != "" { + if json.Unmarshal([]byte(argsStr), &args); err != nil { + return fmt.Errorf("failed to parse \"--rootlesskit-args\": %v", err) + } + } + if len(args) == 0 { + args = []string{ + "--net=slirp4netns", + "--copy-up=/etc", + "--copy-up=/run", + "--disable-host-loopback", + } + } + args = append(args, os.Args...) + logrus.Debugf("running rootlesskit with args: %+v", args) + // Tell the child process that this is the namespace for buildg + env := append(os.Environ(), "_BUILDG_ROOTLESSKIT_ENABLED=1") + return syscall.Exec(arg0, args, env) +} + +func prepareRootlessChild() error { + // rootlesskit creates the "copied-up" symlink on `/run/runc` which is not accessible + // from rootless user. We don't need this because we create runc rootdir for our own usage. + runcRoot := "/run/runc" + if _, err := os.Lstat(runcRoot); err != nil { + if os.IsNotExist(err) { + return nil // nothing to do + } + return err + } + rInfo, err := os.Lstat(runcRoot) + if err != nil { + return fmt.Errorf("failed to stat runc root: %v", err) + } + if mode := rInfo.Mode(); mode&fs.ModeSymlink == 0 { + return fmt.Errorf("unexpected runc root file mode: %v", mode) + } + if err := os.Remove("/run/runc"); err != nil { + return err + } + return nil +} + func newVersionCommand() cli.Command { return cli.Command{ Name: "version", @@ -471,7 +532,7 @@ func dapDuAction(clicontext *cli.Context) error { func parseGlobalWorkerConfig(clicontext *cli.Context) (cfg *config.Config, rootDir string, err error) { cfg = &config.Config{} - cfg.Workers.OCI.Rootless = clicontext.GlobalBool("rootless") + cfg.Workers.OCI.Rootless = userns.RunningInUserNS() cfg.Workers.OCI.NetworkConfig = config.NetworkConfig{ Mode: clicontext.GlobalString("oci-worker-net"), CNIConfigPath: clicontext.GlobalString("oci-cni-config-path"), diff --git a/pkg/buildkit/exec.go b/pkg/buildkit/exec.go index 4facde14..0a98ff5b 100644 --- a/pkg/buildkit/exec.go +++ b/pkg/buildkit/exec.go @@ -151,10 +151,10 @@ func ExecContainer(ctx context.Context, cfg ContainerConfig) (_ gwclient.Contain cfg.WatchSignal(ioCtx, proc, con) } return proc, func() { - logrus.Warnf("cleaning up container exec") + logrus.Debugf("cleaning up container exec") for i := len(cleanups) - 1; i >= 0; i-- { cleanups[i]() } - logrus.Warnf("finished container exec") + logrus.Debugf("finished container exec") }, nil }