mirror of
https://github.com/containers/podman.git
synced 2025-08-06 11:32:07 +08:00
Initial implementation of mac forwarding using a privileged docker sock claim helper
Signed-off-by: Jason T. Greene <jason.greene@redhat.com>
This commit is contained in:

committed by
Matthew Heon

parent
2128236da5
commit
f71dfcb5da
244
cmd/podman-mac-helper/install.go
Normal file
244
cmd/podman-mac-helper/install.go
Normal file
@ -0,0 +1,244 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"text/template"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
rwx_rx_rx = 0755
|
||||
rw_r_r = 0644
|
||||
)
|
||||
|
||||
const launchConfig = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.github.containers.podman.helper-{{.User}}</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>{{.Program}}</string>
|
||||
<string>service</string>
|
||||
<string>{{.Target}}</string>
|
||||
</array>
|
||||
<key>inetdCompatibility</key>
|
||||
<dict>
|
||||
<key>Wait</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>UserName</key>
|
||||
<string>root</string>
|
||||
<key>Sockets</key>
|
||||
<dict>
|
||||
<key>Listeners</key>
|
||||
<dict>
|
||||
<key>SockFamily</key>
|
||||
<string>Unix</string>
|
||||
<key>SockPathName</key>
|
||||
<string>/private/var/run/podman-helper-{{.User}}.socket</string>
|
||||
<key>SockPathOwner</key>
|
||||
<integer>{{.UID}}</integer>
|
||||
<key>SockPathMode</key>
|
||||
<!-- SockPathMode takes base 10 (384 = 0600) -->
|
||||
<integer>384</integer>
|
||||
<key>SockType</key>
|
||||
<string>stream</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
`
|
||||
|
||||
type launchParams struct {
|
||||
Program string
|
||||
User string
|
||||
UID string
|
||||
Target string
|
||||
}
|
||||
|
||||
var installCmd = &cobra.Command{
|
||||
Use: "install",
|
||||
Short: "installs the podman helper agent",
|
||||
Long: "installs the podman helper agent, which manages the /var/run/docker.sock link",
|
||||
PreRun: silentUsage,
|
||||
RunE: install,
|
||||
}
|
||||
|
||||
func init() {
|
||||
addPrefixFlag(installCmd)
|
||||
rootCmd.AddCommand(installCmd)
|
||||
}
|
||||
|
||||
func install(cmd *cobra.Command, args []string) error {
|
||||
userName, uid, homeDir, err := getUser()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
labelName := fmt.Sprintf("com.github.containers.podman.helper-%s.plist", userName)
|
||||
fileName := filepath.Join("/Library", "LaunchDaemons", labelName)
|
||||
|
||||
if _, err := os.Stat(fileName); err == nil || !os.IsNotExist(err) {
|
||||
return errors.New("helper is already installed, uninstall first")
|
||||
}
|
||||
|
||||
prog, err := installExecutable(userName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
target := filepath.Join(homeDir, ".local", "share", "containers", "podman", "machine", "podman.sock")
|
||||
var buf bytes.Buffer
|
||||
t := template.Must(template.New("launchdConfig").Parse(launchConfig))
|
||||
err = t.Execute(&buf, launchParams{prog, userName, uid, target})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_EXCL, rw_r_r)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error creating helper plist file")
|
||||
}
|
||||
defer file.Close()
|
||||
_, err = buf.WriteTo(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = runDetectErr("launchctl", "load", fileName); err != nil {
|
||||
return errors.Wrap(err, "launchctl failed loading service")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func restrictRecursive(targetDir string, until string) error {
|
||||
for targetDir != until && len(targetDir) > 1 {
|
||||
info, err := os.Lstat(targetDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Mode()&fs.ModeSymlink != 0 {
|
||||
return errors.Errorf("symlinks not allowed in helper paths (remove them and rerun): %s", targetDir)
|
||||
}
|
||||
if err = os.Chown(targetDir, 0, 0); err != nil {
|
||||
return errors.Wrap(err, "could not update ownership of helper path")
|
||||
}
|
||||
if err = os.Chmod(targetDir, rwx_rx_rx|fs.ModeSticky); err != nil {
|
||||
return errors.Wrap(err, "could not update permissions of helper path")
|
||||
}
|
||||
targetDir = filepath.Dir(targetDir)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifyRootDeep(path string) error {
|
||||
path = filepath.Clean(path)
|
||||
current := "/"
|
||||
segs := strings.Split(path, "/")
|
||||
depth := 0
|
||||
for i := 1; i < len(segs); i++ {
|
||||
seg := segs[i]
|
||||
current = filepath.Join(current, seg)
|
||||
info, err := os.Lstat(current)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stat := info.Sys().(*syscall.Stat_t)
|
||||
if stat.Uid != 0 {
|
||||
return errors.Errorf("installation target path must be solely owned by root: %s is not", current)
|
||||
}
|
||||
|
||||
if info.Mode()&fs.ModeSymlink != 0 {
|
||||
target, err := os.Readlink(current)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
targetParts := strings.Split(target, "/")
|
||||
segs = append(targetParts, segs[i+1:]...)
|
||||
|
||||
if depth++; depth > 1000 {
|
||||
return errors.New("reached max recursion depth, link structure is cyclical or too complex")
|
||||
}
|
||||
|
||||
if !filepath.IsAbs(target) {
|
||||
current = filepath.Dir(current)
|
||||
i = -1 // Start at 0
|
||||
} else {
|
||||
current = "/"
|
||||
i = 0 // Skip empty first segment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func installExecutable(user string) (string, error) {
|
||||
// Since the installed executable runs as root, as a precaution verify root ownership of
|
||||
// the entire installation path, and utilize sticky + read only perms for the helper path
|
||||
// suffix. The goal is to help users harden against privilege escalation from loose
|
||||
// filesystem permissions.
|
||||
//
|
||||
// Since userpsace package management tools, such as brew, delegate management of system
|
||||
// paths to standard unix users, the daemon executable is copied into a separate more
|
||||
// restricted area of the filesystem.
|
||||
if err := verifyRootDeep(installPrefix); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
targetDir := filepath.Join(installPrefix, "podman", "helper", user)
|
||||
if err := os.MkdirAll(targetDir, rwx_rx_rx); err != nil {
|
||||
return "", errors.Wrap(err, "could not create helper directory structure")
|
||||
}
|
||||
|
||||
// Correct any incorrect perms on previously existing directories and verify no symlinks
|
||||
if err := restrictRecursive(targetDir, installPrefix); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
exec, err := os.Executable()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
install := filepath.Join(targetDir, filepath.Base(exec))
|
||||
|
||||
return install, copyFile(install, exec, rwx_rx_rx)
|
||||
}
|
||||
|
||||
func copyFile(dest string, source string, perms fs.FileMode) error {
|
||||
in, err := os.Open(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer in.Close()
|
||||
out, err := os.OpenFile(dest, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, perms)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer out.Close()
|
||||
if _, err := io.Copy(out, in); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user