podman

Форк
0
245 строк · 6.0 Кб
1
//go:build darwin
2

3
package main
4

5
import (
6
	"bytes"
7
	"errors"
8
	"fmt"
9
	"io"
10
	"io/fs"
11
	"os"
12
	"path/filepath"
13
	"strings"
14
	"syscall"
15
	"text/template"
16

17
	"github.com/containers/storage/pkg/fileutils"
18
	"github.com/spf13/cobra"
19
)
20

21
const (
22
	mode755 = 0755
23
	mode644 = 0644
24
)
25

26
const launchConfig = `<?xml version="1.0" encoding="UTF-8"?>
27
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
28
<plist version="1.0">
29
<dict>
30
	<key>Label</key>
31
	<string>com.github.containers.podman.helper-{{.User}}</string>
32
	<key>ProgramArguments</key>
33
	<array>
34
		<string>{{.Program}}</string>
35
		<string>service</string>
36
		<string>{{.Target}}</string>
37
	</array>
38
	<key>inetdCompatibility</key>
39
	<dict>
40
		<key>Wait</key>
41
		<false/>
42
	</dict>
43
	<key>UserName</key>
44
	<string>root</string>
45
	<key>Sockets</key>
46
	<dict>
47
		<key>Listeners</key>
48
		<dict>
49
			<key>SockFamily</key>
50
			<string>Unix</string>
51
			<key>SockPathName</key>
52
			<string>/private/var/run/podman-helper-{{.User}}.socket</string>
53
			<key>SockPathOwner</key>
54
			<integer>{{.UID}}</integer>
55
			<key>SockPathMode</key>
56
			<!-- SockPathMode takes base 10 (384 = 0600) -->
57
			<integer>384</integer>
58
			<key>SockType</key>
59
			<string>stream</string>
60
		</dict>
61
	</dict>
62
</dict>
63
</plist>
64
`
65

66
type launchParams struct {
67
	Program string
68
	User    string
69
	UID     string
70
	Target  string
71
}
72

73
var installCmd = &cobra.Command{
74
	Use:    "install",
75
	Short:  "Install the podman helper agent",
76
	Long:   "Install the podman helper agent, which manages the /var/run/docker.sock link",
77
	PreRun: silentUsage,
78
	RunE:   install,
79
}
80

81
func init() {
82
	addPrefixFlag(installCmd)
83
	rootCmd.AddCommand(installCmd)
84
}
85

86
func install(cmd *cobra.Command, args []string) error {
87
	userName, uid, homeDir, err := getUser()
88
	if err != nil {
89
		return err
90
	}
91

92
	labelName := fmt.Sprintf("com.github.containers.podman.helper-%s.plist", userName)
93
	fileName := filepath.Join("/Library", "LaunchDaemons", labelName)
94

95
	if err := fileutils.Exists(fileName); err == nil || !errors.Is(err, fs.ErrNotExist) {
96
		fmt.Fprintln(os.Stderr, "helper is already installed, skipping the install, uninstall first if you want to reinstall")
97
		return nil
98
	}
99

100
	prog, err := installExecutable(userName)
101
	if err != nil {
102
		return err
103
	}
104

105
	target := filepath.Join(homeDir, ".local", "share", "containers", "podman", "machine", "podman.sock")
106
	var buf bytes.Buffer
107
	t := template.Must(template.New("launchdConfig").Parse(launchConfig))
108
	err = t.Execute(&buf, launchParams{prog, userName, uid, target})
109
	if err != nil {
110
		return err
111
	}
112

113
	file, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_EXCL, mode644)
114
	if err != nil {
115
		return fmt.Errorf("creating helper plist file: %w", err)
116
	}
117
	defer file.Close()
118
	_, err = buf.WriteTo(file)
119
	if err != nil {
120
		return err
121
	}
122

123
	if err = runDetectErr("launchctl", "load", fileName); err != nil {
124
		return fmt.Errorf("launchctl failed loading service: %w", err)
125
	}
126

