Browse code

Merge pull request #40950 from AkihiroSuda/dockerd-rootless-setuptool.sh

add dockerd-rootless-setuptool.sh

Brian Goff authored on 2020/05/22 03:52:07
Showing 5 changed files
... ...
@@ -234,6 +234,7 @@ RUN --mount=type=cache,target=/root/.cache/go-build \
234 234
     --mount=type=bind,src=hack/dockerfile/install,target=/tmp/install \
235 235
         PREFIX=/build /tmp/install/install.sh rootlesskit
236 236
 COPY ./contrib/dockerd-rootless.sh /build
237
+COPY ./contrib/dockerd-rootless-setuptool.sh /build
237 238
 
238 239
 FROM djs55/vpnkit:${VPNKIT_VERSION} AS vpnkit
239 240
 
240 241
new file mode 100755
... ...
@@ -0,0 +1,439 @@
0
+#!/bin/sh
1
+# dockerd-rootless-setuptool.sh: setup tool for dockerd-rootless.sh
2
+# Needs to be executed as a non-root user.
3
+#
4
+# Typical usage: dockerd-rootless-setuptool.sh install --force
5
+#
6
+# Documentation: https://docs.docker.com/engine/security/rootless/
7
+set -eu
8
+
9
+# utility functions
10
+INFO() {
11
+	/bin/echo -e "\e[104m\e[97m[INFO]\e[49m\e[39m $@"
12
+}
13
+
14
+WARNING() {
15
+	/bin/echo >&2 -e "\e[101m\e[97m[WARNING]\e[49m\e[39m $@"
16
+}
17
+
18
+ERROR() {
19
+	/bin/echo >&2 -e "\e[101m\e[97m[ERROR]\e[49m\e[39m $@"
20
+}
21
+
22
+# constants
23
+DOCKERD_ROOTLESS_SH="dockerd-rootless.sh"
24
+SYSTEMD_UNIT="docker.service"
25
+
26
+# CLI opt: --force
27
+OPT_FORCE=""
28
+# CLI opt: --skip-iptables
29
+OPT_SKIP_IPTABLES=""
30
+
31
+# global vars
32
+ARG0="$0"
33
+DOCKERD_ROOTLESS_SH_FLAGS=""
34
+BIN=""
35
+SYSTEMD=""
36
+CFG_DIR=""
37
+XDG_RUNTIME_DIR_CREATED=""
38
+
39
+# run checks and also initialize global vars
40
+init() {
41
+	# OS verification: Linux only
42
+	case "$(uname)" in
43
+		Linux) ;;
44
+
45
+		*)
46
+			ERROR "Rootless Docker cannot be installed on $(uname)"
47
+			exit 1
48
+			;;
49
+	esac
50
+
51
+	# User verification: deny running as root
52
+	if [ "$(id -u)" = "0" ]; then
53
+		ERROR "Refusing to install rootless Docker as the root user"
54
+		exit 1
55
+	fi
56
+
57
+	# set BIN
58
+	if ! BIN="$(command -v "$DOCKERD_ROOTLESS_SH" 2> /dev/null)"; then
59
+		ERROR "$DOCKERD_ROOTLESS_SH needs to be present under \$PATH"
60
+		exit 1
61
+	fi
62
+	BIN=$(dirname "$BIN")
63
+
64
+	# set SYSTEMD
65
+	if systemctl --user show-environment > /dev/null 2>&1; then
66
+		SYSTEMD=1
67
+	fi
68
+
69
+	# HOME verification
70
+	if [ -z "${HOME:-}" ] || [ ! -d "$HOME" ]; then
71
+		ERROR "HOME needs to be set"
72
+		exit 1
73
+	fi
74
+	if [ ! -w "$HOME" ]; then
75
+		ERROR "HOME needs to be writable"
76
+		exit 1
77
+	fi
78
+
79
+	# set CFG_DIR
80
+	CFG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}"
81
+
82
+	# Existing rootful docker verification
83
+	if [ -w /var/run/docker.sock ] && [ -z "$OPT_FORCE" ]; then
84
+		ERROR "Aborting because rootful Docker (/var/run/docker.sock) is running and accessible. Set --force to ignore."
85
+		exit 1
86
+	fi
87
+
88
+	# Validate XDG_RUNTIME_DIR and set XDG_RUNTIME_DIR_CREATED
89
+	if [ -z "${XDG_RUNTIME_DIR:-}" ] || [ ! -w "$XDG_RUNTIME_DIR" ]; then
90
+		if [ -n "$SYSTEMD" ]; then
91
+			ERROR "Aborting because systemd was detected but XDG_RUNTIME_DIR (\"$XDG_RUNTIME_DIR\") is not set, does not exist, or is not writable"
92
+			ERROR "Hint: this could happen if you changed users with 'su' or 'sudo'. To work around this:"
93
+			ERROR "- try again by first running with root privileges 'loginctl enable-linger <user>' where <user> is the unprivileged user and export XDG_RUNTIME_DIR to the value of RuntimePath as shown by 'loginctl show-user <user>'"
94
+			ERROR "- or simply log back in as the desired unprivileged user (ssh works for remote machines, machinectl shell works for local machines)"
95
+			exit 1
96
+		fi
97
+		export XDG_RUNTIME_DIR="/tmp/docker-$(id -u)"
98
+		mkdir -p "$XDG_RUNTIME_DIR"
99
+		XDG_RUNTIME_DIR_CREATED=1
100
+	fi
101
+
102
+	instructions=""
103
+	# instruction: uidmap dependency check
104
+	if ! command -v newuidmap > /dev/null 2>&1; then
105
+		if command -v apt-get > /dev/null 2>&1; then
106
+			instructions=$(
107
+				cat <<- EOI
108
+					${instructions}
109
+					# Install newuidmap & newgidmap binaries
110
+					apt-get install -y uidmap
111
+				EOI
112
+			)
113
+		elif command -v dnf > /dev/null 2>&1; then
114
+			instructions=$(
115
+				cat <<- EOI
116
+					${instructions}
117
+					# Install newuidmap & newgidmap binaries
118
+					dnf install -y shadow-utils
119
+				EOI
120
+			)
121
+		elif command -v yum > /dev/null 2>&1; then
122
+			instructions=$(
123
+				cat <<- EOI
124
+					${instructions}
125
+					# Install newuidmap & newgidmap binaries
126
+					yum install -y shadow-utils
127
+				EOI
128
+			)
129
+		else
130
+			ERROR "newuidmap binary not found. Please install with a package manager."
131
+			exit 1
132
+		fi
133
+	fi
134
+
135
+	# instruction: iptables dependency check
136
+	faced_iptables_error=""
137
+	if ! command -v iptables > /dev/null 2>&1 && [ ! -f /sbin/iptables ] && [ ! -f /usr/sbin/iptables ]; then
138
+		faced_iptables_error=1
139
+		if [ -z "$OPT_SKIP_IPTABLES" ]; then
140
+			if command -v apt-get > /dev/null 2>&1; then
141
+				instructions=$(
142
+					cat <<- EOI
143
+						${instructions}
144
+						# Install iptables
145
+						apt-get install -y iptables
146
+					EOI
147
+				)
148
+			elif command -v dnf > /dev/null 2>&1; then
149
+				instructions=$(
150
+					cat <<- EOI
151
+						${instructions}
152
+						# Install iptables
153
+						dnf install -y iptables
154
+					EOI
155
+				)
156
+			elif command -v yum > /dev/null 2>&1; then
157
+				instructions=$(
158
+					cat <<- EOI
159
+						${instructions}
160
+						# Install iptables
161
+						yum install -y iptables
162
+					EOI
163
+				)
164
+			else
165
+				ERROR "iptables binary not found. Please install with a package manager."
166
+				exit 1
167
+			fi
168
+		fi
169
+	fi
170
+
171
+	# instruction: ip_tables module dependency check
172
+	if ! grep -q ip_tables /proc/modules 2> /dev/null && ! grep -q ip_tables /lib/modules/$(uname -r)/modules.builtin 2> /dev/null; then
173
+		faced_iptables_error=1
174
+		if [ -z "$OPT_SKIP_IPTABLES" ]; then
175
+			instructions=$(
176
+				cat <<- EOI
177
+					${instructions}
178
+					# Load ip_tables module
179
+					modprobe ip_tables
180
+				EOI
181
+			)
182
+		fi
183
+	fi
184
+
185
+	# set DOCKERD_ROOTLESS_SH_FLAGS
186
+	if [ -n "$faced_iptables_error" ] && [ -n "$OPT_SKIP_IPTABLES" ]; then
187
+		DOCKERD_ROOTLESS_SH_FLAGS="${DOCKERD_ROOTLESS_SH_FLAGS} --iptables=false"
188
+	fi
189
+
190
+	# instruction: Debian and Arch require setting unprivileged_userns_clone
191
+	if [ -f /proc/sys/kernel/unprivileged_userns_clone ]; then
192
+		if [ "1" != "$(cat /proc/sys/kernel/unprivileged_userns_clone)" ]; then
193
+			instructions=$(
194
+				cat <<- EOI
195
+					${instructions}
196
+					# Set kernel.unprivileged_userns_clone
197
+					cat <<EOT > /etc/sysctl.d/50-rootless.conf
198
+					kernel.unprivileged_userns_clone = 1
199
+					EOT
200
+					sysctl --system
201
+				EOI
202
+			)
203
+		fi
204
+	fi
205
+
206
+	# instruction: RHEL/CentOS 7 requires setting max_user_namespaces
207
+	if [ -f /proc/sys/user/max_user_namespaces ]; then
208
+		if [ "0" = "$(cat /proc/sys/user/max_user_namespaces)" ]; then
209
+			instructions=$(
210
+				cat <<- EOI
211
+					${instructions}
212
+					# Set user.max_user_namespaces
213
+					cat <<EOT > /etc/sysctl.d/51-rootless.conf
214
+					user.max_user_namespaces = 28633
215
+					EOT
216
+					sysctl --system
217
+				EOI
218
+			)
219
+		fi
220
+	fi
221
+
222
+	# instructions: validate subuid/subgid files for current user
223
+	if ! grep -q "^$(id -un):\|^$(id -u):" /etc/subuid 2> /dev/null; then
224
+		instructions=$(
225
+			cat <<- EOI
226
+				${instructions}
227
+				# Add subuid entry for $(id -un)
228
+				echo "$(id -un):100000:65536" >> /etc/subuid
229
+			EOI
230
+		)
231
+	fi
232
+	if ! grep -q "^$(id -un):\|^$(id -u):" /etc/subgid 2> /dev/null; then
233
+		instructions=$(
234
+			cat <<- EOI
235
+				${instructions}
236
+				# Add subgid entry for $(id -un)
237
+				echo "$(id -un):100000:65536" >> /etc/subgid
238
+			EOI
239
+		)
240
+	fi
241
+
242
+	# fail with instructions if requirements are not satisfied.
243
+	if [ -n "$instructions" ]; then
244
+		ERROR "Missing system requirements. Run the following commands to"
245
+		ERROR "install the requirements and run this tool again."
246
+		if [ -n "$faced_iptables_error" ] && [ -z "$OPT_SKIP_IPTABLES" ]; then
247
+			ERROR "Alternatively iptables checks can be disabled with --skip-iptables ."
248
+		fi
249
+		echo
250
+		echo "########## BEGIN ##########"
251
+		echo "sudo sh -eux <<EOF"
252
+		echo "$instructions" | sed -e '/^$/d'
253
+		echo "EOF"
254
+		echo "########## END ##########"
255
+		echo
256
+		exit 1
257
+	fi
258
+	# TODO: support printing non-essential but recommended instructions:
259
+	# - sysctl: "net.ipv4.ping_group_range"
260
+	# - sysctl: "net.ipv4.ip_unprivileged_port_start"
261
+	# - external binary: slirp4netns
262
+	# - external binary: fuse-overlayfs
263
+}
264
+
265
+# CLI subcommand: "check"
266
+cmd_entrypoint_check() {
267
+	# requirements are already checked in init()
268
+	INFO "Requirements are satisfied"
269
+}
270
+
271
+# install (systemd)
272
+install_systemd() {
273
+	mkdir -p "${CFG_DIR}/systemd/user"
274
+	unit_file="${CFG_DIR}/systemd/user/${SYSTEMD_UNIT}"
275
+	if [ -f "${unit_file}" ]; then
276
+		WARNING "File already exists, skipping: ${unit_file}"
277
+	else
278
+		INFO "Creating ${unit_file}"
279
+		cat <<- EOT > "${unit_file}"
280
+			[Unit]
281
+			Description=Docker Application Container Engine (Rootless)
282
+			Documentation=https://docs.docker.com/engine/security/rootless/
283
+
284
+			[Service]
285
+			Environment=PATH=$BIN:/sbin:/usr/sbin:$PATH
286
+			ExecStart=$BIN/dockerd-rootless.sh $DOCKERD_ROOTLESS_SH_FLAGS
287
+			ExecReload=/bin/kill -s HUP \$MAINPID
288
+			TimeoutSec=0
289
+			RestartSec=2
290
+			Restart=always
291
+			StartLimitBurst=3
292
+			StartLimitInterval=60s
293
+			LimitNOFILE=infinity
294
+			LimitNPROC=infinity
295
+			LimitCORE=infinity
296
+			TasksMax=infinity
297
+			Delegate=yes
298
+			Type=simple
299
+
300
+			[Install]
301
+			WantedBy=default.target
302
+		EOT
303
+		systemctl --user daemon-reload
304
+	fi
305
+	if ! systemctl --user --no-pager status "${SYSTEMD_UNIT}" > /dev/null 2>&1; then
306
+		INFO "starting systemd service ${SYSTEMD_UNIT}"
307
+		(
308
+			set -x
309
+			systemctl --user start "${SYSTEMD_UNIT}"
310
+			sleep 3
311
+		)
312
+	fi
313
+	(
314
+		set -x
315
+		systemctl --user --no-pager --full status "${SYSTEMD_UNIT}"
316
+		DOCKER_HOST="unix://$XDG_RUNTIME_DIR/docker.sock" $BIN/docker version
317
+		systemctl --user enable "${SYSTEMD_UNIT}"
318
+	)
319
+	INFO "Installed ${SYSTEMD_UNIT} successfully."
320
+	INFO "To control ${SYSTEMD_UNIT}, run: \`systemctl --user (start|stop|restart) ${SYSTEMD_UNIT}\`"
321
+	INFO "To run ${SYSTEMD_UNIT} on system startup, run: \`sudo loginctl enable-linger $(id -un)\`"
322
+	echo
323
+}
324
+
325
+# install (non-systemd)
326
+install_nonsystemd() {
327
+	INFO "systemd not detected, ${DOCKERD_ROOTLESS_SH} needs to be started manually:"
328
+	echo
329
+	echo "PATH=$BIN:/sbin:/usr/sbin:\$PATH ${DOCKERD_ROOTLESS_SH} ${DOCKERD_ROOTLESS_SH_FLAGS}"
330
+	echo
331
+}
332
+
333
+# CLI subcommand: "install"
334
+cmd_entrypoint_install() {
335
+	# requirements are already checked in init()
336
+	if [ -z "$SYSTEMD" ]; then
337
+		install_nonsystemd
338
+	else
339
+		install_systemd
340
+	fi
341
+
342
+	INFO "Make sure the following environment variables are set (or add them to ~/.bashrc):"
343
+	echo
344
+	if [ -n "$XDG_RUNTIME_DIR_CREATED" ]; then
345
+		echo "export XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR}"
346
+	fi
347
+	echo "export PATH=${BIN}:\$PATH"
348
+	echo "export DOCKER_HOST=unix://${XDG_RUNTIME_DIR}/docker.sock"
349
+	echo
350
+
351
+}
352
+
353
+# CLI subcommand: "uninstall"
354
+cmd_entrypoint_uninstall() {
355
+	# requirements are already checked in init()
356
+	if [ -z "$SYSTEMD" ]; then
357
+		INFO "systemd not detected, ${DOCKERD_ROOTLESS_SH} needs to be stopped manually:"
358
+	else
359
+		unit_file="${CFG_DIR}/systemd/user/${SYSTEMD_UNIT}"
360
+		(
361
+			set -x
362
+			systemctl --user stop "${SYSTEMD_UNIT}"
363
+		) || :
364
+		(
365
+			set -x
366
+			systemctl --user disable "${SYSTEMD_UNIT}"
367
+		) || :
368
+		rm -f "${unit_file}"
369
+		INFO "Uninstalled ${SYSTEMD_UNIT}"
370
+	fi
371
+
372
+	INFO "This uninstallation tool does NOT remove Docker binaries and data."
373
+	INFO "To remove data, run: \`$BIN/rootlesskit rm -rf $HOME/.local/share/docker\`"
374
+}
375
+
376
+# text for --help
377
+usage() {
378
+	echo "Usage: ${ARG0} [OPTIONS] COMMAND"
379
+	echo
380
+	echo "A setup tool for Rootless Docker (${DOCKERD_ROOTLESS_SH})."
381
+	echo
382
+	echo "Documentation: https://docs.docker.com/engine/security/rootless/"
383
+	echo
384
+	echo "Options:"
385
+	echo "  -f, --force                Ignore rootful Docker (/var/run/docker.sock)"
386
+	echo "      --skip-iptables        Ignore missing iptables"
387
+	echo
388
+	echo "Commands:"
389
+	echo "  check        Check prerequisites"
390
+	echo "  install      Install systemd unit (if systemd is available) and show how to manage the service"
391
+	echo "  uninstall    Uninstall systemd unit"
392
+}
393
+
394
+# parse CLI args
395
+if ! args="$(getopt -o hf --long help,force,skip-iptables -n "$ARG0" -- "$@")"; then
396
+	usage
397
+	exit 1
398
+fi
399
+eval set -- "$args"
400
+while [ "$#" -gt 0 ]; do
401
+	arg="$1"
402
+	shift
403
+	case "$arg" in
404
+		-h | --help)
405
+			usage
406
+			exit 0
407
+			;;
408
+		-f | --force)
409
+			OPT_FORCE=1
410
+			;;
411
+		--skip-iptables)
412
+			OPT_SKIP_IPTABLES=1
413
+			;;
414
+		--)
415
+			break
416
+			;;
417
+		*)
418
+			# XXX this means we missed something in our "getopt" arguments above!
419
+			ERROR "Scripting error, unknown argument '$arg' when parsing script arguments."
420
+			exit 1
421
+			;;
422
+	esac
423
+done
424
+
425
+command="${1:-}"
426
+if [ -z "$command" ]; then
427
+	ERROR "No command was specified. Run with --help to see the usage. Maybe you want to run \`$ARG0 install\`?"
428
+	exit 1
429
+fi
430
+
431
+if ! command -v "cmd_entrypoint_${command}" > /dev/null 2>&1; then
432
+	ERROR "Unknown command: ${command}. Run with --help to see the usage."
433
+	exit 1
434
+fi
435
+
436
+# main
437
+init
438
+"cmd_entrypoint_${command}"
... ...
@@ -11,3 +11,4 @@ DOCKER_ROOTLESSKIT_BINARY_NAME='rootlesskit'
11 11
 DOCKER_ROOTLESSKIT_DOCKER_PROXY_BINARY_NAME='rootlesskit-docker-proxy'
