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