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