12 12
 DOCKER_VPNKIT_BINARY_NAME='vpnkit'
13 13
 DOCKER_DAEMON_ROOTLESS_SH_BINARY_NAME='dockerd-rootless.sh'
14
+DOCKER_DAEMON_ROOTLESS_SETUPTOOL_SH_BINARY_NAME='dockerd-rootless-setuptool.sh'
... ...
@@ -14,7 +14,7 @@ copy_binaries() {
14 14
 		return
15 15
 	fi
16 16
 	echo "Copying nested executables into $dir"
17
-	for file in containerd containerd-shim containerd-shim-runc-v2 ctr runc docker-init docker-proxy rootlesskit rootlesskit-docker-proxy dockerd-rootless.sh; do
17
+	for file in containerd containerd-shim containerd-shim-runc-v2 ctr runc docker-init docker-proxy rootlesskit rootlesskit-docker-proxy dockerd-rootless.sh dockerd-rootless-setuptool.sh; do
18 18
 		cp -f "$(command -v "$file")" "$dir/"
19 19
 		if [ "$hash" = "hash" ]; then
20 20
 			hash_files "$dir/$file"
... ...
@@ -29,6 +29,7 @@ install_binary() {
29 29
 	install_binary "${DEST}/${DOCKER_ROOTLESSKIT_BINARY_NAME}"
30 30
 	install_binary "${DEST}/${DOCKER_ROOTLESSKIT_DOCKER_PROXY_BINARY_NAME}"
31 31
 	install_binary "${DEST}/${DOCKER_DAEMON_ROOTLESS_SH_BINARY_NAME}"
32
+	install_binary "${DEST}/${DOCKER_DAEMON_ROOTLESS_SETUPTOOL_SH_BINARY_NAME}"
32 33
 	if [ -f "${DEST}/${DOCKER_VPNKIT_BINARY_NAME}" ]; then
33 34
 		install_binary "${DEST}/${DOCKER_VPNKIT_BINARY_NAME}"
34 35
 	fi