Исходящие вызовы
Наружу ходит шлюз, а не база. Расширений вроде 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перепроверяется свежим списком.