diff --git a/.forgejo/workflows/deploy-obsidian.yml b/.forgejo/workflows/deploy-obsidian.yml index 9dfdbd2f..cbba26d2 100644 --- a/.forgejo/workflows/deploy-obsidian.yml +++ b/.forgejo/workflows/deploy-obsidian.yml @@ -32,13 +32,21 @@ name: Deploy Obsidian # v1.5.0 означает ssh.InsecureIgnoreHostKey(), то есть ровно как до этого PR. # Включается одной настройкой, как INFRA_DEPLOY_HOST (#3059) и fail-open у # TRADEIN_INTERNAL_AUTH_SECRET (#2989). -# NB: этот workflow ходит на secrets.DEPLOY_HOST, поэтому и отпечаток берётся -# DEPLOY_SSH_FINGERPRINT — пара «хост+отпечаток» обязана меняться вместе. Но -# CouchDB по плану ОСТАЁТСЯ на Beget вместе с Forgejo/GlitchTip (#3062), а -# DEPLOY_HOST после 30.08 будет указывать на Selectel. Значит у этого файла есть -# отдельная, ещё не закрытая проблема адресата — тот же класс, что #3059. Здесь -# намеренно НЕ трогаем: правка про подлинность хоста, а не про маршрутизацию. -# При переводе на INFRA_DEPLOY_HOST не забыть переключить и отпечаток. +# АДРЕСАТ (#3062). CouchDB/Obsidian ОСТАЁТСЯ на Beget вместе с Forgejo и +# GlitchTip, а DEPLOY_HOST после 30.08 будет указывать на Selectel. Раньше этот +# workflow ходил на DEPLOY_HOST безусловно — то есть в день переезда молча начал +# бы разворачивать стек CouchDB не на той машине: git reset на /opt/gendesign +# продуктового хоста, а волт на Beget тем временем перестал бы обновляться. +# Отказа при этом не было бы — деплой зелёный, адресат другой. +# +# Теперь адресат берётся как INFRA_DEPLOY_HOST, а если он не задан — DEPLOY_HOST. +# До переезда это одна и та же машина, поэтому поведение не меняется; после — +# workflow сам остаётся на инфраструктурном хосте, без правки этого файла. +# +# Отпечаток идёт В ПАРЕ с адресатом и БЕЗ перекрёстного фолбэка: сверять ключ +# Beget'а с отпечатком Selectel'а — гарантированный отказ. Задан INFRA_DEPLOY_HOST +# → берётся INFRA_DEPLOY_SSH_FINGERPRINT; не задан → DEPLOY_SSH_FINGERPRINT. +# Пусто в выбранной ветке → проверка подлинности пропускается, как и раньше. # ───────────────────────────────────────────────────────────────────────────── on: push: @@ -68,17 +76,46 @@ jobs: # кого, отказ сломал бы деплой в момент мержа этого PR, а правило здесь — # «инертно по умолчанию, включается одной настройкой». Заведут секрет — # предупреждение исчезнет само. - - name: Подлинность хоста — статус проверки (#3029) + - name: Адресат и подлинность хоста (#3062, #3029) + id: target env: - HOST_FINGERPRINT: ${{ secrets.DEPLOY_SSH_FINGERPRINT }} + INFRA_HOST: ${{ secrets.INFRA_DEPLOY_HOST }} + INFRA_FINGERPRINT: ${{ secrets.INFRA_DEPLOY_SSH_FINGERPRINT }} + MAIN_FINGERPRINT: ${{ secrets.DEPLOY_SSH_FINGERPRINT }} run: | set -euo pipefail - if [ -n "${HOST_FINGERPRINT:-}" ]; then - echo "Подлинность хоста: сверяется по DEPLOY_SSH_FINGERPRINT." + # Отпечаток — публичный хеш ключа хоста, не секрет: его можно + # передать через output. Приватный ключ так передавать нельзя, + # поэтому он остаётся прямой ссылкой на секрет в шаге ниже. + if [ -n "${INFRA_HOST:-}" ]; then + echo "Адресат: INFRA_DEPLOY_HOST — хосты разъехались, стек CouchDB едет на инфраструктурный хост." + HOST_FINGERPRINT="${INFRA_FINGERPRINT:-}" + FINGERPRINT_SOURCE="INFRA_DEPLOY_SSH_FINGERPRINT" else - echo '::warning title=SSH без проверки подлинности хоста::DEPLOY_SSH_FINGERPRINT не задан — ключ хоста НЕ проверяется (#3029). По каналу едет DEPLOY_SSH_KEY и разворачивается стек CouchDB/Obsidian. После переезда на Selectel (#3057) соединение идёт через интернет. Как снять отпечаток — см. шапку этого файла.' + echo "Адресат: DEPLOY_HOST — INFRA_DEPLOY_HOST не задан, хосты ещё одна машина." + HOST_FINGERPRINT="${MAIN_FINGERPRINT:-}" + FINGERPRINT_SOURCE="DEPLOY_SSH_FINGERPRINT" + fi + # $GITHUB_OUTPUT — формат «ключ=значение» построчно, поэтому перевод + # строки внутри значения означает инъекцию произвольного output'а. + # Отпечаток однострочный по определению (SHA256:...), а вот копипаста + # в поле секрета лишний \n добавляет легко — шапка этого файла об этом + # прямо предупреждает. Не вычищаем молча: сверка побайтовая, тихий trim + # изменил бы результат проверки. Падаем с внятным текстом. + case "${HOST_FINGERPRINT}" in + *[![:print:]]*) + echo "ОШИБКА: ${FINGERPRINT_SOURCE} содержит перевод строки или непечатный символ." >&2 + echo "ОШИБКА: значение должно быть одной строкой вида SHA256:xxxx — перезадай секрет без лишних символов." >&2 + exit 1 + ;; + esac + echo "fingerprint=${HOST_FINGERPRINT}" >> "$GITHUB_OUTPUT" + if [ -n "${HOST_FINGERPRINT:-}" ]; then + echo "Подлинность хоста: сверяется по ${FINGERPRINT_SOURCE}." + else + echo "::warning title=SSH без проверки подлинности хоста::${FINGERPRINT_SOURCE} не задан — ключ хоста НЕ проверяется (#3029). По каналу едет ssh-ключ и разворачивается стек CouchDB/Obsidian. После разъезда хостов (#3057) соединение идёт через интернет. Как снять отпечаток — см. шапку этого файла." echo '###############################################################' - echo '# ВНИМАНИЕ (#3029): DEPLOY_SSH_FINGERPRINT не задан.' + echo "# ВНИМАНИЕ (#3029): ${FINGERPRINT_SOURCE} не задан." echo '# Ключ хоста НЕ проверяется — канал уязвим к MITM.' echo '# Как снять отпечаток — см. шапку этого файла.' echo '###############################################################' @@ -87,13 +124,19 @@ jobs: - name: Deploy obsidian stack via SSH uses: appleboy/ssh-action@v1.0.3 with: - host: ${{ secrets.DEPLOY_HOST }} - username: ${{ secrets.DEPLOY_USER }} - key: ${{ secrets.DEPLOY_SSH_KEY }} - port: ${{ secrets.DEPLOY_PORT || 22 }} - # #3029: подлинность хоста. Секрет НЕ задан → пустая строка → easyssh-proxy - # оставляет ssh.InsecureIgnoreHostKey(), то есть сегодняшнее поведение. - fingerprint: ${{ secrets.DEPLOY_SSH_FINGERPRINT }} + # #3062: адресат — инфраструктурный хост, если хосты уже разъехались. + # До этого INFRA_DEPLOY_HOST пуст и всё идёт на DEPLOY_HOST, как раньше. + host: ${{ secrets.INFRA_DEPLOY_HOST || secrets.DEPLOY_HOST }} + # user/key/port с фолбэком: у двух хостов они совпадают, а отдельные + # INFRA_*-секреты может и не завести — тогда работают общие. + username: ${{ secrets.INFRA_DEPLOY_USER || secrets.DEPLOY_USER }} + key: ${{ secrets.INFRA_DEPLOY_SSH_KEY || secrets.DEPLOY_SSH_KEY }} + port: ${{ secrets.INFRA_DEPLOY_PORT || secrets.DEPLOY_PORT || 22 }} + # #3029: подлинность хоста. Отпечаток выбран шагом выше В ПАРЕ с + # адресатом — перекрёстного фолбэка здесь быть не должно, иначе после + # переезда ключ Beget'а сверялся бы с отпечатком Selectel'а. + # Пусто → easyssh-proxy оставляет ssh.InsecureIgnoreHostKey(), как сегодня. + fingerprint: ${{ steps.target.outputs.fingerprint }} script: | set -euo pipefail cd /opt/gendesign