понедельник, 19 октября 2020 г.

Хранение и автоматическая загрузка сертификатов Let's Encrypt в репозиторий GitLab

Let's Encrypt предоставляет удобный сервис для получения бесплатного SSL-сертификата, но у него есть один нюанс, сертификат выпускается со сроком действия 90 дней. Это не будет вызвать каких-либо неудобств, если сертификат используется на той же машине, на которой происходит его выпуск и обновление. В случае же, если получается wildcard-сертификат, то он будет использоваться и на других машинах, так что нужно его как-то туда доставить.
Первое, что нужно решить, это определить место хранения сертификата, доступное с любой дугой машины. Решено было использовать для этого репозиторй на локальном GitLab. Это даёт возможность контролировать версионность файлов и откатить изменения, в случае ошибки, чего не позволяет использование обычного общего smb-ресурса.

Чтобы избежать привязанности репозитория к опеределённому пользователю создадим группу automation, а уже в ней проект ssl, так что путь к этому проекту будет выглядеть так
https://gitlab.oldfag.ru/automation/ssl.git
На машине, где выпускаются сертификаты создадим каталог repo, в котором будут дополнительные каталоги, соответствующие именам доменов, для которых будут выпускаться сертификаты от Let's Encrypt. Доступ к этому каталогу будет только у пользователя root.
mkdir /repo
chmod 700 /repo
В самом репозитории на GitLab для каждого домена будет создана отдельная ветка, имя которой так же будет соответствовать имени домена.

Теперь перейдём непосредственно к скрипту, который будет проверять наличие свежего сертификата.
Логика его работы достаточно проста. При запуске получается список каталогов в /repo, после чего соответствующий каталог ищется в /etc/letsencrypt/live, если каталог найден, то с помощью openssl проверяется количество дней, оставшихся до истчения срока действия сертификата. Если количество дней больше, чем было при прошлом запуске, которое хранится в файле days_counter, то сертификат, его цепочка сертификации и ключ загружаются в соответствующую ветку репозитория.
touch /scripts/ssl_check.sh
chmod +x /scripts/ssl_check.sh
Сам скрипт выглядит вот так
#!/bin/bash
# Получаем список всех каталогов в /repo
arr=(/repo/*)
# Удаляем последний слэш у каждого элемента
arr=("${arr[@]%/}")
# Удаляем префикс пути
arr=("${arr[@]##*/}")
# Корневой сертификат DST Root CA X3 (https://www.identrust.com/dst-root-ca-x3)
ca="-----BEGIN CERTIFICATE-----
MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow
PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD
Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O
rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq
OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b
xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw
7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD
aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG
SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69
ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr
AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz
R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5
JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo
Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
-----END CERTIFICATE-----"

for entry in ${arr[@]}; do
    # Проверяем существование соответствующего каталога в /etc/letsencrypt/live
    if [ -d "/etc/letsencrypt/live/${entry}" ]; then
        counter_file="/repo/${entry}/days_counter"
        # Получаем дату окончания срока действия сертификата
        exp_date=`echo | openssl x509 -enddate -noout -in "/etc/letsencrypt/live/${entry}/cert.pem" | grep notAfter |cut -d'=' -f2`
        let days_left=(`date -d "${exp_date}" +%s` - `date +%s` )/86400
        # Если файл счётчика не сущестует в папке, то создаём его, записываем счёчик и ждём следущего запуска
        if  [ ! -f ${counter_file} ]; then
            echo ${days_left} > ${counter_file}
            exit
        fi
        previous_days_counter=$(head -n 1 ${counter_file})
        if [ ${previous_days_counter} -lt ${days_left} ]; then
            yes | cp -Lr /etc/letsencrypt/live/${entry}/* /repo/${entry}
            # Добавляем корневой сертификат в цепочку
            echo "${ca[*]}" >> /repo/${entry}/chain.pem
            cd /repo/${entry}
            # Инициализируем репозиторий, если это не было сделано ранее
            if [ ! -d "/repo/${entry}/.git" ]; then
                git init
                git checkout -b ${entry}
                git remote add origin https://gituser:gituserpassword@gitlab.oldfag.ru/automation/ssl.git
                echo $'days_counter\nREADME' >> /repo/${entry}/.git/info/exclude
            fi
            git add .
            git commit -m "New certicate issued"
            git push -u origin ${entry}
        fi
        echo ${days_left} > ${counter_file}
    fi
done
При инициализации репозитория используется строка с указанием имени пользователя и пароля т.к. репозиторий не является публичным и доступ к нему ограничен определённым списком пользователей.

Теперь добавим задание запуска скрипта в /etc/crontab
# Let's Encrypt certificates check and update repo
00 3    * * *   root    /scripts/ssl_check.sh
Каждый день в 3 часа будет запускаться скрипт и прверять наличи нового сертификата.

В блоге есть пример использования сертификатов, хранимых подобным образом, на Zimbra Collaboration OSE.

Комментариев нет:

Отправить комментарий