Автоматическое получение и обновление сертификатов Let's Encrypt

Автоматическое получение и обновление сертификатов Let's Encrypt

Скрипт для автоматизации массового получения и обновления сертификатов. Базируется на библиотеке #LeGo.

Установка

  • Скопировать файлы app.acme.lego.conf, app.acme.lego.example.com.conf и app.acme.lego.sh в директорию /root/apps/acme/.
  • Указать бит выполнения для *.sh скриптов: chmod +x /root/apps/acme/*.sh.
  • Скопировать файл app_acme_lego в директорию /etc/cron.d/.
  • Настроить параметры скрипта в файлах app.acme.lego.conf и app.acme.lego.example.com.conf.
  • При заказе первого сертификата для домена, необходимо выполнить следующую команду:
1
~/apps/acme/app.acme.lego.sh 'example.com' 'run'

Скрипт

Скрипт состоит из 4-х компонентов:

  • Файл с основными настройками.
  • Файл с настройками домена.
  • Приложение.
  • Задача для CRON.

Общая конфигурация

Файл в общими параметрами скрипта. Этот файл подключается первым и содержит в себе общие параметры для всех доменов. Общие параметры можно переопределять файлом конфигурации домена.

app.acme.lego.conf
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
ACME_SERVER='https://acme-staging-v02.api.letsencrypt.org/directory'
# ACME_SERVER='https://acme-v02.api.letsencrypt.org/directory'
ACME_EMAIL='mail@example.com'
ACME_PATH='/etc/ssl/acme'
ACME_METHOD='http'
ACME_HTTP_PORT=':80'
ACME_HTTP_PROXY_HEADER='Host'
ACME_TLS_PORT=':443'
ACME_KEY_TYPE='ec256'
ACME_PFX_PASSWORD='pa$$word'
ACME_PFX_FORMAT='RC2'
ACME_CRT_TIMEOUT='30'
ACME_DAYS='30'
ACME_SERVICES=(
  'angie.service'
  'apache2.service'
  'nginx.service'
  'postfix@-.service'
  'prosody.service'
)
ACME_DNS_PROVIDER='cloudflare'
ACME_DNS_RESOLVERS=(
  '8.8.8.8'
  '8.8.4.4'
)
export CLOUDFLARE_API_EMAIL='mail@example.com'
export CLOUDFLARE_API_KEY=''
export CLOUDFLARE_DNS_API_TOKEN=''
export CLOUDFLARE_ZONE_API_TOKEN=''

Параметры

  • ACME_SERVER - сервер Let’s Encrypt для заказа сертификата. По умолчанию указан тестовый сервер. Может принимать следующие значения:
    • https://acme-staging-v02.api.letsencrypt.org/directory - сервер для тестирования получения проверочных сертификатов. Используется для проверки работы скрипта.
    • https://acme-v02.api.letsencrypt.org/directory - сервер для получения настоящих сертификатов.
  • ACME_EMAIL - адрес электронной почты для регистрации и восстановления сертификата Let’s Encrypt.
  • ACME_PATH - путь к хранилищу сертификатов. По умолчанию: /etc/ssl/acme.
  • ACME_METHOD - метод получения/обновления сертификата. По умолчанию http. Может принимать следующие значения:
    • dns - получения сертификата методом DNS-01.
    • http - получение сертификата методом HTTP-01.
    • tls - получения сертификата методом TLS-ALPN-01.
  • ACME_HTTP_PORT - порт, который будет слушать LeGo при получении сертификата методом HTTP-01. По умолчанию: :80. Может принимать следующие значения:
    • interface:port - слушать на конкретном интерфейсе и номере порта.
    • :port - слушать на всех интерфейсах и конкретном порту.
  • ACME_HTTP_PROXY_HEADER - проверка по указанному заголовку, если LeGo находится за обратным proxy-сервером.
  • ACME_TLS_PORT - порт, который будет слушать LeGo при получении сертификата методом TLS-ALPN-01. По умолчанию: :443. Может принимать следующие значения:
    • interface:port - слушать на конкретном интерфейсе и номере порта.
    • :port - слушать на всех интерфейсах и конкретном порту.
  • ACME_KEY_TYPE - тип приватного ключа сертификата. По умолчанию: ec256. Может принимать следующие значения:
    • rsa2048 - тип RSA-2048.
    • rsa3072 - тип RSA-3072.
    • rsa4096 - тип RSA-4096.
    • rsa8192 - тип RSA-8192.
    • ec256 - тип EC-256.
    • ec384 - тип EC-384.
  • ACME_PFX_PASSWORD - пароль для контейнера PFX.
  • ACME_PFX_FORMAT - формат контейнера PFX. По умолчанию: RC2. Может принимать следующие значения:
    • RC2 - формат RC2.
    • DES - формат DES.
    • SHA256 - формат SHA256.
  • ACME_CRT_TIMEOUT - время ожидания сертификата в секундах. По умолчанию: 30 секунд. Используется только при получении нового сертификата.
  • ACME_DAYS - количество дней, оставшихся у сертификата для его продления.
  • ACME_SERVICES - сервисы, которые необходимо перезапустить после обновления сертификата. Каждый из сервисов проверяется на наличие в системе.
  • ACME_DNS_PROVIDER - выбор DNS-провайдера для получения сертификата методом DNS-01.
  • ACME_DNS_RESOLVERS - Адреса DNS-резолверов. По умолчанию: 8.8.8.8 и 8.8.4.4. Может принимать следующие значения:
    • host:port - название хоста и номер порта.

Конфигурация домена

Файл с индивидуальной конфигурацией для каждого домена. Название файла должно строиться по шаблону app.acme.lego.[domain.com].conf. Этот файл подключается после файла с общими параметрами и позволяет переопределять их.

app.acme.lego.example.com.conf
1
2
3
4
5
ACME_DOMAINS=(
  'example.com'
  'mail.example.com'
)
ACME_HTTP_WEBROOT='/var/www/html'

Параметры

  • ACME_DOMAINS - список доменов для получения сертификата.
  • ACME_HTTP_WEBROOT - WEB-директория сервера для интеграции директории идентификации .well-known/acme-challenge. Если параметр пустой, LeGo поднимает свой внутренний сервер для получения сертификата.

Приложение

Тело скрипта. Скрипт подключает файлы с основными параметрами и параметрами домена.

app.acme.lego.sh
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#!/usr/bin/env -S bash -eu
# -------------------------------------------------------------------------------------------------------------------- #
# ACME: CERTIFICATE
# Script for generating a certificate using Lego.
# -------------------------------------------------------------------------------------------------------------------- #
# @package    Bash
# @author     Kai Kimera
# @license    MIT
# @version    0.1.0
# @link       https://lib.onl/ru/2024/11/1f5924fa-7a3d-574e-a15d-8a41208fe10d/
# -------------------------------------------------------------------------------------------------------------------- #

(( EUID != 0 )) && { echo >&2 'This script should be run as root!'; exit 1; }

# Sources.
SRC_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd -P )"; readonly SRC_DIR # Script directory.
SRC_NAME="$( basename "$( readlink -f "${BASH_SOURCE[0]}" )" )"; readonly SRC_NAME # Script name.
SRC_DOMAIN="${1:?}"; readonly SRC_DOMAIN # Domain name.
# shellcheck source=/dev/null
. "${SRC_DIR}/${SRC_NAME%.*}.conf" # Loading main configuration file.
# shellcheck source=/dev/null
. "${SRC_DIR}/${SRC_NAME%.*}.${SRC_DOMAIN}.conf" # Loading domain configuration file.

# Environment variables.
export LEGO_PATH="${SRC_DIR:?}"
export LEGO_SERVER="${ACME_SERVER:?}"
export LEGO_PFX_PASSWORD="${ACME_PFX_PASSWORD:?}"
export LEGO_PFX_FORMAT="${ACME_PFX_FORMAT:?}"

# Parameters.
ACME_CMD="${2:?}"; readonly ACME_CMD; [[ ! "${ACME_CMD}" =~ ^(run|renew)$ ]] && exit 1
ACME_EMAIL="${ACME_EMAIL:?}"; readonly ACME_EMAIL
ACME_PATH="${ACME_PATH:?}"; readonly ACME_PATH
ACME_METHOD="${ACME_METHOD:?}"; readonly ACME_METHOD
ACME_HTTP_PORT="${ACME_HTTP_PORT:?}"; readonly ACME_HTTP_PORT
ACME_HTTP_PROXY_HEADER="${ACME_HTTP_PROXY_HEADER:?}"; readonly ACME_HTTP_PROXY_HEADER
ACME_HTTP_WEBROOT="${ACME_HTTP_WEBROOT?}"; readonly ACME_HTTP_WEBROOT
ACME_TLS_PORT="${ACME_TLS_PORT:?}"; readonly ACME_TLS_PORT
ACME_KEY_TYPE="${ACME_KEY_TYPE:?}"; readonly ACME_KEY_TYPE
ACME_CRT_TIMEOUT="${ACME_CRT_TIMEOUT:?}"; readonly ACME_CRT_TIMEOUT
ACME_DAYS="${ACME_DAYS:?}"; readonly ACME_DAYS
ACME_SERVICES=("${ACME_SERVICES[@]:?}"); readonly ACME_SERVICES
ACME_DOMAINS=("${ACME_DOMAINS[@]:?}"); readonly ACME_DOMAINS
ACME_DNS_PROVIDER="${ACME_DNS_PROVIDER:?}"; readonly ACME_DNS_PROVIDER
ACME_DNS_RESOLVERS=("${ACME_DNS_RESOLVERS[@]:?}"); readonly ACME_DNS_RESOLVERS

# -------------------------------------------------------------------------------------------------------------------- #
# INITIALIZATION
# -------------------------------------------------------------------------------------------------------------------- #

run() { lego; }

# -------------------------------------------------------------------------------------------------------------------- #
# LEGO
# Let's Encrypt client and ACME library written in Go.
# -------------------------------------------------------------------------------------------------------------------- #

lego() {
  local cmd; cmd=("${ACME_CMD}")

  case "${ACME_CMD}" in
    'renew')
      cmd+=('--days' "${ACME_DAYS}")
      ;;
  esac

  local opts
  opts=(
    '--accept-tos'
    '--key-type' "${ACME_KEY_TYPE}"
    '--email' "${ACME_EMAIL}"
    '--cert.timeout' "${ACME_CRT_TIMEOUT}"
    '--pem'
    '--pfx'
  )

  case "${ACME_METHOD}" in
    'dns')
      opts+=('--dns' "${ACME_DNS_PROVIDER}")
      for i in "${ACME_DNS_RESOLVERS[@]}"; do opts+=('--dns.resolvers' "${i}"); done
      ;;
    'http')
      opts+=('--http' '--http.port' "${ACME_HTTP_PORT}" '--http.proxy-header' "${ACME_HTTP_PROXY_HEADER}")
      [[ -n "${ACME_HTTP_WEBROOT}" ]] && opts+=('--http.webroot' "${ACME_HTTP_WEBROOT}")
      ;;
    'tls')
      opts+=('--tls' '--tls.port' "${ACME_TLS_PORT}")
      ;;
    *)
      echo 'ACME_METHOD is not supported!'; exit 1
      ;;
  esac

  for i in "${ACME_DOMAINS[@]}"; do opts+=('--domains' "${i}"); done

  if "${SRC_DIR}/lego" "${opts[@]}" "${cmd[@]}"; then
    [[ ! -d "${ACME_PATH}" ]] && mkdir -p "${ACME_PATH}"
    cp -f "${LEGO_PATH}/certificates/"{*.crt,*.key,*.pem,*.pfx} "${ACME_PATH}" \
      && chmod 644 "${ACME_PATH}/"{*.crt,*.key,*.pem,*.pfx}
    for s in "${ACME_SERVICES[@]}"; do _if_service "${s}" && systemctl reload "${s}"; done
  fi
}

# -------------------------------------------------------------------------------------------------------------------- #
# ------------------------------------------------< COMMON FUNCTIONS >------------------------------------------------ #
# -------------------------------------------------------------------------------------------------------------------- #

# Checking service availability.
_if_service() {
  local s; s="${1}"
  if systemctl list-units --full -all | grep -Fq "${s}"; then return 0; fi
  return 1
}

# -------------------------------------------------------------------------------------------------------------------- #
# -------------------------------------------------< RUNNING SCRIPT >------------------------------------------------- #
# -------------------------------------------------------------------------------------------------------------------- #

run && exit 0 || exit 1

Задача

Файл задачи размещается в директории /etc/cron.d/. Задача запускает скрипт каждый день для проверки состояния сертификатов.

app_acme_lego
1
2
3
4
5
SHELL=/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin
HOME=/root

0 0 * * * root "${HOME}/apps/acme/app.acme.lego.sh" 'example.com' 'renew' > /dev/null 2>&1

Параметры

  • example.com - название домена для загрузки файла с его параметрами.
  • renew - команда LeGo для проверки и обновления сертификатов.
Авторы
Мета
Лицензия
ID файла
UUID
Системный путь
Тип
Статистика
Количество слов
Время чтения
мин.