127
	return nil
128
}
129

130
func restrictRecursive(targetDir string, until string) error {
131
	for targetDir != until && len(targetDir) > 1 {
132
		info, err := os.Lstat(targetDir)
133
		if err != nil {
134
			return err
135
		}
136
		if info.Mode()&fs.ModeSymlink != 0 {
137
			return fmt.Errorf("symlinks not allowed in helper paths (remove them and rerun): %s", targetDir)
138
		}
139
		if err = os.Chown(targetDir, 0, 0); err != nil {
140
			return fmt.Errorf("could not update ownership of helper path: %w", err)
141
		}
142
		if err = os.Chmod(targetDir, mode755|fs.ModeSticky); err != nil {
143
			return fmt.Errorf("could not update permissions of helper path: %w", err)
144
		}
145
		targetDir = filepath.Dir(targetDir)
146
	}
147

148
	return nil
149
}
150

151
func verifyRootDeep(path string) error {
152
	path = filepath.Clean(path)
153
	current := "/"
154
	segs := strings.Split(path, "/")
155
	depth := 0
156
	for i := 1; i < len(segs); i++ {
157
		seg := segs[i]
158
		current = filepath.Join(current, seg)
159
		info, err := os.Lstat(current)
160
		if err != nil {
161
			return err
162
		}
163

164
		stat := info.Sys().(*syscall.Stat_t)
165
		if stat.Uid != 0 {
166
			return fmt.Errorf("installation target path must be solely owned by root: %s is not", current)
167
		}
168

169
		if info.Mode()&fs.ModeSymlink != 0 {
170
			target, err := os.Readlink(current)
171
			if err != nil {
172
				return err
173
			}
174

175
			targetParts := strings.Split(target, "/")
176
			segs = append(targetParts, segs[i+1:]...)
177

178
			if depth++; depth > 1000 {
179
				return errors.New("reached max recursion depth, link structure is cyclical or too complex")
180
			}
181

182
			if !filepath.IsAbs(target) {
183
				current = filepath.Dir(current)
184
				i = -1 // Start at 0
185
			} else {
186
				current = "/"
187
				i = 0 // Skip empty first segment
188
			}
189
		}
190
	}
191

192
	return nil
193
}
194

195
func installExecutable(user string) (string, error) {
196
	// Since the installed executable runs as root, as a precaution verify root ownership of
197
	// the entire installation path, and utilize sticky + read-only perms for the helper path
198
	// suffix. The goal is to help users harden against privilege escalation from loose
199
	// filesystem permissions.
200
	//
201
	// Since userspace package management tools, such as brew, delegate management of system
202
	// paths to standard unix users, the daemon executable is copied into a separate more
203
	// restricted area of the filesystem.
204
	if err := verifyRootDeep(installPrefix); err != nil {
205
		return "", err
206
	}
207

208
	targetDir := filepath.Join(installPrefix, "podman", "helper", user)
209
	if err := os.MkdirAll(targetDir, mode755); err != nil {
210
		return "", fmt.Errorf("could not create helper directory structure: %w", err)
211
	}
212

213
	// Correct any incorrect perms on previously existing directories and verify no symlinks
214
	if err := restrictRecursive(targetDir, installPrefix); err != nil {
215
		return "", err
216
	}
217

218
	exec, err := os.Executable()
219
	if err != nil {
220
		return "", err
221
	}
222
	install := filepath.Join(targetDir, filepath.Base(exec))
223

224
	return install, copyFile(install, exec, mode755)
225
}
226

227
func copyFile(dest string, source string, perms fs.FileMode) error {
228
	in, err := os.Open(source)
229
	if err != nil {
230
		return err
231
	}
232

233
	defer in.Close()
234
	out, err := os.OpenFile(dest, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, perms)
235
	if err != nil {
236
		return err
237
	}
238

239
	defer out.Close()
240
	if _, err := io.Copy(out, in); err != nil {
241
		return err
242
	}
243

244
	return nil
245
}
246

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.