Опыты в домашней лаборатории: собственный Let’s Encrypt в OpenWRT

от автора

Углубляясь в тему DevOps в своей домашней лаборатории, я начал замечать, что зачастую проще задействовать TLS/mTLS, чем настраивать и отлаживать способы обойтись без него.

Задумавшись о надежном хостинге для приватного CA, обнаружил, что среди всего моего электрооборудования только у двух приборов аптайм близок к 100%: у холодильника и интернет-роутера.

Идея получать из холодильника не только напитки, но и SSL-сертификаты так грела душу, что я почти начал искать, где купить умный холодильник. Потом немного остыл и решил сначала попробовать роутер с прошивкой OpenWRT.

Уверен, в комментариях подскажут много классных решений для приватных CA, я же остановил свой выбор на step-ca. Step-CA представляет собой PKI-ядро и различные подключаемые модули, выдающие сертификаты, если пройдена аутентификация. В этом туториале мы будем получать X.509 сертификаты в обмен на прохождение проверок ACME и в обмен на JWK токены, сгенерированные CLI клиентом step.

Дисклеймер: приведенные ниже шаги довольно инвазивны и не гарантируют успех; возможно, сначала стоит их протестировать на виртуальном OpenWrt роутере. Я начинаю сразу после настройки динамических обновлений DNS зон в OpenWRT, но этот гайд самодостаточен, если у вас уже есть контроль над разрешением имен в вашей сети.

План:

  • Установка step-ca в OpenWRT

  • Генерация ключей для PKI

  • Настройка и тестирование JWK модуля

  • Конфигурируем step-ca как сервис

  • Настройка и тестирование ACME модуля

  • Корректировка параметров сертификатов.

Установка step-ca в OpenWRT

Процесс установки несложен, но имейте в виду, что понадобится ~ 37 МБ свободного места на основном разделе роутера:

$ opkg update $ opkg install curl  $ mkdir -p /tmp/step-ca $ cd /tmp/step-ca  $ curl -LO https://dl.smallstep.com/certificates/docs-ca-install/latest/step-ca_linux_arm64.tar.gz  $ tar -zxf step-ca_linux_arm64.tar.gz && rm step-ca_linux_arm64.tar.gz $ mv step-ca_linux_arm64/step-ca /usr/bin $ rm -rf step-ca_linux_arm64/

Далее нам нужно загрузить step-cli, который понадобится на этапе настройки, но в дальнейшем будет использоваться редко. Оставим его в /tmp и просто сделаем мягкую ссылку в /usr/bin/. Исполняемый файл будет удален после перезагрузки, но его всегда можно восстановить, повторив команды:

$ mkdir -p /tmp/step-ca $ cd /tmp/step-ca  $ curl -LO https://dl.smallstep.com/cli/docs-ca-install/latest/step_linux_arm64.tar.gz  $ tar -zxf step_linux_arm64.tar.gz && rm step_linux_arm64.tar.gz $ ln -s /tmp/step-ca/step_linux_arm64/bin/step /usr/bin/step

Нам понадобится юзер и группа для запуска step-ca в качестве сервиса. В OpenWRT создать юзера можно двумя способами:

Устанавливаем пакет управления пользователями, создаём пользователя, группу и домашний каталог.

$ opkg update $ opkg install shadow-useradd  $ useradd --user-group --system --create-home \ --home-dir /etc/step-ca \ --shell /bin/false step
Второй способ, если жалко места на дополнительные пакеты:

Давайте «просто» воспользуемся функциями из /lib/functions.sh.
Создаем файл useradd.sh

$ touch useradd.sh $ chmod +x useradd.sh

со следующим содержимым:

#!/bin/sh  # Source OpenWRT functions file . /lib/functions.sh  usage() {     echo "Usage: $0 <username> <home_directory>"     exit 1 } # Two non empty strings are required arguments if [ "$#" -ne 2 ] || [ -z "$1" ] || [ -z "$2" ]; then     usage fi  USERNAME="$1" HOME_DIR="$2" SHELL="/bin/false" # Script will choose IDs in this range BASE_ID=900 MAX_ID=999 ID_PAIR=""  fail_fast() {     issues_found=0     if user_exists "$USERNAME"; then         echo "User $USERNAME already exists with UID $(grep "^${USERNAME}:" /etc/passwd | cut -d: -f3)"         issues_found=1     fi      if group_exists "$USERNAME"; then         echo "Group $USERNAME already exists with GID $(grep "^${USERNAME}:" /etc/group | cut -d: -f3)"         issues_found=1     fi      if [ -d "$HOME_DIR" ]; then         echo "Home directory $HOME_DIR already exists."         issues_found=1     fi      if [ "$issues_found" -gt 0 ]; then         echo "Please fix the above. Exiting without changes."         exit 1     fi }  # Find the first pair of unused UID == GID find_available_id() {     id="$BASE_ID"     group_ids=$(cut -d: -f3 /etc/group)     passwd_ids=$(cut -d: -f3 /etc/passwd)     # Loop through IDs from BASE_ID to MAX_ID     while [ "$id" -le "$MAX_ID" ]; do         # Check if ID is not in /etc/group         if ! echo "$group_ids" | grep -qw "$id"; then             # Check if ID is also not in /etc/passwd             if ! echo "$passwd_ids" | grep -qw "$id"; then                 # ID confirmed                 ID_PAIR="$id"                 break             fi         fi         # Increment the ID         id=$((id + 1))     done     # ID not found     if [ -z "$ID_PAIR" ]; then         echo "No available ID found in range $BASE_ID-$MAX_ID" >&2         exit 1     fi }  create_group() {         echo "Creating group $USERNAME with GID $ID_PAIR"         group_add "$USERNAME" "$ID_PAIR" }  create_user() {         echo "Creating user $USERNAME with UID $ID_PAIR"         user_add "$USERNAME" "$ID_PAIR" "$ID_PAIR" "$USERNAME" "$HOME_DIR" "$SHELL" }  create_home_directory() {     echo "Creating home directory $HOME_DIR"     mkdir -p "$HOME_DIR"     chown "$USERNAME:$USERNAME" "$HOME_DIR"     chmod 755 "$HOME_DIR" }  main() {     fail_fast     find_available_id     create_group     create_user     create_home_directory }  #Execute script main

