Перейти к содержимому

Аудит экспозиции — что субъект уже может достать

census audit — это read-only обзор фактического состояния прав файловой системы. Он отвечает на вопрос, на который подготовка ответить не может: при имеющихся на устройстве объектах доступа — не тех, что объявил Census, а всём, что уже лежит на диске, — что субъект реально может прочитать и записать сверх того, что задумывал принцип наименьших привилегий?

Это обратная сторона apply. apply выдаёт доступ вперёд (роль-учётки, группы, sudoers.d, файловые ACL). audit смотрит на устройство, как оно есть, и вскрывает фоновую переизбыточность прав (ambient over-permission), которая тихо подрывает ограниченную учётку: доступный на запись всем (world-writable) спул cron, читаемый всеми ключ, drop-in в sudoers.d, который может отредактировать сервисная учётка. Вы делаете учётку ограниченной, а world-writable файл всё равно позволяет ей подняться — audit показывает, где именно.

audit никогда ничего не мутирует. Как и doctor, он только читает. Он не делает и не может сделать chmod или setfacl над файлом — он сообщает о проблеме и подсказывает исправление.

Два режима, один движок:

КомандаНа какой вопрос отвечает
census audit fsКакие опасные классы прав есть на этом устройстве? (карта позиции, не зависящая от субъекта)
census audit expose --principal <name|uid>Что конкретно эта учётка реально может достать — сверх того, что ей выдали?

Запускайте от root для полной картины. Аудит читает режимы файлов, владельцев и POSIX-ACL; чтобы оценить экспозицию secret-класса (например /etc/shadow), нужно его прочитать, а для этого нужен root. Запускайте под sudo ради полного покрытия — он read-only, так что запуск от root безопасен.


audit fs обходит деревья в области сканирования и перечисляет опасные классы прав, независимо от какого-либо субъекта:

  • world-writable объекты в чувствительных деревьях (cron, sudoers.d, systemd-юниты, конфиги, бинарники из PATH);
  • инвентаризация setuid/setgid — каждый setuid/setgid-бинарник, причём те, что вдобавок world-writable, помечаются как критичные;
  • секреты, читаемые всеми — файлы класса ключ/credential/shadow, которые может прочитать other;
  • объекты, доступные на запись широкой группе — записываемые широкой группой (adm, wheel, sudo, staff, users).
Окно терминала
sudo census audit fs --root /etc --root /var/spool
# audit fs: 1 finding(s)
# high leak secret /etc/ssl/certs/ssl-cert-snakeoil.pem (access r--, via other_bits, fix ambient)
# — remove world read of a secret manually: `chmod 640 /etc/ssl/certs/ssl-cert-snakeoil.pem`

Каждый finding несёт: путь, эффективный доступ (rwx), способ получения доступа (viaother_bits, group:<g>, acl_user:<u>, …), класс объекта, риск (escalation / leak / tamper), производную severity, класс исправления (remediation_class, §4) и конкретную подсказку по исправлению.


audit expose прорезает то же сканирование сквозь одного субъекта. Он разрешает идентичность учётки — UID плюс первичную и дополнительные группы — и для этого субъекта прогоняет POSIX-проверку доступа против каждого объекта в области сканирования, затем сообщает только то, что учётка реально может достать.

Окно терминала
sudo census audit expose --principal daemon --root /etc --root /var/spool
# audit expose: principal daemon (unmanaged)
# note: verdict is DAC-only (mode, owner, POSIX ACL) and is an upper bound:
# MAC layers (SELinux, AppArmor, PARSEC) may restrict actual access further
# 1 finding(s)
# high leak secret /etc/ssl/certs/ssl-cert-snakeoil.pem (access r--, via other_bits, fix ambient) — …

Субъект именуется именем входа или числовым UID. Идентичность разрешается только из локальных /etc/passwd и /etc/group — членство в группах из NSS/LDAP не учитывается (это сознательное ограничение: на хосте, опирающемся на каталог, групповая досягаемость может быть недооценена). Голый UID без записи в passwd всё равно аудируется — по сырой досягаемости, без членства в группах.

2.1 Досягаемость строга — никаких ложных срабатываний из-за закрытого предка

Заголовок раздела «2.1 Досягаемость строга — никаких ложных срабатываний из-за закрытого предка»

Файл «досягаем» субъектом, только если каждый каталог-предок даёт ему поиск (x). Файл с режимом 0777 за каталогом 0700, которым владеет root, недосягаем для не-владельца и не попадает в отчёт — ровно то ложное срабатывание, которое поднял бы наивный find -perm. Сама проверка доступа следует алгоритму POSIX-ACL (владелец → именованный пользователь → класс группы с маской → other), уважая маску ACL и правило «совпало, но запрещено — не проваливается в other».

2.2 Вердикт — только DAC, честная верхняя граница

Заголовок раздела «2.2 Вердикт — только DAC, честная верхняя граница»

