Как обмануть систему или letsencrypt на shared-хостинге с CPanel

Опубликовано 10 Февраля, 2017 под тегами ssl, web, letsencrypt, cpanel, certbot, https

С повсеместным пришествием HTTPS в массы, а особенно в связи с тем, что браузеры собираются помечать HTTP-сайты как небезопасные, возникает резонный вопрос: как сделать себе HTTPS задешево.

Раньше был вариант воспользоваться сервисом starttls/startssl, но в новых версиях Google Chrome и Mozilla Firefox их сертификаты блокируются, что еще хуже, чем отсутствие HTTPS вовсе.

С другой стороны, есть новый-модный-молодежный letsencrypt. Но если нет желания каждые три месяца подтверждать домен вручную (что долго и муторно), нужно гонять на целевом сервере утилиту (например, certbot), а это значит как минимум нужен SSH-доступ, ну или на худой конец webshell, которые прямо скажем не так-то просто организовать.

Дополнительно ситуация усугубляется тем, что на shared-хостинге установка certbot, даже при наличии shell-доступа, может быть совсем отдельным развлечением для мсье, знающих толк.

А что делать если у Вас копеечный shared-хостинг, на котором вообще ничего нет кроме CPanel, MySQL и апача?

Оказывается, выход все-таки есть. Можно гонять certbot на каком-нибудь другом сервере, а сертификаты пихать на shared-хостинг.

Как же добиться этого эффекта? Оказывается, относительно несложно. certbot поддерживает несколько механизмов автоматической валидации, один из них – т.н. webroot, который валидирует сайт, кладя в корень сайта определенный файл. Это все, конечно, замечательно, но он ожидает, что этот файл будет доступен через web практически мгновенно. Если certbot работает на удаленном сервере, это, конечно, несколько усложняет ситуацию.

В принципе, есть два варианта как с этим бороться. Один – использовать что-нибудь в духе curlftpfs, чтобы certbot сразу писал файлы на удаленный сервер. Вариант сам по себе вполне рабочий, но стабильность curlftpfs оставляет желать лучшего. Хотя, это выход в условиях отсуствия “лишнего” выделенного сервера – можно гонять certbot хоть на десктопе.

При наличии VPS, лучшим решением будет поднять минимальный веб-сервер на нем, и перенаправлять запросы с shared-хостинга туда. Конкретно, certbot (да и вообще letsencrypt) кладет файлики в <webroot>/.well-known, и там же их ищет, понятно, валидатор. Это означает, что мы можем использовать апачевские mod_rewrite и mod_proxy, чтобы прозрачно перенаправлять запросы с shared-хостинга на наш VPS.

Добиться этого можно несложной конструкцией в .htaccess в корне shared-хостинга:

RewriteRule "^\.well-known/(.*)" "http://<vps.example>/.well-known/$1"  [P,L]

<vps.example>, ясно – IP или домен VPS. Можно заодно указать порт, если он нестандартный. Флаг P указывает, что запрос должен быть проксирован через mod_proxy, атрибут L – что другие правила mod_rewrite применять не следует.

После этих приготовлений, достаточно поднять статический HTTP-сервер на VPS (nginx? lighttpd?) и натравить certbot certonly на /var/www/htdocs или где там у HTTP-сервера корень.

Собственно, этого достаточно, чтобы certbot с успехом получил сертификаты.

Но тут возникают еще пара вопросов.

Во-первых, сертификаты лежат на VPS, а должны быть на shared-хостинге. Как их туда засунуть? Не руками же.

Во-вторых, как настроить автоматическое обновление? Ведь каждые три месяца вспомнить прогнать certbot – задача не для вечно занятых чем-нибудь вебмастеров.

Ответ на второй вопрос кажется очевидным, ведь certbot renew автоматически обновит сертификаты, если это требуется. Но в свете первого вопроса становится ясно, что обновить-то он их обновит, но на shared-хостинге они сами по себе не появятся.

Тут собственно в игру вступает тот факт, что на большинстве shared-хостингов есть CPanel. И SSL управляется с него. Немного ковыряния и реверс-инжиниринга показвыают, что самый простой способ установить SSL-сертификат на домен в CPanel – это отправить POST-запрос на

https://<cpanel-domain>:<cpanel-port>/execute/SSL/install_ssl

со следующими параметрами:

  • domain – название домена, например mydomain.com
  • cert – содержимое cert.pem
  • key – содержимое privkey.pem
  • cabundle – содержимое chain.pem

<cpanel-port>, как правило, 2083 для HTTPS.

Что касается авторизации, CPanel, как правило, поддерживает HTTP basic auth, и можно использовать ее.

ВНИМАНИЕ! HTTP basic auth посылает логин и пароль открытым текстом! Следует использовать только при наличии HTTPS-соединения! Да и вообще, посылать сертификаты по открытым каналом как-то неправильно, особенно учитывая, что приватный ключ без пароля.

Собственно вышесказанное позволяет использовать, например, cURL для посылки сертификатов. Например, вот так:

curl "https://<cpanel-domain>:<cpanel-port>/execute/SSL/install_ssl" \
    --user <cpanel_user>:<cpanel_password> \
    --data-urlencode domain=<cert_domain> \
    --data-urlencode "cert@/path/to/cert.pem" \
    --data-urlencode "key@/path/to/privkey.pem" \
    --data-urlencode "cabundle@/path/to/chain.pem"

cURL при этом прочитает сертификаты прямо с диска (что собственно обеспечивается оператором @).

Автоматизировать это тоже можно, используя механизм хуков certbot. Для этого, можно написать какой-то такой скрипт:

#!/bin/bash
# upload-certs.sh
contains() {
    [[ " $1 " =~ [[:space:]]"$2"[[:space:]] ]]
}

if contains "${RENEWED_DOMAINS}" "<cert_domain>"; then
    curl "https://<cpanel-domain>:<cpanel-port>/execute/SSL/install_ssl" \
        --user <cpanel_user>:<cpanel_password> \
        --data-urlencode domain=<cert_domain> \
        --data-urlencode "cert@${RENEWED_LINEAGE}/cert.pem" \
        --data-urlencode "key@${RENEWED_LINEAGE}/privkey.pem" \
        --data-urlencode "cabundle@${RENEWED_LINEAGE}/chain.pem"
fi

Здесь ${RENEWED_DOMAINS} и ${RENEWED_LINEAGE} – переменные окружения, которые устанавливает certbot.

И использовать с certbot следующим образом:

certbot renew --renew-hook "/path/to/upload-certs.sh"

Конечно, следует сделать upload-certs.sh исполняемым.

Последнюю команду можно засунуть в cron на ежедневное исполнение (certbot renew не обновляет сертификаты если это не требуется, а --renew-hook не исполняется, если сертификаты не обновлены)

Из очевидных минусов решения можно отметить, что логин/пароль от CPanel хранятся на сторонней VPS. Мало того, что это дополнительная точка уязвимости всей системы, так еще и коэффициент доверия к VPS должен быть достаточно высок. С другой стороны, если сайт крутится на копеечном shared-хостинге, вероятно, что он не слишком важен, и потенциальная утечка пароля не является вселенской катастрофой. Решать, конечно, вам.

Ну и само собой, если в бюджете есть деньги на нормальный человеческий SSL, то стоит взять нормальный человеческий SSL. Это решение подходит скорее в случае, когда бюжет нулевой, а то и отрицательный, а SSL все равно хочется.