Знание того, что на самом деле разрешает ваша конфигурация nginx — аудитор, вычисляющий эффективный доступ
Конфигурация nginx редко говорит вам с первого взгляда, кто на самом деле может получить доступ к чему. Директивы allow/deny наследуются вниз, блоки location перекрывают друг друга, директива auth_basic, находящаяся на два уровня выше, может применяться или нет, а одно пропущенное default deny незаметно оставляет дверь открытой. nginx-acl-guard детерминированно отвечает на один вопрос: для каждого server/location, какова эффективная политика доступа — и не расширил ли изменение, которое я собираюсь внести, эту политику?
Вычисляйте политику, а не читайте её
Аудит никогда не использует grep для конфигурации. Он парсит с помощью crossplane в реальное дерево директив, разрешает наследование так, как это делает nginx на самом деле, и выводит эффективные allow/deny для каждой конечной точки. Кроме того, есть именованные проверки — случайные раскрытия (A1..A7), уязвимости TLS (T1..T6), рискованные шаблоны вышестоящих серверов (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 → is it even valid?
├─ diff the EFFECTIVE policy → what access actually changed?
├─ access expanded? ───────────→ require an explicit confirm token
├─ atomic swap (symlink flip)
└─ rollback on any error
Диф — это весь смысл. Это не текстовый диф конфигурации — это диф вычисленной эффективной политики. Ужесточение доступа применяется тихо; расширение — новое allow, удаленный deny, маршрут, который становится публичным — отказывается продолжаться без явного токена подтверждения. Вы не можете случайно расширить доступ, потому что расширение обнаруживается по конструкции, а не выявляется при проверке.
Принцип наименьших привилегий, до самого низа
Сервис работает от имени непривилегированного пользователя nginxguard с правами только на чтение по умолчанию и записывает данные только в собственное директорию профилей. Несколько привилегированных шагов — тестирование стейджингового дерева, атомарный обмен — проходят через крошечный вспомогательный бинарник с узким контрактом, а не через сам веб-процесс. При любой ошибке правило является жесткой остановкой: нет автоматического обходного пути, нет полупримененного состояния "наилучших усилий", которое нужно будет распутывать позже. И генерация блоков server с нуля намеренно вне области действия — инструмент защищает доступ к сайтам, он их не придумывает.
Стек небольшой и скучный намеренно: Python, FastAPI, Jinja2 + HTMX, чистый SQLite с версионированными миграциями, crossplane для парсинга. Ценность никогда не была в фреймворке. Она заключается в том, чтобы рассматривать "что разрешает эта конфигурация" как нечто, что вы вычисляете и диф'ите — так же, как вы бы рассматривали число — а не как то, что вы бросаете взглядом и надеетесь прочитать правильно.
Прозрачность: большинство постов здесь написаны с помощью ИИ, а неанглийские версии переведены локальной LLM и вычитаны. Заметили ошибку? Напишите мне.