Знаючи, що насправді дозволяє ваша конфігурація Nginx — аудитор, який обчислює фактичний доступ
Конфігурація 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 і вичитані. Помітили помилку? Напишіть мені.