Создаем юзера и группу:

$ ./useradd.sh step /etc/step-ca Creating group step with GID 900 Creating user step with UID 900 Creating home directory /etc/step-ca

Инициализируем PKI

Для начала инициализируем PKI без конфигурации самого CA. Это позволит нам сначала подготовить и забэкапить ключи, а уже потом заняться настройкой.

ПРИМЕЧАНИЕ: step-ca применит один и тот же пароль для ключей корневого CA и промежуточного CA. Мы же установим разные пароли для этих ключей. Во время инициализации и на последующих этапах step-ca предложит на выбор сгенерировать пароль или ввести ваш.

$ export STEPPATH=/tmp/step-ca $ step ca init --pki --name="Homelab" --deployment-type standalone  Choose a password for your CA keys. ✔ [leave empty and we'll generate one]: ✔ Password: ZbeS;.)JR`=^Jak%`)3:Xy9\NwnXTA]_  Generating root certificate... done! Generating intermediate certificate... done!  ✔ Root certificate: /tmp/step-ca/certs/root_ca.crt ✔ Root private key: /tmp/step-ca/secrets/root_ca_key ✔ Root fingerprint: cd6555dce8cfcab515223fdea99531a86d329df31d453d3d0eab4e5d185f6130 ✔ Intermediate certificate: /tmp/step-ca/certs/intermediate_ca.crt ✔ Intermediate private key: /tmp/step-ca/secrets/intermediate_ca_key

Корневой ключ будет использоваться очень редко. На самом деле, мы вообще не будем оставлять его на OpenWRT. Промежуточный CA будет использоваться чаще, поэтому мы сменим пароль для закрытого ключа промежуточного CA:

$ step crypto change-pass /tmp/step-ca/secrets/intermediate_ca_key  Please enter the password to encrypt /tmp/step-ca/secrets/intermediate_ca_key: ✔ Would you like to overwrite /tmp/step-ca/secrets/intermediate_ca_key [y/n]: y Your key has been saved in /tmp/step-ca/secrets/intermediate_ca_key.

Сейчас подходящий момент забэкапить root_ca_key и root_ca.crt в каком-нибудь надежном месте на другом устройстве.

Настраиваем и тестируем JWK модуль

Для тестирования и для выдачи сертификатов вручную нам потребуется активировать модуль JWK. На данном этапе самый простой способ — это снова запустить команду step ca init с дополнительными параметрами, а затем заменить сгенерированные сертификаты и ключи на те, которые у нас уже есть.

ВНИМАНИЕ: Пароль, который вы выберете на этом этапе, будет также и паролем к JWK модулю. Не используйте пароли от приватных ключей корневого или промежуточного CA.

Мы будем использовать порт 8443, поскольку порт 433 уже используется uhttpd для LuCi WebUI. В приведенной ниже команде предполагается, что IP-адрес роутера на стороне локальной сети — 192.168.1.1, а его полное доменное имя — openwrt.lan. Также на этот раз мы сгенерируем базу и файл конфигурации в каталоге /etc/step-ca:

$ export STEPPATH=/etc/step-ca  $ step ca init \ --name="Homelab CA" \ --dns="openwrt.lan" \ --dns="192.168.1.1" \ --address=":8443" \ --provisioner="JWK@openwrt.lan" \ --deployment-type standalone  Choose a password for your CA keys and first provisioner. ✔ [leave empty and we'll generate one]:XXXXX  Generating root certificate... done! Generating intermediate certificate... done!  ✔ Root certificate: /etc/step-ca/certs/root_ca.crt ✔ Root private key: /etc/step-ca/secrets/root_ca_key ✔ Root fingerprint: 5e4390f0581c479b7e36db4a3642d128f1160d745708e64f588c5f3dabaabd2f ✔ Intermediate certificate: /etc/step-ca/certs/intermediate_ca.crt ✔ Intermediate private key: /etc/step-ca/secrets/intermediate_ca_key ✔ Database folder: /etc/step-ca/db ✔ Default configuration: /etc/step-ca/config/defaults.json ✔ Certificate Authority configuration: /etc/step-ca/config/ca.json  Your PKI is ready to go. To generate certificates for individual services see 'step help ca'.

Удалим сгенерированные только что сертификаты и ключи и заменим на те, которые мы сгенерировали на первом шаге:

$ rm /etc/step-ca/certs/root_ca.crt $ rm /etc/step-ca/secrets/root_ca_key $ rm /etc/step-ca/certs/intermediate_ca.crt $ rm /etc/step-ca/secrets/intermediate_ca_key  $ mv /tmp/step-ca/secrets/intermediate_ca_key /etc/step-ca/secrets/ $ mv /tmp/step-ca/certs/intermediate_ca.crt /etc/step-ca/certs/ $ mv /tmp/step-ca/certs/root_ca.crt  /etc/step-ca/certs/

Также удалим ключ корневого CA, который вы уже должны были сохранить где-нибудь в безопасном месте за пределами OpenWRT.

$ rm /tmp/step-ca/secrets/root_ca_key

Каждый раз, когда step-ca стартует, нам нужно будет вводить пароль для приватного ключа промежуточного CA. Эту проблему можно решить, поместив пароль в файл, из которого step-ca прочитает пароль при загрузке.
Чтобы не оставлять следов в хистори, считаем пароль из stdin (после вставки пароля введите ctrl+d на новой строке):

$ cat -> /etc/step-ca/secrets/intermediate_ca_key_pass

Примечание: загрузка секрета из файла — приемлемый компромисс, если вы запускаете step-ca в контейнере в безопасной инфраструктуре. Но на голом железе это лишь чуть лучше, чем задать пустую строку в качестве пароля к ключу.

Сменим владельца файлов на step:step

$ chown -R step:step /etc/step-ca

и запустим step-ca от имени пользователя step, чтобы все новые файлы имели нужные права. Поскольку OpenWRT не предоставляет sudo «из коробки», воспользуемся командой start-stop-daemon :

$ start-stop-daemon -S \ -c step:step \ -x /usr/bin/step-ca -- \ /etc/step-ca/config/ca.json \ --password-file /etc/step-ca/secrets/intermediate_ca_key_pass  badger 2024/07/02 11:03:14 INFO: All 0 tables opened in 13ms 2024/07/02 11:03:14 Building new tls configuration using step-ca x509 Signer Interface 2024/07/02 11:03:16 Starting Smallstep CA/0.26.2 (linux/arm64) 2024/07/02 11:03:16 Documentation: https://u.step.sm/docs/ca 2024/07/02 11:03:16 Community Discord: https://u.step.sm/discord 2024/07/02 11:03:16 Config file: /etc/step-ca/config/ca.json 2024/07/02 11:03:16 The primary server URL is https://openwrt.lan:8443 2024/07/02 11:03:16 Root certificates are available at https://openwrt.lan:8443/roots.pem 2024/07/02 11:03:16 Additional configured hostnames: 192.168.1.1 2024/07/02 11:03:16 X.509 Root Fingerprint: cd6555dce8cfcab515223fdea99531a86d329df31d453d3d0eab4e5d185f6130 2024/07/02 11:03:16 Serving HTTPS on :8443 ...

Как видно из X.509 Root Fingerprint:, отпечаток соответствует ключу, который мы сгенерировали в самом начале. Этот отпечаток понадобится нам на следующем шаге.

Во второй ssh сессии сгенерируем тестовый сертификат для самих себя. Сначала инициализируем CLI:

$ unset STEPPATH $ step ca bootstrap \ --ca-url "https://openwrt.lan:8443" \ --fingerprint cd6555dce8cfcab515223fdea99531a86d329df31d453d3d0eab4e5d185f6130 The root certificate has been saved in /root/.step/certs/root_ca.crt. The authority configuration has been saved in /root/.step/config/defaults.json.

Затем сгенерируем приватный ключ и сертификат с Common Name="openwrt.lan". Нам также нужно будет ввести пароль, который мы задали, когда добавляли JWK модуль:

$ step ca certificate \ "openwrt.lan" \ localhost.crt localhost.key \ --san="openwrt.lan" \ --san="192.168.1.1"  ✔ Provisioner: JWK@openwrt.lan (JWK) [kid: sNXCP0f2uaMH3Nvj9wFHPwzQiSxQfSKVwLqO_73kstE] Please enter the password to decrypt the provisioner key: ✔ CA: https://openwrt.lan:8443 ✔ Certificate: localhost.crt ✔ Private Key: localhost.key

Взглянем на сертификат:

$ step certificate inspect localhost.crt --format=text  Certificate:     Data: ...     Signature Algorithm: ECDSA-SHA256         Issuer: O=Homelab,CN=Homelab Intermediate CA         Validity             Not Before: Jul 2 11:09:15 2024 UTC             Not After : Jul 3 11:10:15 2024 UTC         Subject: CN=openwrt.lan ...         X509v3 extensions:             X509v3 Key Usage: critical                 Digital Signature             X509v3 Extended Key Usage:                 Server Authentication, Client Authentication ...             X509v3 Subject Alternative Name:                 DNS:openwrt.lan                 IP Address:192.168.1.1             X509v3 Step Provisioner:                 Type: JWK                 Name: JWK@openwrt.lan                 CredentialID: sNXCP0f2uaMH3Nvj9wFHPwzQiSxQfSKVwLqO_73kstE ....

Супер, у нас есть сертификат, валидный целый день! Нам нужно автоматизировать выдачу сертификатов, но сначала запустим step-ca как сервис.

Завершите процесс step-ca или закройте второй терминал.

Запускаем step-ca как сервис в OpenWRT

Создадим файл /etc/init.d/step-ca

$ touch /etc/init.d/step-ca $ chmod +x /etc/init.d/step-ca

со следующим содержимым:

#!/bin/sh /etc/rc.common START=99 USE_PROCD=1 SERVICE_COMMAND='/usr/bin/step-ca' SERVICE_CONFIG='/etc/step-ca/config/ca.json' SERVICE_ARGS='--password-file /etc/step-ca/secrets/intermediate_ca_key_pass' SERVICE_PIDFILE=/var/run/step-ca.pid SERVICE_USER=step SERVICE_GROUP=step start_service() {     procd_open_instance     procd_set_param command $SERVICE_COMMAND     procd_append_param command $SERVICE_CONFIG     procd_append_param command $SERVICE_ARGS     procd_set_param user $SERVICE_USER     procd_set_param group $SERVICE_GROUP     procd_set_param pidfile $SERVICE_PIDFILE     procd_set_param stdout 1     procd_set_param stderr 1     procd_set_param file $SERVICE_CONFIG     procd_set_param respawn     procd_close_instance } reload_service() {         procd_send_signal step-ca }

Во второй ssh сессии запустим мониторинг syslog

$ logread -f 

и попробуем команды:

$ /etc/init.d/step-ca start $ /etc/init.d/step-ca stop $ /etc/init.d/step-ca restart $ /etc/init.d/step-ca status $ /etc/init.d/step-ca reload

Добавим сервис в автозагрузку

$ /etc/init.d/step-ca enable

и проверим, что сервис добавлен:

$ ls -l /etc/rc.d/ | grep step-ca lrwxrwxrwx  1  root root  17 Jul  2  11:20  S99step-ca -> ../init.d/step-ca

Теперь мы готовы запустить свой Let’s Encrypt.

Настраиваем и тестируем ACME модуль

Дополним наш CA модулем ACME:

$ export STEPPATH=/etc/step-ca  $ step ca provisioner add ACME@openwrt.lan --type ACME ✔ CA Configuration: /etc/step-ca/config/ca.json  Success! Your `step-ca` config has been updated.  To pick up the new configuration SIGHUP (kill -1 <pid>) or restart the step-ca process.

Применим обновленную конфигурацию:

$ /etc/init.d/step-ca reload

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

Первым делом проверим доступность API:

[rocky@test ~]$ curl -s --insecure https://openwrt.lan:8443/acme/ACME@openwrt.lan/directory | jq .  {   "newNonce": "https://openwrt.lan:8443/acme/ACME@openwrt.lan/new-nonce",   "newAccount": "https://openwrt.lan:8443/acme/ACME@openwrt.lan/new-account",   "newOrder": "https://openwrt.lan:8443/acme/ACME@openwrt.lan/new-order",   "revokeCert": "https://openwrt.lan:8443/acme/ACME@openwrt.lan/revoke-cert",   "keyChange": "https://openwrt.lan:8443/acme/ACME@openwrt.lan/key-change" }

Примечание: в URL часть ACME@openwrt.lan чувствительна к регистру. Она должна быть точно такой, как вводили в команде step ca provider add.

Теперь добавим в систему корневой сертификат нашего CA , чтобы TLS-соединения устанавливались без ошибок:

[rocky@test ~]$ curl -s --insecure https://openwrt.lan:8443/roots.pem | tee - openwrt.crt  [rocky@test ~]$ sudo mv openwrt.crt /etc/pki/ca-trust/source/anchors/openwrt.crt  [rocky@test ~]$ sudo update-ca-trust extract

Примечание: справочник по командам для установки корневых сертификатов в различных операционных системах и дистрибутивах Linux

Убедимся, что мы доверяем нашему CA:

[rocky@test ~]$ curl https://openwrt.lan:8443/roots

На этом этапе не должно возникать ошибок на Linux машинах.

Теперь загрузим acme.sh

[rocky@test ~]$ curl -LO https://raw.githubusercontent.com/acmesh-official/acme.sh/master/acme.sh  [rocky@test ~]$ chmod +x acme.sh

и последовательно протестируем проверки, которые поддерживает ACME модуль в step-ca:

  • http-01 — ACME предлагает клиенту разместить заданное случайное значение в /.well-known/acme-challenge на порту 80. Затем ACME отправляет HTTP GET запрос на этот URL.

  • tls-alpn-01 — ACME предлагает клиенту сгенерировать самоподписанный X.509 сертификат с заданной строкой в X509v3 расширении. Затем ACME устанавливает с запрошенным IP и/или доменным именем TLS соединение на порту 443 с использованием ALPN и проверяет наличие заданной строки в расширении сертификата.

  • dns-01 — ACME предлагает клиенту сделать ресурсную TXT запись в DNS для запрошенного доменного имени. Затем ACME отправляет запрос на чтение этой записи в DNS.

Тестируем http-01 проверку ACME

Для прохождения проверки типа http-01 нам понадобится ответить на http GET запрос на порту 80. Самым простым способом будет дать возможность acme.sh сделать все за нас, установив socat :

[rocky@test ~]$ sudo dnf install socat -y

Также, возможно, нужно будет открыть порт 80 в файрволе:

[rocky@test ~]$ sudo firewall-cmd --permanent --add-service=http [rocky@test ~]$ sudo firewall-cmd --reload

Теперь запросим сертификат с полным именем хоста и IP-адресом в качестве альтернативных имен субъекта (SAN). Поскольку задействован порт 80, проще всего запустить команду с помощью sudo:

[rocky@test ~]$ sudo ./acme.sh --force --issue --standalone \ --server https://openwrt.lan:8443/acme/ACME@openwrt.lan/directory \ -d $(ip -4 addr show eth0 | awk '/inet/ {split($2, a, "/"); print a[1]}') \ -d $(hostname -f)  [] Using CA: https://openwrt.lan:8443/acme/ACME@openwrt.lan/directory [] Standalone mode. [] Standalone mode. [] Create account key ok. [] Registering account: https://openwrt.lan:8443/acme/ACME@openwrt.lan/directory [] Registered [] ACCOUNT_THUMBPRINT='NYf5qbz9bWo_lBNytbgDaaFCv8MyUHpukWncBvZY7_E' [] Creating domain key [] The domain key is here: /root/.acme.sh/192.168.1.179_ecc/192.168.1.179.key [] Multi domain='IP:192.168.1.179,DNS:test.lan' [] Getting webroot for domain='192.168.1.179' [] Getting webroot for domain='test.lan' [] Verifying: 192.168.1.179 [] Standalone mode server [] Success [] Verifying: test.lan [] Standalone mode server [] Success [] Verify finished, start to sign. [] Lets finalize the order. [] Le_OrderFinalize='https://openwrt.lan:8443/acme/ACME@openwrt.lan/order/zYqWGaDfUJX3EaIxitPCFnGxvnKu9aJ6/finalize' [] Downloading cert. [] Le_LinkCert='https://openwrt.lan:8443/acme/ACME@openwrt.lan/certificate/X1jfcvk9jxso0KBJWDRYYceNE3TkKM3J' [] Cert success. -----BEGIN CERTIFICATE----- MIIB7jCCAZWgAwIBAgIRAMd9th9qDVmO3W1592vVCDEwCgYIKoZIzj0EAwIwNDEQ MA4GA1UEChMHSG9tZWxhYjEgMB4GA1UEAxMXSG9tZWxhYiBJbnRlcm1lZGlhdGUg Q0EwHhcNMjQwNzAyMTIyOTIyWhcNMjQwNzAzMTIzMDIyWjAAMFkwEwYHKoZIzj0C AQYIKoZIzj0DAQcDQgAEiW9mGrhg7ENqXP2c1xFRQLzBEFiiKk8hYD8nQ89Yv8Lp pOjLQCZtE0hwtyx1bquxUjToO9J5jCef9A+Bwy8luqOBuzCBuDAOBgNVHQ8BAf8E BAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBSr Alsat1jF0nWnyyy5BrGNJJV0VDAfBgNVHSMEGDAWgBQ/JWh0aHYV//iBsDVMo8jN 1kWpeDAcBgNVHREBAf8EEjAQggh0ZXN0LmxhbocEwKgBszApBgwrBgEEAYKkZMYo QAEEGTAXAgEGBBBBQ01FQG9wZW53cnQubGFuBAAwCgYIKoZIzj0EAwIDRwAwRAIg atUHqsKBI0X331v4raTjMGD4yqW2DNFaMOjb455zMmYCIGi9402P30gb+8uIdZtk y+OK2HMpRjoivujbaIG45qVi -----END CERTIFICATE----- [] Your cert is in: /root/.acme.sh/192.168.1.179_ecc/192.168.1.179.cer [] Your cert key is in: /root/.acme.sh/192.168.1.179_ecc/192.168.1.179.key [] The intermediate CA cert is in: /root/.acme.sh/192.168.1.179_ecc/ca.cer [] And the full chain certs is there: /root/.acme.sh/192.168.1.179_ecc/fullchain.cer

Взглянем на детали сертификата:

[rocky@test ~]$ sudo openssl x509 --noout --text -in /root/.acme.sh/192.168.1.179_ecc/192.168.1.179.cer  Certificate:     Data: ...         Signature Algorithm: ecdsa-with-SHA256         Issuer: O = Homelab, CN = Homelab Intermediate CA         Validity             Not Before: Jul  2 12:29:22 2024 GMT             Not After : Jul  3 12:30:22 2024 GMT         Subject: ...         X509v3 extensions:             X509v3 Key Usage: critical                 Digital Signature             X509v3 Extended Key Usage:                 TLS Web Server Authentication, TLS Web Client Authentication ...             X509v3 Subject Alternative Name: critical                 DNS:test.lan, IP Address:192.168.1.179             1.3.6.1.4.1.37476.9000.64.1:                 0......ACME@openwrt.lan.. ...

Прекрасно! Теперь протестируем проверку tls-alpn-01.

Тестируем tls-alpn-01 проверку ACME

Возможно, понадобится открыть порт 443 в файрволе:

[rocky@test ~]$ sudo firewall-cmd --permanent --add-service=https [rocky@test ~]$ sudo firewall-cmd --reload

Запросим сертификат с теми же SAN, что и ранее, но на этот раз с использованием опции --alpn:

[rocky@test ~]$ sudo ./acme.sh --force --issue --alpn \ --server https://openwrt.lan:8443/acme/ACME@openwrt.lan/directory \ -d $(ip -4 addr show eth0 | awk '/inet/ {split($2, a, "/"); print a[1]}') \ -d $(hostname -f)  [] Using CA: https://openwrt.lan:8443/acme/ACME@openwrt.lan/directory [] Standalone alpn mode. [] Standalone alpn mode. [] Multi domain='IP:192.168.1.179,DNS:test.lan' [] Getting webroot for domain='192.168.1.179' [] Getting webroot for domain='test.lan' [] Verifying: 192.168.1.179 [] Starting tls server. [] Success [] Verifying: test.lan [] Starting tls server. [] Success [] Verify finished, start to sign. [] Lets finalize the order. [] Le_OrderFinalize='https://openwrt.lan:8443/acme/ACME@openwrt.lan/order/AShNFavQaGD6NUQzAloClC25U7FpiaI5/finalize' [] Downloading cert. [] Le_LinkCert='https://openwrt.lan:8443/acme/ACME@openwrt.lan/certificate/bSqEfweMCa8Zrm0ACa9RvBR8m1EXYv72' [] Cert success. -----BEGIN CERTIFICATE----- MIIB7jCCAZSgAwIBAgIQCGxsU1/Od7+w9pOhGC4stTAKBggqhkjOPQQDAjA0MRAw DgYDVQQKEwdIb21lbGFiMSAwHgYDVQQDExdIb21lbGFiIEludGVybWVkaWF0ZSBD QTAeFw0yNDA3MDIxMjUyNTRaFw0yNDA3MDMxMjUzNTRaMAAwWTATBgcqhkjOPQIB BggqhkjOPQMBBwNCAASJb2YauGDsQ2pc/ZzXEVFAvMEQWKIqTyFgPydDz1i/wumk 6MtAJm0TSHC3LHVuq7FSNOg70nmMJ5/0D4HDLyW6o4G7MIG4MA4GA1UdDwEB/wQE AwIHgDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFKsC Wxq3WMXSdafLLLkGsY0klXRUMB8GA1UdIwQYMBaAFD8laHRodhX/+IGwNUyjyM3W Ral4MBwGA1UdEQEB/wQSMBCCCHRlc3QubGFuhwTAqAGzMCkGDCsGAQQBgqRkxihA AQQZMBcCAQYEEEFDTUVAb3BlbndydC5sYW4EADAKBggqhkjOPQQDAgNIADBFAiEA ubZ2PGzXa/s3QNCalEzPBP90yChTz68WOKDcBOWRaz8CIHiUZijOd4Z9oiR1/nLq L4CspCrIZjTSalfjoGRJ2Jhp -----END CERTIFICATE----- [] Your cert is in: /root/.acme.sh/192.168.1.179_ecc/192.168.1.179.cer [] Your cert key is in: /root/.acme.sh/192.168.1.179_ecc/192.168.1.179.key [] The intermediate CA cert is in: /root/.acme.sh/192.168.1.179_ecc/ca.cer [] And the full chain certs is there: /root/.acme.sh/192.168.1.179_ecc/fullchain.cer

Сработало!

Тестируем dns-01 проверку ACME

Дисклеймер: поскольку мы настроили в OpenWRT свою приватную зону DNS с динамическими обновлениями, то будем использовать DNS-хук dns_nsupdate для acme.sh. Если вы используете публичный DNS провайдер, загляните в интеграции acme.sh для соответствующих инструкций.

Скачиваем DNS-хук:

[rocky@test ~]$ curl -L --create-dirs -o dnsapi/dns_nsupdate.sh \ https://raw.githubusercontent.com/acmesh-official/acme.sh/master/dnsapi/dns_nsupdate.sh

Для этого теста воспользуемся уже имеющимся TSIG ключом:

[rocky@test ~]$ cat <<EOK> keys.conf key "tsig-key" {         algorithm hmac-sha256;         secret "HAyLN66//YxVF2lrZ6kSZK4TZEpV7WMvzYnNUQ0BvEo="; }; EOK

В общем случае, нужно через переменные среды $NSUPDATE_SERVER, $NSUPDATE_SERVER_PORT, $NSUPDATE_KEY и $NSUPDATE_ZONE передать в dns_nsupdate.sh информацию о параметрах DNS-сервера. В нашем же случае достаточно задать две из них:

[rocky@test ~]$ export NSUPDATE_SERVER="openwrt.lan" [rocky@test ~]$ export NSUPDATE_KEY="/home/rocky/keys.conf"

По умолчанию acme.sh пытается проверить появление запрошенной записи TXT, опрашивая основных публичных DNS провайдеров. Поскольку у нас приватная зона DNS, нам нужно запустить acme.sh с опцией --dnssleep=<int>, которая заставит его ждать <int> секунд перед опросом DNS-сервера по умолчанию вместо публичных.

С этим типом проверки мы не сможем использовать IP в качестве SAN, поэтому давайте вместо этого попросим вайлдкард (*) сертификат:

[rocky@test ~]$ ./acme.sh --force --issue --dns dns_nsupdate --dnssleep 0 \ --server https://openwrt.lan:8443/acme/ACME@openwrt.lan/directory \ -d $(hostname -f) \ -d *.$(hostname -f)  [] Using CA: https://openwrt.lan:8443/acme/ACME@openwrt.lan/directory [] Multi domain='DNS:test.lan,DNS:*.test.lan' [] Getting webroot for domain='test.lan' [] Getting webroot for domain='*.test.lan' [] Adding txt value: klj5Ewi9WBRJe3qDw6MMYuHcOScy7WJxhRFj-NAlQbE for domain:  _acme-challenge.test.lan [] adding _acme-challenge.test.lan. 60 in txt "klj5Ewi9WBRJe3qDw6MMYuHcOScy7WJxhRFj-NAlQbE" [] The txt record is added: Success. [] Adding txt value: J-Td2g5phdsjapXEMdIKlD-kJdPbzJmKlqhYxri35V0 for domain:  _acme-challenge.test.lan [] adding _acme-challenge.test.lan. 60 in txt "J-Td2g5phdsjapXEMdIKlD-kJdPbzJmKlqhYxri35V0" [] The txt record is added: Success. [] Sleep 0 seconds for the txt records to take effect [] Verifying: test.lan [] Success [] Verifying: *.test.lan [] Success [] Removing DNS records. [] Removing txt: klj5Ewi9WBRJe3qDw6MMYuHcOScy7WJxhRFj-NAlQbE for domain: _acme-challenge.test.lan [] removing _acme-challenge.test.lan. txt [] Removed: Success [] Removing txt: J-Td2g5phdsjapXEMdIKlD-kJdPbzJmKlqhYxri35V0 for domain: _acme-challenge.test.lan [] removing _acme-challenge.test.lan. txt [] Removed: Success [] Verify finished, start to sign. [] Lets finalize the order. [] Le_OrderFinalize='https://openwrt.lan:8443/acme/ACME@openwrt.lan/order/FO5Jl80UWqfNI3oQkapt7wv2U2rOP3F8/finalize' [] Downloading cert. [] Le_LinkCert='https://openwrt.lan:8443/acme/ACME@openwrt.lan/certificate/PcJd1MPhm1quJPCYrYm8m5UOJVVjDPGF' [] Cert success. -----BEGIN CERTIFICATE----- MIICBTCCAaqgAwIBAgIQH+jtwcPGB55FOSejBblXnDAKBggqhkjOPQQDAjA0MRAw DgYDVQQKEwdIb21lbGFiMSAwHgYDVQQDExdIb21lbGFiIEludGVybWVkaWF0ZSBD QTAeFw0yNDA3MDIxNTUwMjRaFw0yNDA3MDMxNTUxMjRaMBMxETAPBgNVBAMTCHRl c3QubGFuMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEiGQ65642wyOn5kdzlGTM kwi7/9KnRl6wLGOJ1hPGC0CE5FG4G9MpnyfuFfndL+4H3UZzIP0oN1D4DCoPj5kj gaOBvjCBuzAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG AQUFBwMCMB0GA1UdDgQWBBQqnem/GMhwtJVGRY6wn8ALxAYswjAfBgNVHSMEGDAW gBQ/JWh0aHYV//iBsDVMo8jN1kWpeDAfBgNVHREEGDAWggoqLnRlc3QubGFuggh0 ZXN0LmxhbjApBgwrBgEEAYKkZMYoQAEEGTAXAgEGBBBBQ01FQG9wZW53cnQubGFu BAAwCgYIKoZIzj0EAwIDSQAwRgIhAIp5TAMCK1RxiZKBELENjMPRwP+5kRJDl3dD aDk1gxbxAiEA34ARTiq8HXB+XAqpgKk++YP4ZjpKWOYr+2+sGt5gro4= -----END CERTIFICATE----- [] Your cert is in: /home/rocky/.acme.sh/test.lan_ecc/test.lan.cer [] Your cert key is in: /home/rocky/.acme.sh/test.lan_ecc/test.lan.key [] The intermediate CA cert is in: /home/rocky/.acme.sh/test.lan_ecc/ca.cer [] And the full chain certs is there: /home/rocky/.acme.sh/test.lan_ecc/fullchain.cer

Взглянем на детали сертификата:

[rocky@test ~]$ openssl x509 --noout --text -in /home/rocky/.acme.sh/test.lan_ecc/test.lan.cer  Certificate:     Data: ...         Signature Algorithm: ecdsa-with-SHA256         Issuer: O = Homelab, CN = Homelab Intermediate CA         Validity             Not Before: Jul  2 15:50:24 2024 GMT             Not After : Jul  3 15:51:24 2024 GMT         Subject: CN = test.lan ...         X509v3 extensions:             X509v3 Key Usage: critical                 Digital Signature             X509v3 Extended Key Usage:                 TLS Web Server Authentication, TLS Web Client Authentication ...             X509v3 Subject Alternative Name:                 DNS:*.test.lan, DNS:test.lan             1.3.6.1.4.1.37476.9000.64.1:                 0......ACME@openwrt.lan.. ...

Из любопытства заглянем в логи DNS сервера:

tsig-key: updating zone 'lan/IN': adding an RR at '_acme-challenge.test.lan' TXT "klj5Ewi9WBRJe3qDw6MMYuHcOScy7WJxhRFj-NAlQbE" ... tsig-key: updating zone 'lan/IN': adding an RR at '_acme-challenge.test.lan' TXT "J-Td2g5phdsjapXEMdIKlD-kJdPbzJmKlqhYxri35V0" ... tsig-key: updating zone 'lan/IN': deleting rrset at '_acme-challenge.test.lan' TXT ... tsig-key: updating zone 'lan/IN': deleting rrset at '_acme-challenge.test.lan' TXT

Что ж, ACME модуль работает, однако осталось решить еще несколько вопросов.

Настраиваем параметры сертификатов

В данный момент файл конфигурации нашего CA /etc/step-ca/config/ca.json должен выглядеть примерно так:

{     "root": "/etc/step-ca/certs/root_ca.crt",     "federatedRoots": null,     "crt": "/etc/step-ca/certs/intermediate_ca.crt",     "key": "/etc/step-ca/secrets/intermediate_ca_key",     "address": ":8443",     "insecureAddress": "",     "dnsNames": [             "openwrt.lan",             "192.168.1.1"     ],     "logger": {             "format": "text"     },     "db": {             "type": "badgerv2",             "dataSource": "/etc/step-ca/db",             "badgerFileLoadingMode": ""     },     "authority": {             "provisioners": [                     {                             "type": "JWK",                             "name": "JWK@openwrt.lan",                             "key": { ... },                             "encryptedKey": " ... "                     },                     {                             "type": "ACME",                             "name": "ACME@openwrt.lan",                             "claims": {                                     "enableSSHCA": true,                                     "disableRenewal": false,                                     "allowRenewalAfterExpiry": false,                                     "disableSmallstepExtensions": false                             },                             "options": {                                     "x509": {},                                     "ssh": {}                             }                     }             ],             "template": {},             "backdate": "1m0s"     },     "tls": { ... },     "commonName": "Step Online CA" }

Теперь давайте подправим несколько вещей:

  • Добавим больше деталей в поле Subject:;

  • Настроим срок действия сертификатов;

  • Включим расширение CRLDistributionPoints, чтобы побороть ошибку curl в Windows.

Настраиваем Subject:

На данный момент в Subject: сертификата присутствует только Common Name. Чтобы там появилось еще что-то, нам нужно заполнить authority.template:

...   "authority": { ...             "template":{                 "Country": "AQ",                 "Province": "South Pole",                 "Locality": "Amundsen-Scott South Pole Station",                 "Organization": "Homelab",                 "OrganizationalUnit": "Homelab cluster"                 }, } ... 

и перезагрузить конфигурацию:

$ /etc/init.d/step-ca reload

Примечание: технически не должно иметь значения, что у нас записано в поле "Country":, но некоторые системы зачем-то выполняют проверку на соответствие кодам ISO 3166-1 alpha-2. Поэтому на всякий случай рекомендуется выбрать что-нибудь из этого списка

Настраиваем срок действия сертификатов

По умолчанию step-ca не позволяет создавать сертификаты со сроком действия более чем 24 часа. Например, такой запрос не пройдет:

[rocky@test ~]$ ./acme.sh --force --issue --dns dns_nsupdate --dnssleep 0 \ --server https://openwrt.lan:8443/acme/ACME@openwrt.lan/directory \ -d $(hostname -f) \ --valid-to  "+25h"  ... [] Sign failed, finalize code is not 200. ...

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

Для каждого модуля в списке authority.provisioners[] и в самом authority можно задать словарь claims, который управляет проверкой запрошенных параметров сертификата. Полный список опций доступен здесь, но для нашей задачи нас интересуют:

  • maxTLSCertDuration — максимальный срок действия сертификата,

  • defaultTLSCertDuration — срок действия сертификата по умолчанию.

Давайте настроим наш CA так, чтобы модуль для ручной выдачи сертификатов JWK@openwrt.lan мог создавать сертификаты со сроком действия до года (8765h):

"provisioners": [     {      "type": "JWK",      "name": "JWK@openwrt.lan",      "claims": {          "maxTLSCertDuration": "8765h"      }, ....

а модуль для автоматической выдачи сертификатов ACME@openwrt.lan выдавал сертификаты со сроком действия два дня:

"provisioners": [ ...    {     "type": "ACME",     "name": "ACME@openwrt.lan",     "claims": {       ...       "maxTLSCertDuration": "48h",       "defaultTLSCertDuration": "48h",       ...     }, ...

Теперь файл конфигурации /etc/step-ca/config/ca.json выглядит примерно так:

{     "root": "/etc/step-ca/certs/root_ca.crt",     "federatedRoots": null,     "crt": "/etc/step-ca/certs/intermediate_ca.crt",     "key": "/etc/step-ca/secrets/intermediate_ca_key",     "address": ":8443",     "insecureAddress": "",     "dnsNames": [             "openwrt.lan",             "192.168.1.1"     ],     "logger": {             "format": "text"     },     "db": {             "type": "badgerv2",             "dataSource": "/etc/step-ca/db",             "badgerFileLoadingMode": ""     },     "authority": {             "provisioners": [                     {                             "type": "JWK",                             "name": "JWK@openwrt.lan",                             "claims": {                                 "maxTLSCertDuration": "8765h"                             },                             "key": {...},                             "encryptedKey": "..."                     },                     {                             "type": "ACME",                             "name": "ACME@openwrt.lan",                             "claims": {                                     "enableSSHCA": true,                                     "disableRenewal": false,                                     "allowRenewalAfterExpiry": false,                                     "disableSmallstepExtensions": false,                                     "maxTLSCertDuration": "48h",                                     "defaultTLSCertDuration": "48h"                             },                             "options": {                                     "x509": {},                                     "ssh": {}                             }                     }             ],             "template": {                 "Country": "AQ",                 "Province": "South Pole",                 "Locality": "Amundsen-Scott South Pole Station",                 "Organization": "Homelab",                 "OrganizationalUnit": "Homelab cluster"                 },             "backdate": "1m0s"     },     "tls": {...},     "commonName": "Step Online CA" }

Перезагружаем настройки

 $ /etc/init.d/step-ca reload

и просим ACME выдать сертификат на 2 дня:

[rocky@test ~]$ ./acme.sh --force --issue --dns dns_nsupdate --dnssleep 0 \ --server https://openwrt.lan:8443/acme/ACME@openwrt.lan/directory \ -d $(hostname -f) \ --valid-to  "+2d"  ... [] Cert success. ...

Включаем расширение CRLDistributionPoints

В Windows, даже если мы добавим наш CA в хранилище доверенных сертификатов с помощью следующих команд

> curl -k -LO https://openwrt.lan:8443/roots.pem > certutil -addstore -enterprise -f "Root" roots.pem

некоторые версии curl при https запросе на любой URL, который возвращает сертификат, выданный нашим CA, могут выдать ошибку:
curl: (35) schannel: next InitializeSecurityContext failed: CRYPT_E_NO_REVOCATION_CHECK (0x80092012)- The revocation function was unable to check revocation for the certificate.

Чтобы исправить такое поведение, нам нужно включить функцию CRL (списки отзыва сертификатов). Для этого в step-ca мы подымем незащищенный http эндпоинт crl на порту 8080 (как правило, это порт 80, но он уже используется веб-интерфейсом роутера) и настроим CA так, чтобы он добавлял соответствующее расширение X509v3 в сертификаты, генерируемые нашими модулями.

В /etc/step-ca/config/ca.json заполняем "insecureAddress" и добавляем словарь "crl":

{ ...     "insecureAddress": ":8080",     "crl": {         "enabled": true,         "generateOnRevoke": true,         "idpURL": "http://openwrt.lan:8080/crl"     }, ... }

Создаем файл шаблона для наших сертификатов:

$ mkdir -p /etc/step-ca/templates/x509 $ touch /etc/step-ca/templates/x509/leaf-crl.tpl $ chown -R step:step /etc/step-ca/templates/x509

и просто добавляем в базовый шаблон поле "crlDistributionPoints": ["http://openwrt.lan:8080/crl"]

$ cat <<EOF> /etc/step-ca/templates/x509/leaf-crl.tpl {      "subject": {{ toJson .Subject }},     "sans": {{ toJson .SANs }},  {{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }}      "keyUsage": ["keyEncipherment", "digitalSignature"],  {{- else }}      "keyUsage": ["digitalSignature"],  {{- end }}      "extKeyUsage": ["serverAuth", "clientAuth"],     "crlDistributionPoints": ["http://openwrt.lan:8080/crl"]  } EOF

Теперь в файле /etc/step-ca/config/ca.json для каждого модуля в списке authority.provisioners[]  нам нужно сослаться в options.x509 на путь к шаблону "templateFile": "/etc/step-ca/templates/x509/leaf-crl.tpl":

... "options": {     "x509": {         "templateFile": "/etc/step-ca/templates/x509/leaf-crl.tpl"     }, ... } ...

В конечном итоге файл конфигурации /etc/step-ca/config/ca.json будет выглядеть примерно так:

{     "root": "/etc/step-ca/certs/root_ca.crt",     "federatedRoots": null,     "crt": "/etc/step-ca/certs/intermediate_ca.crt",     "key": "/etc/step-ca/secrets/intermediate_ca_key",     "address": ":8443",     "insecureAddress": ":8080",     "crl": {         "enabled": true,         "generateOnRevoke": true,         "idpURL": "http://openwrt.lan:8080/crl"     },     "dnsNames": [             "openwrt.lan",             "192.168.1.1"     ],     "logger": {             "format": "text"     },     "db": {             "type": "badgerv2",             "dataSource": "/etc/step-ca/db",             "badgerFileLoadingMode": ""     },     "authority": {             "provisioners": [                     {                             "type": "JWK",                             "name": "JWK@openwrt.lan",                             "claims": {                                 "maxTLSCertDuration": "8765h"                             },                             "key": {...},                             "encryptedKey": "...",                             "options": {                                     "x509": {                                         "templateFile": "/etc/step-ca/templates/x509/leaf-crl.tpl"                                     }                                 }                     },                     {                             "type": "ACME",                             "name": "ACME@openwrt.lan",                             "claims": {                                     "enableSSHCA": true,                                     "disableRenewal": false,                                     "allowRenewalAfterExpiry": false,                                     "disableSmallstepExtensions": false,                                     "maxTLSCertDuration": "48h",                                     "defaultTLSCertDuration": "48h"                             },                             "options": {                                     "x509": {                                         "templateFile": "/etc/step-ca/templates/x509/leaf-crl.tpl"                                     },                                     "ssh": {}                             }                     }             ],             "template": {                 "Country": "AQ",                 "Province": "South Pole",                 "Locality": "Amundsen-Scott South Pole Station",                 "Organization": "Homelab",                 "OrganizationalUnit": "Homelab cluster"                 },             "backdate": "1m0s"     },     "tls": {...},     "commonName": "Step Online CA" }

На этот раз придется перезагрузить сам сервис:

$ /etc/init.d/step-ca restart

Самый быстрый способ проверить, присутствует ли в сертификатах расширение X509v3 CRL Distribution Points: — это создать сертификат для webUI самого роутера:

$ step ca certificate openwrt.lan \ /etc/uhttpd.crt /etc/uhttpd.key \ --san openwrt.lan \ --not-after=8765h  ✔ Provisioner: JWK@openwrt.lan (JWK) [kid: sNXCP0f2uaMH3Nvj9wFHPwzQiSxQfSKVwLqO_73kstE]  Please enter the password to decrypt the provisioner key: ✔ CA: https://openwrt.lan:8443 ✔ Would you like to overwrite /etc/uhttpd.crt [y/n]: y ✔ Would you like to overwrite /etc/uhttpd.key [y/n]: y ✔ Certificate: /etc/uhttpd.crt ✔ Private Key: /etc/uhttpd.key

Перезагружаем сервис uhttpd

$ /etc/init.d/uhttpd restart

и проверяем, ушла ли проблема:

C:\>curl -I https://openwrt.lan:443 HTTP/1.1 200 OK

Заключение

Если вы дошли до этого места — в ваших руках находится инструмент, который может значительно упростить использование TLS в домашней лаборатории.

Удачи в экспериментах!


ссылка на оригинал статьи https://habr.com/ru/articles/827206/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *