Example #1
0
// MountGnupg bind mounts $GNUPGHOME or ~/.gnupg and the agent socket
// if available. The agent is ignored if the home dir isn't available.
func (e *enter) MountGnupg() error {
	origHome := os.Getenv("GNUPGHOME")
	if origHome == "" {
		origHome = filepath.Join(e.User.HomeDir, ".gnupg")
	}

	if _, err := os.Stat(origHome); err != nil {
		// Skip but do not pass along $GNUPGHOME
		return os.Unsetenv("GNUPGHOME")
	}

	newHome, err := ioutil.TempDir(e.UserRunDir, "gnupg-")
	if err != nil {
		return err
	}

	if err := system.Bind(origHome, newHome); err != nil {
		return err
	}

	chrootHome := strings.TrimPrefix(newHome, e.Chroot)
	if err := os.Setenv("GNUPGHOME", chrootHome); err != nil {
		return err
	}

	return e.MountAgent("GPG_AGENT_INFO")
}
Example #2
0
// MountAgent bind mounts a SSH or GnuPG agent socket into the chroot
func (e *enter) MountAgent(env string) error {
	origPath := os.Getenv(env)
	if origPath == "" {
		return nil
	}

	origDir, origFile := filepath.Split(origPath)
	if _, err := os.Stat(origDir); err != nil {
		// Just skip if the agent has gone missing.
		return nil
	}

	newDir, err := ioutil.TempDir(e.UserRunDir, "agent-")
	if err != nil {
		return err
	}

	if err := system.Bind(origDir, newDir); err != nil {
		return err
	}

	newPath := filepath.Join(newDir, origFile)
	chrootPath := strings.TrimPrefix(newPath, e.Chroot)
	return os.Setenv(env, chrootPath)
}
Example #3
0
// MountAPI mounts standard Linux API filesystems.
// When possible the filesystems are mounted read-only.
func (e *enter) MountAPI() error {
	var apis = []struct {
		Path string
		Type string
		Opts string
	}{
		{"/proc", "proc", "ro,nosuid,nodev,noexec"},
		{"/sys", "sysfs", "ro,nosuid,nodev,noexec"},
		{"/run", "tmpfs", "nosuid,nodev,mode=755"},
	}

	for _, fs := range apis {
		target := filepath.Join(e.Chroot, fs.Path)
		if err := system.Mount("", target, fs.Type, fs.Opts); err != nil {
			return err
		}
	}

	// Since loop devices are dynamic we need the host's managed /dev
	if err := system.ReadOnlyBind("/dev", filepath.Join(e.Chroot, "dev")); err != nil {
		return err
	}
	// /dev/pts must be read-write because emerge chowns tty devices.
	if err := system.Bind("/dev/pts", filepath.Join(e.Chroot, "dev/pts")); err != nil {
		return err
	}

	// Unfortunately using the host's /dev complicates /dev/shm which may
	// be a directory or a symlink into /run depending on the distro. :(
	// XXX: catalyst does not work on systems with a /dev/shm symlink!
	if system.IsSymlink("/dev/shm") {
		shmPath, err := filepath.EvalSymlinks("/dev/shm")
		if err != nil {
			return err
		}
		// Only accept known values to avoid surprises.
		if shmPath != "/run/shm" {
			return fmt.Errorf("Unexpected shm path: %s", shmPath)
		}
		newPath := filepath.Join(e.Chroot, shmPath)
		if err := os.Mkdir(newPath, 01777); err != nil {
			return err
		}
		if err := os.Chmod(newPath, 01777); err != nil {
			return err
		}
	} else {
		shmPath := filepath.Join(e.Chroot, "dev/shm")
		if err := system.Mount("", shmPath, "tmpfs", "nosuid,nodev"); err != nil {
			return err
		}
	}

	return nil
}
Example #4
0
// bind mount the repo source tree into the chroot and run a command
func enterChrootHelper(args []string) (err error) {
	if len(args) < 3 {
		return fmt.Errorf("got %d args, need at least 3", len(args))
	}

	e := enter{
		RepoRoot: args[0],
		Chroot:   args[1],
		Cmd:      args[2:],
	}

	username := os.Getenv("SUDO_USER")
	if username == "" {
		return fmt.Errorf("SUDO_USER environment variable is not set.")
	}
	if e.User, err = user.Lookup(username); err != nil {
		return err
	}
	e.UserRunDir = filepath.Join(e.Chroot, "run", "user", e.User.Uid)

	newRepoRoot := filepath.Join(e.Chroot, chrootRepoRoot)
	if err := os.MkdirAll(newRepoRoot, 0755); err != nil {
		return err
	}

	// Only copy if resolv.conf exists, if missing resolver uses localhost
	resolv := "/etc/resolv.conf"
	if _, err := os.Stat(resolv); err == nil {
		chrootResolv := filepath.Join(e.Chroot, resolv)
		if err := system.InstallRegularFile(resolv, chrootResolv); err != nil {
			return err
		}
	}

	// namespaces are per-thread attributes
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	if err := syscall.Unshare(syscall.CLONE_NEWNS); err != nil {
		return fmt.Errorf("Unsharing mount namespace failed: %v", err)
	}

	if err := system.RecursiveSlave("/"); err != nil {
		return err
	}

	if err := system.Bind(e.RepoRoot, newRepoRoot); err != nil {
		return err
	}

	if err := e.MountAPI(); err != nil {
		return err
	}

	if err = os.MkdirAll(e.UserRunDir, 0755); err != nil {
		return err
	}

	if err = os.Chown(e.UserRunDir, e.User.UidNo, e.User.GidNo); err != nil {
		return err
	}

	if err := e.MountAgent("SSH_AUTH_SOCK"); err != nil {
		return err
	}

	if err := e.MountGnupg(); err != nil {
		return err
	}

	if err := e.CopyGoogleCreds(); err != nil {
		return err
	}

	if err := syscall.Chroot(e.Chroot); err != nil {
		return fmt.Errorf("Chrooting to %q failed: %v", e.Chroot, err)
	}

	if err := os.Chdir(chrootRepoRoot); err != nil {
		return err
	}

	sudo := "/usr/bin/sudo"
	sudoArgs := append([]string{sudo, "-u", username, "--"}, e.Cmd...)
	return syscall.Exec(sudo, sudoArgs, os.Environ())
}