diff options
author | Toke Høiland-Jørgensen <toke@toke.dk> | 2017-04-09 18:43:34 +0200 |
---|---|---|
committer | Toke Høiland-Jørgensen <toke@toke.dk> | 2017-04-26 16:32:24 +0200 |
commit | 34ed7a9f2c660dbffd90ae12657ba3c1c68b1174 (patch) | |
tree | e873d992803b306940e655e19db2643e44a01f79 /net | |
parent | 424f4e2c632ec00b1480b43fdcc5750a94d787c2 (diff) |
acme: Support running in webroot mode, detect other daemons on port 80
For configurations where another web server is running on port 80, running
acme.sh in standalone mode fails. Try to detect this and refuse to run; and
allow the user to configure a webroot directory to use the running webserver for
certificate verification.
This also updates acme.sh to the latest version.
Signed-off-by: Toke Høiland-Jørgensen <toke@toke.dk>
Diffstat (limited to 'net')
-rw-r--r-- | net/acme/Makefile | 3 | ||||
-rw-r--r-- | net/acme/files/acme-cbi.lua | 9 | ||||
-rw-r--r-- | net/acme/files/acme.config | 3 | ||||
-rw-r--r-- | net/acme/files/run.sh | 125 |
4 files changed, 105 insertions, 35 deletions
diff --git a/net/acme/Makefile b/net/acme/Makefile index a7f066450..88148545b 100644 --- a/net/acme/Makefile +++ b/net/acme/Makefile @@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=acme PKG_SOURCE_VERSION:=7b40cbe8c1a52041351524bcde4b37665a7cdf79 -PKG_VERSION:=1.5 +PKG_VERSION:=1.6 PKG_RELEASE:=1 PKG_LICENSE:=GPLv3 @@ -47,6 +47,7 @@ define Build/Compile endef define Package/acme/install + $(INSTALL_DIR) $(1)/etc/acme $(INSTALL_DIR) $(1)/etc/config $(INSTALL_CONF) ./files/acme.config $(1)/etc/config/acme $(INSTALL_DIR) $(1)/etc/init.d diff --git a/net/acme/files/acme-cbi.lua b/net/acme/files/acme-cbi.lua index a4f7956de..c20cba203 100644 --- a/net/acme/files/acme-cbi.lua +++ b/net/acme/files/acme-cbi.lua @@ -25,11 +25,12 @@ s.anonymous = true st = s:option(Value, "state_dir", translate("State directory"), translate("Where certs and other state files are kept.")) st.rmempty = false -st.datatype = "string" +st.datatype = "directory" ae = s:option(Value, "account_email", translate("Account email"), translate("Email address to associate with account key.")) ae.rmempty = false +ae.datatype = "minlength(1)" d = s:option(Flag, "debug", translate("Enable debug logging")) d.rmempty = false @@ -56,6 +57,12 @@ u = cs:option(Flag, "update_uhttpd", translate("Use for uhttpd"), "(only select this for one certificate).")) u.rmempty = false +wr = cs:option(Value, "webroot", translate("Webroot directory"), + translate("Webserver root directory. Set this to the webserver " .. + "document root to run Acme in webroot mode. The web " .. + "server must be accessible from the internet on port 80.")) +wr.rmempty = false + dom = cs:option(DynamicList, "domains", translate("Domain names"), translate("Domain names to include in the certificate. " .. "The first name will be the subject name, subsequent names will be alt names. " .. diff --git a/net/acme/files/acme.config b/net/acme/files/acme.config index c5cd7d3ea..af12ce1fb 100644 --- a/net/acme/files/acme.config +++ b/net/acme/files/acme.config @@ -5,7 +5,8 @@ config acme config cert 'example' option enabled 0 - option use_staging 0 + option use_staging 1 option keylength 2048 option update_uhttpd 1 + option webroot "" list domains example.org diff --git a/net/acme/files/run.sh b/net/acme/files/run.sh index 0a4cad1c5..6bedaca16 100644 --- a/net/acme/files/run.sh +++ b/net/acme/files/run.sh @@ -27,45 +27,85 @@ check_cron() /etc/init.d/cron start } -debug() +log() { - [ "$DEBUG" -eq "1" ] && echo "$@" >&2 + logger -t acme -s -p daemon.info "$@" } -pre_checks() +err() { - echo "Running pre checks." - check_cron - - [ -d "$STATE_DIR" ] || mkdir -p "$STATE_DIR" - - if [ -e /etc/init.d/uhttpd ]; then + logger -t acme -s -p daemon.err "$@" +} - UHTTPD_LISTEN_HTTP=$(uci get uhttpd.main.listen_http) +debug() +{ + [ "$DEBUG" -eq "1" ] && logger -t acme -s -p daemon.debug "$@" +} - uci set uhttpd.main.listen_http='' - uci commit uhttpd - /etc/init.d/uhttpd reload || return 1 - fi +get_listeners() +{ + netstat -nptl 2>/dev/null | awk 'match($4, /:80$/){split($7, parts, "/"); print parts[2];}' | uniq | tr "\n" " " +} - iptables -I input_rule -p tcp --dport 80 -j ACCEPT || return 1 - ip6tables -I input_rule -p tcp --dport 80 -j ACCEPT || return 1 +pre_checks() +{ + main_domain="$1" + + log "Running pre checks for $main_domain." + + listeners="$(get_listeners)" + debug "port80 listens: $listeners" + + case "$listeners" in + "uhttpd") + debug "Found uhttpd listening on port 80; trying to disable." + + UHTTPD_LISTEN_HTTP=$(uci get uhttpd.main.listen_http) + + if [ -z "$UHTTPD_LISTEN_HTTP" ]; then + err "$main_domain: Unable to find uhttpd listen config." + err "Manually disable uhttpd or set webroot to continue." + return 1 + fi + + uci set uhttpd.main.listen_http='' + uci commit uhttpd || return 1 + if ! /etc/init.d/uhttpd reload ; then + uci set uhttpd.main.listen_http="$UHTTPD_LISTEN_HTTP" + uci commit uhttpd + return 1 + fi + ;; + "") + debug "Nothing listening on port 80." + ;; + *) + err "$main_domain: Cannot run in standalone mode; another daemon is listening on port 80." + err "Disable other daemon or set webroot to continue." + return 1 + ;; + esac + + iptables -I input_rule -p tcp --dport 80 -j ACCEPT -m comment --comment "ACME" || return 1 + ip6tables -I input_rule -p tcp --dport 80 -j ACCEPT -m comment --comment "ACME" || return 1 debug "v4 input_rule: $(iptables -nvL input_rule)" debug "v6 input_rule: $(ip6tables -nvL input_rule)" - debug "port80 listens: $(netstat -ntpl | grep :80)" return 0 } post_checks() { - echo "Running post checks (cleanup)." - iptables -D input_rule -p tcp --dport 80 -j ACCEPT - ip6tables -D input_rule -p tcp --dport 80 -j ACCEPT + log "Running post checks (cleanup)." + # The comment ensures we only touch our own rules. If no rules exist, that + # is fine, so hide any errors + iptables -D input_rule -p tcp --dport 80 -j ACCEPT -m comment --comment "ACME" 2>/dev/null + ip6tables -D input_rule -p tcp --dport 80 -j ACCEPT -m comment --comment "ACME" 2>/dev/null - if [ -e /etc/init.d/uhttpd ]; then + if [ -e /etc/init.d/uhttpd ] && [ -n "$UHTTPD_LISTEN_HTTP" ]; then uci set uhttpd.main.listen_http="$UHTTPD_LISTEN_HTTP" uci commit uhttpd /etc/init.d/uhttpd reload + UHTTPD_LISTEN_HTTP= fi } @@ -102,12 +142,14 @@ issue_cert() local main_domain local moved_staging=0 local failed_dir + local webroot config_get_bool enabled "$section" enabled 0 config_get_bool use_staging "$section" use_staging config_get_bool update_uhttpd "$section" update_uhttpd config_get domains "$section" domains config_get keylength "$section" keylength + config_get webroot "$section" webroot [ "$enabled" -eq "1" ] || return @@ -116,13 +158,17 @@ issue_cert() set -- $domains main_domain=$1 + [ -n "$webroot" ] || pre_checks "$main_domain" || return 1 + + log "Running ACME for $main_domain" + if [ -e "$STATE_DIR/$main_domain" ]; then if [ "$use_staging" -eq "0" ] && is_staging "$main_domain"; then - echo "Found previous cert issued using staging server. Moving it out of the way." + log "Found previous cert issued using staging server. Moving it out of the way." mv "$STATE_DIR/$main_domain" "$STATE_DIR/$main_domain.staging" moved_staging=1 else - echo "Found previous cert config. Issuing renew." + log "Found previous cert config. Issuing renew." $ACME --home "$STATE_DIR" --renew -d "$main_domain" $acme_args || return 1 return 0 fi @@ -130,17 +176,28 @@ issue_cert() acme_args="$acme_args $(for d in $domains; do echo -n "-d $d "; done)" - acme_args="$acme_args --standalone" acme_args="$acme_args --keylength $keylength" [ -n "$ACCOUNT_EMAIL" ] && acme_args="$acme_args --accountemail $ACCOUNT_EMAIL" [ "$use_staging" -eq "1" ] && acme_args="$acme_args --staging" + if [ -z "$webroot" ]; then + log "Using standalone mode" + acme_args="$acme_args --standalone" + else + if [ ! -d "$webroot" ]; then + err "$main_domain: Webroot dir '$webroot' does not exist!" + return 1 + fi + log "Using webroot dir: $webroot" + acme_args="$acme_args --webroot \"$webroot\"" + fi + if ! $ACME --home "$STATE_DIR" --issue $acme_args; then failed_dir="$STATE_DIR/${main_domain}.failed-$(date +%s)" - echo "Issuing cert for $main_domain failed. Moving state to $failed_dir" >&2 + err "Issuing cert for $main_domain failed. Moving state to $failed_dir" [ -d "$STATE_DIR/$main_domain" ] && mv "$STATE_DIR/$main_domain" "$failed_dir" if [ "$moved_staging" -eq "1" ]; then - echo "Restoring staging certificate" >&2 + err "Restoring staging certificate" mv "$STATE_DIR/${main_domain}.staging" "$STATE_DIR/${main_domain}" fi return 1 @@ -152,6 +209,7 @@ issue_cert() # commit and reload is in post_checks fi + post_checks } load_vars() @@ -163,19 +221,22 @@ load_vars() DEBUG=$(config_get "$section" debug) } -if [ -n "$CHECK_CRON" ]; then - check_cron - exit 0 -fi +check_cron +[ -n "$CHECK_CRON" ] && exit 0 config_load acme config_foreach load_vars acme -pre_checks || exit 1 +if [ -z "$STATE_DIR" ] || [ -z "$ACCOUNT_EMAIL" ]; then + err "state_dir and account_email must be set" + exit 1 +fi + +[ -d "$STATE_DIR" ] || mkdir -p "$STATE_DIR" + trap err_out HUP TERM trap int_out INT config_foreach issue_cert cert -post_checks exit 0 |