← 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), рискованные шаблоны вышестоящих серверов (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 и вычитаны. Заметили ошибку? Напишите мне.