]> git.proxmox.com Git - mirror_lxc.git/blob - templates/lxc-alpine.in
templates: improve refusing to run unprivileged
[mirror_lxc.git] / templates / lxc-alpine.in
1 #!/bin/bash
2
3 # Detect use under userns (unsupported)
4 for arg in "$@"; do
5 [ "$arg" == "--" ] && break
6 if [ "$arg" == "--mapped-uid" -o "$arg" == "--mapped-gid" ]; then
7 echo "This template can't be used for unprivileged containers." 1>&2
8 echo "You may want to try the \"download\" template instead." 1>&2
9 exit 1
10 fi
11 done
12
13 key_sha256sums="9c102bcc376af1498d549b77bdbfa815ae86faa1d2d82f040e616b18ef2df2d4 alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub
14 2adcf7ce224f476330b5360ca5edb92fd0bf91c92d83292ed028d7c4e26333ab alpine-devel@lists.alpinelinux.org-4d07755e.rsa.pub"
15
16 get_static_apk () {
17 wget="wget -q -O -"
18 pkglist=alpine-keys:apk-tools-static
19 auto_repo_dir=
20
21 if [ -z "$repository" ]; then
22 url=http://wiki.alpinelinux.org/cgi-bin/dl.cgi
23 if [ -z "$release" ]; then
24 echo -n "Determining the latest release... "
25 release=$($wget $url/.latest.$apk_arch.txt | \
26 cut -d " " -f 3 | cut -d / -f 1 | uniq)
27 if [ -z "$release" ]; then
28 echo failed
29 return 1
30 fi
31 echo $release
32 fi
33 auto_repo_dir=$release/main
34 repository=$url/$auto_repo_dir
35 pkglist=$pkglist:alpine-mirrors
36 fi
37
38 rootfs="$1"
39 echo "Using static apk from $repository/$apk_arch"
40 wget="$wget $repository/$apk_arch"
41
42 # parse APKINDEX to find the current versions
43 static_pkgs=$($wget/APKINDEX.tar.gz | \
44 tar -Oxz APKINDEX | \
45 awk -F: -v pkglist=$pkglist '
46 BEGIN { split(pkglist,pkg) }
47 $0 != "" { f[$1] = $2 }
48 $0 == "" { for (i in pkg)
49 if (pkg[i] == f["P"])
50 print(f["P"] "-" f["V"] ".apk") }')
51 [ "$static_pkgs" ] || return 1
52
53 mkdir -p "$rootfs" || return 1
54 for pkg in $static_pkgs; do
55 echo "Downloading $pkg"
56 $wget/$pkg | tar -xz -C "$rootfs"
57 done
58
59 # clean up .apk meta files
60 rm -f "$rootfs"/.[A-Z]*
61
62 # verify checksum of the key
63 keyname=$(echo $rootfs/sbin/apk.static.*.pub | sed 's/.*\.SIGN\.RSA\.//')
64 checksum=$(echo "$key_sha256sums" | grep -w "$keyname")
65 if [ -z "$checksum" ]; then
66 echo "ERROR: checksum is missing for $keyname"
67 return 1
68 fi
69 (cd $rootfs/etc/apk/keys && echo "$checksum" | sha256sum -c -) || return 1
70
71 # verify the static apk binary signature
72 APK=$rootfs/sbin/apk.static
73 openssl dgst -verify $rootfs/etc/apk/keys/$keyname \
74 -signature "$APK.SIGN.RSA.$keyname" "$APK" || return 1
75
76 if [ "$auto_repo_dir" ]; then
77 mirror_list=$rootfs/usr/share/alpine-mirrors/MIRRORS.txt
78 mirror_count=$(wc -l $mirror_list | cut -d " " -f 1)
79 repository=$(sed $(expr $RANDOM % $mirror_count + 1)\!d \
80 $mirror_list)$auto_repo_dir
81 echo "Selecting mirror $repository"
82 fi
83 }
84
85 install_alpine() {
86 rootfs="$1"
87 shift
88 mkdir -p "$rootfs"/etc/apk || return 1
89 : ${keys_dir:=/etc/apk/keys}
90 if ! [ -d "$rootfs"/etc/apk/keys ] && [ -d "$keys_dir" ]; then
91 cp -r "$keys_dir" "$rootfs"/etc/apk/keys
92 fi
93 if [ -n "$repository" ]; then
94 echo "$repository" > "$rootfs"/etc/apk/repositories
95 else
96 cp /etc/apk/repositories "$rootfs"/etc/apk/repositories || return 1
97 if [ -n "$release" ]; then
98 sed -i -e "s:/[^/]\+/\([^/]\+\)$:/$release/\1:" \
99 "$rootfs"/etc/apk/repositories
100 fi
101 fi
102 opt_arch=
103 if [ -n "$apk_arch" ]; then
104 opt_arch="--arch $apk_arch"
105 fi
106 $APK add -U --initdb --root $rootfs $opt_arch "$@" alpine-base
107 }
108
109 configure_alpine() {
110 rootfs="$1"
111 echo "Setting up /etc/inittab"
112 cat >"$rootfs"/etc/inittab<<EOF
113 ::sysinit:/sbin/rc sysinit
114 ::wait:/sbin/rc default
115 tty1:12345:respawn:/sbin/getty 38400 tty1
116 tty2:12345:respawn:/sbin/getty 38400 tty2
117 tty3:12345:respawn:/sbin/getty 38400 tty3
118 tty4:12345:respawn:/sbin/getty 38400 tty4
119 ::ctrlaltdel:/sbin/reboot
120 ::shutdown:/sbin/rc shutdown
121 EOF
122 # set up timezone
123 if [ -f /etc/TZ ]; then
124 cp /etc/TZ "$rootfs/etc/TZ"
125 fi
126
127 # set up nameserver
128 grep nameserver /etc/resolv.conf > "$rootfs/etc/resolv.conf"
129
130 # configure the network using the dhcp
131 cat <<EOF > $rootfs/etc/network/interfaces
132 auto lo
133 iface lo inet loopback
134
135 auto eth0
136 iface eth0 inet dhcp
137 EOF
138
139 # set the hostname
140 echo $hostname > $rootfs/etc/hostname
141
142 # missing device nodes
143 echo "Setting up device nodes"
144 mkdir -p -m 755 "$rootfs/dev/pts"
145 mkdir -p -m 1777 "$rootfs/dev/shm"
146 mknod -m 666 "$rootfs/dev/zero" c 1 5
147 mknod -m 666 "$rootfs/dev/full" c 1 7
148 mknod -m 666 "$rootfs/dev/random" c 1 8
149 mknod -m 666 "$rootfs/dev/urandom" c 1 9
150 mknod -m 666 "$rootfs/dev/tty0" c 4 0
151 mknod -m 666 "$rootfs/dev/tty1" c 4 1
152 mknod -m 666 "$rootfs/dev/tty2" c 4 2
153 mknod -m 666 "$rootfs/dev/tty3" c 4 3
154 mknod -m 666 "$rootfs/dev/tty4" c 4 4
155 # mknod -m 600 "$rootfs/dev/initctl" p
156 mknod -m 666 "$rootfs/dev/tty" c 5 0
157 mknod -m 666 "$rootfs/dev/console" c 5 1
158 mknod -m 666 "$rootfs/dev/ptmx" c 5 2
159
160 # start services
161 ln -s /etc/init.d/bootmisc "$rootfs"/etc/runlevels/boot/bootmisc
162 ln -s /etc/init.d/syslog "$rootfs"/etc/runlevels/boot/syslog
163
164 return 0
165 }
166
167 copy_configuration() {
168 path=$1
169 rootfs=$2
170 hostname=$3
171
172 grep -q "^lxc.rootfs" $path/config 2>/dev/null \
173 || echo "lxc.rootfs = $rootfs" >> $path/config
174 if [ -n "$lxc_arch" ]; then
175 echo "lxc.arch = $lxc_arch" >> $path/config
176 fi
177
178 lxc_network_link_line="# lxc.network.link = br0"
179 for br in lxcbr0 virbr0 br0; do
180 if [ -d /sys/class/net/$br/bridge ]; then
181 lxc_network_link_line="lxc.network.link = $br"
182 break
183 fi
184 done
185
186 if ! grep -q "^lxc.network.type" $path/config 2>/dev/null; then
187 cat <<EOF >> $path/config
188 lxc.network.type = veth
189 $lxc_network_link_line
190 lxc.network.flags = up
191 EOF
192 fi
193
194 # if there is exactly one veth or macvlan network entry, make sure
195 # it has an associated mac address.
196 nics=$(awk -F '[ \t]*=[ \t]*' \
197 '$1=="lxc.network.type" && ($2=="veth" || $2=="macvlan") {print $2}' \
198 $path/config | wc -l)
199 if [ "$nics" -eq 1 ] && ! grep -q "^lxc.network.hwaddr" $path/config; then
200 # see http://sourceforge.net/tracker/?func=detail&aid=3411497&group_id=163076&atid=826303
201 hwaddr="fe:$(dd if=/dev/urandom bs=8 count=1 2>/dev/null |od -t x8 | \
202 head -n 1 |awk '{print $2}' | cut -c1-10 |\
203 sed 's/\(..\)/\1:/g; s/.$//')"
204 echo "lxc.network.hwaddr = $hwaddr" >> $path/config
205 fi
206
207 cat <<EOF >> $path/config
208
209 lxc.tty = 4
210 lxc.pts = 1024
211 lxc.utsname = $hostname
212 lxc.cap.drop = sys_module mac_admin mac_override sys_time sys_admin
213
214 # When using LXC with apparmor, uncomment the next line to run unconfined:
215 #lxc.aa_profile = unconfined
216
217 # devices
218 lxc.cgroup.devices.deny = a
219 # /dev/null, zero and full
220 lxc.cgroup.devices.allow = c 1:3 rwm
221 lxc.cgroup.devices.allow = c 1:5 rwm
222 lxc.cgroup.devices.allow = c 1:7 rwm
223 # consoles
224 lxc.cgroup.devices.allow = c 5:1 rwm
225 lxc.cgroup.devices.allow = c 5:0 rwm
226 lxc.cgroup.devices.allow = c 4:0 rwm
227 lxc.cgroup.devices.allow = c 4:1 rwm
228 # /dev/{,u}random
229 lxc.cgroup.devices.allow = c 1:9 rwm
230 lxc.cgroup.devices.allow = c 1:8 rwm
231 lxc.cgroup.devices.allow = c 136:* rwm
232 lxc.cgroup.devices.allow = c 5:2 rwm
233 # rtc
234 lxc.cgroup.devices.allow = c 254:0 rm
235
236 # mounts point
237 lxc.mount.entry=proc proc proc nodev,noexec,nosuid 0 0
238 lxc.mount.entry=run run tmpfs nodev,noexec,nosuid,relatime,size=1m,mode=0755 0 0
239 lxc.mount.entry=none dev/pts devpts gid=5,mode=620 0 0
240 lxc.mount.entry=shm dev/shm tmpfs nodev,nosuid,noexec 0 0
241
242 EOF
243
244 return 0
245 }
246
247 die() {
248 echo "$@" >&2
249 exit 1
250 }
251
252 usage() {
253 cat >&2 <<EOF
254 Usage: $(basename $0) [-h|--help] [-r|--repository <url>]
255 [-R|--release <release>] [-a|--arch <arch>]
256 [--rootfs <rootfs>] -p|--path <path> -n|--name <name>
257 [PKG...]
258 EOF
259 }
260
261 usage_err() {
262 usage
263 exit 1
264 }
265
266 optarg_check() {
267 if [ -z "$2" ]; then
268 usage_err "option '$1' requires an argument"
269 fi
270 }
271
272 default_path=@LXCPATH@
273 release=
274 arch=$(uname -m)
275
276 # template mknods, requires root
277 if [ $(id -u) -ne 0 ]; then
278 echo "$(basename $0): must be run as root" >&2
279 exit 1
280 fi
281
282 while [ $# -gt 0 ]; do
283 opt="$1"
284 shift
285 case "$opt" in
286 -h|--help)
287 usage
288 exit 0
289 ;;
290 -n|--name)
291 optarg_check $opt "$1"
292 name=$1
293 shift
294 ;;
295 --rootfs)
296 optarg_check $opt "$1"
297 rootfs=$1
298 shift
299 ;;
300 -p|--path)
301 optarg_check $opt "$1"
302 path=$1
303 shift
304 ;;
305 -r|--repository)
306 optarg_check $opt "$1"
307 repository=$1
308 shift
309 ;;
310 -R|--release)
311 optarg_check $opt "$1"
312 release=$1
313 shift
314 ;;
315 -a|--arch)
316 optarg_check $opt "$1"
317 arch=$1
318 shift
319 ;;
320 --)
321 break;;
322 --*=*)
323 # split --myopt=foo=bar into --myopt foo=bar
324 set -- ${opt%=*} ${opt#*=} "$@"
325 ;;
326 -?)
327 usage_err "unknown option '$opt'"
328 ;;
329 -*)
330 # split opts -abc into -a -b -c
331 set -- $(echo "${opt#-}" | sed 's/\(.\)/ -\1/g') "$@"
332 ;;
333 esac
334 done
335
336
337 [ -z "$name" ] && usage_err
338
339 if [ -z "${path}" ]; then
340 path="${default_path}/${name}"
341 fi
342
343 if [ -z "$rootfs" ]; then
344 rootfs=`awk -F= '$1 ~ /^lxc.rootfs/ { print $2 }' "$path/config" 2>/dev/null`
345 if [ -z "$rootfs" ]; then
346 rootfs="${path}/rootfs"
347 fi
348 fi
349
350 lxc_arch=$arch
351 apk_arch=$arch
352
353 case "$arch" in
354 i[3-6]86)
355 apk_arch=x86
356 ;;
357 x86)
358 lxc_arch=i686
359 ;;
360 x86_64|"")
361 ;;
362 *)
363 die "unsupported architecture: $arch"
364 ;;
365 esac
366
367 : ${APK:=apk}
368 if ! which $APK >/dev/null; then
369 get_static_apk "$rootfs" || die "Failed to download a valid static apk"
370 fi
371
372 install_alpine "$rootfs" "$@" || die "Failed to install rootfs for $name"
373 configure_alpine "$rootfs" "$name" || die "Failed to configure $name"
374 copy_configuration "$path" "$rootfs" "$name"