← Blog

Знаючи, що насправді дозволяє ваша конфігурація Nginx — аудитор, який обчислює фактичний доступ

DEENESRUUK

Конфігурація nginx рідко показує з першого погляду, хто насправді може отримати доступ до чого. Директиви allow/deny успадковуються вниз, блоки location затінюють один одного, auth_basic, розташований на два рівні вище, може або не застосовуватися, а відсутність директиви default deny тихо залишає двері відчиненими. nginx-acl-guard відповідає на одне питання детерміністично: для кожного server/location, яка ефективна політика доступу — і чи не розширив я щойно змін, які збираюся внести?

Обчислюйте політику, не читайте її

Аудит ніколи не виконує grep конфігурації. Він парсить за допомогою crossplane у справжнє дерево директив, розв'язуючи успадкування так, як це робить nginx насправді, та виводить ефективний allow/deny для кожної кінцевої точки. Окрім цього є названі перевірки — випадкові викриття (A1..A7), слабкі місця TLS (T1..T6), ризиковані шаблони upstream (U1..U4) — тому "цей location доступний з публічного інтернету" відображається як знахідка, а не як те, що ви виявляєте під час інциденту.

Одна з золотих правил проєкту чітко визначає, як це має працювати:

Детерміністичний парсинг лише — crossplane, без regex та без LLM, які приймають рішення щодо безпеки.

Regex, який на 99% правильний щодо правила доступу, є вразливістю. Постава безпеки має бути обчисленою, відтворюваною, інакше їй не можна довіряти.

Захищайте зміну, а не лише аудит

Аудит — це легка частина. Цікава частина — дозволити комусь редагувати доступ безпечно. Профілі ACL можуть імпортуватися з існуючих фрагментів або генеруватися з нуля, призначатися сайтам та комбінуватися як union-allow + default-deny. Але жива конфігурація на диску є священною — ніщо не пише в неї напряму. Кожне застосування проходить через один пайплайн:

edit / assign an ACL profile
        │
        ├─ git backup of the current config
        ├─ render into a staging tree
        ├─ nginx -t on staging          → чи це взагалі валідно?
        ├─ diff the EFFECTIVE policy     → який доступ насправді змінився?
        ├─ access expanded? ───────────→ вимагає явного токена підтвердження
        ├─ atomic swap (symlink flip)
        └─ rollback on any error

Diff — це вся суть. Це не текстовий diff конфігурації — це diff обчисленої ефективної політики. Посилення доступу застосовується тихо; розширення — новий allow, видалений deny, маршрут, який стає публічним — відмовляється продовжувати роботу без явного токена підтвердження. Ви не можете випадково розширити доступ, тому що розширення виявляється за конструкцією, а не ловиться при перегляді.

Найменші привілеї, аж до самого низу

Сервіс працює як непривілейований користувач nginxguard, за замовчуванням лише для читання, пишучи лише у свій каталог профілів. Кілька привілейованих кроків — тестування staging-дерева, атомарний обмін — проходять через крихітний допоміжний бінарник з вузьким контрактом, ніколи сам вебпроцес. При будь-якій помилці правило є жорсткою зупинкою: жодних автоматичних обхідних шляхів, жодного напівзастосованого стану "найкращих зусиль", який потрібно розплутувати пізніше. І генерація блоків server з нуля навмисно виключена — інструмент захищає доступ до сайтів, він їх не винаходить.

Стек маленький і нудний навмисно: Python, FastAPI, Jinja2 + HTMX, сирий SQLite з версіонованими міграціями, crossplane для парсингу. Цінність ніколи була у фреймворку. Вона полягає в тому, щоб розглядати "що дозволяє ця конфігурація" як те, що ви обчислюєте та порівнюєте — так само, як ви б поводилися з числом — а не як те, що ви оцінюєте оком і сподіваєтеся прочитати правильно.

Прозорість: більшість дописів тут створено за допомогою ШІ, а неанглійські версії перекладені локальною LLM і вичитані. Помітили помилку? Напишіть мені.