В open-билде audit оценивает только дискреционное управление доступом (DAC): биты режима, владение и POSIX-ACL. Он не моделирует мандатное управление доступом (SELinux, AppArmor или мандатную целостность PARSEC в Astra). Мандатный (MAC) слой может ограничить доступ ещё сильнее, поэтому вердикт — верхняя граница, «досягаемо при DAC». Каждый отчёт expose это проговаривает. Чтобы уточнить вердикт по MAC хоста, см. §2.4.

2.3 Killer-фильтр — только избыток для managed-учётки

Заголовок раздела «2.3 Killer-фильтр — только избыток для managed-учётки»

Когда субъект — роль-учётка под управлением Census, expose вычитает её намеченный базис (intended baseline) — её домашний каталог плюс пути, которые выдал каталог, — и сообщает только тот доступ, что у неё есть сверх этого замысла. Вы объявили, что учётка должна доставать /etc/ssh; если она вдобавок может писать в /var/spool/cron, остаётся только finding про cron. Для учётки, которой Census не управляет, базиса для вычитания нет, поэтому показывается сырая досягаемость.

Именно это делает expose специфичным для Census, а не очередным generic- сканером прав: он знает, что учётка должна была иметь, и показывает разницу.

Open-билд работает только с DAC (§2.2). Коммерческий MAC-провайдер может уточнить вердикт по мандатному управлению доступом хоста — сегодня Astra PARSEC, позже SELinux — через seam, нейтральный к MAC-системе, так что один и тот же аудит работает поверх разных MAC-систем. Когда провайдер активен:

  • Finding, досягаемый при DAC, но заблокированный MAC в каждом контексте безопасности, который может принять субъект, подавляется — он доказанно недосягаем, а не просто верхняя граница. Finding, всё ещё досягаемый в каком-то контексте, сохраняется и аннотируется этим контекстом (контекстами).
  • Каждый finding получает поле mac: { system, object_label, reachable_in } (или null, когда провайдера нет либо объект без метки). В режиме audit fs (позиция) субъекта нет, поэтому провайдер лишь аннотирует метку каждого объекта — он никогда не подавляет.
  • Заметка в отчёте меняется с оговорки «только DAC» на DAC + <system>.

Мандатные правила (целостность/конфиденциальность PARSEC, type-enforcement SELinux) живут целиком в коммерческом провайдере — открытое ядро не несёт MAC-логики и зависимости от libpdp/libselinux. --mac в open-билде честно отказывает с «no MAC backend in this build», а не молча ничего не делает; на хосте без запрошенной MAC-системы провайдер — no-op, и вердикт остаётся только-DAC. Аудит остаётся read-only — он осматривает метки, но никогда их не выставляет.


Каждый объект в области сканирования классифицируется по glob-таблице (и битам setuid/setgid):

