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