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.

403 lines
15 KiB

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