fix(observability): конфиг Alertmanager читается контейнером — гейт падал на своих же правах (#3078)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped

Как только канал алертов реально включился (#3126), профиль alerts впервые
дошёл до проверки конфига — и деплой встал:

    amtool: error: failed to validate 1 file(s)
    Checking '/tmp/am.yml'  FAILED: open /tmp/am.yml: permission denied

Отрендеренный конфиг пишется с правами 600 и принадлежит деплой-пользователю,
а и amtool, и сам Alertmanager в образе prom/alertmanager работают под nobody
(65534). Прочитать чужой файл 600 они не могут. Проверка падала не на
содержимом конфига, а на доступе к нему — и контейнер после подъёма упал бы
ровно там же.

Дефект не поймали раньше по понятной причине: без токена профиль alerts не
включался, и эта ветка не исполнялась НИ РАЗУ с момента появления гейта в
#3111. Проверка, которая никогда не запускалась, ничем не отличается от
отсутствующей — это ровно тот класс тихой поломки, ради которого весь стек и
заводится.

Права не ослабляем: в файле лежит токен бота. Вместо chmod 644 (который внёс
бы токен в список файлов, читаемых любым локальным пользователем машины)
отдаём файл во владение 65534 одноразовым контейнером от root — passwordless
sudo на хосте нет, а бинд-маунт правит host-инод напрямую. Доступ остаётся
ровно у того, кто конфиг читает.

Следствие, которое легко проглядеть: после смены владельца `>` в этот файл
на следующем деплое уже не запишет, поэтому добавлен rm перед рендером —
каталог принадлежит деплой-пользователю, пересоздать файл он может.
This commit is contained in:
bot-backend 2026-08-27 12:58:15 +03:00
parent 70db090813
commit 630f3e6e76

View file

@ -153,11 +153,33 @@ jobs:
METRICS_TELEGRAM_BOT_TOKEN="$METRICS_TELEGRAM_BOT_TOKEN" \
METRICS_TELEGRAM_CHAT_ID="$METRICS_TELEGRAM_CHAT_ID" \
METRICS_TELEGRAM_TOPIC_LINE="$METRICS_TELEGRAM_TOPIC_LINE" \
# rm перед записью обязателен: после chown ниже файл принадлежит
# 65534 с правами 600, и на СЛЕДУЮЩЕМ деплое `>` в него уже не
# запишет. Каталог принадлежит деплой-пользователю, поэтому
# пересоздать файл он может, а перезаписать — нет.
rm -f ops/metrics/alertmanager/alertmanager.yml
envsubst '${METRICS_TELEGRAM_BOT_TOKEN} ${METRICS_TELEGRAM_CHAT_ID} ${METRICS_TELEGRAM_TOPIC_LINE}' \
< ops/metrics/alertmanager/alertmanager.yml.tmpl \
> ops/metrics/alertmanager/alertmanager.yml
chmod 600 ops/metrics/alertmanager/alertmanager.yml
# В файле лежит токен бота, поэтому 600 не ослабляем. Но и amtool
# ниже, и сам Alertmanager в образе prom/alertmanager работают под
# `nobody` (65534) и файл владельца-деплойщика прочитать не могут:
# проверка падала на `open /tmp/am.yml: permission denied`, а
# контейнер после подъёма упал бы ровно там же. Гейт не поймали
# раньше только потому, что без токена профиль alerts вообще не
# включался и эта ветка не исполнялась ни разу.
#
# Отдаём файл тому, кто его читает. chown делаем одноразовым
# контейнером от root: passwordless sudo на хосте нет, а
# бинд-маунт правит host-инод напрямую.
#
# Почему не 644: это внесло бы токен в список файлов, читаемых
# любым локальным пользователем машины. Владение 65534 при 600
# оставляет доступ ровно у контейнера, и ни у кого больше.
docker run --rm --user 0:0 -v "$PWD/ops/metrics/alertmanager/alertmanager.yml:/tmp/am.yml" --entrypoint chown "$(grep -oE 'prom/alertmanager:[^ ]+' docker-compose.metrics.yml | head -1)" 65534:65534 /tmp/am.yml
# Проверяем ДО подъёма, как и Caddyfile ниже. Битый конфиг
# Alertmanager не «деградирует» — контейнер не стартует вовсе, и
# алертинг молча исчезает целиком. amtool берём из того же образа,