RabbitMQ: як повідомлення можуть витікати без зупинки сервісу

Сервіс працює. А дані вже прочитали

Витік даних із RabbitMQ не обов’язково супроводжується збоєм. Застосунок обробляє завдання, черга розвантажується, користувачі не помічають відмови. Але частина повідомлень могла пройти через стороннього споживача, перш ніж потрапити до застосунку. Споживач (consumer) — це клієнт, який отримує повідомлення з черги.

Механізм простий: отримати повідомлення → прочитати вміст → повернути в чергу через NACK(requeue=true). Повернення зберігає можливість подальшої обробки, але не скасовує читання, яке вже відбулося.

У цьому й ризик: доступність сервісу та конфіденційність повідомлень — різні властивості. Успішно оброблене завдання не доводить, що його вміст бачив лише потрібний сервіс.

Які права для цього потрібні

Це не обхід автентифікації й не мережева атака «людина посередині» (MITM). Сценарій передбачає, що зловмисник уже може підключитися до брокера й має дозвіл читати цільову чергу — наприклад, через скомпрометований сервісний обліковий запис або надто широкі права у списках контролю доступу (ACL). Конфігурація також має дозволяти ще одного активного споживача. [1][3]

Право read не означає пасивного перегляду. Споживач бере участь у доставці повідомлень. TLS захищає з’єднання, але не приховує повідомлення від клієнта, якому брокер дозволив їх отримати.

Отримати, прочитати, повернути

У звичайній черзі повідомлення розподіляються між активними споживачами, а не надсилаються кожному у вигляді копій. Частина повідомлень дістається застосунку, частина може дістатися сторонньому споживачу. Розподіл залежить від ліміту prefetch (кількості допустимих непідтверджених доставок), доступності споживачів і налаштувань черги. Ексклюзивний споживач (exclusive consumer) або режим Single Active Consumer можуть не дозволити іншому споживачу отримувати повідомлення одночасно із застосунком. [1]

publisher → queue → додатковий споживач
                         │
                         ├─ читає вміст
                         └─ NACK(requeue=true) → queue → наступна доставка

У режимі ручних підтверджень доставлене повідомлення залишається непідтвердженим, доки споживач не відповість. ACK підтверджує отримання й дозволяє брокеру видалити повідомлення з черги. NACK відхиляє доставку; з параметром requeue=true повідомлення знову стає доступним для доставки. Це не створення копії й не вказівка брокеру обов’язково передати повідомлення застосунку. Наступним одержувачем може бути застосунок — або той самий сторонній клієнт. [2]

Якщо повідомлення потім отримує та успішно обробляє застосунок, бізнес-операція може завершитися без видимої відмови. Конфіденційність на цей момент уже порушена.

Чому це може залишитися непоміченим

Якщо моніторинг перевіряє лише доступність застосунку та завершення завдань, стороннє читання може не спричинити явного сигналу тривоги. Механізм не потребує зміни коду застосунку або навмисної зупинки сервісу.

Але відсутність тривоги не означає відсутності слідів. У брокері з’являються додаткові споживачі та з’єднання, відбуваються повторні доставки. Ці зміни можна виявити. Відсутність скарг користувачів не замінює їх моніторингу.

Немає гарантії, що сервіс і далі працюватиме без змін: можливі затримки, зміна порядку доставки, додаткове навантаження й цикли повторної доставки. Політики черги, її тип і версія RabbitMQ також впливають на результат. [2]

Приклад коду: повернення не скасовує читання

Нижче — локальна модель одного можливого сценарію, а не клієнт RabbitMQ. Вона працює із синтетичним повідомленням у пам’яті, нікуди не підключається й показує головне: повідомлення можна успішно обробити, хоча його вміст уже прочитав інший учасник.

Збережіть приклад у delivery_model.py та запустіть python delivery_model.py. Зовнішні бібліотеки не потрібні.

from collections import deque

# Synthetic data only: no network connection or external credentials.
queue = deque([{"id": "demo-1", "body": "synthetic payload"}])
observed = []
processed = []

# Model one possible delivery to an additional consumer.
message = queue.popleft()
observed.append(message.copy())
queue.appendleft(message)  # Model NACK(requeue=True), not a broker call.

# For this trace, explicitly choose the application as the next recipient.
# A real broker does not guarantee this recipient after requeue.
message = queue.popleft()
processed.append(message["id"])  # Model successful processing and ACK.

assert observed[0]["id"] == processed[0]
assert not queue
print("Observed:", observed[0]["id"])
print("Processed:", processed[0])
print("Queue empty:", not queue)

Приклад навмисно обирає застосунок наступним одержувачем. У реальному RabbitMQ requeue цього не гарантує. Ця модель не вимірює розподіл доставок, швидкість обробки або помітність з’єднання.

А якщо споживачів більше?

Кількість споживачів може впливати на розподіл доставок, але універсальної формули для частки повідомлень, яку зможе прочитати сторонній клієнт, немає. Частка сторонніх споживачів не дорівнює частці унікальних прочитаних ними повідомлень: повторне отримання того самого повідомлення не збільшує цю частку, а вже підтверджене застосунком повідомлення більше недоступне з черги.

Більше з’єднань означає більше учасників, яких можна виявити, і потенційно більше навантаження. Це не гарантує повного перехоплення й не робить втручання невидимим.

Що перевіряти власнику брокера

  • Облікові записи та ACL. Використовуйте окремі облікові записи для сервісів, дозволяйте читання лише потрібних черг і за потреби ізолюйте сервіси в окремих віртуальних хостах. [3]
  • Споживачі та з’єднання. Порівнюйте їх з очікуваною топологією, а не лише перевіряйте, чи розвантажується черга.
  • Повторні доставки та затримки. Їх зростання може мати звичайні причини, наприклад збої застосунку, але потребує пояснення.
  • Мережевий доступ. Обмежте доступ до AMQP і Management API тими сегментами й сервісами, яким він справді потрібен.
  • Реакція на компрометацію. Відкличте доступ і завершіть небажані з’єднання. Успішна обробка повідомлень не виключає витоку.

Висновок

Небезпека схеми «прочитати й повернути» не в гарантованій невидимості, а в тому, що витік може не супроводжуватися явною відмовою сервісу. Повідомлення може повернутися в чергу й бути оброблене, але скасувати розкриття даних, яке вже відбулося, неможливо.

Джерела

  1. RabbitMQ: Consumers
  2. RabbitMQ: Consumer Acknowledgements and Publisher Confirms
  3. RabbitMQ: Access Control