КлассПримеры
cron/var/spool/cron/**, /etc/cron*/**, /etc/crontab
systemd-unit/etc/systemd/**, /lib/systemd/system/**
sudoers/etc/sudoers, /etc/sudoers.d/**
path-binary/usr/bin/**, /bin/**, /usr/local/bin/**, /sbin/**
secret/etc/shadow*, **/*.key, **/*.pem, **/id_rsa*, **/.env*, **/*credentials*
configsecurity-значимая конфигурация в /etc
setuid-binaryлюбой setuid/setgid-исполняемый файл
genericвсё остальное

Риск и severity выводятся детерминированно:

  • запись в cron / sudoers / systemd-unit / бинарник из PATH / setuid- бинарник → escalation, High;
  • чтение секрета → leak, High;
  • запись в конфиг-файл → tamper, Medium;
  • world-writable generic-объект → Low;
  • чтение не-секретного файла finding’ом не является.

audit завершается с ненулевым кодом, когда хотя бы один finding имеет severity High или выше (та же конвенция, что у doctor), — так его можно завести в CI или мониторинг; чистое сканирование (или только подпороговые findings) выходит с 0.


4. Класс исправления — Census честен в том, что он может починить

Заголовок раздела «4. Класс исправления — Census честен в том, что он может починить»

Каждый finding помечен классом исправления (remediation_class), который говорит, кто его чинит:

  • ambient — доступ исходит из объекта, которым Census не владеет: чужой world-writable каталог, читаемый всеми секрет, чужая группа. Census не может это убрать — декларация подготавливает собственные объекты Census, она не трогает базовый режим файла или чужой ACL. Подсказка — это ручная команда (chmod o-w …, chmod 640 …, setfacl -x …); Census никогда не утверждает, что починит за вас.
  • in-model — доступ исходит из объекта, которым Census владеет: членство в группе под управлением Census либо один из собственных файловых grant’ов учётки, более широкий, чем нужно. Здесь исправление и есть изменение декларации, и подсказка так и говорит («сузьте декларацию»).

Это разделение снимает очевидное возражение — «декларация не может отозвать world-write на каждом файле». Верно: не может, и audit не делает вид, что может. Для фоновой переизбыточности прав его задача — точно сообщить о проблеме: какой субъект, какой путь, почему доступ существует и какая ручная команда его закрывает.

Сам отчёт — чувствительный артефакт. Это карта слабых мест устройства. Вывод несёт только метаданные — путь, режим, класс — и никогда содержимое секретного файла, но и сам отчёт держите как конфиденциальный: не вставляйте его в публичный канал или в незащищённый лог.


census audit fs [--root <PATH>]… [--full] [--format text|json]
[--config <PATH>] [--managed <PATH>]
census audit expose --principal <name|uid>
[--root <PATH>]… [--full] [--format text|json]
[--config <PATH>] [--managed <PATH>]
ФлагСмысл
--root <PATH>Корень сканирования (повторяемо). Конфликтует с --full. Должен быть абсолютным.
--fullОбойти всю файловую систему от / (псевдо-ФС всё равно пропускаются).
--principal <name|uid>(только expose) учётка для оценки.
--format text|jsonФормат вывода. text (по умолчанию) — для человека; json — стабильный, контрактно-залоченный вывод на stdout.
--config <PATH>Конфиг аудита (по умолчанию /etc/census/exposure.toml); отсутствие файла ⇒ встроенные дефолты.
--managed <PATH>Managed-реестр (по умолчанию /var/lib/census/managed.toml), для базиса managed-учётки.
--macУточнить вердикт по MAC хоста (§2.4). Нужен коммерческий MAC-провайдер; в open-билде честно отказывает с «no MAC backend in this build».

Область по умолчанию — курируемый набор security-значимых деревьев (/etc, /var, /opt, /usr/local, /srv, /home, /root). Псевдо-ФС (/proc, /sys, /dev, /run) и сетевые точки монтирования всегда пропускаются — включая под --full, — и любая пропущенная точка отмечается в уведомлении, так что покрытие никогда не урезается молча. Сканирование не переходит на другой локальный том неявно: локальные суб-монты (отдельный раздел /var/log или /home) обходятся; сетевые ФС (NFS, CIFS, …) — нет.

При запуске в интерактивном терминале без --root/--full audit предлагает выбор области (security-значимая / полная / свои корни). Неинтерактивный запуск (CI, пайп) никогда не блокируется на этом запросе — он молча использует область по умолчанию. Диагностика и запрос идут в stderr, так что --format json оставляет stdout чистым и парсимым.

JSON-вывод залочен golden-схемой (contract/exposure-report.schema.json).


Область сканирования и классификаторы настраиваются. Файл парсится строго (deny_unknown_fields); отсутствие файла или отсутствие ключа откатывается к встроенному дефолту, но присутствующий, но битый файл — это честная ошибка (никогда не молчаливый дефолт).

# /etc/census/exposure.toml — все ключи опциональны; отсутствие ⇒ встроенный дефолт
# Деревья, которые покрывает сканирование по умолчанию. Должны быть абсолютными
# путями; пустой список отвергается (security-инструмент, который ничего не
# сканирует и рапортует «всё чисто», — это ловушка).
scan_roots = ["/etc", "/var", "/opt", "/usr/local", "/srv", "/home", "/root"]
# Globs, помечающие объект как secret-класс. Каждый шаблон несёт не более одного
# `**` (матчер бэктрекает через `**`; несколько были бы экспоненциальны на
# --full-сканировании).
secret_globs = ["/etc/shadow*", "**/*.key", "**/*.pem", "**/id_rsa*", "**/.env*", "**/*credentials*"]
# Имена групп, считаемых «широкими» для оси broad-group-writable. Сопоставляются
# по реальному имени группы, разрешённому из /etc/group (так что перенумерованный
# gid всё равно ловится).
broad_groups = ["adm", "wheel", "sudo", "staff", "users"]

Заметка по тюнингу. Дефолтный glob **/*.pem ловит и публичные сертификаты (например /etc/ssl/certs), которые читаемы всеми по замыслу, — они всплывают как малосигнальные findings класса secret. Сузьте secret_globs или исключите дерево публичных сертификатов, если этот шум на ваших хостах нежелателен.

См. справочник TOML для таблицы полей.


Окно терминала
# Сканирование позиции всего устройства, JSON для пайплайна мониторинга:
sudo census audit fs --full --format json > posture.json
# Что реально достаёт эта ограниченная сервисная учётка?
sudo census audit expose --principal app-svc
# Прицельная проверка после подготовки новой ограниченной роли:
sudo census audit expose --principal kiosk-oper --root /etc --root /var

audit read-only и не нуждается в декларации — наведите его на устройство, и он рапортует. Заведите audit fs в тот же путь мониторинга, что и doctor (оба выходят с ненулевым кодом при реальной проблеме), и запускайте audit expose всякий раз, когда создаёте или ужесточаете ограниченную учётку, чтобы убедиться: фоновая файловая система не выдаёт ей больше, чем вы задумали.


  • getting-started.md — установка, настройка, первый apply, эксплуатация.
  • toml-reference.md — каждый TOML-файл, который читает Census, включая exposure.toml.
  • README.md репозитория — модель продукта, свойства безопасности и полный справочник CLI.