haproxy.sh 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. #!/usr/bin/env sh
  2. # Script for acme.sh to deploy certificates to haproxy
  3. #
  4. # The following variables can be exported:
  5. #
  6. # export DEPLOY_HAPROXY_PEM_NAME="${domain}.pem"
  7. #
  8. # Defines the name of the PEM file.
  9. # Defaults to "<domain>.pem"
  10. #
  11. # export DEPLOY_HAPROXY_PEM_PATH="/etc/haproxy"
  12. #
  13. # Defines location of PEM file for HAProxy.
  14. # Defaults to /etc/haproxy
  15. #
  16. # export DEPLOY_HAPROXY_RELOAD="systemctl reload haproxy"
  17. #
  18. # OPTIONAL: Reload command used post deploy
  19. # This defaults to be a no-op (ie "true").
  20. # It is strongly recommended to set this something that makes sense
  21. # for your distro.
  22. #
  23. # export DEPLOY_HAPROXY_ISSUER="no"
  24. #
  25. # OPTIONAL: Places CA file as "${DEPLOY_HAPROXY_PEM}.issuer"
  26. # Note: Required for OCSP stapling to work
  27. #
  28. # export DEPLOY_HAPROXY_BUNDLE="no"
  29. #
  30. # OPTIONAL: Deploy this certificate as part of a multi-cert bundle
  31. # This adds a suffix to the certificate based on the certificate type
  32. # eg RSA certificates will have .rsa as a suffix to the file name
  33. # HAProxy will load all certificates and provide one or the other
  34. # depending on client capabilities
  35. # Note: This functionality requires HAProxy was compiled against
  36. # a version of OpenSSL that supports this.
  37. #
  38. # export DEPLOY_HAPROXY_HOT_UPDATE="yes"
  39. # export DEPLOY_HAPROXY_STATS_SOCKET="UNIX:/run/haproxy/admin.sock"
  40. #
  41. # OPTIONAL: Deploy the certificate over the HAProxy stats socket without
  42. # needing to reload HAProxy. Default is "no".
  43. #
  44. # Require the socat binary. DEPLOY_HAPROXY_STATS_SOCKET variable uses the socat
  45. # address format.
  46. ######## Public functions #####################
  47. #domain keyfile certfile cafile fullchain
  48. haproxy_deploy() {
  49. _cdomain="$1"
  50. _ckey="$2"
  51. _ccert="$3"
  52. _cca="$4"
  53. _cfullchain="$5"
  54. # Some defaults
  55. DEPLOY_HAPROXY_PEM_PATH_DEFAULT="/etc/haproxy"
  56. DEPLOY_HAPROXY_PEM_NAME_DEFAULT="${_cdomain}.pem"
  57. DEPLOY_HAPROXY_BUNDLE_DEFAULT="no"
  58. DEPLOY_HAPROXY_ISSUER_DEFAULT="no"
  59. DEPLOY_HAPROXY_RELOAD_DEFAULT="true"
  60. DEPLOY_HAPROXY_HOT_UPDATE_DEFAULT="no"
  61. DEPLOY_HAPROXY_STATS_SOCKET_DEFAULT="UNIX:/run/haproxy/admin.sock"
  62. _debug _cdomain "${_cdomain}"
  63. _debug _ckey "${_ckey}"
  64. _debug _ccert "${_ccert}"
  65. _debug _cca "${_cca}"
  66. _debug _cfullchain "${_cfullchain}"
  67. # PEM_PATH is optional. If not provided then assume "${DEPLOY_HAPROXY_PEM_PATH_DEFAULT}"
  68. _getdeployconf DEPLOY_HAPROXY_PEM_PATH
  69. _debug2 DEPLOY_HAPROXY_PEM_PATH "${DEPLOY_HAPROXY_PEM_PATH}"
  70. if [ -n "${DEPLOY_HAPROXY_PEM_PATH}" ]; then
  71. Le_Deploy_haproxy_pem_path="${DEPLOY_HAPROXY_PEM_PATH}"
  72. _savedomainconf Le_Deploy_haproxy_pem_path "${Le_Deploy_haproxy_pem_path}"
  73. elif [ -z "${Le_Deploy_haproxy_pem_path}" ]; then
  74. Le_Deploy_haproxy_pem_path="${DEPLOY_HAPROXY_PEM_PATH_DEFAULT}"
  75. fi
  76. # Ensure PEM_PATH exists
  77. if [ -d "${Le_Deploy_haproxy_pem_path}" ]; then
  78. _debug "PEM_PATH ${Le_Deploy_haproxy_pem_path} exists"
  79. else
  80. _err "PEM_PATH ${Le_Deploy_haproxy_pem_path} does not exist"
  81. return 1
  82. fi
  83. # PEM_NAME is optional. If not provided then assume "${DEPLOY_HAPROXY_PEM_NAME_DEFAULT}"
  84. _getdeployconf DEPLOY_HAPROXY_PEM_NAME
  85. _debug2 DEPLOY_HAPROXY_PEM_NAME "${DEPLOY_HAPROXY_PEM_NAME}"
  86. if [ -n "${DEPLOY_HAPROXY_PEM_NAME}" ]; then
  87. Le_Deploy_haproxy_pem_name="${DEPLOY_HAPROXY_PEM_NAME}"
  88. _savedomainconf Le_Deploy_haproxy_pem_name "${Le_Deploy_haproxy_pem_name}"
  89. elif [ -z "${Le_Deploy_haproxy_pem_name}" ]; then
  90. Le_Deploy_haproxy_pem_name="${DEPLOY_HAPROXY_PEM_NAME_DEFAULT}"
  91. fi
  92. # BUNDLE is optional. If not provided then assume "${DEPLOY_HAPROXY_BUNDLE_DEFAULT}"
  93. _getdeployconf DEPLOY_HAPROXY_BUNDLE
  94. _debug2 DEPLOY_HAPROXY_BUNDLE "${DEPLOY_HAPROXY_BUNDLE}"
  95. if [ -n "${DEPLOY_HAPROXY_BUNDLE}" ]; then
  96. Le_Deploy_haproxy_bundle="${DEPLOY_HAPROXY_BUNDLE}"
  97. _savedomainconf Le_Deploy_haproxy_bundle "${Le_Deploy_haproxy_bundle}"
  98. elif [ -z "${Le_Deploy_haproxy_bundle}" ]; then
  99. Le_Deploy_haproxy_bundle="${DEPLOY_HAPROXY_BUNDLE_DEFAULT}"
  100. fi
  101. # ISSUER is optional. If not provided then assume "${DEPLOY_HAPROXY_ISSUER_DEFAULT}"
  102. _getdeployconf DEPLOY_HAPROXY_ISSUER
  103. _debug2 DEPLOY_HAPROXY_ISSUER "${DEPLOY_HAPROXY_ISSUER}"
  104. if [ -n "${DEPLOY_HAPROXY_ISSUER}" ]; then
  105. Le_Deploy_haproxy_issuer="${DEPLOY_HAPROXY_ISSUER}"
  106. _savedomainconf Le_Deploy_haproxy_issuer "${Le_Deploy_haproxy_issuer}"
  107. elif [ -z "${Le_Deploy_haproxy_issuer}" ]; then
  108. Le_Deploy_haproxy_issuer="${DEPLOY_HAPROXY_ISSUER_DEFAULT}"
  109. fi
  110. # RELOAD is optional. If not provided then assume "${DEPLOY_HAPROXY_RELOAD_DEFAULT}"
  111. _getdeployconf DEPLOY_HAPROXY_RELOAD
  112. _debug2 DEPLOY_HAPROXY_RELOAD "${DEPLOY_HAPROXY_RELOAD}"
  113. if [ -n "${DEPLOY_HAPROXY_RELOAD}" ]; then
  114. Le_Deploy_haproxy_reload="${DEPLOY_HAPROXY_RELOAD}"
  115. _savedomainconf Le_Deploy_haproxy_reload "${Le_Deploy_haproxy_reload}"
  116. elif [ -z "${Le_Deploy_haproxy_reload}" ]; then
  117. Le_Deploy_haproxy_reload="${DEPLOY_HAPROXY_RELOAD_DEFAULT}"
  118. fi
  119. # HOT_UPDATE is optional. If not provided then assume "${DEPLOY_HAPROXY_HOT_UPDATE_DEFAULT}"
  120. _getdeployconf DEPLOY_HAPROXY_HOT_UPDATE
  121. _debug2 DEPLOY_HAPROXY_HOT_UPDATE "${DEPLOY_HAPROXY_HOT_UPDATE}"
  122. if [ -n "${DEPLOY_HAPROXY_HOT_UPDATE}" ]; then
  123. Le_Deploy_haproxy_hot_update="${DEPLOY_HAPROXY_HOT_UPDATE}"
  124. _savedomainconf Le_Deploy_haproxy_hot_update "${Le_Deploy_haproxy_hot_update}"
  125. elif [ -z "${Le_Deploy_haproxy_hot_update}" ]; then
  126. Le_Deploy_haproxy_hot_update="${DEPLOY_HAPROXY_HOT_UPDATE_DEFAULT}"
  127. fi
  128. # STATS_SOCKET is optional. If not provided then assume "${DEPLOY_HAPROXY_STATS_SOCKET_DEFAULT}"
  129. _getdeployconf DEPLOY_HAPROXY_STATS_SOCKET
  130. _debug2 DEPLOY_HAPROXY_STATS_SOCKET "${DEPLOY_HAPROXY_STATS_SOCKET}"
  131. if [ -n "${DEPLOY_HAPROXY_STATS_SOCKET}" ]; then
  132. Le_Deploy_haproxy_stats_socket="${DEPLOY_HAPROXY_STATS_SOCKET}"
  133. _savedomainconf Le_Deploy_haproxy_stats_socket "${Le_Deploy_haproxy_stats_socket}"
  134. elif [ -z "${Le_Deploy_haproxy_stats_socket}" ]; then
  135. Le_Deploy_haproxy_stats_socket="${DEPLOY_HAPROXY_STATS_SOCKET_DEFAULT}"
  136. fi
  137. # Set the suffix depending if we are creating a bundle or not
  138. if [ "${Le_Deploy_haproxy_bundle}" = "yes" ]; then
  139. _info "Bundle creation requested"
  140. # Initialise $Le_Keylength if its not already set
  141. if [ -z "${Le_Keylength}" ]; then
  142. Le_Keylength=""
  143. fi
  144. if _isEccKey "${Le_Keylength}"; then
  145. _info "ECC key type detected"
  146. _suffix=".ecdsa"
  147. else
  148. _info "RSA key type detected"
  149. _suffix=".rsa"
  150. fi
  151. else
  152. _suffix=""
  153. fi
  154. _debug _suffix "${_suffix}"
  155. # Set variables for later
  156. _pem="${Le_Deploy_haproxy_pem_path}/${Le_Deploy_haproxy_pem_name}${_suffix}"
  157. _issuer="${_pem}.issuer"
  158. _ocsp="${_pem}.ocsp"
  159. _reload="${Le_Deploy_haproxy_reload}"
  160. _statssock="${Le_Deploy_haproxy_stats_socket}"
  161. _info "Deploying PEM file"
  162. # Create a temporary PEM file
  163. _temppem="$(_mktemp)"
  164. _debug _temppem "${_temppem}"
  165. cat "${_ccert}" "${_cca}" "${_ckey}" | grep . >"${_temppem}"
  166. _ret="$?"
  167. # Check that we could create the temporary file
  168. if [ "${_ret}" != "0" ]; then
  169. _err "Error code ${_ret} returned during PEM file creation"
  170. [ -f "${_temppem}" ] && rm -f "${_temppem}"
  171. return ${_ret}
  172. fi
  173. # Move PEM file into place
  174. _info "Moving new certificate into place"
  175. _debug _pem "${_pem}"
  176. cat "${_temppem}" >"${_pem}"
  177. _ret=$?
  178. # Clean up temp file
  179. [ -f "${_temppem}" ] && rm -f "${_temppem}"
  180. # Deal with any failure of moving PEM file into place
  181. if [ "${_ret}" != "0" ]; then
  182. _err "Error code ${_ret} returned while moving new certificate into place"
  183. return ${_ret}
  184. fi
  185. # Update .issuer file if requested
  186. if [ "${Le_Deploy_haproxy_issuer}" = "yes" ]; then
  187. _info "Updating .issuer file"
  188. _debug _issuer "${_issuer}"
  189. cat "${_cca}" >"${_issuer}"
  190. _ret="$?"
  191. if [ "${_ret}" != "0" ]; then
  192. _err "Error code ${_ret} returned while copying issuer/CA certificate into place"
  193. return ${_ret}
  194. fi
  195. else
  196. [ -f "${_issuer}" ] && _err "Issuer file update not requested but .issuer file exists"
  197. fi
  198. # Update .ocsp file if certificate was requested with --ocsp/--ocsp-must-staple option
  199. if [ -z "${Le_OCSP_Staple}" ]; then
  200. Le_OCSP_Staple="0"
  201. fi
  202. if [ "${Le_OCSP_Staple}" = "1" ]; then
  203. _info "Updating OCSP stapling info"
  204. _debug _ocsp "${_ocsp}"
  205. _info "Extracting OCSP URL"
  206. _ocsp_url=$(${ACME_OPENSSL_BIN:-openssl} x509 -noout -ocsp_uri -in "${_pem}")
  207. _debug _ocsp_url "${_ocsp_url}"
  208. # Only process OCSP if URL was present
  209. if [ "${_ocsp_url}" != "" ]; then
  210. # Extract the hostname from the OCSP URL
  211. _info "Extracting OCSP URL"
  212. _ocsp_host=$(echo "${_ocsp_url}" | cut -d/ -f3)
  213. _debug _ocsp_host "${_ocsp_host}"
  214. # Only process the certificate if we have a .issuer file
  215. if [ -r "${_issuer}" ]; then
  216. # Check if issuer cert is also a root CA cert
  217. _subjectdn=$(${ACME_OPENSSL_BIN:-openssl} x509 -in "${_issuer}" -subject -noout | cut -d'/' -f2,3,4,5,6,7,8,9,10)
  218. _debug _subjectdn "${_subjectdn}"
  219. _issuerdn=$(${ACME_OPENSSL_BIN:-openssl} x509 -in "${_issuer}" -issuer -noout | cut -d'/' -f2,3,4,5,6,7,8,9,10)
  220. _debug _issuerdn "${_issuerdn}"
  221. _info "Requesting OCSP response"
  222. # If the issuer is a CA cert then our command line has "-CAfile" added
  223. if [ "${_subjectdn}" = "${_issuerdn}" ]; then
  224. _cafile_argument="-CAfile \"${_issuer}\""
  225. else
  226. _cafile_argument=""
  227. fi
  228. _debug _cafile_argument "${_cafile_argument}"
  229. # if OpenSSL/LibreSSL is v1.1 or above, the format for the -header option has changed
  230. _openssl_version=$(${ACME_OPENSSL_BIN:-openssl} version | cut -d' ' -f2)
  231. _debug _openssl_version "${_openssl_version}"
  232. _openssl_major=$(echo "${_openssl_version}" | cut -d '.' -f1)
  233. _openssl_minor=$(echo "${_openssl_version}" | cut -d '.' -f2)
  234. if [ "${_openssl_major}" -eq "1" ] && [ "${_openssl_minor}" -ge "1" ] || [ "${_openssl_major}" -ge "2" ]; then
  235. _header_sep="="
  236. else
  237. _header_sep=" "
  238. fi
  239. # Request the OCSP response from the issuer and store it
  240. _openssl_ocsp_cmd="${ACME_OPENSSL_BIN:-openssl} ocsp \
  241. -issuer \"${_issuer}\" \
  242. -cert \"${_pem}\" \
  243. -url \"${_ocsp_url}\" \
  244. -header Host${_header_sep}\"${_ocsp_host}\" \
  245. -respout \"${_ocsp}\" \
  246. -verify_other \"${_issuer}\" \
  247. ${_cafile_argument} \
  248. | grep -q \"${_pem}: good\""
  249. _debug _openssl_ocsp_cmd "${_openssl_ocsp_cmd}"
  250. eval "${_openssl_ocsp_cmd}"
  251. _ret=$?
  252. else
  253. # Non fatal: No issuer file was present so no OCSP stapling file created
  254. _err "OCSP stapling in use but no .issuer file was present"
  255. fi
  256. else
  257. # Non fatal: No OCSP url was found int the certificate
  258. _err "OCSP update requested but no OCSP URL was found in certificate"
  259. fi
  260. # Non fatal: Check return code of openssl command
  261. if [ "${_ret}" != "0" ]; then
  262. _err "Updating OCSP stapling failed with return code ${_ret}"
  263. fi
  264. else
  265. # An OCSP file was already present but certificate did not have OCSP extension
  266. if [ -f "${_ocsp}" ]; then
  267. _err "OCSP was not requested but .ocsp file exists."
  268. # Could remove the file at this step, although HAProxy just ignores it in this case
  269. # rm -f "${_ocsp}" || _err "Problem removing stale .ocsp file"
  270. fi
  271. fi
  272. if [ "${Le_Deploy_haproxy_hot_update}" = "yes" ]; then
  273. # Update certificate over HAProxy stats socket.
  274. if _exists socat; then
  275. # look for the certificate on the stats socket, to chose between updating or creating one
  276. _socat_cert_cmd="echo 'show ssl cert' | socat '${_statssock}' - | grep -q '^${_pem}$'"
  277. _debug _socat_cert_cmd "${_socat_cert_cmd}"
  278. eval "${_socat_cert_cmd}"
  279. _ret=$?
  280. if [ "${_ret}" != "0" ]; then
  281. _newcert="1"
  282. _info "Creating new certificate '${_pem}' over HAProxy stats socket."
  283. # certificate wasn't found, it's a new one. We should check if the crt-list exists and creates/inserts the certificate.
  284. _socat_crtlist_show_cmd="echo 'show ssl crt-list' | socat '${_statssock}' - | grep -q '^${Le_Deploy_haproxy_pem_path}$'"
  285. _debug _socat_crtlist_show_cmd "${_socat_crtlist_show_cmd}"
  286. eval "${_socat_crtlist_show_cmd}"
  287. _ret=$?
  288. if [ "${_ret}" != "0" ]; then
  289. _err "Couldn't find '${Le_Deploy_haproxy_pem_path}' in haproxy 'show ssl crt-list'"
  290. return "${_ret}"
  291. fi
  292. # create a new certificate
  293. _socat_new_cmd="echo 'new ssl cert ${_pem}' | socat '${_statssock}' - | grep -q 'New empty'"
  294. _debug _socat_new_cmd "${_socat_new_cmd}"
  295. eval "${_socat_new_cmd}"
  296. _ret=$?
  297. if [ "${_ret}" != "0" ]; then
  298. _err "Couldn't create '${_pem}' in haproxy"
  299. return "${_ret}"
  300. fi
  301. else
  302. _info "Update existing certificate '${_pem}' over HAProxy stats socket."
  303. fi
  304. _socat_cert_set_cmd="echo -e 'set ssl cert ${_pem} <<\n$(cat "${_pem}")\n' | socat '${_statssock}' - | grep -q 'Transaction created'"
  305. _debug _socat_cert_set_cmd "${_socat_cert_set_cmd}"
  306. eval "${_socat_cert_set_cmd}"
  307. _ret=$?
  308. if [ "${_ret}" != "0" ]; then
  309. _err "Can't update '${_pem}' in haproxy"
  310. return "${_ret}"
  311. fi
  312. _socat_cert_commit_cmd="echo 'commit ssl cert ${_pem}' | socat '${_statssock}' - | grep -q '^Success!$'"
  313. _debug _socat_cert_commit_cmd "${_socat_cert_commit_cmd}"
  314. eval "${_socat_cert_commit_cmd}"
  315. _ret=$?
  316. if [ "${_ret}" != "0" ]; then
  317. _err "Can't commit '${_pem}' in haproxy"
  318. return ${_ret}
  319. fi
  320. if [ "${_newcert}" = "1" ]; then
  321. # if this is a new certificate, it needs to be inserted into the crt-list`
  322. _socat_cert_add_cmd="echo 'add ssl crt-list ${Le_Deploy_haproxy_pem_path} ${_pem}' | socat '${_statssock}' - | grep -q 'Success!'"
  323. _debug _socat_cert_add_cmd "${_socat_cert_add_cmd}"
  324. eval "${_socat_cert_add_cmd}"
  325. _ret=$?
  326. if [ "${_ret}" != "0" ]; then
  327. _err "Can't update '${_pem}' in haproxy"
  328. return "${_ret}"
  329. fi
  330. fi
  331. else
  332. _err "'socat' is not available, couldn't update over stats socket"
  333. fi
  334. else
  335. # Reload HAProxy
  336. _debug _reload "${_reload}"
  337. eval "${_reload}"
  338. _ret=$?
  339. if [ "${_ret}" != "0" ]; then
  340. _err "Error code ${_ret} during reload"
  341. return ${_ret}
  342. else
  343. _info "Reload successful"
  344. fi
  345. fi
  346. return 0
  347. }