diff --git a/deploy/haproxy.sh b/deploy/haproxy.sh index d638abb8..4b6ca0e1 100644 --- a/deploy/haproxy.sh +++ b/deploy/haproxy.sh @@ -36,6 +36,19 @@ # Note: This functionality requires HAProxy was compiled against # a version of OpenSSL that supports this. # +# export DEPLOY_HAPROXY_HOT_UPDATE="yes" +# export DEPLOY_HAPROXY_STATS_SOCKET="UNIX:/run/haproxy/admin.sock" +# +# OPTIONAL: Deploy the certificate over the HAProxy stats socket without +# needing to reload HAProxy. Default is "no". +# +# Require the socat binary. DEPLOY_HAPROXY_STATS_SOCKET variable uses the socat +# address format. +# +# export DEPLOY_HAPROXY_MASTER_CLI="UNIX:/run/haproxy-master.sock" +# +# OPTIONAL: To use the master CLI with DEPLOY_HAPROXY_HOT_UPDATE="yes" instead +# of a stats socket, use this variable. ######## Public functions ##################### @@ -46,6 +59,7 @@ haproxy_deploy() { _ccert="$3" _cca="$4" _cfullchain="$5" + _cmdpfx="" # Some defaults DEPLOY_HAPROXY_PEM_PATH_DEFAULT="/etc/haproxy" @@ -53,6 +67,8 @@ haproxy_deploy() { DEPLOY_HAPROXY_BUNDLE_DEFAULT="no" DEPLOY_HAPROXY_ISSUER_DEFAULT="no" DEPLOY_HAPROXY_RELOAD_DEFAULT="true" + DEPLOY_HAPROXY_HOT_UPDATE_DEFAULT="no" + DEPLOY_HAPROXY_STATS_SOCKET_DEFAULT="UNIX:/run/haproxy/admin.sock" _debug _cdomain "${_cdomain}" _debug _ckey "${_ckey}" @@ -86,6 +102,11 @@ haproxy_deploy() { _savedomainconf Le_Deploy_haproxy_pem_name "${Le_Deploy_haproxy_pem_name}" elif [ -z "${Le_Deploy_haproxy_pem_name}" ]; then Le_Deploy_haproxy_pem_name="${DEPLOY_HAPROXY_PEM_NAME_DEFAULT}" + # We better not have '*' as the first character + if [ "${Le_Deploy_haproxy_pem_name%%"${Le_Deploy_haproxy_pem_name#?}"}" = '*' ]; then + # removes the first characters and add a _ instead + Le_Deploy_haproxy_pem_name="_${Le_Deploy_haproxy_pem_name#?}" + fi fi # BUNDLE is optional. If not provided then assume "${DEPLOY_HAPROXY_BUNDLE_DEFAULT}" @@ -118,6 +139,36 @@ haproxy_deploy() { Le_Deploy_haproxy_reload="${DEPLOY_HAPROXY_RELOAD_DEFAULT}" fi + # HOT_UPDATE is optional. If not provided then assume "${DEPLOY_HAPROXY_HOT_UPDATE_DEFAULT}" + _getdeployconf DEPLOY_HAPROXY_HOT_UPDATE + _debug2 DEPLOY_HAPROXY_HOT_UPDATE "${DEPLOY_HAPROXY_HOT_UPDATE}" + if [ -n "${DEPLOY_HAPROXY_HOT_UPDATE}" ]; then + Le_Deploy_haproxy_hot_update="${DEPLOY_HAPROXY_HOT_UPDATE}" + _savedomainconf Le_Deploy_haproxy_hot_update "${Le_Deploy_haproxy_hot_update}" + elif [ -z "${Le_Deploy_haproxy_hot_update}" ]; then + Le_Deploy_haproxy_hot_update="${DEPLOY_HAPROXY_HOT_UPDATE_DEFAULT}" + fi + + # STATS_SOCKET is optional. If not provided then assume "${DEPLOY_HAPROXY_STATS_SOCKET_DEFAULT}" + _getdeployconf DEPLOY_HAPROXY_STATS_SOCKET + _debug2 DEPLOY_HAPROXY_STATS_SOCKET "${DEPLOY_HAPROXY_STATS_SOCKET}" + if [ -n "${DEPLOY_HAPROXY_STATS_SOCKET}" ]; then + Le_Deploy_haproxy_stats_socket="${DEPLOY_HAPROXY_STATS_SOCKET}" + _savedomainconf Le_Deploy_haproxy_stats_socket "${Le_Deploy_haproxy_stats_socket}" + elif [ -z "${Le_Deploy_haproxy_stats_socket}" ]; then + Le_Deploy_haproxy_stats_socket="${DEPLOY_HAPROXY_STATS_SOCKET_DEFAULT}" + fi + + # MASTER_CLI is optional. No defaults are used. When the master CLI is used, + # all commands are sent with a prefix. + _getdeployconf DEPLOY_HAPROXY_MASTER_CLI + _debug2 DEPLOY_HAPROXY_MASTER_CLI "${DEPLOY_HAPROXY_MASTER_CLI}" + if [ -n "${DEPLOY_HAPROXY_MASTER_CLI}" ]; then + Le_Deploy_haproxy_stats_socket="${DEPLOY_HAPROXY_MASTER_CLI}" + _savedomainconf Le_Deploy_haproxy_stats_socket "${Le_Deploy_haproxy_stats_socket}" + _cmdpfx="@1 " # command prefix used for master CLI only. + fi + # Set the suffix depending if we are creating a bundle or not if [ "${Le_Deploy_haproxy_bundle}" = "yes" ]; then _info "Bundle creation requested" @@ -142,12 +193,13 @@ haproxy_deploy() { _issuer="${_pem}.issuer" _ocsp="${_pem}.ocsp" _reload="${Le_Deploy_haproxy_reload}" + _statssock="${Le_Deploy_haproxy_stats_socket}" _info "Deploying PEM file" # Create a temporary PEM file _temppem="$(_mktemp)" _debug _temppem "${_temppem}" - cat "${_ccert}" "${_cca}" "${_ckey}" >"${_temppem}" + cat "${_ccert}" "${_cca}" "${_ckey}" | grep . >"${_temppem}" _ret="$?" # Check that we could create the temporary file @@ -265,15 +317,86 @@ haproxy_deploy() { fi fi - # Reload HAProxy - _debug _reload "${_reload}" - eval "${_reload}" - _ret=$? - if [ "${_ret}" != "0" ]; then - _err "Error code ${_ret} during reload" - return ${_ret} + if [ "${Le_Deploy_haproxy_hot_update}" = "yes" ]; then + # set the socket name for messages + if [ -n "${_cmdpfx}" ]; then + _socketname="master CLI" + else + _socketname="stats socket" + fi + + # Update certificate over HAProxy stats socket or master CLI. + if _exists socat; then + # look for the certificate on the stats socket, to chose between updating or creating one + _socat_cert_cmd="echo '${_cmdpfx}show ssl cert' | socat '${_statssock}' - | grep -q '^${_pem}$'" + _debug _socat_cert_cmd "${_socat_cert_cmd}" + eval "${_socat_cert_cmd}" + _ret=$? + if [ "${_ret}" != "0" ]; then + _newcert="1" + _info "Creating new certificate '${_pem}' over HAProxy ${_socketname}." + # certificate wasn't found, it's a new one. We should check if the crt-list exists and creates/inserts the certificate. + _socat_crtlist_show_cmd="echo '${_cmdpfx}show ssl crt-list' | socat '${_statssock}' - | grep -q '^${Le_Deploy_haproxy_pem_path}$'" + _debug _socat_crtlist_show_cmd "${_socat_crtlist_show_cmd}" + eval "${_socat_crtlist_show_cmd}" + _ret=$? + if [ "${_ret}" != "0" ]; then + _err "Couldn't find '${Le_Deploy_haproxy_pem_path}' in haproxy 'show ssl crt-list'" + return "${_ret}" + fi + # create a new certificate + _socat_new_cmd="echo '${_cmdpfx}new ssl cert ${_pem}' | socat '${_statssock}' - | grep -q 'New empty'" + _debug _socat_new_cmd "${_socat_new_cmd}" + eval "${_socat_new_cmd}" + _ret=$? + if [ "${_ret}" != "0" ]; then + _err "Couldn't create '${_pem}' in haproxy" + return "${_ret}" + fi + else + _info "Update existing certificate '${_pem}' over HAProxy ${_socketname}." + fi + _socat_cert_set_cmd="echo -e '${_cmdpfx}set ssl cert ${_pem} <<\n$(cat "${_pem}")\n' | socat '${_statssock}' - | grep -q 'Transaction created'" + _debug _socat_cert_set_cmd "${_socat_cert_set_cmd}" + eval "${_socat_cert_set_cmd}" + _ret=$? + if [ "${_ret}" != "0" ]; then + _err "Can't update '${_pem}' in haproxy" + return "${_ret}" + fi + _socat_cert_commit_cmd="echo '${_cmdpfx}commit ssl cert ${_pem}' | socat '${_statssock}' - | grep -q '^Success!$'" + _debug _socat_cert_commit_cmd "${_socat_cert_commit_cmd}" + eval "${_socat_cert_commit_cmd}" + _ret=$? + if [ "${_ret}" != "0" ]; then + _err "Can't commit '${_pem}' in haproxy" + return ${_ret} + fi + if [ "${_newcert}" = "1" ]; then + # if this is a new certificate, it needs to be inserted into the crt-list` + _socat_cert_add_cmd="echo '${_cmdpfx}add ssl crt-list ${Le_Deploy_haproxy_pem_path} ${_pem}' | socat '${_statssock}' - | grep -q 'Success!'" + _debug _socat_cert_add_cmd "${_socat_cert_add_cmd}" + eval "${_socat_cert_add_cmd}" + _ret=$? + if [ "${_ret}" != "0" ]; then + _err "Can't update '${_pem}' in haproxy" + return "${_ret}" + fi + fi + else + _err "'socat' is not available, couldn't update over ${_socketname}" + fi else - _info "Reload successful" + # Reload HAProxy + _debug _reload "${_reload}" + eval "${_reload}" + _ret=$? + if [ "${_ret}" != "0" ]; then + _err "Error code ${_ret} during reload" + return ${_ret} + else + _info "Reload successful" + fi fi return 0