obtain-certificate.sh 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. #!/bin/bash
  2. # Return values / exit codes
  3. # 0 = cert created successfully
  4. # 1 = cert renewed successfully
  5. # 2 = cert not due for renewal
  6. # * = errors
  7. source /srv/functions.sh
  8. CERT_DOMAINS=(${DOMAINS[@]})
  9. CERT_DOMAIN=${CERT_DOMAINS[0]}
  10. ACME_BASE=/var/lib/acme
  11. TYPE=${1}
  12. PREFIX=""
  13. # only support rsa certificates for now
  14. if [[ "${TYPE}" != "rsa" ]]; then
  15. log_f "Unknown certificate type '${TYPE}' requested"
  16. exit 5
  17. fi
  18. DOMAINS_FILE=${ACME_BASE}/${CERT_DOMAIN}/domains
  19. CERT=${ACME_BASE}/${CERT_DOMAIN}/${PREFIX}cert.pem
  20. SHARED_KEY=${ACME_BASE}/acme/${PREFIX}key.pem # must already exist
  21. KEY=${ACME_BASE}/${CERT_DOMAIN}/${PREFIX}key.pem
  22. CSR=${ACME_BASE}/${CERT_DOMAIN}/${PREFIX}acme.csr
  23. if [[ -z ${CERT_DOMAINS[*]} ]]; then
  24. log_f "Missing CERT_DOMAINS to obtain a certificate"
  25. exit 3
  26. fi
  27. if [[ "${LE_STAGING}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
  28. log_f "Using Let's Encrypt staging servers"
  29. STAGING_PARAMETER='--directory-url https://acme-staging-v02.api.letsencrypt.org/directory'
  30. else
  31. STAGING_PARAMETER=
  32. fi
  33. if [[ -f ${DOMAINS_FILE} && "$(cat ${DOMAINS_FILE})" == "${CERT_DOMAINS[*]}" ]]; then
  34. if [[ ! -f ${CERT} || ! -f "${KEY}" ]]; then
  35. log_f "Certificate ${CERT} doesn't exist yet - start obtaining"
  36. # Certificate exists and did not change but could be due for renewal (30 days)
  37. elif ! openssl x509 -checkend 2592000 -noout -in ${CERT} > /dev/null; then
  38. log_f "Certificate ${CERT} is due for renewal (< 30 days) - start renewing"
  39. else
  40. log_f "Certificate ${CERT} validation done, neither changed nor due for renewal."
  41. exit 2
  42. fi
  43. else
  44. log_f "Certificate ${CERT} missing or changed domains '${CERT_DOMAINS[*]}' - start obtaining"
  45. fi
  46. # Make backup
  47. if [[ -f ${CERT} ]]; then
  48. DATE=$(date +%Y-%m-%d_%H_%M_%S)
  49. BACKUP_DIR=${ACME_BASE}/backups/${CERT_DOMAIN}/${PREFIX}${DATE}
  50. log_f "Creating backups in ${BACKUP_DIR} ..."
  51. mkdir -p ${BACKUP_DIR}/
  52. [[ -f ${DOMAINS_FILE} ]] && cp ${DOMAINS_FILE} ${BACKUP_DIR}/
  53. [[ -f ${CERT} ]] && cp ${CERT} ${BACKUP_DIR}/
  54. [[ -f ${KEY} ]] && cp ${KEY} ${BACKUP_DIR}/
  55. [[ -f ${CSR} ]] && cp ${CSR} ${BACKUP_DIR}/
  56. fi
  57. mkdir -p ${ACME_BASE}/${CERT_DOMAIN}
  58. if [[ ! -f ${KEY} ]]; then
  59. log_f "Copying shared private key for this certificate..."
  60. cp ${SHARED_KEY} ${KEY}
  61. chmod 600 ${KEY}
  62. fi
  63. # Generating CSR
  64. printf "[SAN]\nsubjectAltName=" > /tmp/_SAN
  65. printf "DNS:%s," "${CERT_DOMAINS[@]}" >> /tmp/_SAN
  66. sed -i '$s/,$//' /tmp/_SAN
  67. openssl req -new -sha256 -key ${KEY} -subj "/" -reqexts SAN -config <(cat /etc/ssl/openssl.cnf /tmp/_SAN) > ${CSR}
  68. # acme-tiny writes info to stderr and ceritifcate to stdout
  69. # The redirects will do the following:
  70. # - redirect stdout to temp certificate file
  71. # - redirect acme-tiny stderr to stdout (logs to variable ACME_RESPONSE)
  72. # - tee stderr to get live output and log to dockerd
  73. ACME_RESPONSE=$(acme-tiny ${STAGING_PARAMETER} \
  74. --account-key ${ACME_BASE}/acme/account.pem \
  75. --disable-check \
  76. --csr ${CSR} \
  77. --acme-dir /var/www/acme/ 2>&1 > /tmp/_cert.pem | tee /dev/fd/5; exit ${PIPESTATUS[0]})
  78. SUCCESS="$?"
  79. ACME_RESPONSE_B64=$(echo "${ACME_RESPONSE}" | openssl enc -e -A -base64)
  80. log_f "${ACME_RESPONSE_B64}" redis_only b64
  81. case "$SUCCESS" in
  82. 0) # cert requested
  83. log_f "Deploying certificate ${CERT}..."
  84. # Deploy the new certificate and key
  85. # Moving temp cert to {domain} folder
  86. if verify_hash_match /tmp/_cert.pem ${KEY}; then
  87. RETURN=0 # certificate created
  88. if [[ -f ${CERT} ]]; then
  89. RETURN=1 # certificate renewed
  90. fi
  91. mv -f /tmp/_cert.pem ${CERT}
  92. echo -n ${CERT_DOMAINS[*]} > ${DOMAINS_FILE}
  93. rm /var/www/acme/* 2> /dev/null
  94. log_f "Certificate successfully obtained"
  95. exit ${RETURN}
  96. else
  97. log_f "Certificate was successfully requested, but key and certificate have non-matching hashes, ignoring certificate"
  98. exit 4
  99. fi
  100. ;;
  101. *) # non-zero is non-fun
  102. log_f "Failed to obtain certificate ${CERT} for domains '${CERT_DOMAINS[*]}'"
  103. redis-cli -h redis SET ACME_FAIL_TIME "$(date +%s)"
  104. exit 100${SUCCESS}
  105. ;;
  106. esac