Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify installation using a script #4

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "acme.sh"]
path = acme.sh
url = https://github.com/acmesh-official/acme.sh
96 changes: 26 additions & 70 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,90 +14,47 @@ using lighttpd.

## Key features

* Uses [Acme.sh](https://github.com/acmesh-official/acme.sh) client for free TLS certificates from [Let's Encrypt](https://letsencrypt.org/)
* Uses the [acme.sh](https://github.com/acmesh-official/acme.sh) client to
obtain free TLS certificates from [Let's Encrypt](https://letsencrypt.org/)
* Uses hook scripts to simplify issue and renewal process
* Opportunistically opens and closes firewall port 80
* Restarts lighttpd to deploy certificates
* Configures lighttpd for TLSv1.3 only following the [Mozilla SSL Configuration
Generator](https://ssl-config.mozilla.org/).
* Disables lighttpd from running insecurely on port 80

* HSTS handles the odd case where you forget or are too lazy to type in the
`https://` at the start. Just load the `https://` URL once and your browser
will remember for you forever.
* Configures lighttpd to upgrade unencrypted connections.
* Configures the [HSTS
header](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security).

## Installation

This installs the project and files in `/srv`, which is the default path for
external storage on a Turris device, but you can install wherever you'd like.

1. Download this project:
1. Download this project, including the `acme.sh` submodule:

opkg install git-http
git clone https://github.com/davidjb/turris-omnia-tls.git /srv/turris-omnia-tls

1. Determine the latest version of `acme.sh` by checking
https://github.com/acmesh-official/acme.sh/releases. Note the release
version (which is the tag name); you'll use it in the next step,
substituting for `[VERSION]`.

1. Install `acme.sh` client and its dependency, `socat`; taking care to
substitute `[VERSION]` and `[YOUREMAIL]` with correct values:

opkg install socat
git clone https://github.com/acmesh-official/acme.sh -b [VERSION] /srv/acme.sh
cd /srv/acme.sh
./acme.sh --install --home /srv/.acme.sh --nocron --email [YOUREMAIL] --set-default-ca --server letsencrypt

1. Disable the existing SSL configuration by removing the
`lighttpd-https-cert` package:

opkg remove lighttpd-https-cert

1. Stop `updater` from automatically reinstalling the `lighttpd-https-cert`
package:

cp /srv/turris-omnia-tls/updater_custom.lua /etc/updater/conf.d/no-upstream-ssl.lua

1. Make sure the `lighttpd-mod-openssl` package is installed:

opkg install lighttpd-mod-openssl
git clone --recurse-submodules https://github.com/davidjb/turris-omnia-tls.git /srv/turris-omnia-tls

1. Lighttpd needs to stop listening on port 80 so modify
`/etc/lighttpd/conf.d/90-turris-root.conf` to comment out these lines:
1. Run the `install.sh` script and answer the questions:

$SERVER["socket"] == "*:80" { }
$SERVER["socket"] == "[::]:80" { }
/srv/turris-omnia-tls/install.sh

1. Stop lighttpd; we will enable it again shortly:
1. Alternatively, the answer to the questions can be provided via environment
variables for non-interactive/scripted use (check the source of `install.sh`
for a current list of supported variables):

/etc/init.d/lighttpd stop
TOT_EMAIL="[email protected]" TOT_FQDN="turris.example.com" /srv/turris-omnia-tls/install.sh

1. Issue the certificate, taking care to specify your FQDN in place of
`[YOUR.DOMAIN.COM]`:
## Uninstallation

/srv/turris-omnia-tls/cert-issue.sh [YOUR.DOMAIN.COM]
Note that this will not touch issued certificates, which will be left in place
under `/etc/lighttpd/certs`. Also, `acme.sh` related state information will be
left untouched under the `/srv/turris-omnia-tls/var/` hierarchy.

1. Reconfigure lighttpd with the supplied custom configuration:
1. Run the `uninstall.sh` script to uninstall modifications performed by the
`install.sh` script:

cp /srv/turris-omnia-tls/lighttpd_custom.conf /etc/lighttpd/conf.d/40-ssl-acme-enable.conf

Inside this file, replace the `domain.example.com` placeholders with your
FQDN. You can do this automatically by running the following command,
again taking care to specify your FQDN in place of `[YOUR.DOMAIN.COM]`:

sed -i 's/domain.example.com/[YOUR.DOMAIN.COM]/g' /etc/lighttpd/conf.d/40-ssl-acme-enable.conf

1. Restart `lighttpd`:

/etc/init.d/lighttpd start

1. Add crontab entry for renewal; pick a random minute and hour:

echo '34 0 * * * /srv/turris-omnia-tls/cert-renew.sh > /dev/null' >> /etc/crontabs/root

The renewal process will automatically re-use the settings for certificates
that were issued.
/srv/turris-omnia-tls/uninstall.sh

## Issuing more certificates

Expand All @@ -111,15 +68,14 @@ inside `cert-issue.sh` before you run it the first time or go and modify the con
that `acme.sh` generates in `/etc/lighttpd/certs/extra.example.com/extra.example.com.conf`,
where `extra.example.com` is the name of your domain.

## Upgrading acme.sh
## Upgrading turris-omnia-tls and acme.sh

Run the following; after `fetch`ing, you'll see the latest version tag:

cd /srv/acme.sh
git fetch
git checkout [VERSION]
./acme.sh --install --home /srv/.acme.sh --nocron
Run the following:

cd /srv/turris-omnia-tls
git pull --recurse-submodules
./install.sh

## License

MIT. See LICENSE.txt.
1 change: 1 addition & 0 deletions acme.sh
Submodule acme.sh added at e6959f
38 changes: 27 additions & 11 deletions cert-issue.sh
Original file line number Diff line number Diff line change
@@ -1,18 +1,34 @@
#!/bin/sh
#!/usr/bin/env bash
#
# Copyright (C) 2018-2022 David Beitey <[email protected]>
# Copyright (C) 2022 David Härdeman <[email protected]>
#
# SPDX-License-Identifier: MIT
#
# This script is used once for the initial issuance of a certificate.

set -o nounset -o pipefail -o errexit -o errtrace

cd "${0%/*}"
tothome="$(pwd)"
certhome="/etc/lighttpd/certs"
acmehome="${tothome}/var/acme"
ca_path="/etc/ssl/certs"
webroot="${tothome}/var/webroot"
domain="$1"

mkdir -p "$certhome"
/srv/.acme.sh/acme.sh \
--home "/srv/.acme.sh" \
mkdir -p "$webroot"

"${acmehome}/acme.sh" \
--home "${acmehome}" \
--issue \
--standalone \
--domain "$domain" \
--webroot "${webroot}" \
--domain "${domain}" \
--keylength 4096 \
--certhome "$certhome" \
--ca-path "$ca_path" \
--pre-hook "/srv/turris-omnia-tls/pre-hook.sh '$domain'" \
--post-hook "/srv/turris-omnia-tls/post-hook.sh '$domain'" \
--renew-hook "/srv/turris-omnia-tls/renew-hook.sh '$domain'" \
--reloadcmd "/srv/turris-omnia-tls/reloadcmd.sh '$domain'"
--certhome "${certhome}" \
--ca-path "${ca_path}" \
--pre-hook "${tothome}/pre-hook.sh '$domain'" \
--post-hook "${tothome}/post-hook.sh '$domain'" \
--renew-hook "${tothome}/renew-hook.sh '$domain'" \
--reloadcmd "${tothome}/reloadcmd.sh '$domain'"
25 changes: 20 additions & 5 deletions cert-renew.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
#!/bin/sh
#!/usr/bin/env bash
#
# Copyright (C) 2018-2022 David Beitey <[email protected]>
# Copyright (C) 2022 David Härdeman <[email protected]>
#
# SPDX-License-Identifier: MIT
#
# This script renews already issued certs and is meant to be executed
# periodically via e.g. cron.

set -o nounset -o pipefail -o errexit -o errtrace

cd "${0%/*}"
tothome="$(pwd)"
certhome="/etc/lighttpd/certs"
acmehome="${tothome}/var/acme"
ca_path="/etc/ssl/certs"
/srv/.acme.sh/acme.sh \
--home "/srv/.acme.sh" \

"${acmehome}/acme.sh" \
--home "${tothome}" \
--cron \
--certhome "$certhome" \
--ca-path "$ca_path"
--certhome "${certhome}" \
--ca-path "${ca_path}"
148 changes: 148 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
#!/usr/bin/env bash
#
# Copyright (C) 2022 David Härdeman <[email protected]>
#
# SPDX-License-Identifier: MIT
#
# This script automates the installation of the Turris Omnia TLS project
# on a Turris Omnia router by asking a few questions (or obtaining them
# from environment variables).

set -o nounset -o pipefail -o errexit -o errtrace

cd "${0%/*}"

TOT_SCRIPT="$(basename "${0}")"
readonly TOT_SCRIPT
TOT_BASEDIR="$(pwd)"
readonly TOT_BASEDIR
readonly TOT_ACMEDIR="${TOT_BASEDIR}/acme.sh"
readonly TOT_ACMEHOME="${TOT_BASEDIR}/var/acme"

# Supported environment variables
TOT_EMAIL="${TOT_EMAIL:-}"
TOT_FQDN="${TOT_FQDN:-}"
TOT_HOSTNAME="${TOT_HOSTNAME:-}"

log_message() {
# Display a log message if the script is used interactively, otherwise
# write the log message to syslog.
local msg="${1:-}"

if [ -n "${msg}" ]; then
if [ -t 0 ]; then
echo "${TOT_SCRIPT}: ${msg}" 1>&2
else
if type logger > /dev/null 2>&1; then
logger -t "${TOT_SCRIPT}[${PID}]" "${msg}"
fi
fi
fi
}

template_install() {
# Install a template file to a given location, replacing variables
# formatted as %VARIABLE% with the proper value
local src="${1:-}"
local dst="${2:-}"

if [ -n "${dst}" ] && [ -n "${src}" ]; then
sed \
-e "s|%TOT_EMAIL%|${TOT_EMAIL}|g" \
-e "s|%TOT_FQDN%|${TOT_FQDN}|g" \
-e "s|%TOT_HOSTNAME%|${TOT_HOSTNAME}|g" \
-e "s|%TOT_BASEDIR%|${TOT_BASEDIR}|g" \
"${src}" > "${dst}"
fi
}

add_crontab() {
local crontab="${1:-}"
local cmd="${TOT_BASEDIR}/cert-renew.sh"

if [ -n "${crontab}" ]; then
echo 'MAILTO=""' > "${crontab}"
echo "$(( RANDOM % 60 )) $(( RANDOM % 24 )) * * * root ${cmd} > /dev/null" >> "${crontab}"
fi
}

prompt_input() {
# Ask the user for some input, unless a given variable is
# already defined (e.g. via environment variables)
local varname="${1:-}"
local msg="${2:-}"
local value=""

if [ -n "${varname}" ] && [ -z "${!varname}" ] && [ -n "${msg}" ]; then
if [ ! -t 0 ]; then
log_message "non-interactive mode failed, missing options"
exit 1
fi

read -r -p "${msg}: " value

if [ -z "${value}" ]; then
log_message "missing value for $varname"
exit 1
fi
declare -g "${varname}"="${value}"
fi
}

prompt_input "TOT_EMAIL" "Enter your email address"
prompt_input "TOT_FQDN" "Enter the FQDN of your router"
if [ -n "${TOT_FQDN}" ] && [ -z "${TOT_HOSTNAME}" ]; then
TOT_HOSTNAME="$(echo "${TOT_FQDN}" | cut -d '.' -f1)"
fi

log_message "Installing the socat package"
opkg install socat > /dev/null

log_message "Installing the acme.sh script"
mkdir -p "${TOT_ACMEHOME}"
# Note that acme.sh fails to perform the installation if we don't chdir
cd "${TOT_ACMEDIR}"

./acme.sh \
--home "${TOT_ACMEHOME}" \
--install \
--no-profile \
--nocron \
--email "${TOT_EMAIL}"

log_message "Setting the default CA"
./acme.sh \
--home "${TOT_ACMEHOME}" \
--set-default-ca \
--server letsencrypt

cd "${TOT_BASEDIR}"

log_message "Removing the lighttpd-https-cert package"
opkg remove lighttpd-https-cert > /dev/null

log_message "Stop updater from automatically reinstalling lighttpd-https-cert"
cp updater_custom.lua /etc/updater/conf.d/no-upstream-ssl.lua

log_message "Installing the lighttpd-mod-openssl package"
opkg install lighttpd-mod-openssl > /dev/null

log_message "Installing custom lighttp webroot configuration"
template_install "lighttpd_webroot.conf" "/etc/lighttpd/conf.d/39-acme-webroot.conf"

log_message "Restarting lighttpd"
/etc/init.d/lighttpd restart
sleep 3

log_message "Issuing certificate"
./cert-issue.sh "${TOT_FQDN}"

log_message "Installing custom lighttp TLS configuration"
template_install "lighttpd_tls.conf" "/etc/lighttpd/conf.d/40-acme-tls.conf"

log_message "Restarting lighttpd again"
/etc/init.d/lighttpd restart
sleep 3

log_message "Adding a cron job for certificate renewal"
add_crontab "/etc/cron.d/turris-omnia-tls"
14 changes: 8 additions & 6 deletions lighttpd_custom.conf → lighttpd_tls.conf
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
# https://ssl-config.mozilla.org/
# Last verified: 22 October 2022

server.port = 443

# Port 80 is disabled, but this doesn't hurt...
$HTTP["scheme"] == "http" {
url.redirect = ("" => "https://${url.authority}${url.path}${qsa}")
$HTTP["host"] == "%TOT_HOSTNAME%" {
url.redirect = ("" => "http://%TOT_FQDN%${url.path}${qsa}")
} else $HTTP["url"] !~ "^/.well-known/acme-challenge/" {
url.redirect = ("" => "https://${url.authority}${url.path}${qsa}")
}
}

$HTTP["scheme"] == "https" {
Expand All @@ -24,7 +25,8 @@ $HTTP["scheme"] == "https" {
# (to avoid having to repeat ssl.* directives in both ":443" and "[::]:443")
$SERVER["socket"] == ":443" { ssl.engine = "enable" }
$SERVER["socket"] == "[::]:443" { ssl.engine = "enable" }
ssl.privkey = "/etc/lighttpd/certs/domain.example.com/domain.example.com.key"
ssl.pemfile = "/etc/lighttpd/certs/domain.example.com/domain.example.com.cer"
ssl.privkey = "/etc/lighttpd/certs/%TOT_FQDN%/%TOT_FQDN%.key"
ssl.pemfile = "/etc/lighttpd/certs/%TOT_FQDN%/%TOT_FQDN%.cer"
ssl.ca-file = "/etc/lighttpd/certs/%TOT_FQDN%/fullchain.cer"
ssl.openssl.ssl-conf-cmd = ("MinProtocol" => "TLSv1.3")
ssl.openssl.ssl-conf-cmd += ("Options" => "-ServerPreference")
6 changes: 6 additions & 0 deletions lighttpd_webroot.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Handle the acme.sh webroot

alias.url = (
"/.well-known/acme-challenge" =>
"%TOT_BASEDIR%/var/webroot/.well-known/acme-challenge"
)
Loading