diff --git a/Containerfile b/Containerfile index 9645045..da56548 100644 --- a/Containerfile +++ b/Containerfile @@ -6,16 +6,38 @@ FROM stagex/cpio AS cpio FROM stagex/linux-airgap AS linux FROM stagex/mtools AS mtools FROM stagex/xz AS xz +FROM stagex/eudev AS eudev +FROM stagex/keyfork AS keyfork +FROM stagex/openpgp-card-tools AS openpgp-card-tools +FROM stagex/gpg AS gpg +FROM stagex/bash AS bash FROM stagex/grub:local AS grub +FROM stagex/npth AS npth +FROM stagex/libksba AS libksba +FROM stagex/libgpg-error AS libgpg-error +FROM stagex/libassuan AS libassuan +FROM stagex/libgcrypt AS libgcrypt +FROM stagex/jq AS jq +FROM stagex/bc AS bc +FROM stagex/git AS git +FROM stagex/zlib AS zlib +FROM stagex/tpm2-tools AS tpm2-tools +FROM stagex/tpm2-tss AS tpm2-tss +FROM stagex/openssl AS openssl +FROM stagex/pcsc-lite AS pcsc-lite +FROM stagex/flashtools AS flashtools FROM scratch AS base +ARG VERSION development +ARG GIT_TIMESTAMP null +ARG GIT_AUTHOR null +ARG GIT_REF null +ARG GIT_KEY null COPY --from=busybox . / COPY --from=musl . / COPY --from=xorriso . / COPY --from=cpio . / COPY --from=mtools . / -COPY --from=linux . / -COPY --from=syslinux . / COPY --from=xz . / COPY --from=grub . / @@ -25,41 +47,55 @@ FROM base AS build COPY --from=linux /bzImage iso/boot/vmlinuz ## Initramfs -COPY --from=stagex/busybox . initramfs -COPY --chmod=0755 <<-EOF initramfs/init - #!/bin/sh - /bin/sh +COPY --from=busybox . initramfs +COPY --from=eudev . initramfs +COPY --from=musl . initramfs +COPY --from=zlib . initramfs +COPY --from=npth . initramfs +COPY --from=libksba . initramfs +COPY --from=libgpg-error . initramfs +COPY --from=libassuan . initramfs +COPY --from=libgcrypt . initramfs +COPY --from=keyfork . initramfs +COPY --from=bash . initramfs +COPY --from=gpg . initramfs +COPY --from=jq . initramfs +COPY --from=bc . initramfs +COPY --from=git . initramfs +COPY --from=flashtools . initramfs +COPY --from=tpm2-tools . initramfs +COPY --from=tpm2-tss . initramfs +COPY --from=openssl . initramfs +COPY --from=pcsc-lite . initramfs +COPY --from=openpgp-card-tools . initramfs +COPY rootfs/ initramfs +COPY <<-EOF initramfs/etc/environment + export VERSION="$VERSION" + export GIT_TIMESTAMP="$GIT_TIMESTAMP" + export GIT_AUTHOR="$GIT_AUTHOR" + export GIT_REF="$GIT_REF" + export GIT_KEY="$GIT_KEY" EOF RUN <<-EOF - set -eux - cd initramfs - find . \ - | cpio -o -H newc \ - | gzip -9 \ - > ../iso/boot/initramfs + cd initramfs + find . -print0 \ + | cpio --null --create --verbose --format=newc \ + | gzip --best > ../iso/boot/initramfs EOF ## Grub (EFI Boot) -COPY <<-EOF iso/boot/grub/grub.cfg - menuentry "Linux Airgap" { - linux /boot/vmlinuz - initrd /boot/initramfs - } -EOF -COPY <<-EOF grub_early.cfg - search --no-floppy --set=root --label "Airgap" - set prefix=(\$root)/boot/grub -EOF +COPY config/grub.cfg iso/boot/grub/grub.cfg +COPY config/grub_early.cfg grub_early.cfg RUN <<-EOF set -eux - mkdir -p iso/efi/boot + mkdir -p efi/boot grub-mkimage \ - --config="grub_early.cfg" \ - --prefix="/boot/grub" \ - --output="iso/efi/boot/bootx64.efi" \ - --format="x86_64-efi" \ + --config="grub_early.cfg" \ + --prefix="/boot/grub" \ + --output="efi/boot/bootx64.efi" \ + --format="x86_64-efi" \ --compression="xz" \ - all_video \ + all_video \ disk \ part_gpt \ part_msdos \ @@ -71,44 +107,24 @@ RUN <<-EOF efi_gop \ fat \ iso9660 \ - cat \ - echo \ - ls \ - test \ - true \ - help \ - gzio -EOF -RUN <<-EOF + gzio \ + serial \ + terminal mformat -i iso/boot/grub/efi.img -C -f 1440 -N 0 :: - mcopy -i iso/boot/grub/efi.img iso/efi + mcopy -i iso/boot/grub/efi.img -s efi :: touch -md "@0" iso/boot/grub/efi.img EOF - ## Syslinux (BIOS Boot) -COPY <<-EOF iso/boot/syslinux/syslinux.cfg - TIMEOUT 2 - PROMPT -1 - DEFAULT Airgap - LABEL Airgap - MENU LABEL Linux Airgap - KERNEL /boot/vmlinuz - INITRD /boot/initramfs -EOF -RUN <<-EOF - mkdir -p iso/boot/syslinux - for file in \ - isohdpfx.bin \ - isolinux.bin \ - ldlinux.c32 \ - libutil.c32 \ - libcom32.c32 \ - mboot.c32; \ - do - mv /usr/share/syslinux/$file iso/boot/syslinux/$file || return 1 - done -EOF +COPY config/syslinux.cfg iso/boot/syslinux/ +COPY --from=syslinux \ + /usr/share/syslinux/isohdpfx.bin \ + /usr/share/syslinux/isolinux.bin \ + /usr/share/syslinux/ldlinux.c32 \ + /usr/share/syslinux/libutil.c32 \ + /usr/share/syslinux/libcom32.c32 \ + /usr/share/syslinux/mboot.c32 \ + iso/boot/syslinux/ ## Build Hybrid EFI/BIOS ISO FROM build AS install @@ -118,6 +134,7 @@ RUN xorrisofs \ -joliet \ -rational-rock \ -sysid LINUX \ + -volid "airgap" \ -isohybrid-mbr iso/boot/syslinux/isohdpfx.bin \ -eltorito-boot boot/syslinux/isolinux.bin \ -eltorito-catalog boot/syslinux/boot.cat \ @@ -132,5 +149,5 @@ RUN xorrisofs \ iso/ FROM scratch AS package -COPY --from=install /iso /iso +COPY --from=install /initramfs /initramfs COPY --from=install /airgap.iso / diff --git a/Makefile b/Makefile index 25a6647..f9d39e4 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,9 @@ +VERSION := $(shell git tag --points-at HEAD) +GIT_REF := $(shell git log -1 --format=%H) +GIT_AUTHOR := $(shell git log -1 --format=%an) +GIT_KEY := $(shell git log -1 --format=%GP) +GIT_TIMESTAMP := $(shell git log -1 --format=%cd --date=iso) + .DEFAULT_GOAL := .PHONY: default default: \ @@ -15,7 +21,7 @@ vm-bios: out/airgap.iso -cdrom "out/airgap.iso" .PHONY: vm-efi -vm-efi: +vm-efi: out/airgap.iso qemu-system-x86_64 \ -m 4G \ -machine pc \ @@ -23,9 +29,14 @@ vm-efi: -bios /usr/share/ovmf/OVMF.fd \ -cdrom "out/airgap.iso" -out/airgap.iso: Containerfile +out/airgap.iso: Containerfile $(shell git ls-files rootfs) docker build \ --progress=plain \ --output type=local,dest=out \ + --build-arg VERSION="$(or $(VERSION),"development")" \ + --build-arg GIT_REF="$(GIT_REF)" \ + --build-arg GIT_AUTHOR="$(GIT_AUTHOR)" \ + --build-arg GIT_KEY="$(GIT_KEY)" \ + --build-arg GIT_TIMESTAMP="$(GIT_TIMESTAMP)" \ -f Containerfile \ . diff --git a/config/grub.cfg b/config/grub.cfg new file mode 100644 index 0000000..9ce0217 --- /dev/null +++ b/config/grub.cfg @@ -0,0 +1,5 @@ +set timeout=1 +menuentry "Linux Airgap" { + linux /boot/vmlinuz init=/init console=ttyS0 console=tty0 ro + initrd /boot/initramfs +} diff --git a/config/grub_early.cfg b/config/grub_early.cfg new file mode 100644 index 0000000..b36b429 --- /dev/null +++ b/config/grub_early.cfg @@ -0,0 +1,2 @@ +search --no-floppy --set=root --label "airgap" +set prefix=($root)/boot/grub diff --git a/config/syslinux.cfg b/config/syslinux.cfg new file mode 100644 index 0000000..6d02fde --- /dev/null +++ b/config/syslinux.cfg @@ -0,0 +1,8 @@ +TIMEOUT 2 +PROMPT -1 +DEFAULT Airgap +LABEL Airgap + MENU LABEL Linux Airgap + KERNEL /boot/vmlinuz + INITRD /boot/initramfs + APPEND init=/init console=ttyS0 console=tty0 ro diff --git a/rootfs/etc/init.d/S01syslogd b/rootfs/etc/init.d/S01syslogd new file mode 100755 index 0000000..15006bc --- /dev/null +++ b/rootfs/etc/init.d/S01syslogd @@ -0,0 +1,55 @@ +#!/bin/sh + +DAEMON="syslogd" +PIDFILE="/var/run/$DAEMON.pid" + +SYSLOGD_ARGS="" + +# shellcheck source=/dev/null +[ -r "/etc/default/$DAEMON" ] && . "/etc/default/$DAEMON" + +# BusyBox' syslogd does not create a pidfile, so pass "-n" in the command line +# and use "-m" to instruct start-stop-daemon to create one. +start() { + printf 'Starting %s: ' "$DAEMON" + # shellcheck disable=SC2086 # we need the word splitting + start-stop-daemon -b -m -S -q -p "$PIDFILE" -x "/sbin/$DAEMON" \ + -- -n $SYSLOGD_ARGS + status=$? + if [ "$status" -eq 0 ]; then + echo "OK" + else + echo "FAIL" + fi + return "$status" +} + +stop() { + printf 'Stopping %s: ' "$DAEMON" + start-stop-daemon -K -q -p "$PIDFILE" + status=$? + if [ "$status" -eq 0 ]; then + rm -f "$PIDFILE" + echo "OK" + else + echo "FAIL" + fi + return "$status" +} + +restart() { + stop + sleep 1 + start +} + +case "$1" in + start|stop|restart) + "$1";; + reload) + # Restart, since there is no true "reload" feature. + restart;; + *) + echo "Usage: $0 {start|stop|restart|reload}" + exit 1 +esac diff --git a/rootfs/etc/init.d/S02klogd b/rootfs/etc/init.d/S02klogd new file mode 100755 index 0000000..0677e1e --- /dev/null +++ b/rootfs/etc/init.d/S02klogd @@ -0,0 +1,55 @@ +#!/bin/sh + +DAEMON="klogd" +PIDFILE="/var/run/$DAEMON.pid" + +KLOGD_ARGS="" + +# shellcheck source=/dev/null +[ -r "/etc/default/$DAEMON" ] && . "/etc/default/$DAEMON" + +# BusyBox' klogd does not create a pidfile, so pass "-n" in the command line +# and use "-m" to instruct start-stop-daemon to create one. +start() { + printf 'Starting %s: ' "$DAEMON" + # shellcheck disable=SC2086 # we need the word splitting + start-stop-daemon -b -m -S -q -p "$PIDFILE" -x "/sbin/$DAEMON" \ + -- -n $KLOGD_ARGS + status=$? + if [ "$status" -eq 0 ]; then + echo "OK" + else + echo "FAIL" + fi + return "$status" +} + +stop() { + printf 'Stopping %s: ' "$DAEMON" + start-stop-daemon -K -q -p "$PIDFILE" + status=$? + if [ "$status" -eq 0 ]; then + rm -f "$PIDFILE" + echo "OK" + else + echo "FAIL" + fi + return "$status" +} + +restart() { + stop + sleep 1 + start +} + +case "$1" in + start|stop|restart) + "$1";; + reload) + # Restart, since there is no true "reload" feature. + restart;; + *) + echo "Usage: $0 {start|stop|restart|reload}" + exit 1 +esac diff --git a/rootfs/etc/init.d/S02sysctl b/rootfs/etc/init.d/S02sysctl new file mode 100755 index 0000000..85d9ed5 --- /dev/null +++ b/rootfs/etc/init.d/S02sysctl @@ -0,0 +1,94 @@ +#!/bin/sh +# +# This script is used by busybox and procps-ng. +# +# With procps-ng, the "--system" option of sysctl also enables "--ignore", so +# errors are not reported via syslog. Use the run_logger function to mimic the +# --system behavior, still reporting errors via syslog. Users not interested +# on error reports can add "-e" to SYSCTL_ARGS. +# +# busybox does not have a "--system" option neither reports errors via syslog, +# so the scripting provides a consistent behavior between the implementations. +# Testing the busybox sysctl exit code is fruitless, as at the moment, since +# its exit status is zero even if errors happen. Hopefully this will be fixed +# in a future busybox version. + +PROGRAM="sysctl" + +SYSCTL_ARGS="" + +# shellcheck source=/dev/null +[ -r "/etc/default/$PROGRAM" ] && . "/etc/default/$PROGRAM" + +# Files are read from directories in the SYSCTL_SOURCES list, in the given +# order. A file may be used more than once, since there can be multiple +# symlinks to it. No attempt is made to prevent this. +SYSCTL_SOURCES="/etc/sysctl.d/ /usr/local/lib/sysctl.d/ /usr/lib/sysctl.d/ /lib/sysctl.d/ /etc/sysctl.conf" + +# If the logger utility is available all messages are sent to syslog, except +# for the final status. The file redirections do the following: +# +# - stdout is redirected to syslog with facility.level "kern.info" +# - stderr is redirected to syslog with facility.level "kern.err" +# - file dscriptor 4 is used to pass the result to the "start" function. +# +run_logger() { + # shellcheck disable=SC2086 # we need the word splitting + find $SYSCTL_SOURCES -maxdepth 1 -name '*.conf' -print0 2> /dev/null | \ + xargs -0 -r -n 1 readlink -f | { + prog_status="OK" + while :; do + read -r file || { + echo "$prog_status" >&4 + break + } + echo "* Applying $file ..." + /sbin/sysctl -p "$file" $SYSCTL_ARGS || prog_status="FAIL" + done 2>&1 >&3 | /usr/bin/logger -t sysctl -p kern.err + } 3>&1 | /usr/bin/logger -t sysctl -p kern.info +} + +# If logger is not available all messages are sent to stdout/stderr. +run_std() { + # shellcheck disable=SC2086 # we need the word splitting + find $SYSCTL_SOURCES -maxdepth 1 -name '*.conf' -print0 2> /dev/null | \ + xargs -0 -r -n 1 readlink -f | { + prog_status="OK" + while :; do + read -r file || { + echo "$prog_status" >&4 + break + } + echo "* Applying $file ..." + /sbin/sysctl -p "$file" $SYSCTL_ARGS || prog_status="FAIL" + done + } +} + +if [ -x /usr/bin/logger ]; then + run_program="run_logger" +else + run_program="run_std" +fi + +start() { + printf '%s %s: ' "$1" "$PROGRAM" + status=$("$run_program" 4>&1) + echo "$status" + if [ "$status" = "OK" ]; then + return 0 + fi + return 1 +} + +case "$1" in + start) + start "Running";; + restart|reload) + start "Rerunning";; + stop) + :;; + *) + echo "Usage: $0 {start|stop|restart|reload}" + exit 1 +esac diff --git a/rootfs/etc/init.d/S10udev b/rootfs/etc/init.d/S10udev new file mode 100755 index 0000000..a51ddef --- /dev/null +++ b/rootfs/etc/init.d/S10udev @@ -0,0 +1,29 @@ +#!/bin/sh + +# Check for config file and read it +UDEV_CONFIG=/etc/udev/udev.conf +test -r $UDEV_CONFIG || exit 6 +. $UDEV_CONFIG + +case "$1" in + start) + printf "Populating %s using udev: " "${udev_root:-/dev}" + [ -e /proc/sys/kernel/hotplug ] && printf '\000\000\000\000' > /proc/sys/kernel/hotplug + /sbin/udevd -d || { echo "FAIL"; exit 1; } + udevadm trigger --type=subsystems --action=add + udevadm trigger --type=devices --action=add + udevadm settle --timeout=30 || echo "udevadm settle failed" + echo "done" + ;; + stop) + # Stop execution of events + udevadm control --stop-exec-queue + killall udevd + ;; + *) + echo "Usage: $0 {start|stop}" + exit 1 + ;; +esac + +exit 0 diff --git a/rootfs/etc/init.d/S20urandom b/rootfs/etc/init.d/S20urandom new file mode 100755 index 0000000..6c6aea9 --- /dev/null +++ b/rootfs/etc/init.d/S20urandom @@ -0,0 +1,70 @@ +#! /bin/sh +# +# Preserve the random seed between reboots. See urandom(4). +# + +# Quietly do nothing if /dev/urandom does not exist +[ -c /dev/urandom ] || exit 0 + +URANDOM_SEED="/var/lib/random-seed" + +# shellcheck source=/dev/null +[ -r "/etc/default/urandom" ] && . "/etc/default/urandom" + +if pool_bits=$(cat /proc/sys/kernel/random/poolsize 2> /dev/null); then + pool_size=$((pool_bits/8)) +else + pool_size=512 +fi + +init_rng() { + [ -f "$URANDOM_SEED" ] || return 0 + printf 'Initializing random number generator: ' + dd if="$URANDOM_SEED" bs="$pool_size" of=/dev/urandom count=1 2> /dev/null + status=$? + if [ "$status" -eq 0 ]; then + echo "OK" + else + echo "FAIL" + fi + return "$status" +} + +save_random_seed() { + printf 'Saving random seed: ' + status=1 + if touch "$URANDOM_SEED.new" 2> /dev/null; then + old_umask=$(umask) + umask 077 + dd if=/dev/urandom of="$URANDOM_SEED.tmp" bs="$pool_size" count=1 2> /dev/null + cat "$URANDOM_SEED" "$URANDOM_SEED.tmp" 2>/dev/null \ + | sha256sum \ + | cut -d ' ' -f 1 > "$URANDOM_SEED.new" && \ + mv "$URANDOM_SEED.new" "$URANDOM_SEED" && status=0 + rm -f "$URANDOM_SEED.tmp" + umask "$old_umask" + if [ "$status" -eq 0 ]; then + echo "OK" + else + echo "FAIL" + fi + + else + echo "SKIP (read-only file system detected)" + fi + return "$status" +} + +case "$1" in + start|restart|reload) + # Carry a random seed from start-up to start-up + # Load and then save the whole entropy pool + init_rng && save_random_seed;; + stop) + # Carry a random seed from shut-down to start-up + # Save the whole entropy pool + save_random_seed;; + *) + echo "Usage: $0 {start|stop|restart|reload}" + exit 1 +esac diff --git a/rootfs/etc/init.d/rcK b/rootfs/etc/init.d/rcK new file mode 100755 index 0000000..59e9c54 --- /dev/null +++ b/rootfs/etc/init.d/rcK @@ -0,0 +1,27 @@ +#!/bin/sh + + +# Stop all init scripts in /etc/init.d +# executing them in reversed numerical order. +# +for i in $(ls -r /etc/init.d/S??*) ;do + + # Ignore dangling symlinks (if any). + [ ! -f "$i" ] && continue + + case "$i" in + *.sh) + # Source shell script for speed. + ( + trap - INT QUIT TSTP + set stop + . $i + ) + ;; + *) + # No sh extension, so fork subprocess. + $i stop + ;; + esac +done + diff --git a/rootfs/etc/init.d/rcS b/rootfs/etc/init.d/rcS new file mode 100755 index 0000000..de41153 --- /dev/null +++ b/rootfs/etc/init.d/rcS @@ -0,0 +1,27 @@ +#!/bin/sh + + +# Start all init scripts in /etc/init.d +# executing them in numerical order. +# +for i in /etc/init.d/S??* ;do + + # Ignore dangling symlinks (if any). + [ ! -f "$i" ] && continue + + case "$i" in + *.sh) + # Source shell script for speed. + ( + trap - INT QUIT TSTP + set start + . $i + ) + ;; + *) + # No sh extension, so fork subprocess. + $i start + ;; + esac +done + diff --git a/rootfs/etc/inittab b/rootfs/etc/inittab index d66c906..a9787cf 100644 --- a/rootfs/etc/inittab +++ b/rootfs/etc/inittab @@ -1,11 +1,5 @@ # /etc/inittab -# -# Copyright (C) 2001 Erik Andersen -# -# Note: BusyBox init doesn't support runlevels. The runlevels field is -# completely ignored by BusyBox init. If you want runlevels, use -# sysvinit. -# + # Format for each entry: ::: # # id == tty to run on, or empty for /dev/console @@ -14,16 +8,15 @@ # process == program to run # Startup the system +::sysinit:/bin/mount -t devtmpfs devtmpfs /dev +::sysinit:/bin/mkdir -p /proc /run /dev/pts /dev/shm ::sysinit:/bin/mount -t proc proc /proc ::sysinit:/bin/mount -o remount,rw / -::sysinit:/bin/mkdir -p /dev/pts /dev/shm ::sysinit:/bin/mount -a -::sysinit:/sbin/swapon -a null::sysinit:/bin/ln -sf /proc/self/fd /dev/fd null::sysinit:/bin/ln -sf /proc/self/fd/0 /dev/stdin null::sysinit:/bin/ln -sf /proc/self/fd/1 /dev/stdout null::sysinit:/bin/ln -sf /proc/self/fd/2 /dev/stderr -::sysinit:/bin/hostname -F /etc/hostname # now run any rc scripts ::sysinit:/etc/init.d/rcS @@ -36,5 +29,4 @@ null::sysinit:/bin/ln -sf /proc/self/fd/2 /dev/stderr # Stuff to do before rebooting ::shutdown:/etc/init.d/rcK -::shutdown:/sbin/swapoff -a ::shutdown:/bin/umount -a -r diff --git a/rootfs/etc/profile b/rootfs/etc/profile index 998b188..4c74793 100644 --- a/rootfs/etc/profile +++ b/rootfs/etc/profile @@ -3,8 +3,7 @@ export PATH="/usr/local/bin:/bin:/sbin:/usr/bin:/usr/sbin" export PS1="[\h \t] \\$ " export GNUPGHOME=/.gnupg source /etc/environment - -dmesg -n1 +cd /root clear cat << "EOF" _ _ ___ ____ diff --git a/rootfs/init b/rootfs/init new file mode 100755 index 0000000..15bd0d1 --- /dev/null +++ b/rootfs/init @@ -0,0 +1,2 @@ +#!/bin/sh +exec /bin/init