diff --git a/evoacme/files/evoacme.sh b/evoacme/files/evoacme.sh index 4d8fa6fd..1b2c060d 100755 --- a/evoacme/files/evoacme.sh +++ b/evoacme/files/evoacme.sh @@ -28,16 +28,36 @@ error() { exit 1 } -mkconf_apache() { - debug "Apache detected... first configuration" - [ -f "/etc/apache2/ssl/${vhost}.conf" ] && sed -i "s~^SSLCertificateFile.*$~SSLCertificateFile $CRT_DIR/${vhost}/live/fullchain.pem~" "/etc/apache2/ssl/${vhost}.conf" - apache2ctl -t +change_cert_path_for_apache() { + vhost=$1 + vhost_full_path="/etc/apache2/ssl/${vhost}.conf" + cert_path=$2 + + debug "Apache detected... first configuration in ${vhost_full_path}" + [ -f "${vhost_full_path}" ] && sed -i "s~^SSLCertificateFile.*$~SSLCertificateFile ${cert_path}~" "${vhost_full_path}" + ${APACHE2CTL_BIN} -t } -mkconf_nginx() { - debug "Nginx detected... first configuration" - [ -f "/etc/nginx/ssl/${vhost}.conf" ] && sed -i "s~^ssl_certificate[^_].*$~ssl_certificate $CRT_DIR/${vhost}/live/fullchain.pem;~" "/etc/nginx/ssl/${vhost}.conf" - nginx -t +change_cert_path_for_nginx() { + vhost=$1 + vhost_full_path="/etc/nginx/ssl/${vhost}.conf" + cert_path=$2 + + debug "Nginx detected... first configuration in ${vhost_full_path}" + [ -f "${vhost_full_path}" ] && sed -i "s~^ssl_certificate[^_].*$~ssl_certificate ${cert_path};~" "${vhost_full_path}" + ${NGINX_BIN} -t +} + +x509_verify() { + file=$1 + + ${OPENSSL_BIN} x509 -noout -modulus -in "${file}" >/dev/null +} + +x509_enddate() { + file=$1 + + ${OPENSSL_BIN} x509 -noout -enddate -in "${file}" } main() { @@ -51,87 +71,168 @@ main() { [ -z "${SELF_SIGNED_DIR}" ] && SELF_SIGNED_DIR=/etc/ssl/self-signed [ -z "${DH_DIR}" ] && DH_DIR=etc/ssl/dhparam - # misc verifications [ "$1" = "-h" ] || [ "$1" = "--help" ] && usage && exit 0 - which openssl >/dev/null || error "openssl command not installed" - which certbot >/dev/null || error "certbot command not installed" - [ ! -d $ACME_DIR ] && error "$ACME_DIR is not a directory" - [ ! -d $CSR_DIR ] && error "$CSR_DIR is not a directory" - [ ! -d $LOG_DIR ] && error "$LOG_DIR is not a directory" + # check arguments [ "$#" -ge 3 ] || [ "$#" -le 0 ] && error "invalid argument(s)" [ "$#" -eq 2 ] && [ "$1" != "--cron" ] && error "invalid argument(s)" - [ "$#" -eq 1 ] && vhost=$(basename "$1" .conf) && CRON=NO - [ "$#" -eq 2 ] && vhost=$(basename "$2" .conf) && CRON=YES + [ "$#" -eq 1 ] && VHOST=$(basename "$1" .conf) && CRON=NO + [ "$#" -eq 2 ] && VHOST=$(basename "$2" .conf) && CRON=YES + + # check for important programs + OPENSSL_BIN=$(command -v openssl) + if [ "$?" -eq 0 ]; then + error "openssl command not installed" + fi + CERTBOT_BIN=$(command -v certbot) + if [ "$?" -eq 0 ]; then + error "certbot command not installed" + fi + + APACHE2CTL_BIN=$(command -v apache2ctl) + NGINX_BIN=$(command -v nginx) + + # double check for directories + [ ! -d "${ACME_DIR}" ] && error "${ACME_DIR} is not a directory" + [ ! -d "${CSR_DIR}" ] && error "${CSR_DIR} is not a directory" + [ ! -d "${LOG_DIR} "] && error "${LOG_DIR} is not a directory" + + #### CSR VALIDATION # verify .csr file - [ ! -f "$CSR_DIR/${vhost}.csr" ] && error "$CSR_DIR/${vhost}.csr absent" - [ ! -r "$CSR_DIR/${vhost}.csr" ] && error "$C´SR_DIR/${vhost}.csr is not readable" - openssl req -noout -modulus -in "$CSR_DIR/${vhost}.csr" >/dev/null || error "$CSR_DIR/${vhost}.csr is invalid" - debug "Using CSR file: $CSR_DIR/${vhost}.csr" + CSR_FILE="${CSR_DIR}/${VHOST}.csr" + debug "Using CSR file: ${CSR_FILE}" + [ ! -f "${CSR_FILE}" ] && error "${CSR_FILE} absent" + [ ! -r "${CSR_FILE}" ] && error "${CSR_FILE} is not readable" + + x509_verify "${CSR_FILE}" || error "${CSR_FILE} is invalid" # Hook for evoadmin-web in cluster mode : check master status - if [ -f "/home/${vhost}/state" ]; then - grep -q "STATE=master" "/home/${vhost}/state" || exit 0 + evoadmin_state_file="/home/${VHOST}/state" + if [ -f "${evoadmin_state_file}" ]; then + grep -q "STATE=master" "${evoadmin_state_file}" + if [ "$?" != 0 ]; then + debug "We are not the master of this evoadmin cluster. Quit!" + exit 0 + fi fi - if [ -n "$SSL_EMAIL" ]; then - emailopt="-m $SSL_EMAIL" - else - emailopt="--register-unsafely-without-email" - fi + #### INIT OR RENEW? - DATE=$(date "+%Y%m%d") - [ ! -n "$DATE" ] && error "invalid date" + LIVE_DIR="${CRT_DIR}/${VHOST}/live" + LIVE_CERT="${LIVE_DIR}/cert.crt" + LIVE_FULLCHAIN="${LIVE_DIR}/fullchain.pem" + LIVE_CHAIN="${LIVE_DIR}/chain.pem" - - # If live link already exists, it's not our first time... - if [ -h "$CRT_DIR/${vhost}/live" ]; then - openssl x509 -noout -modulus -in "$CRT_DIR/${vhost}/live/cert.crt" >/dev/null || error "$CRT_DIR/${vhost}/live/cert.crt is invalid" + # If live symlink already exists, it's not our first time... + if [ -h "${LIVE_DIR}" ]; then + # we have a live symlink + # let's see if there is a cert to renew + x509_verify "${LIVE_CERT}" || error "${LIVE_CERT} is invalid" # Verify if our certificate will expire - crt_end_date=$(openssl x509 -noout -enddate -in "$CRT_DIR/${vhost}/live/cert.crt" | cut -d= -f2) - date_renew=$(date -ud "$crt_end_date - $SSL_MINDAY days" +"%s") + crt_end_date=$(x509_enddate "${LIVE_CERT}" | cut -d= -f2) + date_renew=$(date -ud "${crt_end_date} - ${SSL_MINDAY} days" +"%s") date_today=$(date +'%s') - [ "$date_today" -lt "$date_renew" ] && debug "Cert $CRT_DIR/${vhost}/live/cert.crt expires at $crt_end_date => more than $SSL_MINDAY days: thxbye." && exit 0 + if [ "${date_today}" -lt "${date_renew}" ]; then + debug "Cert ${LIVE_CERT} expires at ${crt_end_date} => more than ${SSL_MINDAY} days: kthxbye." + exit 0 + fi else - which apache2ctl >/dev/null && mkconf_apache - which nginx >/dev/null && mkconf_nginx + # We don't have a live symlink yet + # Let's start from scratch and configure our web server(s) + if [ -n "${APACHE2CTL_BIN}" ]; then + change_cert_path_for_apache "${VHOST}" "${LIVE_FULLCHAIN}" + fi + if [ -n "${NGINX_BIN}" ]; then + change_cert_path_for_nginx "${VHOST}" "${LIVE_FULLCHAIN}" + fi fi - # renew certificate with certbot - [ -d "$CRT_DIR/${vhost}/${DATE}" ] && error "$CRT_DIR/${vhost}/${DATE} directory already exists, remove it manually." - mkdir -pm 755 "$CRT_DIR/${vhost}/${DATE}" - chown -R acme: "$CRT_DIR/${vhost}/${DATE}" - [ "$CRON" = "YES" ] && CERTBOT_OPTS="--quiet" - sudo -u acme certbot certonly $CERTBOT_OPTS --webroot --csr "$CSR_DIR/${vhost}.csr" --webroot-path "$ACME_DIR" -n --agree-tos --cert-path="$CRT_DIR/${vhost}/${DATE}/cert.crt" --fullchain-path="$CRT_DIR/${vhost}/${DATE}/fullchain.pem" --chain-path="$CRT_DIR/${vhost}/${DATE}/chain.pem" "$emailopt" --logs-dir "$LOG_DIR" 2>&1 | grep -v "certbot.crypto_util" + #### CERTIFICATE CREATION WITH CERTBOT + + ITERATION=$(date "+%Y%m%d") + [ -z "${ITERATION}" ] && error "invalid iteration (${ITERATION})" + + NEW_DIR="${CRT_DIR}/${VHOST}/${ITERATION}" + + [ -d "${NEW_DIR}" ] && error "${NEW_DIR} directory already exists, remove it manually." + mkdir -pm 755 "${NEW_DIR}" + chown -R acme: "${NEW_DIR}" + debug "New cert will be created in ${NEW_DIR}" + + NEW_CERT="${NEW_DIR}/cert.crt" + NEW_FULLCHAIN="${NEW_DIR}/fullchain.pem" + NEW_CHAIN="${NEW_DIR}/chain.pem" + + CERTBOT_MODE="" + [ "$TEST" = "YES" ] && CERTBOT_MODE="${CERTBOT_MODE} --test-cert" + [ "$CRON" = "YES" ] && CERTBOT_MODE="${CERTBOT_MODE} --quiet" + [ "$DRY_RUN" = "YES" ] && CERTBOT_MODE="${CERTBOT_MODE} --dry-run" + + CERTBOT_REGISTRATION="--agree-tos" + if [ -n "${SSL_EMAIL}" ]; then + debug "Registering at certbot with ${SSL_EMAIL} as email" + CERTBOT_REGISTRATION="${CERTBOT_REGISTRATION} -m ${SSL_EMAIL}" + else + debug "Registering at certbot without email" + CERTBOT_REGISTRATION="${CERTBOT_REGISTRATION} --register-unsafely-without-email" + fi + + # create a certificate with certbot + sudo -u acme ${CERTBOT_BIN} \ + certonly \ + ${CERTBOT_MODE} \ + ${CERTBOT_REGISTRATION} \ + --non-interactive \ + --webroot \ + --csr "${CSR_FILE}" \ + --webroot-path "${ACME_DIR}" \ + --cert-path "${NEW_CERT}" \ + --fullchain-path "${NEW_FULLCHAIN}" \ + --chain-path "${NEW_CHAIN}" \ + --logs-dir "$LOG_DIR" \ + 2>&1 \ + | grep -v "certbot.crypto_util" # verify if all is right - openssl x509 -noout -modulus -in "$CRT_DIR/${vhost}/${DATE}/cert.crt" >/dev/null || error "new $CRT_DIR/${vhost}/${DATE}/cert.crt is invalid" - openssl x509 -noout -modulus -in "$CRT_DIR/${vhost}/${DATE}/fullchain.pem" >/dev/null || error "new $CRT_DIR/${vhost}/${DATE}/fullchain.pem is invalid" - openssl x509 -noout -modulus -in "$CRT_DIR/${vhost}/${DATE}/chain.pem" >/dev/null || error "new $CRT_DIR/${vhost}/${DATE}/chain.pem is invalid" + x509_verify "${NEW_CERT}" || error "${NEW_CERT} is invalid" + x509_verify "${NEW_FULLCHAIN}" || error "${NEW_FULLCHAIN} is invalid" + x509_verify "${NEW_CHAIN}" || error "${NEW_CHAIN} is invalid" + + #### CERTIFICATE ACTIVATION # link dance - [ -h "$CRT_DIR/${vhost}/live" ] && rm "$CRT_DIR/${vhost}/live" - ln -s "$CRT_DIR/${vhost}/${DATE}" "$CRT_DIR/${vhost}/live" - openssl x509 -noout -modulus -in "$CRT_DIR/${vhost}/live/cert.crt" >/dev/null || error "new $CRT_DIR/{vhost}/live/cert.crt is invalid" + if [ -h "${LIVE_DIR}" ]; then + rm "${LIVE_DIR}" + debug "Remove ${LIVE_DIR} link" + fi + ln -s "${NEW_DIR}" "${LIVE_DIR}" + debug "Link ${NEW_DIR} to ${LIVE_DIR}" + # verify final path + x509_verify "${LIVE_CERT}" || error "${LIVE_CERT} is invalid" - # reload apache or nginx + # disable error catching + # below this point anything can break set +e - pidof apache2 >/dev/null - if [ "$?" -eq 0 ]; then - apache2ctl -t 2>/dev/null + + # reload apache if present + if [ -n "$(pidof apache2)" ]; then + ${APACHE2CTL_BIN} -t 2>/dev/null if [ "$?" -eq 0 ]; then - debug "Apache detected... reloading" && service apache2 reload + debug "Apache detected... reloading" + service apache2 reload else error "Apache config is broken, you must fix it !" fi fi - pidof nginx >/dev/null - if [ "$?" -eq 0 ]; then - nginx -t 2>/dev/null + + # reload nginx if present + if [ -n "$(pidof nginx)" ]; then + ${NGINX_BIN} -t 2>/dev/null if [ "$?" -eq 0 ]; then - debug "Nginx detected... reloading" && service nginx reload + debug "Nginx detected... reloading" + service nginx reload else error "Nginx config is broken, you must fix it !" fi