]>
Commit | Line | Data |
---|---|---|
b62671d8 | 1 | #!/bin/sh |
eb960fea | 2 | |
b62671d8 | 3 | # Client script for LXC container images. |
eb960fea | 4 | # |
b62671d8 CB |
5 | # Copyright @ Daniel Lezcano <daniel.lezcano@free.fr> |
6 | # Copyright © 2018 Christian Brauner <christian.brauner@ubuntu.com> | |
7 | # | |
8 | # This library is free software; you can redistribute it and/or | |
9 | # modify it under the terms of the GNU Lesser General Public | |
10 | # License as published by the Free Software Foundation; either | |
11 | # version 2.1 of the License, or (at your option) any later version. | |
eb960fea | 12 | |
b62671d8 CB |
13 | # This library is distributed in the hope that it will be useful, |
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
16 | # Lesser General Public License for more details. | |
eb960fea | 17 | |
b62671d8 CB |
18 | # You should have received a copy of the GNU Lesser General Public |
19 | # License along with this library; if not, write to the Free Software | |
20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 | |
21 | # USA | |
eb960fea | 22 | |
a542dd3c BP |
23 | LXC_MAPPED_UID= |
24 | LXC_MAPPED_GID= | |
8ec981fc | 25 | |
e7962394 | 26 | BUSYBOX_EXE=`which busybox` |
1c9bca6b | 27 | |
207bf0e4 SG |
28 | # Make sure the usual locations are in PATH |
29 | export PATH=$PATH:/usr/sbin:/usr/bin:/sbin:/bin | |
30 | ||
b62671d8 CB |
31 | in_userns() { |
32 | [ -e /proc/self/uid_map ] || { echo no; return; } | |
33 | while read -r line; do | |
34 | fields="$(echo "$line" | awk '{ print $1 " " $2 " " $3 }')" | |
35 | if [ "${fields}" = "0 0 4294967295" ]; then | |
36 | echo no; | |
37 | return; | |
88e38993 | 38 | fi |
b62671d8 CB |
39 | if echo "${fields}" | grep -q " 0 1$"; then |
40 | echo userns-root; | |
41 | return; | |
88e38993 | 42 | fi |
b62671d8 | 43 | done < /proc/self/uid_map |
88e38993 | 44 | |
16a312e1 LP |
45 | if [ -e /proc/1/uid_map ]; then |
46 | if [ "$(cat /proc/self/uid_map)" = "$(cat /proc/1/uid_map)" ]; then | |
47 | echo userns-root | |
48 | return | |
49 | fi | |
50 | fi | |
b62671d8 | 51 | echo yes |
88e38993 BP |
52 | } |
53 | ||
b62671d8 CB |
54 | USERNS="$(in_userns)" |
55 | ||
eb960fea DL |
56 | install_busybox() |
57 | { | |
b62671d8 CB |
58 | rootfs="${1}" |
59 | name="${2}" | |
60 | res=0 | |
61 | fstree="\ | |
62 | ${rootfs}/selinux \ | |
63 | ${rootfs}/dev \ | |
64 | ${rootfs}/home \ | |
65 | ${rootfs}/root \ | |
66 | ${rootfs}/etc \ | |
67 | ${rootfs}/etc/init.d \ | |
68 | ${rootfs}/bin \ | |
69 | ${rootfs}/usr/bin \ | |
70 | ${rootfs}/sbin \ | |
71 | ${rootfs}/usr/sbin \ | |
72 | ${rootfs}/proc \ | |
73 | ${rootfs}/sys \ | |
74 | ${rootfs}/mnt \ | |
75 | ${rootfs}/tmp \ | |
76 | ${rootfs}/var/log \ | |
c65973ad | 77 | ${rootfs}/var/run \ |
b62671d8 | 78 | ${rootfs}/dev/pts \ |
b62671d8 CB |
79 | ${rootfs}/lib \ |
80 | ${rootfs}/usr/lib \ | |
81 | ${rootfs}/lib64 \ | |
82 | ${rootfs}/usr/lib64" | |
83 | ||
84 | # shellcheck disable=SC2086 | |
85 | mkdir -p ${fstree} || return 1 | |
86 | # shellcheck disable=SC2086 | |
87 | chmod 755 ${fstree} || return 1 | |
88 | ||
89 | # minimal devices needed for busybox | |
90 | if [ "${USERNS}" = "yes" ]; then | |
91 | for dev in tty console tty0 tty1 ram0 null urandom; do | |
92 | echo "lxc.mount.entry = /dev/${dev} dev/${dev} none bind,optional,create=file 0 0" >> "${path}/config" | |
93 | done | |
94 | else | |
28eb86bd RK |
95 | mknod -m 666 "${rootfs}/dev/tty" c 5 0 || res=1 |
96 | mknod -m 666 "${rootfs}/dev/console" c 5 1 || res=1 | |
97 | mknod -m 666 "${rootfs}/dev/tty0" c 4 0 || res=1 | |
98 | mknod -m 666 "${rootfs}/dev/tty1" c 4 0 || res=1 | |
99 | mknod -m 666 "${rootfs}/dev/tty5" c 4 0 || res=1 | |
100 | mknod -m 600 "${rootfs}/dev/ram0" b 1 0 || res=1 | |
101 | mknod -m 666 "${rootfs}/dev/null" c 1 3 || res=1 | |
102 | mknod -m 666 "${rootfs}/dev/zero" c 1 5 || res=1 | |
103 | mknod -m 666 "${rootfs}/dev/urandom" c 1 9 || res=1 | |
b62671d8 | 104 | fi |
a725bbc4 RK |
105 | |
106 | # make /tmp accessible to any user (with sticky bit) | |
107 | chmod 1777 "${rootfs}/tmp" || return 1 | |
b62671d8 CB |
108 | |
109 | # root user defined | |
110 | cat <<EOF >> "${rootfs}/etc/passwd" | |
eb960fea DL |
111 | root:x:0:0:root:/root:/bin/sh |
112 | EOF | |
113 | ||
b62671d8 | 114 | cat <<EOF >> "${rootfs}/etc/group" |
eb960fea DL |
115 | root:x:0:root |
116 | EOF | |
117 | ||
eb960fea | 118 | # mount everything |
b62671d8 | 119 | cat <<EOF >> "${rootfs}/etc/init.d/rcS" |
eb960fea | 120 | #!/bin/sh |
b09ecaf3 DL |
121 | /bin/syslogd |
122 | /bin/mount -a | |
123 | /bin/udhcpc | |
eb960fea DL |
124 | EOF |
125 | ||
b62671d8 CB |
126 | # executable |
127 | chmod 744 "${rootfs}/etc/init.d/rcS" || return 1 | |
eb960fea | 128 | |
b62671d8 CB |
129 | # launch rcS first then make a console available |
130 | # and propose a shell on the tty, the last one is | |
131 | # not needed | |
132 | cat <<EOF >> "${rootfs}/etc/inittab" | |
eb960fea | 133 | ::sysinit:/etc/init.d/rcS |
0016af97 DL |
134 | tty1::respawn:/bin/getty -L tty1 115200 vt100 |
135 | console::askfirst:/bin/sh | |
eb960fea | 136 | EOF |
b62671d8 CB |
137 | # writable and readable for other |
138 | chmod 644 "${rootfs}/etc/inittab" || return 1 | |
eb960fea | 139 | |
1c9bca6b | 140 | # Look for the pathname of "default.script" from the help of udhcpc |
e76e315c | 141 | DEF_SCRIPT=`${BUSYBOX_EXE} udhcpc --help 2>&1 | egrep -- '-s.*Run PROG' | cut -d'/' -f2- | cut -d')' -f1` |
1c9bca6b RK |
142 | DEF_SCRIPT_DIR=`dirname /${DEF_SCRIPT}` |
143 | mkdir -p ${rootfs}/${DEF_SCRIPT_DIR} | |
144 | chmod 644 ${rootfs}/${DEF_SCRIPT_DIR} || return 1 | |
145 | ||
146 | cat <<EOF >> ${rootfs}/${DEF_SCRIPT} | |
eb960fea | 147 | #!/bin/sh |
eb960fea | 148 | case "\$1" in |
b62671d8 CB |
149 | deconfig) |
150 | ip addr flush dev \$interface | |
151 | ;; | |
152 | ||
153 | renew|bound) | |
154 | # flush all the routes | |
155 | if [ -n "\$router" ]; then | |
156 | ip route del default 2> /dev/null | |
157 | fi | |
eb960fea | 158 | |
b62671d8 CB |
159 | # check broadcast |
160 | if [ -n "\$broadcast" ]; then | |
161 | broadcast="broadcast \$broadcast" | |
162 | fi | |
eb960fea | 163 | |
b62671d8 CB |
164 | # add a new ip address |
165 | ip addr add \$ip/\$mask \$broadcast dev \$interface | |
eb960fea | 166 | |
b62671d8 CB |
167 | if [ -n "\$router" ]; then |
168 | ip route add default via \$router dev \$interface | |
ed52814c BP |
169 | fi |
170 | ||
b62671d8 CB |
171 | [ -n "\$domain" ] && echo search \$domain > /etc/resolv.conf |
172 | for i in \$dns ; do | |
567f8915 RK |
173 | grep "nameserver \$i" /etc/resolv.conf > /dev/null 2>&1 |
174 | if [ \$? -ne 0 ]; then | |
175 | echo nameserver \$i >> /etc/resolv.conf | |
176 | fi | |
4432b512 | 177 | done |
b62671d8 CB |
178 | ;; |
179 | esac | |
180 | exit 0 | |
ed52814c BP |
181 | EOF |
182 | ||
1c9bca6b | 183 | chmod 744 ${rootfs}/${DEF_SCRIPT} |
ed52814c | 184 | |
b62671d8 | 185 | return "${res}" |
ed52814c BP |
186 | } |
187 | ||
eb960fea DL |
188 | configure_busybox() |
189 | { | |
b62671d8 CB |
190 | rootfs="${1}" |
191 | ||
b62671d8 | 192 | # copy busybox in the rootfs |
4765b926 | 193 | if ! cp "${BUSYBOX_EXE}" "${rootfs}/bin"; then |
634ad935 | 194 | echo "ERROR: Failed to copy busybox binary" 1>&2 |
b62671d8 CB |
195 | return 1 |
196 | fi | |
197 | ||
198 | # symlink busybox for the commands it supports | |
199 | # it would be nice to just use "chroot $rootfs busybox --install -s /bin" | |
200 | # but that only works right in a chroot with busybox >= 1.19.0 | |
201 | ( | |
202 | cd "${rootfs}/bin" || return 1 | |
8b7681f3 | 203 | ./busybox --list | grep -v busybox | xargs -n1 ln -s busybox |
b62671d8 | 204 | ) |
eb960fea | 205 | |
b62671d8 CB |
206 | # relink /sbin/init |
207 | ln "${rootfs}/bin/busybox" "${rootfs}/sbin/init" | |
eb960fea | 208 | |
b62671d8 CB |
209 | # /etc/fstab must exist for "mount -a" |
210 | touch "${rootfs}/etc/fstab" | |
6ab1ca03 | 211 | |
b62671d8 CB |
212 | # passwd exec must be setuid |
213 | chmod +s "${rootfs}/bin/passwd" | |
214 | touch "${rootfs}/etc/shadow" | |
19d618b1 | 215 | |
b62671d8 | 216 | return 0 |
eb960fea DL |
217 | } |
218 | ||
219 | copy_configuration() | |
220 | { | |
b62671d8 CB |
221 | path="${1}" |
222 | rootfs="${2}" | |
223 | name="${3}" | |
eb960fea | 224 | |
b62671d8 CB |
225 | grep -q "^lxc.rootfs.path" "${path}/config" 2>/dev/null || echo "lxc.rootfs.path = ${rootfs}" >> "${path}/config" |
226 | cat <<EOF >> "${path}/config" | |
55c84efc | 227 | lxc.signal.halt = SIGUSR1 |
fd998241 | 228 | lxc.signal.reboot = SIGTERM |
b62671d8 | 229 | lxc.uts.name = "${name}" |
fe1c5887 | 230 | lxc.tty.max = 1 |
232763d6 | 231 | lxc.pty.max = 1 |
eee3ba81 | 232 | lxc.cap.drop = sys_module mac_admin mac_override sys_time |
69d66f1e SG |
233 | |
234 | # When using LXC with apparmor, uncomment the next line to run unconfined: | |
a1d5fdfd | 235 | #lxc.apparmor.profile = unconfined |
f24a52d5 SG |
236 | |
237 | lxc.mount.auto = cgroup:mixed proc:mixed sys:mixed | |
83e280f6 | 238 | lxc.mount.entry = shm dev/shm tmpfs defaults,create=dir 0 0 |
bff93997 | 239 | lxc.mount.entry = mqueue dev/mqueue mqueue defaults,optional,create=dir 0 0 |
1881820a SH |
240 | EOF |
241 | ||
b62671d8 CB |
242 | libdirs="\ |
243 | lib \ | |
244 | usr/lib \ | |
245 | lib64 \ | |
246 | usr/lib64" | |
8b7681f3 | 247 | |
b62671d8 CB |
248 | for dir in ${libdirs}; do |
249 | if [ -d "/${dir}" ] && [ -d "${rootfs}/${dir}" ]; then | |
250 | echo "lxc.mount.entry = /${dir} ${dir} none ro,bind 0 0" >> "${path}/config" | |
251 | fi | |
252 | done | |
253 | echo "lxc.mount.entry = /sys/kernel/security sys/kernel/security none ro,bind,optional 0 0" >> "${path}/config" | |
eb960fea DL |
254 | } |
255 | ||
a542dd3c BP |
256 | remap_userns() |
257 | { | |
b62671d8 | 258 | path="${1}" |
a542dd3c | 259 | |
b62671d8 CB |
260 | if [ -n "$LXC_MAPPED_UID" ] && [ "$LXC_MAPPED_UID" != "-1" ]; then |
261 | chown "${LXC_MAPPED_UID}" "${path}/config" > /dev/null 2>&1 | |
262 | chown -R root "${path}/rootfs" > /dev/null 2>&1 | |
263 | fi | |
a542dd3c | 264 | |
b62671d8 CB |
265 | if [ -n "$LXC_MAPPED_GID" ] && [ "$LXC_MAPPED_GID" != "-1" ]; then |
266 | chgrp "${LXC_MAPPED_GID}" "${path}/config" > /dev/null 2>&1 | |
267 | chgrp -R root "${path}/rootfs" > /dev/null 2>&1 | |
268 | fi | |
a542dd3c BP |
269 | } |
270 | ||
b62671d8 CB |
271 | usage() { |
272 | cat <<EOF | |
273 | LXC busybox image builder | |
274 | ||
275 | Special arguments: | |
e7962394 RK |
276 | |
277 | [ -h | --help ]: Print this help message and exit. | |
278 | ||
279 | LXC internal arguments: | |
280 | ||
281 | [ --name <name> ]: The container name | |
282 | [ --path <path> ]: The path to the container | |
283 | [ --rootfs <rootfs> ]: The path to the container's rootfs (default: config or <path>/rootfs) | |
284 | [ --mapped-uid <map> ]: A uid map (user namespaces) | |
285 | [ --mapped-gid <map> ]: A gid map (user namespaces) | |
286 | ||
287 | BUSYBOX template specific arguments: | |
288 | ||
5f0fb855 | 289 | [ --busybox-path <path> ]: busybox pathname (default: ${BUSYBOX_EXE}) |
e7962394 | 290 | |
eb960fea | 291 | EOF |
b62671d8 | 292 | return 0 |
eb960fea DL |
293 | } |
294 | ||
5f0fb855 | 295 | if ! options=$(getopt -o hp:n: -l help,rootfs:,path:,name:,mapped-uid:,mapped-gid:,busybox-path: -- "$@"); then |
b62671d8 CB |
296 | usage |
297 | exit 1 | |
eb960fea DL |
298 | fi |
299 | eval set -- "$options" | |
300 | ||
301 | while true | |
302 | do | |
b62671d8 | 303 | case "$1" in |
634ad935 | 304 | -h|--help) usage && exit 0;; |
b62671d8 CB |
305 | -n|--name) name=$2; shift 2;; |
306 | -p|--path) path=$2; shift 2;; | |
307 | --rootfs) rootfs=$2; shift 2;; | |
308 | --mapped-uid) LXC_MAPPED_UID=$2; shift 2;; | |
309 | --mapped-gid) LXC_MAPPED_GID=$2; shift 2;; | |
5f0fb855 | 310 | --busybox-path) BUSYBOX_EXE=$2; shift 2;; |
b62671d8 CB |
311 | --) shift 1; break ;; |
312 | *) break ;; | |
313 | esac | |
eb960fea DL |
314 | done |
315 | ||
b62671d8 | 316 | # Check that we have all variables we need |
e7962394 RK |
317 | if [ -z "${name}" ] || [ -z "${path}" ]; then |
318 | echo "ERROR: Please pass the name and path for the container" 1>&2 | |
eb960fea DL |
319 | exit 1 |
320 | fi | |
321 | ||
1c9bca6b | 322 | # Make sure busybox is present |
e7962394 RK |
323 | if [ -z "${BUSYBOX_EXE}" ]; then |
324 | echo "ERROR: Please pass a pathname for busybox binary" 1>&2 | |
325 | exit 1 | |
326 | fi | |
327 | if [ ! -x "${BUSYBOX_EXE}" ]; then | |
328 | echo "ERROR: Failed to find busybox binary (${BUSYBOX_EXE})" 1>&2 | |
1c9bca6b RK |
329 | exit 1 |
330 | fi | |
331 | ||
1881820a SH |
332 | # detect rootfs |
333 | config="$path/config" | |
1897e3bc | 334 | if [ -z "$rootfs" ]; then |
b62671d8 CB |
335 | if grep -q '^lxc.rootfs.path' "${config}" 2> /dev/null ; then |
336 | rootfs=$(awk -F= '/^lxc.rootfs.path =/{ print $2 }' "${config}") | |
337 | else | |
338 | rootfs="${path}/rootfs" | |
339 | fi | |
1881820a | 340 | fi |
eb960fea | 341 | |
b62671d8 | 342 | if ! install_busybox "${rootfs}" "${name}"; then |
634ad935 | 343 | echo "ERROR: Failed to install rootfs" 1>&2 |
b62671d8 | 344 | exit 1 |
eb960fea DL |
345 | fi |
346 | ||
b62671d8 | 347 | if ! configure_busybox "${rootfs}"; then |
634ad935 | 348 | echo "ERROR: Failed to configure busybox" 1>&2 |
b62671d8 | 349 | exit 1 |
eb960fea DL |
350 | fi |
351 | ||
b62671d8 | 352 | if ! copy_configuration "${path}" "${rootfs}" "${name}"; then |
634ad935 | 353 | echo "ERROR: Failed to write config file" 1>&2 |
b62671d8 | 354 | exit 1 |
eb960fea | 355 | fi |
a542dd3c | 356 | |
b62671d8 | 357 | if ! remap_userns "${path}"; then |
634ad935 | 358 | echo "ERROR: Failed to change idmappings" 1>&2 |
b62671d8 | 359 | exit 1 |
ed52814c | 360 | fi |