#!/bin/bash #---------------------------------------------------------------------- # ACME client written with process isolation and minimal privileges in mind # (test suite) # Copyright © 2015-2021 Guilhem Moulin # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . #---------------------------------------------------------------------- set -ue PATH="/usr/bin:/bin" export PATH usage() { local rv="${1-0}" echo "Usage: $0 [--deb|--dev] [TEST..]" >&2 exit $rv } # Setup: for any subdomain under $DOMAINNAME, # http://$subdomain.$DOMAINNAME/.well-known/acme-challenge/$challenge # must be routed to this machine. # This can be done with a wildcard DNS record and opening tcp/80 in firewall. DOMAINNAME="lacme-test.guilhem.org" MODE="dev" DISTRIBUTION="sid" BUILDDIR="build/test" while [ $# -gt 0 ]; do case "$1" in --deb) MODE="deb"; shift;; --dev) MODE="dev"; shift;; --help|-h) usage 0;; -*) echo "Error: Unknown option $1" >&2; usage 1;; --) shift; break;; *) break; esac done cd "$(dirname -- "$0")" declare -a TESTS=() if [ $# -eq 0 ]; then # always start with registration, the account key might be new TESTS+=( "register" ) for t in tests/*; do if [ "$t" != "tests/register" ] && [ "${t#tests/account-encrypted-}" = "$t" ] && [ -f "$t" ]; then # skip registration and non-interactive tests TESTS+=( "${t#tests/}" ) fi done else for t in "$@"; do t="${t#tests/}" if [ -f "tests/$t" ]; then TESTS+=( "$t" ) else echo "Error: '$1': no such test" >&2 exit 1 fi done fi if [ "$MODE" = "deb" ]; then DISTRIBUTION="$(dpkg-parsechangelog -S Distribution)" [ "$DISTRIBUTION" != "UNRELEASED" ] || DISTRIBUTION="sid" PKG_DESTDIR="${XDG_CACHE_HOME:-"$HOME/.cache"}/build-area" fi ACCOUNT_KEY="$BUILDDIR/account.key" mkdir -pv -- "$BUILDDIR" if [ ! -f "$ACCOUNT_KEY" ]; then # keep the account key (up to `make clean`) to avoid hitting # rate-liming -- currently 50 registrations per 3h per IP, see # https://letsencrypt.org/docs/staging-environment/ echo "Generating account key $ACCOUNT_KEY..." >&2 openssl genpkey -algorithm RSA -out "$ACCOUNT_KEY" fi ARCH="$(dpkg-architecture -qDEB_BUILD_ARCH)" CHROOT="" cleanup() { if [ -n "$CHROOT" ]; then schroot -c "$CHROOT" -e fi } trap cleanup EXIT INT TERM run() { local t="tests/$1" rootdir version sub if [ ! -f "$t" ]; then echo "Error: '$1': no such test" >&2 exit 1 fi # Don't need to rebuild for each test, but editing the code at the # same time might cause `make install` to rebuild a wrong version make all -- \ BUILDDIR="$BUILDDIR" \ DESTDIR="" \ exec_prefix="/usr" \ datadir="/usr/share" \ runstatedir="/run" \ lacme_www_user=_lacme-www \ lacme_www_group=nogroup \ lacme_client_user=_lacme-client \ lacme_client_group=nogroup \ acmeapi_server="https://acme-staging-v02.api.letsencrypt.org/directory" CHROOT="$(schroot -c "$DISTRIBUTION-$ARCH-sbuild" -b)" rootdir="/run/schroot/mount/$CHROOT" if [ "$MODE" = "deb" ]; then version="$(dpkg-parsechangelog -S Version)" echo "Installing lacme $version into $CHROOT..." >&2 install -vt "$rootdir/dev/shm" -m0644 -- \ "$PKG_DESTDIR/lacme_${version}_all.deb" \ "$PKG_DESTDIR/lacme-accountd_${version}_all.deb" sudo schroot -d"/" -c "$CHROOT" -r -- \ env DEBIAN_FRONTEND="noninteractive" apt install -y \ "/dev/shm/lacme_${version}_all.deb" \ "/dev/shm/lacme-accountd_${version}_all.deb" elif [ "$MODE" = "dev" ]; then echo "Installing lacme dev into $CHROOT..." >&2 sudo make install -- \ BUILDDIR="$BUILDDIR" \ DESTDIR="$rootdir" \ exec_prefix="$rootdir/usr" \ datadir="$rootdir/usr/share" \ runstatedir="$rootdir/run" sudo schroot -d"/" -c "$CHROOT" -r -- \ env DEBIAN_FRONTEND="noninteractive" apt install -y \ adduser \ libconfig-tiny-perl \ libcrypt-openssl-rsa-perl \ libjson-perl \ libnet-ssleay-perl \ libtimedate-perl \ libwww-perl \ openssl sudo schroot -d"/" -c "$CHROOT" -r -- \ adduser --force-badname --system \ --home /nonexistent --no-create-home \ --gecos "lacme www user" \ --quiet _lacme-www sudo schroot -d"/" -c "$CHROOT" -r -- \ adduser --force-badname --system \ --home /nonexistent --no-create-home \ --gecos "lacme client user" \ --quiet _lacme-client fi # set up staging environment, see https://letsencrypt.org/docs/staging-environment/ sudo install -oroot -groot -m0644 -vt "$rootdir/usr/share/lacme" certs-staging/*.pem sudo install -oroot -groot -m0644 -vT "$BUILDDIR/certs-staging/ca-certificates.crt" \ "$rootdir/usr/share/lacme/ca-certificates.crt" sudo schroot -d"/" -c "$CHROOT" -r -- \ sed -ri '0,/^#?server\s*=.*/ {s||server = https://acme-staging-v02.api.letsencrypt.org/directory|}' \ /etc/lacme/lacme.conf # install account key and configure lacme accordingly sudo install -oroot -groot -m0600 -vT -- "$BUILDDIR/account.key" \ "$rootdir/etc/lacme/account.key" sudo schroot -d"/" -c "$CHROOT" -r -- \ sed -ri '0,\|^#?privkey\s*=.*| {s||privkey = file:/etc/lacme/account.key|}' \ /etc/lacme/lacme-accountd.conf # use lacme's internal webserver bound to INADDR_ANY port 80 sudo schroot -d"/" -c "$CHROOT" -r -- \ sed -ri 's|^#?listen\s*=.*|listen = 0.0.0.0|' /etc/lacme/lacme.conf # use a sample lacme-certs.conf, with a random subdomain so we can # verify that challenges are answered correctly sub="$(head -c10 /dev/urandom | base32 -w0)" sudo tee "$rootdir/etc/lacme/lacme-certs.conf.d/simpletest-rsa.conf" >/dev/null <<- EOF [simpletest-rsa] certificate-key = /etc/lacme/simpletest.rsa.key certificate-chain = /etc/lacme/simpletest.rsa.crt subject = /CN=${sub,,[A-Z]}.$DOMAINNAME EOF sudo schroot -d"/" -c "$CHROOT" -r -- \ openssl genpkey -algorithm RSA -out /etc/lacme/simpletest.rsa.key # copy test wrapper and unit file local testdir="/dev/shm/lacme.test" sudo install -oroot -groot -m0700 -d -- "$rootdir$testdir" sudo install -oroot -groot -m0755 -T -- /dev/stdin "$rootdir$testdir/run" <<-EOF STDERR="$testdir/stderr" touch "\$STDERR" fail() { set +x local rv=\$? i if [ \$rv -eq 0 ]; then rv=1; fi if [ -s "\$STDERR" ]; then echo "vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv" cat <"\$STDERR" >&2 echo "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" fi [ \$# -eq 0 ] || echo "Error: \$*" >&2 exit \$rv } grepstderr() { grep "\$@" <"\$STDERR" || fail } ngrepstderr() { ! grep "\$@" <"\$STDERR" || fail } set -x EOF sudo tee -a "$rootdir$testdir/run" >/dev/null <"$t" sudo schroot -d"/" -c "$CHROOT" -r -- env -i \ USER="root" \ HOME="/root" \ SHELL="/bin/sh" \ LOGNAME="root" \ TERM="$TERM" \ PATH="/usr/sbin:/usr/bin:/sbin:/bin" \ DOMAINNAME="$DOMAINNAME" \ sh -ue "$testdir/run" || return $? } RV=0 declare -a PASSED=() FAILED=() for t in "${TESTS[@]}"; do run "$t" && rv=0 || rv=$? if [ -n "$CHROOT" ]; then # clean up schroot -c "$CHROOT" -e CHROOT="" fi if [ $rv -eq 0 ]; then PASSED+=( "$t" ) else FAILED+=( "$t" ) RV=$rv break # stop at the first failure fi done echo >&2 echo "================================================================================" >&2 echo "PASSED: ${PASSED[*]:-"(none)"}" >&2 if [ ${#FAILED[@]} -gt 0 ]; then echo "FAILED: ${FAILED[*]}" >&2 fi exit $RV