Перейти к основному содержимому

Исходящие вызовы

Наружу ходит шлюз, а не база. Расширений вроде http или pg_net у вашей базы нет осознанно: синхронный HTTP из Postgres выпивает соединения, а возможность ходить куда угодно из SQL — это открытый прокси с нашего адреса.

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

ЧастьЧто это
Секретызначения, которые подставляются в вызов и не возвращаются наружу никогда
Разрешённые хостысписок вида api.telegram.org — без него мы открытый прокси
Описание вызоваметод, адрес, заголовки и тело с подстановками

Все три настраиваются у базы в панели (раздел «API» → «Исходящие»).

Описание вызова

{
"name": "telegram_notify",
"method": "POST",
"url_template": "https://api.telegram.org/bot{{secrets.TELEGRAM_BOT_TOKEN}}/sendMessage",
"body_template": {
"chat_id": "{{secrets.CHAT_ID}}",
"text": "{{args.text}}"
}
}

Подстановок ровно две: {{secrets.X}} — ваш секрет, {{args.y}} — то, что передал вызывающий. Свободного fetch через нас нет: адрес и заголовки задаёт описание, а не тот, кто зовёт.

Два пути доставки

Очередь — из самой базы

CREATE FUNCTION api.submit(payload jsonb) RETURNS jsonb
LANGUAGE plpgsql SECURITY DEFINER AS $$
DECLARE new_id bigint;
BEGIN
INSERT INTO public.entries (payload) VALUES (payload) RETURNING id INTO new_id;
INSERT INTO api.outbox (call_name, args)
VALUES ('telegram_notify',
jsonb_build_object('text', 'Новая заявка № ' || new_id));
RETURN jsonb_build_object('ok', true, 'id', new_id);
END $$;

Таблица api.outbox живёт в вашей базе, поэтому запись и уведомление попадают в одну транзакцию: откатилась заявка — уведомление не ушло. Шлюз разбирает очередь, повторяет с растущей задержкой и складывает исход в те же строки (status, attempts, response_code, last_error).

Повторяются сетевые сбои и 5xx. Не повторяются 4xx и ошибки настройки — их чинит человек, а не время.

Синхронно — из приложения

POST https://data.layero.ru/<база>/call/<имя>

Ответ внешнего сервиса возвращается вызывающему. Публичному ключу такой вызов доступен, только если в описании явно разрешён анонимный вызов: иначе публичный ключ означал бы право слать что угодно куда угодно с нашего адреса.

Что уже учтено

  • Секреты не возвращаются — значения вырезаются и из тела ответа, и из текста ошибки, и из журнала.
  • Защита от SSRF — адрес резолвится и проверяется: внутренние диапазоны запрещены, подмена DNS на них не проходит.
  • Telegram доезжает. DNS отдаёт для api.telegram.org один адрес, и с части сетей он заблокирован намертво. Шлюз пробует и его, и запасные адреса Telegram, идёт на тот, до которого дошло рукопожатие, и сохраняет имя хоста в TLS — сертификат проверяется по имени, а не по адресу.
  • Хост, добавленный только что, работает сразу — отказ host_not_allowed перепроверяется свежим списком.