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

Справочник формата TOML Census

Полный пофайловый и пополевой справочник по всем TOML-файлам, которые читает Census. Дополняет getting-started.md (где показан типовой случай) полной поверхностью: каждое поле, его тип, обязательность, значение по умолчанию и смысл.

Census читает такие виды файлов:

ФайлКто пишетСтрогостьЭтот док
Декларация (declaration.toml)оператор / control planeстрогая (неизвестные ключи отвергаются)§1
Слайс роли (role-store <role>.toml)оператор / Tesseraтолерантный верхний уровень; строгий [[payload.files]]§2
Permission каталога (share/permissions/**/*.toml)автор каталогастрогая§3 (сводка + ссылки)
Framework (frameworks/<fw>/*.toml)автор complianceстрогая§4 (сводка + ссылки)
Managed-реестр (/var/lib/census/managed.toml)только Census§5 (не редактировать)
Конфиг аудита (/etc/census/exposure.toml)операторстрогая§6 (опционально)

Авторитетные машиночитаемые схемы лежат в contract/*.schema.json (сгенерированы из парсеров, контрактно-залочены). Этот документ — их прозаическое зеркало; при любом расхождении побеждает схема.

Конвенции. «Обязательно» — без него парсинг падает. «Строгая» означает deny_unknown_fields — опечатка в ключе это ошибка (fail-closed), а не молчком проигнорированный ключ. «Толерантная» — неизвестные ключи пропускаются (формат, которым совладеет другой инструмент).


Декларация — желаемое состояние устройства: какие роль-учётки и группы должны существовать. Парсится строго — неизвестный ключ где угодно это ошибка.

КлючТипОбязателенСмысл
schemaцелоедаВерсия формата парсера декларации. Сейчас 1. Census проверяет её первой, до любой другой валидации, и отвергает декларацию, чья schema превышает поддерживаемую этой сборкой (fail-closed, без мутаций) — см. §1.1.1.
versionцелоедаМонотонный anti-rollback счётчик контента декларации (защита подписи от replay) — не версия формата. Проверяется только в managed/signed-режиме; под --trust-fs не проверяется. Бампайте при каждой новой подписанной декларации. См. §1.1.1.
role_storeпутьдаПуть к каталогу role-store, разрешается относительно рабочего каталога, из которого запущен Census (или абсолютный).
[defaults]таблицадаАтрибуты учёток по умолчанию (§1.2).
[[role_account]]массив таблицнетРоль-учётки для подготовки (§1.3).
[[group]]массив таблицнетСамостоятельные группы (§1.4).
[[role_group]]массив таблицнетПривязка grant’ов роли к объявленной группе (§1.5).
signatureстрока (hex)только в managedОткрепленная подпись Ed25519 над байтами декларации. Присутствует, когда декларация централизованно подписана; отсутствует под --trust-fs (standalone). Вручную не пишется — её добавляет control plane.

Эти два целочисленных поля похожи и их легко спутать, но они отвечают на разные вопросы:

ПолеНа что отвечаетEnforce
schema«Какой это формат TOML?» — версия формата парсера.Всегда. Проверяется первой, до любого другого поля; schema новее, чем поддерживает сборка → отказ, fail-closed, без мутаций.
version«Насколько свежий это контент?» — монотонный anti-rollback счётчик, защищающий подпись от replay.Только в managed-режиме (с подписью); под --trust-fs не проверяется.
  • schema — про форму файла. Бампайте её только при несовместимом изменении самого формата; сборка, не понимающая более новую schema, останавливается чисто, с внятным сообщением, а не падает где-то внутри на неизвестном ключе.
  • version — про содержимое. Бампайте её при каждой новой подписанной декларации, чтобы атакующий не мог переиграть (replay) старую подписанную копию и откатить устройство к устаревшему доступу. В standalone-режиме (--trust-fs) переигрывать нечего — подписи нет, поэтому version фиксируется, но не проверяется.

Они двигаются независимо: перевыдача доступа устройства бампает version, а schema остаётся; миграция формата файла бампает schema, а version не затрагивается. Слить их в одно поле — это либо ложный rollback-отказ при апгрейде формата, либо дыра в replay-защите при рефакторинге схемы.

Применяется к каждой роль-учётке, если та не переопределяет. Строго.

КлючТипОбязателенСмысл
uid_range[целое, целое]даВключительное окно [low, high] для UID. Каждый uid учётки должен попадать в него.
shellстрокадаLogin-shell по умолчанию (например /bin/bash).
home_baseпутьдаРодительский каталог домашних каталогов; home учётки по умолчанию <home_base>/<role>.
[defaults]
uid_range = [9000, 9999]
shell = "/bin/bash"
home_base = "/var/lib/census/home"

По одной записи на роль-учётку. Строго. Учётка — это один из двух взаимоисключающих видов, различаемых источником идентичности:

  • Created (создаваемая) — несёт явный uid. Census создаёт Unix-юзера (именованного по роли) с этим стабильным для парка UID. Обычный случай.
  • Adopted (усыновляемая) — несёт user (имя существующей OS-учётки) и adopt = true, и не должна нести uid. Census привязывает grant’ы роли к этой предсуществующей учётке и никогда не вызывает useradd/userdel — он не присваивает UID юзеру, которого не создавал.

uid и user взаимоисключающие; объявление обоих отвергается.

КлючТипОбязателенПо умолч.Смысл
roleстрокадаИмя роли; должно совпадать со слайсом в role-store (§2).
uidцелоеCreated: даЯвный стабильный для парка UID. Должен быть в uid_range. Отсутствует ⇒ учётка должна быть Adopted.
userстрокаAdopted: даИмя существующего OS-юзера для усыновления. Взаимоисключающе с uid; требует adopt = true.
adoptboolнетfalsetrue помечает учётку Adopted (требует user, запрещает uid). false — Created-учётка по uid.
shellстроканет[defaults].shellПереопределение login-shell для учётки.
homeпутьнет<home_base>/<role>Переопределение home для учётки.
# Created-учётка (обычный случай)
[[role_account]]
role = "oper"
uid = 9001
# Adopted-учётка — привязать grant'ы роли к существующему юзеру `svc`
[[role_account]]
role = "legacy-svc"
user = "svc"
adopt = true

Created роль-учётка создаётся с заблокированным паролем и без authorized_keys — единственный путь входа это PAM-сервис аутентификатора. Это не поля декларации; Census навязывает их при создании. У Adopted учётки состояние учётных данных остаётся как есть (Census никогда не вызывает над ней useradd/userdel).

Самостоятельные группы, которыми должен владеть Census. Строго.

КлючТипОбязателенПо умолч.Смысл
nameстрокадаИмя группы.
gidцелоенетавтоЗакреплённый GID. Если GID уже принадлежит другой группе, apply отказывается — он никогда не перенумеровывает.
adoptboolнетfalseУсыновить предсуществующую группу с этим именем вместо создания.
membersмассив строкнет[]Имена учёток-членов.
[[group]]
name = "kiosk-ops"
members = ["oper"]

Привязка grant’а: прикрепить разрешённые permissions роли к группе, чтобы каждый член группы их наследовал (многие-к-одному — несколько ролей могут привязываться к одной группе). Строго.

КлючТипОбязателенСмысл
roleстрокадаРоль, чьи grant’ы привязываются.
groupстрокадаЦелевая группа — должна именовать [[group]], объявленную в той же декларации (§1.4).
[[group]]
name = "kiosk-ops"
[[role_group]]
role = "oper"
group = "kiosk-ops"

По одному файлу на роль. Верхний уровень толерантный — Census читает только потребляемые ключи и игнорирует остальные (схемой роли совладеет Tessera, добавляющая adapter-поля, которые Census не нужны). Всё, на что Census действует, лежит под [payload].

КлючТипИспользуется CensusСмысл
roleстрокаинформационноИмя роли (должно совпадать с role декларации).
versionцелоеинформационноВерсия схемы слайса.
osстрокаинформационноЦелевое семейство ОС (например linux).
nameстрокаинформационноЧеловекочитаемое название роли.
levelцелоеинформационноТир/уровень роли (владеет Tessera).
[payload]таблицадаДоступ, который Census материализует (§2.2).

Неизвестные ключи верхнего уровня игнорируются (толерантно).

Все поля опциональны; толерантно (неизвестные ключи игнорируются). Сырые примитивы (groups, sudo, sudo_role, limits, files) — это escape-hatch, который объединяется с разворотом permissions: используйте либо то, либо другое, либо оба.

КлючТипСмысл
permissionsмассивСсылки на permissions, разворачиваемые против каталога (§2.3). Обычный способ выдать доступ.
groupsмассив строкСырые supplementary-группы напрямую (escape-hatch — в обход каталога).
sudoмассив строкСырые inline sudo-правила напрямую (escape-hatch — в обход каталога). Только литеральные абсолютные пути команд; см. §2.7.
sudo_roleстрокаСырое имя sudo-роли напрямую (escape-hatch).
[payload.limits]таблицаРесурсные лимиты (§2.4).
[[payload.files]]массив таблицСырые inline файловые grant’ы (§2.5).
role = "oper"
version = 1
os = "linux"
name = "Оператор устройства"
level = 3
[payload]
permissions = ["service-restart", "log-read", { id = "service-control", units = "nginx" }, "nginx.operate"]
groups = ["video"] # escape-hatch, объединяется
sudo = ["/usr/sbin/reboot"] # escape-hatch, сырая sudo-команда (§2.7)
sudo_role = "operations" # escape-hatch
[payload.limits]
nofile = 8192
[[payload.files]]
path = "/var/lib/app/state"
access = "rw"
recursive = true

Каждый элемент permissions — одно из:

  1. Голый id — строка, именующая leaf, bundle или package:
    permissions = ["log-read", "network-config", "nginx.operate"]
  2. Параметризованная — таблица с обязательным id плюс параметрами, которые заполняют шаблоны {placeholder} permission’а (например unit(ы), к которым применяется permission service-*). Списочный параметр разворачивается в одно правило на элемент:
    permissions = [
    { id = "service-control", units = "nginx" },
    { id = "service-observe", units = ["nginx", "mosquitto"] },
    ]
    Табличная форма толерантна: ключи кроме id захватываются как параметры, так что параметр, который запись каталога не использует, просто инертен (не ошибка).

Leaf — единичная способность; bundle агрегирует другие (разрешается транзитивно, его класс риска = максимум по членам); package — курируемый тир <app>.{observe|operate|admin}. Полный список permissions — см. §3 и сам каталог.

КлючТипСмысл
nofileцелоеRLIMIT_NOFILE (макс. открытых файлов).
nprocцелоеRLIMIT_NPROC (макс. процессов).

Inline файловые grant’ы, той же формы что и каталожный [[file]]. В отличие от остального payload, этот блок строгий (deny_unknown_fields): файловый grant роли материализуется от root через setfacl, так что опечатка в ключе падает fail-closed.

КлючТипОбязателенПо умолч.Смысл
pathстрокадаАбсолютный путь к каталогу, файлу или glob. Должен быть литеральным — placeholder/шаблон в файловом grant’е роли отвергается (никаких {…}).
accessстрокадаБиты доступа — см. §2.6.
recursiveboolнетfalseДля каталога: применить рекурсивно и поставить default-ACL, чтобы новые файлы наследовали доступ.

Каталог против одиночного файла. Grant на каталог (recursive = true) устойчив к перезаписи и обеспечивается всегда доступным ACL-backend’ом. Grant на одиночный файл требует per-file backend; на системе без него apply его отвергает (атомарно — ничего не применяется). Предпочитайте grant’ы на каталоги.

access — набор битов: read (r), write (w), execute (x), traverse (X, поиск по каталогу / условное исполнение). Два legacy-алиаса покрывают типовые случаи, для остального есть канонические компактные строки:

ЗначениеБитыПрименение
"ro"{read, traverse} (r-X)только чтение дерева
"rw"{read, write, traverse} (rwX)чтение-запись дерева
канонические компактные строкилюбая комбинация r w x Xточный контроль

Для большинства grant’ов нужны именно "ro" и "rw".

sudo под [payload] — командный близнец [[payload.files]]: сырой список sudo-правил, несомый напрямую в роль и объединяемый с тем, во что разворачиваются permissions роли — тем же путём, что и поле sudo каталожного permission’а. Используйте для команды, под которую ещё нет permission’а в каталоге.

[payload]
sudo = ["/usr/sbin/reboot", "/usr/bin/systemctl"]

Ограничения — проверяются до записи чего-либо в sudoers, fail-closed при нарушении:

  • Только литеральные абсолютные пути команд — каждый элемент должен начинаться с /.
  • Без аргументов и без шаблонов {placeholder}. Параметризация с confinement ({unit}, ограниченный constraints в [params]) остаётся прерогативой catalog-id — инлайн-параметр подпереть нечем, поэтому он отвергается.
  • Печатный ASCII, без shell-метасимволов (; | & $ < > …) — отвергаются, чтобы значение не протащило вторую команду в строку sudoers.

Каждый элемент материализуется в sudoers.d/census-<role> от root, так что это настоящая выдача привилегий. Поскольку он минует курируемый каталог, у него нет risk-метки — поэтому census show и census compile --lint помечают инлайн-payload.sudo (как и [[payload.files]]) как raw / unlabeled escalation-capable, чтобы ревьюер всегда его видел. Предпочитайте permission каталога, когда он есть; беритесь за payload.sudo только как за осознанный escape-hatch.


Файлы каталога под share/permissions/<layer>/*.toml определяют permissions, на которые ссылается роль. Их авторинг подробно описан в catalog-authoring.md и authoring-packages.md; форма вкратце:

КлючТипСмысл
idстрокаId permission’а (точечный для пакетов, например nginx.operate).
riskстрокаcontained или escalation-capable (у bundle = максимум по членам).
categoryстрокаДоменная группировка (например network, app, os-config).
sudoмассив строкАбсолютные sudo-правила (могут нести шаблоны {placeholder}).
groupsмассив строкВыдаваемые supplementary-группы.
[limits]таблицаnofile / nproc.
[[file]]массив таблицФайловые grant’ы (та же форма что §2.5; каталожные grant’ы могут использовать шаблоны {placeholder}).
includesмассивId других permissions, которые этот агрегирует (bundle); элемент-таблица { id, <bindings> } привязывает параметры члена.
include_categoriesмассив строкАгрегировать все permissions из названных категорий.
[params.<name>]таблицаGuard rail параметра (`kind = token

Слоистость по ОС: permission разрешается по linux → linux-<distro> → linux-<distro>-<version>; слой может replace или append поля базы. Человеческий текст (title / summary / risk_note) лежит в отдельном дереве l10n/<locale>/, ключ [<id>], а не в файле permission’а.

Авторитетная схема: contract/catalog-permission.schema.json.


Advisory cross-reference compliance. Frameworks лежат под frameworks/<fw>/:

  • framework.toml — манифест (dimension = flat | os-layered, версия, provides).
  • mappings/*.toml — ключ по id permission’а; каждый link несёт полярность: satisfies (адресует контрол — единственная полярность, которую считает coverage), risk (подрывает его), related (нейтрально).
  • controls.toml (опц.) — список контролов; флаг owned помечает контролы, которые Census реально покрывает (чтобы framework coverage сообщал gap’ы).

Это read-only и advisory — никогда не участвует в compile/grant/apply, так что подделанный mapping может лишь исказить покрытие, но не поднять привилегии. Авторитетная схема: contract/framework.schema.json. См. раздел «Compliance frameworks» в README.


Доступная только root запись о том, что подготовил Census (учётки, группы, grant’ы при каждом, применённая версия декларации). Этим файлом владеет Census — не редактируйте его руками. По нему Census знает, что его для reconcile или teardown, так что правка может осиротить реальные OS-объекты или заставить Census тронуть то, что он не создавал. Смотрите read-only через census status. Авторитетная схема: contract/managed-registry.schema.json.


Опциональная конфигурация для read-only аудита экспозиции (census audit fs / census audit expose). Парсится строго (deny_unknown_fields). Файл опционален, и каждый ключ опционален: отсутствие файла или отсутствие ключа откатывается к встроенному дефолту. Присутствующий, но битый файл — жёсткая ошибка, никогда не молчаливый дефолт (неправильно настроенный security-инструмент должен падать громко, а не сканировать не то, выглядя при этом здоровым). Нестандартный путь передаётся через --config.

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

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


census plan печатает высокоуровневые действия create/update/delete. Добавьте --diff, чтобы увидеть конкретные артефакты, которые записало бы каждое изменение, как unified-дифф — текущее managed-состояние против разрешённого target:

Окно терминала
census plan --declaration declaration.toml --additional-catalog-dir /opt/census/share/permissions --diff

plan --diff показывает, по каждой изменённой учётке:

  • sudoers-фрагмент, который был бы записан (включая run-as спецификацию), и его целевой путь (/etc/sudoers.d/census-<role>);
  • дельту файловых ACL-grant’ов — какие path-grant’ы добавляются или убираются.

Это read-only: никаких мутаций ФС, root не нужен. Используйте, чтобы перед запуском apply точно осмотреть, что он изменит — особенно после правки permissions роли, чтобы убедиться, что итоговые sudo-строки и ACL — те, что вы задумали.


  • getting-started.md — установка, настройка, первый apply, эксплуатация.
  • catalog-authoring.md — авторинг permissions каталога и слоёв по ОС.
  • authoring-packages.md — авторинг add-on пакетов и курируемых тиров приложений.
  • contract/*.schema.json — авторитетные машиночитаемые схемы.