You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

344 lines
13 KiB

8 years ago
8 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
8 years ago
8 years ago
  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. _info "Update the certificate over HAProxy stats socket."
  275. if _exists socat; then
  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. _err "Couldn't find '${_pem}' in haproxy 'show ssl cert'"
  282. return "${_ret}"
  283. fi
  284. _socat_cert_set_cmd="echo -e 'set ssl cert ${_pem} <<\n$(cat "${_pem}")\n' | socat '${_statssock}' - | grep -q 'Transaction created'"
  285. _debug _socat_cert_set_cmd "${_socat_cert_set_cmd}"
  286. eval "${_socat_cert_set_cmd}"
  287. _ret=$?
  288. if [ "${_ret}" != "0" ]; then
  289. _err "Can't update '${_pem}' in haproxy"
  290. return "${_ret}"
  291. fi
  292. _socat_cert_commit_cmd="echo 'commit ssl cert ${_pem}' | socat '${_statssock}' - | grep -q '^Success!$'"
  293. _debug _socat_cert_commit_cmd "${_socat_cert_commit_cmd}"
  294. eval "${_socat_cert_commit_cmd}"
  295. _ret=$?
  296. if [ "${_ret}" != "0" ]; then
  297. _err "Can't commit '${_pem}' in haproxy"
  298. return ${_ret}
  299. fi
  300. else
  301. _err "'socat' is not available, couldn't update over stats socket"
  302. fi
  303. else
  304. # Reload HAProxy
  305. _debug _reload "${_reload}"
  306. eval "${_reload}"
  307. _ret=$?
  308. if [ "${_ret}" != "0" ]; then
  309. _err "Error code ${_ret} during reload"
  310. return ${_ret}
  311. else
  312. _info "Reload successful"
  313. fi
  314. fi
  315. return 0
  316. }