diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/account-encrypted-gpg | 15 | ||||
-rw-r--r-- | tests/account-encrypted-openssl | 10 | ||||
-rw-r--r-- | tests/accountd | 87 | ||||
-rw-r--r-- | tests/accountd-kid | 59 | ||||
-rw-r--r-- | tests/accountd-remote | 55 | ||||
-rw-r--r-- | tests/apache2-proxy | 33 | ||||
-rw-r--r-- | tests/apache2-static | 47 | ||||
-rw-r--r-- | tests/cert-extensions | 91 | ||||
-rw-r--r-- | tests/cert-install | 176 | ||||
-rw-r--r-- | tests/cert-renew | 21 | ||||
-rw-r--r-- | tests/cert-revoke | 32 | ||||
-rw-r--r-- | tests/cert-verify | 43 | ||||
-rw-r--r-- | tests/drop-privileges | 166 | ||||
-rw-r--r-- | tests/nginx-proxy | 35 | ||||
-rw-r--r-- | tests/nginx-static | 48 | ||||
-rw-r--r-- | tests/old-accountd | 30 | ||||
-rw-r--r-- | tests/old-lacme | 36 | ||||
-rw-r--r-- | tests/register | 8 | ||||
-rw-r--r-- | tests/spec-expansion | 130 | ||||
-rw-r--r-- | tests/webservers | 16 |
20 files changed, 1138 insertions, 0 deletions
diff --git a/tests/account-encrypted-gpg b/tests/account-encrypted-gpg new file mode 100644 index 0000000..fd1e4ac --- /dev/null +++ b/tests/account-encrypted-gpg @@ -0,0 +1,15 @@ +# GnuPG-encrypted account key (WARN: this test is not interactive) + +PASSPHRASE="test" + +DEBIAN_FRONTEND="noninteractive" apt install -y --no-install-recommends gpg gpg-agent + +gpg --batch --passphrase "$PASSPHRASE" --quick-generate-key "nobody <noreply@example.net>" +keyid="$(gpg --list-secret-key --with-colons | grep -m1 ^fpr: | cut -sd: -f10)" +gpg --encrypt -r "$keyid" /etc/lacme/account.key +sed -ri '0,\|^#?privkey\s*=.*| {s||privkey = gpg:/etc/lacme/account.key.gpg|}' /etc/lacme/lacme-accountd.conf + +export GPG_TTY="$(tty)" +lacme account + +# vim: set filetype=sh : diff --git a/tests/account-encrypted-openssl b/tests/account-encrypted-openssl new file mode 100644 index 0000000..e79a528 --- /dev/null +++ b/tests/account-encrypted-openssl @@ -0,0 +1,10 @@ +# OpenSSL-encrypted account key (WARN: this test is not interactive) + +PASSPHRASE="test" + +openssl rsa -aes128 -passout pass:"$PASSPHRASE" </etc/lacme/account.key >/etc/lacme/account.enc.key +sed -ri '0,\|^#?privkey\s*=.*| {s||privkey = file:/etc/lacme/account.enc.key|}' /etc/lacme/lacme-accountd.conf + +lacme account + +# vim: set filetype=sh : diff --git a/tests/accountd b/tests/accountd new file mode 100644 index 0000000..a603c16 --- /dev/null +++ b/tests/accountd @@ -0,0 +1,87 @@ +# Use a separate accountd server process + +adduser --disabled-password \ + --home /home/lacme-account \ + --gecos "lacme account user" \ + --quiet lacme-account + +# non-existent parent directory +! lacme --socket="/nonexistent/S.lacme" account 2>"$STDERR" || fail +grepstderr -Fxq "Error: stat(/nonexistent): No such file or directory" + +# word-writable parent directory +! lacme --socket="/tmp/S.lacme" account 2>"$STDERR" || fail +grepstderr -Fxq "Error: Insecure permissions on /tmp" + +# missing socket +SOCKET=~lacme-account/S.lacme +! lacme --socket="$SOCKET" account 2>"$STDERR" || fail +grepstderr -Fxq "Can't stat $SOCKET: No such file or directory (Is lacme-accountd running?)" + +####################################################################### + +# missing configuration at default location +! runuser -u lacme-account -- lacme-accountd --debug 2>"$STDERR" || fail +grepstderr -Fxq "Ignoring missing configuration file at default location /home/lacme-account/.config/lacme/lacme-accountd.conf" +grepstderr -Fxq "Error: 'privkey' is not specified" + +install -olacme-account -glacme-account -Ddm0700 -- \ + ~lacme-account/.config/lacme ~lacme-account/.local/share/lacme +mv -t ~lacme-account/.config/lacme /etc/lacme/account.key +chown lacme-account: ~lacme-account/.config/lacme/account.key + +cat >~lacme-account/.config/lacme/lacme-accountd.conf <<-EOF + privkey = file:%E/lacme/account.key + logfile = %h/.local/share/lacme/accountd.log +EOF + +# non-existent parent directory +! runuser -u lacme-account -- lacme-accountd --socket="/nonexistent/S.lacme" 2>"$STDERR" || fail +grepstderr -Fxq "Error: stat(/nonexistent): No such file or directory" + +# word-writable parent directory +! runuser -u lacme-account -- lacme-accountd --socket="%T/S.lacme" account 2>"$STDERR" || fail +grepstderr -Fxq "Error: Insecure permissions on /tmp" + +# unset XDG_RUNTIME_DIR +! runuser -u lacme-account -- lacme-accountd 2>"$STDERR" || fail +grepstderr "Error: Undefined expansion %t in \"%t/S.lacme\"" + +# non-existent $XDG_RUNTIME_DIR +! runuser -u lacme-account -- env XDG_RUNTIME_DIR="/nonexistent" lacme-accountd 2>"$STDERR" || fail +grepstderr -Fxq "Error: stat(/nonexistent): No such file or directory" + +# test running accountd +runuser -u lacme-account -- env XDG_RUNTIME_DIR=/home/lacme-account lacme-accountd --debug 2>"$STDERR" & PID=$! +sleep 1 +kill $PID || fail +wait || fail +grepstderr -Fxq "Using configuration file: /home/lacme-account/.config/lacme/lacme-accountd.conf" +grepstderr -Fxq "Starting lacme Account Key Manager at /home/lacme-account/S.lacme" + +# make sure errors are logged too +grep -F "Error: " ~lacme-account/.local/share/lacme/accountd.log + +# rotate the log and start accountd +rm -f ~lacme-account/.local/share/lacme/accountd.log +runuser -u lacme-account -- lacme-accountd --socket="$SOCKET" --quiet & PID=$! + +# run lacme(8) multiple times using that single lacme-accountd(1) instance +lacme --socket="$SOCKET" --debug account 2>"$STDERR" || fail +grepstderr -F "Received extra greeting data from accountd:" +lacme --socket="$SOCKET" newOrder 2>"$STDERR" || fail +test /etc/lacme/simpletest.rsa.crt -nt /etc/lacme/simpletest.rsa.key + +# terminate accountd and check that it removes the socket +kill $PID +wait +! test -e "$SOCKET" + +# ensure signature requests are logged +grep -Fq "Starting lacme Account Key Manager at /home/lacme-account/S.lacme" ~lacme-account/.local/share/lacme/accountd.log +grep -Fq "[0] >>> Accepted new connection" ~lacme-account/.local/share/lacme/accountd.log +grep -Fq "[1] >>> Accepted new connection" ~lacme-account/.local/share/lacme/accountd.log +grep -Fq "Shutting down and closing lacme Account Key Manager" ~lacme-account/.local/share/lacme/accountd.log +grep -F ">>> OK signing request:" ~lacme-account/.local/share/lacme/accountd.log + +# vim: set filetype=sh : diff --git a/tests/accountd-kid b/tests/accountd-kid new file mode 100644 index 0000000..e1bd63d --- /dev/null +++ b/tests/accountd-kid @@ -0,0 +1,59 @@ +# Hide JWK from ACME client and pass KID instead + +# get the key ID +lacme account 2>"$STDERR" || fail +keyid="$(sed -n "/^Key ID: / {s///p;q}" <"$STDERR")" + +# prepare accountd +adduser --disabled-password \ + --home /home/lacme-account \ + --gecos "lacme account user" \ + --quiet lacme-account + +install -olacme-account -glacme-account -Ddm0700 -- \ + ~lacme-account/.config/lacme ~lacme-account/.local/share/lacme +mv -t ~lacme-account/.config/lacme /etc/lacme/account.key +chown lacme-account: ~lacme-account/.config/lacme/account.key + +cat >~lacme-account/.config/lacme/lacme-accountd.conf <<-EOF + privkey = file:%E/lacme/account.key + logfile = %h/.local/share/lacme/accountd.log + keyid = $keyid +EOF + +SOCKET=~lacme-account/S.lacme +runuser -u lacme-account -- lacme-accountd --socket="$SOCKET" --quiet & PID=$! + +# newAccount resource fails as per RFC 8555 sec. 6.2 it requires a JWK +! lacme --socket="$SOCKET" account 2>"$STDERR" || fail +grepstderr -Fxq "WARNING: lacme-accountd supplied an empty JWK; try removing 'keyid' setting from lacme-accountd.conf if the ACME resource request fails." +grepstderr -Fxq "400 Bad Request (Parse error reading JWS)" +! grep -F ">>> OK signing request: header=" ~lacme-account/.local/share/lacme/accountd.log | \ + grep -vF ">>> OK signing request: header=base64url({\"alg\":\"RS256\",\"jwk\":{}," || exit 1 + +# rotate log and restart accountd +kill $PID +wait + +rm ~lacme-account/.local/share/lacme/accountd.log +runuser -u lacme-account -- lacme-accountd --socket="$SOCKET" --quiet & PID=$! + +# newOrder works fine without JWK +lacme --socket="$SOCKET" newOrder +test /etc/lacme/simpletest.rsa.crt -nt /etc/lacme/simpletest.rsa.key + +# and so does revokeCert (for requests authenticated with the account key) +lacme --socket="$SOCKET" revokeCert /etc/lacme/simpletest.rsa.crt +! lacme --socket="$SOCKET" revokeCert /etc/lacme/simpletest.rsa.crt 2>"$STDERR" || fail +grepstderr -Fxq "Revoking /etc/lacme/simpletest.rsa.crt" +grepstderr -Fxq "400 Bad Request (Certificate already revoked)" +grepstderr -Fxq "Warning: Couldn't revoke /etc/lacme/simpletest.rsa.crt" + +kill $PID +wait + +# make sure all signing requests have a KID +! grep -F ">>> OK signing request: header=" ~lacme-account/.local/share/lacme/accountd.log | \ + grep -vF ">>> OK signing request: header=base64url({\"alg\":\"RS256\",\"kid\":\"$keyid\"," || exit 1 + +# vim: set filetype=sh : diff --git a/tests/accountd-remote b/tests/accountd-remote new file mode 100644 index 0000000..9e7f812 --- /dev/null +++ b/tests/accountd-remote @@ -0,0 +1,55 @@ +# Remote accountd server process + +adduser --disabled-password --shell /bin/sh \ + --home /home/lacme-account \ + --gecos "lacme account user" \ + --quiet lacme-account + +chown lacme-account: /etc/lacme/account.key + +DEBIAN_FRONTEND="noninteractive" apt install -y --no-install-recommends \ + openssh-client openssh-server +ssh-keygen -N "" -f ~root/.ssh/id_rsa + +install -olacme-account -glacme-account -dm0700 ~lacme-account/.ssh +install -olacme-account -glacme-account -m0644 ~root/.ssh/id_rsa.pub ~lacme-account/.ssh/authorized_keys +{ echo -n "[127.0.0.1]:2222 "; cat /etc/ssh/ssh_host_rsa_key.pub; } >~root/.ssh/known_hosts + +cat >/etc/ssh/sshd_config <<-EOF + Port 2222 + ListenAddress 127.0.0.1 + ChallengeResponseAuthentication no + PasswordAuthentication no + UsePAM yes +EOF +install -oroot -groot -dm0755 /run/sshd +/usr/sbin/sshd + +sed -ri 's|^#?command\s*=.*/lacme-accountd$|command = ssh -Tp2222 -llacme-account 127.0.0.1 lacme-accountd|' /etc/lacme/lacme.conf +sed -ri 's|^#?config\s*=.*|config = /etc/lacme/lacme-accountd.conf|' /etc/lacme/lacme.conf +lacme newOrder --debug 2>"$STDERR" || fail # intentionally use --debug, ssh should tunnel stdin + stdout + stderr +test /etc/lacme/simpletest.rsa.crt -nt /etc/lacme/simpletest.rsa.key + +# and now with an authorized_keys(5) restriction +sed -ri "s|^[^#]|restrict,from=\"127.0.0.1\",command=\"/usr/bin/lacme-accountd --quiet --stdio\" &|" ~lacme-account/.ssh/authorized_keys +rm -vf /etc/lacme/simpletest.rsa.crt + +! lacme newOrder 2>"$STDERR" || fail # --config= (and --debug) should be ignored +grepstderr -Fxq "Error: 'privkey' is not specified" +grepstderr -Fxq "[simpletest-rsa] Error: Couldn't issue X.509 certificate!" + +install -olacme-account -glacme-account -Ddm0700 -- \ + ~lacme-account/.config/lacme ~lacme-account/.local/share/lacme +mv -t ~lacme-account/.config/lacme /etc/lacme/account.key +cat >~lacme-account/.config/lacme/lacme-accountd.conf <<-EOF + privkey = file:%E/lacme/account.key + logfile = %h/.local/share/lacme/accountd.log +EOF + +lacme newOrder +test /etc/lacme/simpletest.rsa.crt -nt /etc/lacme/simpletest.rsa.key + +# ensure signature requests are logged +grep -F ">>> OK signing request:" ~lacme-account/.local/share/lacme/accountd.log + +# vim: set filetype=sh : diff --git a/tests/apache2-proxy b/tests/apache2-proxy new file mode 100644 index 0000000..016b426 --- /dev/null +++ b/tests/apache2-proxy @@ -0,0 +1,33 @@ +# Use Apache2 as reverse proxy for lacme's internal webserver using the +# provided snippet + +# bind the webserver to the default listening address +sed -i 's|^listen\s*=|#&|' /etc/lacme/lacme.conf + +DEBIAN_FRONTEND="noninteractive" apt install -y --no-install-recommends apache2 curl + +a2enmod proxy_http +a2enconf lacme + +mkdir /run/apache2 +( set +eux && . /etc/apache2/envvars && apache2 ) + +# ensure that requests to the root URI and challenge URIs yield 502 Bad Gateway before starting the webserver +rv="$(curl -w"%{http_code}" -so/dev/null http://127.0.0.1/.well-known/acme-challenge/)"; [ $rv -eq 503 ] +rv="$(curl -w"%{http_code}" -so/dev/null http://127.0.0.1/.well-known/acme-challenge/foo)"; [ $rv -eq 503 ] + +lacme --debug newOrder 2>"$STDERR" || fail +test /etc/lacme/simpletest.rsa.crt -nt /etc/lacme/simpletest.rsa.key + +grepstderr -Fq "Forking ACME webserver bound to /run/lacme-www.socket, child PID " +grepstderr -Fq "Forking lacme-accountd, child PID " +grepstderr -Fq "Forking /usr/libexec/lacme/client, child PID " +grepstderr -Fq "Shutting down lacme-accountd" +grepstderr -Fq "Shutting down ACME webserver bound to /run/lacme-www.socket" +grepstderr -Eq "Incoming connection: GET /\.well-known/acme-challenge/\S+ HTTP/[0-9.]+$" + +# ensure apache2 was indeed used to serve challenge responses (Let's Encrypt caches validation results) +grep -E "\"GET /\.well-known/acme-challenge/\S+ HTTP/[0-9.]+\" 200 .* \(([^)]+; )*Let's Encrypt validation server(; [^)]+)*\)\"$" \ + /var/log/apache2/access.log + +# vim: set filetype=sh : diff --git a/tests/apache2-static b/tests/apache2-static new file mode 100644 index 0000000..f697cd7 --- /dev/null +++ b/tests/apache2-static @@ -0,0 +1,47 @@ +# Use Nginx to directly serve ACME challenge responses using the +# provided snippet + +# bind the webserver to the default listening address +sed -i 's|^listen\s*=|#&|' /etc/lacme/lacme.conf + +DEBIAN_FRONTEND="noninteractive" apt install -y --no-install-recommends apache2 curl + +ln -fs /etc/lacme/apache2-static.conf /etc/apache2/conf-available/lacme.conf +a2enmod proxy_http +a2enconf lacme + +mkdir /run/apache2 +( set +eux && . /etc/apache2/envvars && apache2 ) + +# 'challenge-directory' set to a non-existent directory +sed -ri 's|^#?challenge-directory\s*=.*|challenge-directory = /var/www/acme-challenge|' /etc/lacme/lacme.conf +! lacme newOrder 2>"$STDERR" || fail +grepstderr -Fqx "opendir(/var/www/acme-challenge): No such file or directory" + +# ensure that requests to the root URI and challenge URIs respectively yield 403 Forbidden (no index) and 404 Not Found +install -o_lacme-client -gwww-data -m0750 -d /var/www/acme-challenge +rv="$(curl -w"%{http_code}" -so/dev/null http://127.0.0.1/.well-known/acme-challenge/)"; [ $rv -eq 403 ] +rv="$(curl -w"%{http_code}" -so/dev/null http://127.0.0.1/.well-known/acme-challenge/foo)"; [ $rv -eq 404 ] + +# 'challenge-directory' set to a non-empty directory +touch /var/www/acme-challenge/.stamp +! lacme newOrder 2>"$STDERR" || fail +grepstderr -Fqx "Error: Refusing to use non-empty challenge directory /var/www/acme-challenge" + +rm -f /var/www/acme-challenge/.stamp +lacme --debug newOrder 2>"$STDERR" || fail +test /etc/lacme/simpletest.rsa.crt -nt /etc/lacme/simpletest.rsa.key + +ngrepstderr -Fq "Forking ACME webserver" +grepstderr -Fq "Using existing webserver on /var/www/acme-challenge" +grepstderr -Fq "Forking lacme-accountd, child PID " +grepstderr -Fq "Forking /usr/libexec/lacme/client, child PID " +grepstderr -Fq "Shutting down lacme-accountd" +ngrepstderr -Fq "Shutting down ACME webserver" +ngrepstderr -Eq "Incoming connection( from \S+)?: GET /\.well-known/acme-challenge/\S+ HTTP/[0-9.]+$" + +# ensure apache2 was indeed used to serve challenge responses (Let's Encrypt caches validation results) +grep -E "\"GET /\.well-known/acme-challenge/\S+ HTTP/[0-9.]+\" 200 .* \(([^)]+; )*Let's Encrypt validation server(; [^)]+)*\)\"$" \ + /var/log/apache2/access.log + +# vim: set filetype=sh : diff --git a/tests/cert-extensions b/tests/cert-extensions new file mode 100644 index 0000000..a397ee5 --- /dev/null +++ b/tests/cert-extensions @@ -0,0 +1,91 @@ +# X509v3 certificate extension, cf. x509v3_config(5ssl) + +x509_check() { + local cert="$1" ext out + out="$(mktemp --tmpdir)" + ext="basicConstraints,subjectAltName,keyUsage,extendedKeyUsage,tlsfeature" + openssl x509 -noout -subject -ext "$ext" -nameopt compat <"$cert" >"$out" + diff --unified --color=auto -b --label="a/${cert#/}" --label="b/${cert#/}" -- - "$out" +} + +# default settings (the ACME server adds a subjectAltName with the Common Name) +openssl genpkey -algorithm RSA -out /etc/lacme/test1.key +commonName="$(head -c10 /dev/urandom | base32 -w0 | tr "[A-Z]" "[a-z]").$DOMAINNAME" +cat >"/etc/lacme/lacme-certs.conf.d/test1.conf" <<- EOF + [test1] + certificate-key = /etc/lacme/test1.key + certificate-chain = /etc/lacme/test1.crt + subject = /CN=$commonName +EOF + +lacme newOrder test1 +test /etc/lacme/test1.crt -nt /etc/lacme/test1.key +x509_check /etc/lacme/test1.crt <<-EOF + subject=/CN=$commonName + X509v3 Key Usage: critical + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication + X509v3 Basic Constraints: critical + CA:FALSE + X509v3 Subject Alternative Name: + DNS:$commonName +EOF + +# subjectAltName +openssl genpkey -algorithm RSA -out /etc/lacme/test2.key +commonName="$(head -c10 /dev/urandom | base32 -w0 | tr "[A-Z]" "[a-z]").$DOMAINNAME" +subjectAltName="" +for i in $(seq 1 8); do + subjectAltName="${subjectAltName:+"$subjectAltName "}$(head -c10 /dev/urandom | base32 -w0 | tr "[A-Z]" "[a-z]").$DOMAINNAME" +done +cat >"/etc/lacme/lacme-certs.conf.d/test2.conf" <<- EOF + [test2] + certificate-key = /etc/lacme/test2.key + certificate-chain = /etc/lacme/test2.crt + subject = /CN=$commonName + subjectAltName = DNS:$(echo "$subjectAltName" | sed -r "s/ /, DNS:/g") +EOF + +lacme newOrder test2 +test /etc/lacme/test2.crt -nt /etc/lacme/test2.key +x509_check /etc/lacme/test2.crt <<-EOF + subject=/CN=$commonName + X509v3 Key Usage: critical + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication + X509v3 Basic Constraints: critical + CA:FALSE + X509v3 Subject Alternative Name: + DNS:$(echo "$commonName" "$subjectAltName" | tr " " "\\n" | sort -u | paste -sd" " | sed -r "s/ /, DNS:/g") +EOF + +# tlsfeature +openssl genpkey -algorithm RSA -out /etc/lacme/test3.key +commonName="$(head -c10 /dev/urandom | base32 -w0 | tr "[A-Z]" "[a-z]").$DOMAINNAME" +cat >"/etc/lacme/lacme-certs.conf.d/test3.conf" <<- EOF + [test3] + certificate-key = /etc/lacme/test3.key + certificate-chain = /etc/lacme/test3.crt + subject = /CN=$commonName + tlsfeature = status_request +EOF + +lacme newOrder test3 +test /etc/lacme/test3.crt -nt /etc/lacme/test3.key +x509_check /etc/lacme/test3.crt <<-EOF + subject=/CN=$commonName + X509v3 Key Usage: critical + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication + X509v3 Basic Constraints: critical + CA:FALSE + X509v3 Subject Alternative Name: + DNS:$commonName + TLS Feature: + status_request +EOF + +# vim: set filetype=sh : diff --git a/tests/cert-install b/tests/cert-install new file mode 100644 index 0000000..f2147d2 --- /dev/null +++ b/tests/cert-install @@ -0,0 +1,176 @@ +# Certificate installation and post-issuance notification + +# at least one of 'certificate' or 'certificate-chain' is required +cat >"/etc/lacme/lacme-certs.conf.d/bad1.conf" <<- EOF + [bad1] + certificate-key = /etc/lacme/bad1.key + subject = /CN=bad1.$DOMAINNAME +EOF +! lacme newOrder bad1 2>"$STDERR" || fail newOrder bad1 +grepstderr -Fxq "[bad1] Warning: Missing 'certificate' and 'certificate-chain', skipping" + +# 'subject' is required +cat >"/etc/lacme/lacme-certs.conf.d/bad2.conf" <<- EOF + [bad2] + certificate-key = /etc/lacme/bad2.key + certificate = /etc/lacme/bad2.crt +EOF +! lacme newOrder bad2 2>"$STDERR" || fail newOrder bad2 +grepstderr -Fxq "[bad2] Warning: Couldn't generate CSR, skipping" + +# 'certificate-key' is required +cat >"/etc/lacme/lacme-certs.conf.d/bad3.conf" <<- EOF + [bad3] + certificate = /etc/lacme/bad3.crt + subject = /CN=bad3.$DOMAINNAME +EOF +! lacme newOrder bad3 2>"$STDERR" || fail newOrder bad3 +grepstderr -Fxq "[bad3] Warning: Couldn't generate CSR, skipping" + + +# 'certificate' installs only the leaf certificate +openssl genpkey -algorithm RSA -out /etc/lacme/test1.key +subject="/CN=$(head -c10 /dev/urandom | base32 -w0).$DOMAINNAME" +cat >"/etc/lacme/lacme-certs.conf.d/test1.conf" <<- EOF + [test1] + certificate-key = /etc/lacme/test1.key + certificate = /etc/lacme/test1.crt + subject = $subject +EOF + +lacme newOrder test1 2>"$STDERR" || fail newOrder test1 +test /etc/lacme/test1.crt -nt /etc/lacme/test1.key +sed -n "0,/^-----END CERTIFICATE-----$/ p" /etc/lacme/test1.crt >/etc/lacme/test1.pem +diff --unified /etc/lacme/test1.crt /etc/lacme/test1.pem + + +check_hash() { + local p1="$1" p2 s1 s2 + s1="$(openssl x509 -noout -hash <"$p1")" + for p2 in /usr/share/lacme/ca-certificates.pem.*; do + s2="$(openssl x509 -noout -hash <"$p2")" + if [ "$s1" = "$s2" ]; then + return 0 + fi + done + return 1 +} +csplit -f /usr/share/lacme/ca-certificates.pem. /usr/share/lacme/ca-certificates.crt \ + "/-----BEGIN CERTIFICATE-----/" "{*}" +rm -f /usr/share/lacme/ca-certificates.pem.00 + +# 'certificate-chain' appends the chain of trust +openssl genpkey -algorithm RSA -out /etc/lacme/test2.key +cat >"/etc/lacme/lacme-certs.conf.d/test2.conf" <<- EOF + [test2] + certificate-key = /etc/lacme/test2.key + certificate-chain = /etc/lacme/test2.crt + subject = $subject +EOF + +lacme newOrder test2 2>"$STDERR" || fail newOrder test2 +test /etc/lacme/test2.crt -nt /etc/lacme/test2.key +csplit -f /etc/lacme/test2.chain.pem /etc/lacme/test2.crt \ + "/-----BEGIN CERTIFICATE-----/" "{*}" +test -s /etc/lacme/test2.chain.pem01 # leaf cert (00 is empty) +rm -f /etc/lacme/test2.chain.pem0[01] +test -s /etc/lacme/test2.chain.pem02 # depth 1 + +# all certificates at depth >=1 must be in our CA bundle +for p in /etc/lacme/test2.chain.pem*; do + check_hash "$p" +done + +# 'certificate' + 'certificate-chain' +openssl genpkey -algorithm RSA -out /etc/lacme/test3.key +cat >"/etc/lacme/lacme-certs.conf.d/test3.conf" <<- EOF + [test3] + certificate-key = /etc/lacme/test3.key + certificate = /etc/lacme/test3.pem + certificate-chain = /etc/lacme/test3.crt + subject = $subject +EOF + +lacme newOrder test3 2>"$STDERR" || fail newOrder test3 +test /etc/lacme/test3.pem -nt /etc/lacme/test3.key +test /etc/lacme/test3.crt -nt /etc/lacme/test3.key +csplit -f /etc/lacme/test3.chain.pem /etc/lacme/test3.crt \ + "/-----BEGIN CERTIFICATE-----/" "{*}" +sed -i "/^$/d" /etc/lacme/test3.chain.pem* +diff -q /etc/lacme/test3.chain.pem01 /etc/lacme/test3.pem +st="$(stat -c "%U:%G %#a" /etc/lacme/test3.pem)" +[ "$st" = "root:root 0644" ] +st="$(stat -c "%U:%G %#a" /etc/lacme/test3.crt)" +[ "$st" = "root:root 0644" ] + +# chmod user +openssl genpkey -algorithm RSA -out /etc/lacme/test4.key +cat >"/etc/lacme/lacme-certs.conf.d/test4.conf" <<- EOF + [test4] + certificate-key = /etc/lacme/test4.key + certificate = /etc/lacme/test4.pem + certificate-chain = /etc/lacme/test4.crt + chown = nobody + subject = $subject +EOF + +lacme newOrder test4 2>"$STDERR" || fail newOrder test4 +st="$(stat -c "%U:%G %#a" /etc/lacme/test4.pem)" +[ "$st" = "nobody:root 0644" ] +st="$(stat -c "%U:%G %#a" /etc/lacme/test4.crt)" +[ "$st" = "nobody:root 0644" ] + +# chmod user:group +openssl genpkey -algorithm RSA -out /etc/lacme/test5.key +cat >"/etc/lacme/lacme-certs.conf.d/test5.conf" <<- EOF + [test5] + certificate-key = /etc/lacme/test5.key + certificate = /etc/lacme/test5.pem + certificate-chain = /etc/lacme/test5.crt + chown = nobody:nogroup + subject = $subject +EOF + +lacme newOrder test5 2>"$STDERR" || fail newOrder test5 +st="$(stat -c "%U:%G %#a" /etc/lacme/test5.pem)" +[ "$st" = "nobody:nogroup 0644" ] +st="$(stat -c "%U:%G %#a" /etc/lacme/test5.crt)" +[ "$st" = "nobody:nogroup 0644" ] + +# chown +openssl genpkey -algorithm RSA -out /etc/lacme/test6.key +cat >"/etc/lacme/lacme-certs.conf.d/test6.conf" <<- EOF + [test6] + certificate-key = /etc/lacme/test6.key + certificate = /etc/lacme/test6.pem + certificate-chain = /etc/lacme/test6.crt + chmod = 0400 + subject = $subject +EOF + +lacme newOrder test6 2>"$STDERR" || fail newOrder test6 +st="$(stat -c "%U:%G %#a" /etc/lacme/test6.pem)" +[ "$st" = "root:root 0400" ] +st="$(stat -c "%U:%G %#a" /etc/lacme/test6.crt)" +[ "$st" = "root:root 0400" ] + +# post-issuance notification +openssl genpkey -algorithm RSA -out /etc/lacme/test7.key +cat >"/etc/lacme/lacme-certs.conf.d/test7.conf" <<- EOF + [test7] + certificate-key = /etc/lacme/test7.key + certificate-chain = /etc/lacme/test7.crt + subject = $subject + notify = touch /tmp/test7.notify +EOF + +lacme newOrder test7 2>"$STDERR" || fail newOrder test7 +grepstderr -Fxq "Running notification command \`touch /tmp/test7.notify\`" +test -e /tmp/test7.notify + +rm -f /tmp/test7.notify +lacme newOrder test7 2>"$STDERR" || fail newOrder test7 +ngrepstderr -Fq "Running notification command" +! test -e /tmp/test7.notify + +# vim: set filetype=sh : diff --git a/tests/cert-renew b/tests/cert-renew new file mode 100644 index 0000000..aca1b34 --- /dev/null +++ b/tests/cert-renew @@ -0,0 +1,21 @@ +# Skip renewal for recent enough certificates + +lacme newOrder 2>"$STDERR" || fail +grepstderr -Exq "Installing X.509 certificate chain /etc/lacme/simpletest.rsa.crt" +ino1="$(stat -c%i /etc/lacme/simpletest.rsa.crt)" + +lacme newOrder 2>"$STDERR" || fail +grepstderr -Ex "^\[simpletest-rsa\] Valid until .*, skipping" + +lacme newOrder --force 2>"$STDERR" || fail +grepstderr -Exq "Installing X.509 certificate chain /etc/lacme/simpletest.rsa.crt" +ino2="$(stat -c%i /etc/lacme/simpletest.rsa.crt)" +test "$ino1" != "$ino2" # we never truncate existing certificates + +sed -ri 's|^#?min-days\s*=.*|min-days = 90|' /etc/lacme/lacme-certs.conf +lacme newOrder 2>"$STDERR" || fail +grepstderr -Exq "Installing X.509 certificate chain /etc/lacme/simpletest.rsa.crt" +ino3="$(stat -c%i /etc/lacme/simpletest.rsa.crt)" +test "$ino2" != "$ino3" # we never truncate existing certificates + +# vim: set filetype=sh : diff --git a/tests/cert-revoke b/tests/cert-revoke new file mode 100644 index 0000000..f3d585e --- /dev/null +++ b/tests/cert-revoke @@ -0,0 +1,32 @@ +# Certification revocation, using either the account key or the +# certificate key + +# also check issuance for ECDSA keys +openssl genpkey -algorithm EC -out /etc/lacme/simpletest.ecdsa.key \ + -pkeyopt ec_paramgen_curve:P-256 \ + -pkeyopt ec_param_enc:named_curve + +sed "s/rsa/ecdsa/" /etc/lacme/lacme-certs.conf.d/simpletest-rsa.conf > \ + /etc/lacme/lacme-certs.conf.d/simpletest-ecdsa.conf + +# issue both RSA and ECDSA certificates +lacme newOrder 2>"$STDERR" || fail newOrder +test /etc/lacme/simpletest.rsa.crt -nt /etc/lacme/simpletest.rsa.key +test /etc/lacme/simpletest.ecdsa.crt -nt /etc/lacme/simpletest.ecdsa.key + +# revoke the ECDSA certificate using the account key +lacme revokeCert /etc/lacme/simpletest.ecdsa.crt +! lacme revokeCert /etc/lacme/simpletest.ecdsa.crt 2>"$STDERR" || fail +grepstderr -Fxq "Revoking /etc/lacme/simpletest.ecdsa.crt" +grepstderr -Fxq "400 Bad Request (Certificate already revoked)" +grepstderr -Fxq "Warning: Couldn't revoke /etc/lacme/simpletest.ecdsa.crt" + +# and the RSA certificate using the service key +mv -vfT /etc/lacme/simpletest.rsa.key /etc/lacme/account.key +lacme revokeCert /etc/lacme/simpletest.rsa.crt +! lacme revokeCert /etc/lacme/simpletest.rsa.crt 2>"$STDERR" || fail +grepstderr -Fxq "Revoking /etc/lacme/simpletest.rsa.crt" +grepstderr -Fxq "400 Bad Request (Certificate already revoked)" +grepstderr -Fxq "Warning: Couldn't revoke /etc/lacme/simpletest.rsa.crt" + +# vim: set filetype=sh : diff --git a/tests/cert-verify b/tests/cert-verify new file mode 100644 index 0000000..49629f2 --- /dev/null +++ b/tests/cert-verify @@ -0,0 +1,43 @@ +# Certificate verification + +DEBIAN_FRONTEND="noninteractive" apt install -y --no-install-recommends ssl-cert + +# add staging root certificates to the trust store to emulate the production environment +for ca in /usr/share/lacme/letsencrypt-stg-root-*.pem; do + ln -sT "$ca" "/usr/local/share/ca-certificates/$(basename -s.pem "$ca").crt" +done +update-ca-certificates + +# test (modified) trust store for intermediate certificates +openssl verify -no-CAfile -CApath /etc/ssl/certs -show_chain /usr/share/lacme/letsencrypt-stg-int-*.pem +openssl verify -no-CApath -CAfile /etc/ssl/certs/ca-certificates.crt -show_chain /usr/share/lacme/letsencrypt-stg-int-*.pem + +mv /usr/share/lacme/ca-certificates.crt /usr/share/lacme/ca-certificates.crt.back +! lacme newOrder 2>"$STDERR" || fail +grepstderr -Fxq "Can't open /usr/share/lacme/ca-certificates.crt for reading, No such file or directory" +grepstderr -Fxq "[simpletest-rsa] Error: Received invalid X.509 certificate from ACME server!" + +# verification error for unrelated CA bundle +cat /etc/ssl/certs/ssl-cert-snakeoil.pem >/usr/share/lacme/ca-certificates.crt +! lacme newOrder 2>"$STDERR" || fail +grepstderr -Fxq "error 20 at 0 depth lookup: unable to get local issuer certificate" +grepstderr -Fxq "[simpletest-rsa] Error: Received invalid X.509 certificate from ACME server!" + +# verification error when the CA bundle contains only the root certificates +cat /usr/share/lacme/letsencrypt-stg-root-*.pem >/usr/share/lacme/ca-certificates.crt +! lacme newOrder 2>"$STDERR" || fail +grepstderr -Fxq "error 20 at 0 depth lookup: unable to get local issuer certificate" +grepstderr -Fxq "[simpletest-rsa] Error: Received invalid X.509 certificate from ACME server!" + +# verification error when the CA bundle contains only the intermediate certificates +cat /usr/share/lacme/letsencrypt-stg-int-*.pem >/usr/share/lacme/ca-certificates.crt +! lacme newOrder 2>"$STDERR" || fail +grepstderr -Fxq "error 2 at 1 depth lookup: unable to get issuer certificate" +grepstderr -Fxq "[simpletest-rsa] Error: Received invalid X.509 certificate from ACME server!" + +# use saved bundle as custom CAfile +sed -ri 's|^#?CAfile\s*=.*|CAfile = /usr/share/lacme/ca-certificates.crt.back|' /etc/lacme/lacme-certs.conf +lacme newOrder 2>"$STDERR" || fail +test /etc/lacme/simpletest.rsa.crt -nt /etc/lacme/simpletest.rsa.key + +# vim: set filetype=sh : diff --git a/tests/drop-privileges b/tests/drop-privileges new file mode 100644 index 0000000..0596e31 --- /dev/null +++ b/tests/drop-privileges @@ -0,0 +1,166 @@ +# Check privilige drop: UID/GID changes, chdir, environment, and file +# descriptors + +# create wrapper to inspect processes +STATUSDIR="/dev/shm/lacme-wrap" +install -oroot -groot -m0755 /dev/stdin /run/lacme-wrap <<-EOF + #!/bin/sh + set -ue + PATH="/usr/bin:/bin" + export PATH + + prefix="$STATUSDIR/\${1##*[/-]}" + cat </proc/\$\$/status >"\$prefix/status" + pwd >"\$prefix/cwd" + stat -c "%U:%G %#a" . >"\$prefix/cwd-mod" + sort -z </proc/\$\$/environ | tr "\\0" "\\n" >"\$prefix/environ" + ( find -P /proc/\$\$/fd -mindepth 1 \! -lname "\$0" -printf "%P %#m %U:%G %l\\n" >"\$prefix/fd" ) + + exec "\$@" +EOF + +# also check privilege drop for the spawned accountd +adduser --system --group \ + --home /nonexistent --no-create-home \ + --gecos "lacme account user" \ + --quiet lacme-account +sed -ri 's|^#user\s*=\s*$|user = lacme-account|' /etc/lacme/lacme.conf +sed -ri 's|^#group\s*=\s*$|group = lacme-account|' /etc/lacme/lacme.conf +chown lacme-account: /etc/lacme/account.key + +install -oroot -groot -dm0755 -- "$STATUSDIR" +install -olacme-account -groot -dm0700 -- "$STATUSDIR/accountd" +install -o_lacme-client -groot -dm0700 -- "$STATUSDIR/client" +install -o_lacme-www -groot -dm0700 -- "$STATUSDIR/webserver" + +# test with a group that's not the primary group (nogroup) of _lacme-www etc +addgroup --system nogroup2 +sed -ri 's|^#?group\s*=\s*nogroup$|group = nogroup2|' /etc/lacme/lacme.conf +sed -ri 's|^#?command\s*=.*/lacme-accountd$|command = /run/lacme-wrap /usr/bin/lacme-accountd|' /etc/lacme/lacme.conf +sed -ri 's|^#?command\s*=.*/lacme/client$|command = /run/lacme-wrap /usr/libexec/lacme/client|' /etc/lacme/lacme.conf +sed -ri 's|^#?command\s*=.*/lacme/webserver$|command = /run/lacme-wrap /usr/libexec/lacme/webserver|' /etc/lacme/lacme.conf +sed -ri 's|^#?config\s*=\s*$|config = /etc/lacme/lacme-accountd.conf|' /etc/lacme/lacme.conf + +check_status() { + local path="$STATUSDIR/$1/status" user="$2" group="$3" + UID="$(getent passwd "$user" | cut -sd: -f3)" + GID="$(getent group "$group" | cut -sd: -f3)" + [ -n "$UID" -a -n "$GID" ] || return 1 + grep -Ex "Uid:\\s+$UID\\s+$UID\\s+$UID\\s+$UID" "$path" || return 1 + grep -Ex "Gid:\\s+$GID\\s+$GID\\s+$GID\\s+$GID" "$path" || return 1 + grep -Ex "Groups:\s+$GID\s*" "$path" || return 1 +} +check_cwd() { + local path="$STATUSDIR/$1/cwd" dir="$2" cwd + cwd="$(cat <"$path")" || return 1 + [ "$cwd" = "$dir" ] || return 1 +} + +check_accountd() { + local socket_ino stderr prefix="$STATUSDIR/accountd" + check_status accountd lacme-account lacme-account || return 1 + check_cwd accountd / || return 1 + + diff --label="a/accountd/environ" --label="b/accountd/environ" \ + --color=auto --unified "$prefix/environ" - <<-EOF + HOME=/nonexistent + LOGNAME=lacme-account + PATH=/usr/bin:/bin + SHELL=/usr/sbin/nologin + TERM=$TERM + USER=lacme-account + EOF + + stderr="$(readlink -e "/proc/$$/fd/2")" + socket_ino="$(sed -rn '/^0 .* socket:\[([0-9]+)\]$/ {s//\1/p;q}' "$prefix/fd")" + [ -n "$socket_ino" ] || return 1 + grep -Fxq "0 0700 $UID:$GID socket:[$socket_ino]" "$prefix/fd" || return 1 + grep -Fxq "1 0700 $UID:$GID socket:[$socket_ino]" "$prefix/fd" || return 1 + grep -Fxq "2 0700 $UID:$GID $stderr" "$prefix/fd" || return 1 + sed -ri '\#^[012] #d' "$prefix/fd" + ! test -s "$prefix/fd" || return 1 +} +check_client() { + local command="$1" cwd="$2" UID GID stdout stderr prefix="$STATUSDIR/client" + check_status client _lacme-client nogroup2 + check_cwd client "$cwd" + + diff --label="a/client/environ" --label="b/client/environ" \ + --color=auto --unified "$prefix/environ" - <<-EOF + DEBUG=0 + HOME=/nonexistent + LOGNAME=_lacme-client + PATH=/usr/bin:/bin + SHELL=/usr/sbin/nologin + TERM=$TERM + USER=_lacme-client + EOF + + stdout="$(readlink -e "/proc/$$/fd/1")" + stderr="$(readlink -e "/proc/$$/fd/2")" + if [ "$command" = "account" ]; then # no pipe + grep -Fxq "0 0500 $UID:$GID /dev/null" "$prefix/fd" || return 1 + grep -Fxq "1 0700 $UID:$GID $stdout" "$prefix/fd" || return 1 + elif [ "$command" = "order" ]; then + grep -Exq "0 0500 $UID:$GID pipe:\[[0-9]+\]" "$prefix/fd" || return 1 + grep -Exq "1 0300 $UID:$GID pipe:\[[0-9]+\]" "$prefix/fd" || return 1 + else + exit 1 + fi + grep -Fxq "2 0700 $UID:$GID $stderr" "$prefix/fd" || return 1 + sed -ri '\#^[012] #d' "$prefix/fd" + + grep -Exq "[0-9]+ 0700 $UID:$GID socket:\[[0-9]+\]" "$prefix/fd" || return 1 + sed -ri '0,\#^[0-9]+ .* socket:\[[0-9]+\]$# {//d}' "$prefix/fd" + + grep -Exq "[0-9]+ 0500 $UID:$GID /etc/lacme/lacme\.conf" "$prefix/fd" || return 1 + sed -ri '0,\#^[0-9]+ .* /etc/lacme/lacme\.conf$# {//d}' "$prefix/fd" + ! test -s "$prefix/fd" || return 1 +} +check_webserver() { + local cwd="$1" UID GID stdout stderr prefix="$STATUSDIR/webserver" + check_status webserver _lacme-www nogroup2 + check_cwd webserver "$cwd" + + diff --label="a/webserver/environ" --label="b/webserver/environ" \ + --color=auto --unified "$prefix/environ" - <<-EOF + DEBUG=0 + HOME=/nonexistent + LOGNAME=_lacme-www + PATH=/usr/bin:/bin + SHELL=/usr/sbin/nologin + TERM=$TERM + USER=_lacme-www + EOF + + stdout="$(readlink -e "/proc/$$/fd/1")" + stderr="$(readlink -e "/proc/$$/fd/2")" + grep -Fxq "0 0500 $UID:$GID /dev/null" "$prefix/fd" || return 1 + grep -Fxq "1 0700 $UID:$GID $stdout" "$prefix/fd" || return 1 + grep -Fxq "2 0700 $UID:$GID $stderr" "$prefix/fd" || return 1 + sed -ri '\#^[012] #d' "$prefix/fd" + + grep -Exq "[0-9]+ 0700 $UID:$GID socket:\[[0-9]+\]" "$prefix/fd" || return 1 + sed -ri '0,\#^[0-9]+ .* socket:\[[0-9]+\]$# {//d}' "$prefix/fd" + ! test -s "$prefix/fd" || return 1 +} + +lacme account +check_accountd +check_client account / +! test -e "$STATUSDIR/webserver/status" # account 'command' doesn't start the webserver + +lacme newOrder +check_accountd +challenge_dir="$(cat "$STATUSDIR/webserver/cwd")" +[ "${challenge_dir#"/tmp/acme-challenge."}" != "$challenge_dir" ] || exit 1 +check_client order "$challenge_dir" +check_webserver "$challenge_dir" + +# the temporary challenge directory is created with permissive mode +diff --label="a/webserver/cwd" --label="b/webserver/cwd" \ + --color=auto --unified "$STATUSDIR/webserver/cwd-mod" - <<-EOF + _lacme-client:root 0755 +EOF + +# vim: set filetype=sh : diff --git a/tests/nginx-proxy b/tests/nginx-proxy new file mode 100644 index 0000000..b16fd10 --- /dev/null +++ b/tests/nginx-proxy @@ -0,0 +1,35 @@ +# Use Nginx as reverse proxy for lacme's internal webserver using the +# provided snippet + +# bind the webserver to the default listening address +sed -i 's|^listen\s*=|#&|' /etc/lacme/lacme.conf + +DEBIAN_FRONTEND="noninteractive" apt install -y --no-install-recommends nginx-light curl +cat >/etc/nginx/sites-enabled/default <<-EOF + server { + listen 80 default_server; + server_name _; + include /etc/lacme/nginx.conf; + } +EOF +nginx + +# ensure that requests to the root URI and challenge URIs yield 502 Bad Gateway before starting the webserver +rv="$(curl -w"%{http_code}" -so/dev/null http://127.0.0.1/.well-known/acme-challenge/)"; [ $rv -eq 502 ] +rv="$(curl -w"%{http_code}" -so/dev/null http://127.0.0.1/.well-known/acme-challenge/foo)"; [ $rv -eq 502 ] + +lacme --debug newOrder 2>"$STDERR" || fail +test /etc/lacme/simpletest.rsa.crt -nt /etc/lacme/simpletest.rsa.key + +grepstderr -Fq "Forking ACME webserver bound to /run/lacme-www.socket, child PID " +grepstderr -Fq "Forking lacme-accountd, child PID " +grepstderr -Fq "Forking /usr/libexec/lacme/client, child PID " +grepstderr -Fq "Shutting down lacme-accountd" +grepstderr -Fq "Shutting down ACME webserver bound to /run/lacme-www.socket" +grepstderr -Eq "Incoming connection: GET /\.well-known/acme-challenge/\S+ HTTP/[0-9.]+$" + +# ensure nginx was indeed used to serve challenge responses (Let's Encrypt caches validation results) +grep -E "\"GET /\.well-known/acme-challenge/\S+ HTTP/[0-9.]+\" 200 .* \(([^)]+; )*Let's Encrypt validation server(; [^)]+)*\)\"$" \ + /var/log/nginx/access.log + +# vim: set filetype=sh : diff --git a/tests/nginx-static b/tests/nginx-static new file mode 100644 index 0000000..4bb0e0d --- /dev/null +++ b/tests/nginx-static @@ -0,0 +1,48 @@ +# Use Nginx to directly serve ACME challenge responses using the +# provided snippet + +# bind the webserver to the default listening address +sed -i 's|^listen\s*=|#&|' /etc/lacme/lacme.conf + +DEBIAN_FRONTEND="noninteractive" apt install -y --no-install-recommends nginx-light curl +cat >/etc/nginx/sites-enabled/default <<-EOF + server { + listen 80 default_server; + server_name _; + include /etc/lacme/nginx-static.conf; + } +EOF +nginx + +# 'challenge-directory' set to a non-existent directory +sed -ri 's|^#?challenge-directory\s*=.*|challenge-directory = /var/www/acme-challenge|' /etc/lacme/lacme.conf +! lacme newOrder 2>"$STDERR" || fail +grepstderr -Fqx "opendir(/var/www/acme-challenge): No such file or directory" + +# ensure that requests to the root URI and challenge URIs respectively yield 403 Forbidden (no index) and 404 Not Found +install -o_lacme-client -gwww-data -m0750 -d /var/www/acme-challenge +rv="$(curl -w"%{http_code}" -so/dev/null http://127.0.0.1/.well-known/acme-challenge/)"; [ $rv -eq 403 ] +rv="$(curl -w"%{http_code}" -so/dev/null http://127.0.0.1/.well-known/acme-challenge/foo)"; [ $rv -eq 404 ] + +# 'challenge-directory' set to a non-empty directory +touch /var/www/acme-challenge/.stamp +! lacme newOrder 2>"$STDERR" || fail +grepstderr -Fqx "Error: Refusing to use non-empty challenge directory /var/www/acme-challenge" + +rm -f /var/www/acme-challenge/.stamp +lacme --debug newOrder 2>"$STDERR" || fail +test /etc/lacme/simpletest.rsa.crt -nt /etc/lacme/simpletest.rsa.key + +ngrepstderr -Fq "Forking ACME webserver" +grepstderr -Fq "Using existing webserver on /var/www/acme-challenge" +grepstderr -Fq "Forking lacme-accountd, child PID " +grepstderr -Fq "Forking /usr/libexec/lacme/client, child PID " +grepstderr -Fq "Shutting down lacme-accountd" +ngrepstderr -Fq "Shutting down ACME webserver" +ngrepstderr -Eq "Incoming connection( from \S+)?: GET /\.well-known/acme-challenge/\S+ HTTP/[0-9.]+$" + +# ensure nginx was indeed used to serve challenge responses (Let's Encrypt caches validation results) +grep -E "\"GET /\.well-known/acme-challenge/\S+ HTTP/[0-9.]+\" 200 .* \(([^)]+; )*Let's Encrypt validation server(; [^)]+)*\)\"$" \ + /var/log/nginx/access.log + +# vim: set filetype=sh : diff --git a/tests/old-accountd b/tests/old-accountd new file mode 100644 index 0000000..b44f7ec --- /dev/null +++ b/tests/old-accountd @@ -0,0 +1,30 @@ +# IPC test between lacme(8) and ancient lacme-accountd(1) 0.2 from Debian jessie + +adduser --disabled-password \ + --home /home/lacme-account \ + --gecos "lacme account user" \ + --quiet lacme-account + +install -olacme-account -glacme-account -Ddm0700 -- ~lacme-account/.config/lacme +chown lacme-account: /etc/lacme/account.key + +cat >~lacme-account/.config/lacme/lacme-accountd.conf <<-EOF + privkey = file:/etc/lacme/account.key +EOF + +echo "deb http://deb.debian.org/debian stretch main" >>/etc/apt/sources.list +DEBIAN_FRONTEND="noninteractive" apt update +DEBIAN_FRONTEND="noninteractive" apt install -y --no-install-recommends \ + --reinstall --allow-downgrades \ + -oDPkg::Options::="--force-confdef" -oDPkg::Options::="--force-overwrite" \ + lacme-accountd/stretch + +SOCKET=~lacme-account/S.lacme +runuser -u lacme-account -- lacme-accountd --socket="$SOCKET" & PID=$! +lacme --socket="$SOCKET" account +lacme --socket="$SOCKET" newOrder + +kill $PID +wait + +# vim: set filetype=sh : diff --git a/tests/old-lacme b/tests/old-lacme new file mode 100644 index 0000000..fa7d827 --- /dev/null +++ b/tests/old-lacme @@ -0,0 +1,36 @@ +# IPC test between recent lacme-accountd(1) and ancient lacme(8) 0.5 from Debian buster +# (we don't try earlier versions as we need v2 support of the ACME API) + +adduser --disabled-password \ + --home /home/lacme-account \ + --gecos "lacme account user" \ + --quiet lacme-account + +install -olacme-account -glacme-account -Ddm0700 -- ~lacme-account/.config/lacme +chown lacme-account: /etc/lacme/account.key +mv -f /usr/share/lacme/ca-certificates.crt /usr/share/lacme/ca-certificates.crt.back + +cat >~lacme-account/.config/lacme/lacme-accountd.conf <<-EOF + privkey = file:/etc/lacme/account.key +EOF + +echo "deb http://deb.debian.org/debian buster main" >>/etc/apt/sources.list +DEBIAN_FRONTEND="noninteractive" apt update +DEBIAN_FRONTEND="noninteractive" apt install -y --no-install-recommends \ + --reinstall --allow-downgrades \ + -oDPkg::Options::="--force-confdef" -oDPkg::Options::="--force-overwrite" \ + lacme/buster + +# restore staging environment +mv -f /usr/share/lacme/ca-certificates.crt.back /usr/share/lacme/ca-certificates.crt + +SOCKET=~lacme-account/S.lacme +runuser -u lacme-account -- lacme-accountd --socket="$SOCKET" & PID=$! +sed -ri "s/^\[accountd]$/#&/" /etc/lacme/lacme.conf # https://bugs.debian.org/955767 +lacme --socket="$SOCKET" account +lacme --socket="$SOCKET" newOrder + +kill $PID +wait + +# vim: set filetype=sh : diff --git a/tests/register b/tests/register new file mode 100644 index 0000000..0273377 --- /dev/null +++ b/tests/register @@ -0,0 +1,8 @@ +# Register new account key (or update the contact/ToS URI) + +lacme account --tos-agreed --register "mailto:noreply+lacme=$$@guilhem.org" + +# should return info about the account +lacme account + +# vim: set filetype=sh : diff --git a/tests/spec-expansion b/tests/spec-expansion new file mode 100644 index 0000000..a268637 --- /dev/null +++ b/tests/spec-expansion @@ -0,0 +1,130 @@ +# %-specifiers expansion + +# lacme --config=, all specifiers, root privileges +! lacme --config="%C %E %t %h %T %g %G %u %U %%.conf" account 2>"$STDERR" || fail +grepstderr -Fxq "Can't open /var/cache /etc /run /root /tmp root 0 root 0 %.conf: No such file or directory" + +# lacme --config=, all specifiers, root privileges, defined XDG_* +! env XDG_CACHE_HOME=/foo/cache XDG_CONFIG_HOME=/foo/config XDG_RUNTIME_DIR=/foo/run HOME=/foo/home USER=myuser TMPDIR=/foo/tmp \ + lacme --config="%C %E %t %h %T %g %G %u %U %%.conf" account 2>"$STDERR" || fail +grepstderr -Fxq "Can't open /var/cache /etc /run /root /foo/tmp root 0 root 0 %.conf: No such file or directory" + +# lacme --config=, all specifiers, non-root, unset XDG_RUNTIME_DIR +! runuser -u nobody -- lacme --config="%C %E %t %h %T %g %G %u %U %%.conf" account 2>"$STDERR" || fail +grepstderr -Fxq "Error: Undefined expansion %t in \"%C %E %t %h %T %g %G %u %U %%.conf\"" + +# lacme --config=, all specifiers, non-root, defined XDG_RUNTIME_DIR, no other XDG_* +! runuser -u nobody -g www-data -- env XDG_RUNTIME_DIR=/foo/run \ + lacme --config="%C %E %t %h %T %g %G %u %U %%.conf" account 2>"$STDERR" || fail +grepstderr -Fxq "Can't open /nonexistent/.cache /nonexistent/.config /foo/run /nonexistent /tmp www-data 33 nobody 65534 %.conf: No such file or directory" + +# lacme --config=, all specifiers, non-root, defined XDG_* +! runuser -u nobody -- env XDG_CACHE_HOME=/foo/cache XDG_CONFIG_HOME=/foo/config XDG_RUNTIME_DIR=/foo/run HOME=/foo/home USER=myuser TMPDIR=/foo/tmp \ + lacme --config="%C %E %t %h %T %g %G %u %U %%.conf" account 2>"$STDERR" || fail +grepstderr -Fxq "Can't open /foo/cache /foo/config /foo/run /nonexistent /foo/tmp nogroup 65534 nobody 65534 %.conf: No such file or directory" + +# lacme --socket= +! lacme --config="%E/lacme/lacme.conf" --socket="%t/S.lacme2" account --debug 2>"$STDERR" || fail +grepstderr -Fxq "Using configuration file: /etc/lacme/lacme.conf" +grepstderr -Fxq "Can't stat /run/S.lacme2: No such file or directory (Is lacme-accountd running?)" + +# 'challenge-directory' setting (expands before privilege drop) +sed -ri 's|^#?challenge-directory\s*=.*|challenge-directory = /nonexistent/%u:%g|' /etc/lacme/lacme.conf +! lacme newOrder --debug 2>"$STDERR" || fail +grepstderr -Fq "Using existing webserver on /nonexistent/root:root" + +# lacme --config-certs= and 'config-certs' settings (expands before privilege drop) +! lacme newOrder --debug nonexistent 2>"$STDERR" || fail +grepstderr -Fxq "Reading /etc/lacme/lacme-certs.conf" + +sed -ri 's|^#?config-certs\s*=.*|config-certs = /nonexistent/%u:%g.conf|' /etc/lacme/lacme.conf +! lacme newOrder --debug nonexistent 2>"$STDERR" || fail +grepstderr -Fxq "Reading /nonexistent/root:root.conf" + +! lacme newOrder --config-certs="%E/lacme/certs.conf.d" --debug nonexistent 2>"$STDERR" || fail +grepstderr -vFxq "Reading /etc/lacme/lacme-certs.conf" +grepstderr -Fxq "Reading /etc/lacme/certs.conf.d" + +# 'config' setting in [accountd] section (expands after privilege drop) +sed -ri 's|^#?config\s*=\s*$|config = /nonexistent/%u:%g.conf|' /etc/lacme/lacme.conf +! lacme account 2>"$STDERR" || fail +grepstderr -Fxq "Error: Failed to open file '/nonexistent/root:root.conf' for reading: No such file or directory" + +sed -ri 's|^#?user\s*=\s*$|user = nobody|' /etc/lacme/lacme.conf +! lacme account 2>"$STDERR" || fail +grepstderr -Fxq "Error: Failed to open file '/nonexistent/nobody:root.conf' for reading: No such file or directory" + +# 'command' setting in [accountd] section (expands after privilege drop) +sed -ri 's|^#?command\s*=.*/lacme-accountd$|command = /usr/bin/lacme-accountd --%u|' /etc/lacme/lacme.conf +! lacme account 2>"$STDERR" || fail +grepstderr -Fxq "Unknown option: nobody" + +sed -ri 's|^#?command\s*=.*/lacme-accountd .*|command = /nonexistent/%u/%g %u %g|' /etc/lacme/lacme.conf +! lacme account 2>"$STDERR" || fail +grepstderr -Eq "^Can't exec \"/nonexistent/nobody/root\": No such file or directory" + + +####################################################################### + +# lacme-accountd --config=, all specifiers, root privileges +! lacme-accountd --config="%C %E %t %h %T %g %G %u %U %%.conf" 2>"$STDERR" || fail +grepstderr -Fxq "Error: Failed to open file '/var/cache /etc /run /root /tmp root 0 root 0 %.conf' for reading: No such file or directory" + +# lacme-accountd --config=, all specifiers, root privileges, defined XDG_* +! env XDG_CACHE_HOME=/foo/cache XDG_CONFIG_HOME=/foo/config XDG_RUNTIME_DIR=/foo/run HOME=/foo/home USER=myuser TMPDIR=/foo/tmp \ + lacme-accountd --config="%C %E %t %h %T %g %G %u %U %%.conf" 2>"$STDERR" || fail +grepstderr -Fxq "Error: Failed to open file '/var/cache /etc /run /root /foo/tmp root 0 root 0 %.conf' for reading: No such file or directory" + +# lacme-accountd --config=, all specifiers, non-root, unset XDG_RUNTIME_DIR +! runuser -u nobody -- lacme-accountd --config="%C %E %t %h %T %g %G %u %U %%.conf" account 2>"$STDERR" || fail +grepstderr -Fxq "Error: Undefined expansion %t in \"%C %E %t %h %T %g %G %u %U %%.conf\"" + +# lacme-accountd --config=, all specifiers, non-root, defined XDG_RUNTIME_DIR, no other XDG_* +! runuser -u nobody -g www-data -- env XDG_RUNTIME_DIR=/foo/run \ + lacme-accountd --config="%C %E %t %h %T %g %G %u %U %%.conf" 2>"$STDERR" || fail +grepstderr -Fxq "Error: Failed to open file '/nonexistent/.cache /nonexistent/.config /foo/run /nonexistent /tmp www-data 33 nobody 65534 %.conf' for reading: No such file or directory" + +# lacme-accountd --config=, all specifiers, non-root, defined XDG_* +! runuser -u nobody -- env XDG_CACHE_HOME=/foo/cache XDG_CONFIG_HOME=/foo/config XDG_RUNTIME_DIR=/foo/run HOME=/foo/home USER=myuser TMPDIR=/foo/tmp \ + lacme-accountd --config="%C %E %t %h %T %g %G %u %U %%.conf" 2>"$STDERR" || fail +grepstderr -Fxq "Error: Failed to open file '/foo/cache /foo/config /foo/run /nonexistent /foo/tmp nogroup 65534 nobody 65534 %.conf' for reading: No such file or directory" + +# lacme-accountd --privkey= +! lacme-accountd --privkey="file:%h/lacme-accountd.key" --debug 2>"$STDERR" || fail +grepstderr -Fxq "Error: Can't open /root/lacme-accountd.key: No such file or directory" + +# lacme-accountd, default socket location +lacme-accountd --debug 2>"$STDERR" & PID=$! +sleep 1 +kill $PID || fail +wait || fail +grepstderr -Fxq "Using configuration file: /etc/lacme/lacme-accountd.conf" +grepstderr -Fxq "Starting lacme Account Key Manager at /run/S.lacme" +grepstderr -Fxq "Unlinking /run/S.lacme" + +# lacme-accountd --config= --socket= --privkey= +ln -s lacme-accountd.conf /etc/lacme/accountd.conf +lacme-accountd --config="%E/lacme/accountd.conf" --socket="%t/S.lacme2" --privkey="file:%E/lacme/account.key" --debug 2>"$STDERR" & PID=$! +sleep 1 +kill $PID || fail +wait || fail +grepstderr -Fxq "Using configuration file: /etc/lacme/accountd.conf" +grepstderr -Fxq "Starting lacme Account Key Manager at /run/S.lacme2" +grepstderr -Fxq "Unlinking /run/S.lacme2" + +# lacme-accountd, custom 'socket' setting +sed -ri 's|^#?socket\s*=.*|socket = %t/S.lacme3|' /etc/lacme/lacme-accountd.conf +lacme-accountd --debug 2>"$STDERR" & PID=$! +sleep 1 +kill $PID || fail +wait || fail +grepstderr -Fxq "Using configuration file: /etc/lacme/lacme-accountd.conf" +grepstderr -Fxq "Starting lacme Account Key Manager at /run/S.lacme3" +grepstderr -Fxq "Unlinking /run/S.lacme3" + +# lacme-accountd, custom 'privkey' setting +sed -ri 's|^privkey\s*=.*|privkey = file:%h/lacme-accountd.key|' /etc/lacme/lacme-accountd.conf +! lacme-accountd --debug 2>"$STDERR" || fail +grepstderr -Fxq "Error: Can't open /root/lacme-accountd.key: No such file or directory" + +# vim: set filetype=sh : diff --git a/tests/webservers b/tests/webservers new file mode 100644 index 0000000..0cadea5 --- /dev/null +++ b/tests/webservers @@ -0,0 +1,16 @@ +# Multiple webservers (each binding to a different address) + +sed -ri 's|^#?listen\s*=.*|listen = [::]:80 127.0.0.1:8080|' /etc/lacme/lacme.conf +lacme --debug newOrder 2>"$STDERR" || fail newOrder +test /etc/lacme/simpletest.rsa.crt -nt /etc/lacme/simpletest.rsa.key + +grepstderr -Fq "Forking ACME webserver bound to [::]:80, child PID " +grepstderr -Fq "Forking ACME webserver bound to 127.0.0.1:8080, child PID " +grepstderr -Fq "Forking lacme-accountd, child PID " +grepstderr -Fq "Forking /usr/libexec/lacme/client, child PID " +grepstderr -Fq "Shutting down lacme-accountd" +grepstderr -Fq "Shutting down ACME webserver bound to [::]:80" +grepstderr -Fq "Shutting down ACME webserver bound to 127.0.0.1:8080" +grepstderr -Eq "Incoming connection( from \S+)?: GET /\.well-known/acme-challenge/\S+ HTTP/[0-9.]+$" + +# vim: set filetype=sh : |