浏览代码

Merge pull request #4581 from wlallemand/haproxy-hot-update

haproxy deploy hook updates existing certificate over stats socket
neil 1 年之前
父节点
当前提交
0588fc6b7c
共有 1 个文件被更改,包括 132 次插入9 次删除
  1. 132 9
      deploy/haproxy.sh

+ 132 - 9
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