Збій client-connect в OpenVPN: чи гарантоване очищення при disconnect?
Відхилений клієнт не скасовує правило брандмауера й не видаляє запис. Порівняйте контракт зворотного виклику з відкатом, який ваш hook має виконати сам.
Коли скрипт client-connect завершується з ненульовим кодом, OpenVPN відхиляє клієнта, і адміністратори зазвичай вважають, що відповідний скрипт client-disconnect усе прибере. Часто він узагалі не запускається, бо обробка disconnect залежить від того, що встиг завершити етап connect. Розглядати відхилення й відкат як один механізм — це те, що перетворює невдалий вхід на витік правило брандмауера або осиротілий запис. Це два окремі обов’язки, і лише один із них автоматичний. Прочитайте власний шлях виходу hook, перш ніж звинувачувати сервер у залишеному стані.
Ненульовий вихід відхиляє клієнта, але не скасовує вашу роботу
Код виходу client-connect — це рішення про допуск. Нуль означає допуск, будь-яке інше значення — знесення екземпляра до того, як він повністю встановиться. Ніщо в цьому контракті не перевіряє, що скрипт уже зробив у зовнішньому світі. Якщо скрипт додав правило iptables, записав рядок або збільшив лічильник, ці зміни переживають відхилення.
Та сама асиметрія стосується плагінів, де для одного екземпляра можуть виконуватися кілька функцій connect. Пізніший збій не скасовує заднім числом попередній успіх, і попередня функція не має способу дізнатися, що сусідня функція зазнала невдачі.
Відхилення автоматичне, очищення — ні
Посібник OpenVPN 2.6 пов’язує обробку client-disconnect з успіхом попередньої обробки connect. Документований виняток охоплює каскадні ланцюги: якщо один крок connect уже повернув успіх, відповідні кроки disconnect усе одно виконуються під час знесення екземпляра, навіть якщо пізніший крок connect зазнав невдачі. Цей виняток вузький і залежить від кількості налаштованих зворотних викликів, тому тестування одного shell-скрипта нічого не говорить про ланцюг.
Складіть карту реальної конфігурації, перш ніж проєктувати навколо неї: який скрипт, який плагін і в якому порядку виконуються. Таблиця нижче перелічує, що залишає по собі незавершене налаштування.
| What the hook did before failing | What the client sees | What stays behind |
|---|---|---|
| Created a firewall entry | A rejection, with no tunnel | A rule matching an address nobody owns |
| Wrote a state row | A rejection, with no tunnel | A record of a session that never started |
| Allocated a tunnel address | A rejection, with no tunnel | An address marked busy until something releases it |
Кожен рядок закінчується однаково для клієнта й інакше для хоста. Клієнт не може визначити, що саме сталося, тож hook має відкотити зроблене до повернення статусу помилки.
Компенсуйте всередині скрипта, який зазнав невдачі
Перевірте все, що можна, перш ніж торкатися спільного стану: поля сертифіката, доступність адреси, групу, до якої належить клієнт. Найдешевше відхилення — те, що відбувається до будь-якого виділення. Щойно налаштування охоплює кілька зовнішніх операцій, ведіть облік виконаних і відкочуйте їх у зворотному порядку перед поверненням статусу помилки.
Конкретний випадок: скрипт створює запис брандмауера для кожного клієнта, а потім не може записати рядок стану, який його описує. Гілка, що зазнала невдачі, має видалити щойно створений запис і потім вийти з ненульовим кодом. Очікування події disconnect, яка може ніколи не бути запланована, — це спосіб, у який правило переживає сеанс, для якого його призначено.
Зробіть компенсацію ідемпотентною та з власником
Очищення має бути безпечним для двократного запуску й точним щодо того, що йому можна видаляти. Пізня подія з попередньої спроби не повинна видаляти виділення новішого сеансу лише тому, що обидва мають спільне ім’я або перевикористану адресу тунелю. Записуйте конкретний дескриптор або ідентифікатор операції разом із ресурсом і видаляйте за цим ключем, а не за іменем. Транзакції бази даних допомагають лише з власними змінами бази; правило брандмауера за цією межею потребує явного кроку компенсації.
Припускайте, що процес може померти між зміною й очищенням, і додайте узгоджувач, який за розкладом порівнює зовнішній стан зі списком допущених клієнтів.
Перевірте три шляхи, які справді різняться
Відпрацюйте три випадки: відхилення до того, як щось виділено, відхилення після часткового виділення й звичайний допущений сеанс, який згодом завершується. Ці три дають різні рядки журналу й різні залишки. Перевіряйте зовнішній стан після кожного з них, а не лише повідомлення про помилку клієнта, бо відмова клієнта — очікуваний результат у двох випадках із трьох.
Записуйте етап hook, код виходу й те, що було виділено або звільнено, без облікових даних. Журнал hook, у якому названо створений ресурс, — це те, що робить витік правило знаходимим через пів року.