
Аудит согласования интеграции Discord с Alliance Auth: находит участников гильдии, имеющих управляемые AA роли, которые не были назначены Auth, и удаляет или исключает их в соответствии с политикой, контролируемой оператором. Самостоятельное Django-приложение для сообщества.
Сверочный аудит интеграции Alliance Auth с Discord. Сравнивает фактические назначения ролей в настроенной гильдии Discord с тем состоянием, которое отражено в Alliance Auth (группы и состояние каждого пользователя), и закрывает лазейку, через которую модераторы могут вручную выдавать роли с именами AA пользователям, о которых AA ничего не знает.
Статус: alpha (
0.1.x). Публичный API и настройки могут ещё измениться до1.0.
Аудит безопасен по умолчанию:
InitialAuditAcknowledgement
и доступа в админку или к shell.report;
разрушительные действия включаются явно.AuditRun, AuditFinding, AuditInvocation,
ConfigChangeLog) защищены от обновлений на уровне менеджера и
экземпляра модели — массовые update() и bulk_update()
заблокированы.Каждый участник гильдии относится к одной из трёх категорий:
| Категория | Значение |
|---|---|
unknown_guest | Участник, о котором AA ничего не знает |
linked_no_perm | Личность известна AA, но нет права discord.access_discord |
bot_filtered | Учтённый бот — с ним аудит не делает ничего |
Оператор для каждой категории выбирает действие:
| Действие | Поведение |
|---|---|
report | Только зафиксировать находку, без изменений в Discord |
strip | Снять роли с именами AA |
strip_kick | Снять роли с именами AA, затем удалить из гильдии |
Соответствие задаётся настройкой AA_DISCORD_AUDIT_POLICY. Для каждой
категории можно указать переопределения по группе и по состоянию
пользователя AA.
allianceauth.services.modules.discord) установлен и настроен
(токен бота + гильдия)pip install aa-discord-audit
В local.py Alliance Auth:
# `aa_discord_audit` обязан идти ПОСЛЕ
# `allianceauth.services.modules.discord`, чтобы модели discord-модуля
# загрузились первыми; иначе `apps.ready()` поднимет
# `ImproperlyConfigured`.
INSTALLED_APPS += ["aa_discord_audit"]
MIDDLEWARE += [
"aa_discord_audit.current_user.CurrentUserMiddleware",
]
Затем миграции:
python manage.py migrate aa_discord_audit
CurrentUserMiddleware обязателен — apps.ready() поднимает
ImproperlyConfigured, если его нет в MIDDLEWARE. Без него
обработчик сигналов ConfigChangeLog не может определить автора
правки в админке, и каждое изменение пишется на безличного
<system>.
Выдайте право aa_discord_audit.run_audit тому, кто будет
запускать аудит.
Запустите аудит в режиме отчёта:
python manage.py audit_discord_roles --action report
Посмотрите находки в разделе Discord Audit → Прогоны аудита на панели Alliance Auth.
Снимите блокировку первого прогона — либо создайте в админке запись
InitialAuditAcknowledgement, либо выполните
python manage.py audit_acknowledge_initial. И то и другое требует
aa_discord_audit.run_audit и
aa_discord_audit.acknowledge_initial_audit.
Когда готовы — перезапустите с нужным разрушительным действием.
Группа прав manage_* намеренно разнесена по таблицам — сотрудник,
которому выдан только manage_bot_account_uid, не сможет
одновременно подменить ManagedRoleException и обезвредить аудит.
Все настройки необязательные; значения по умолчанию подобраны безопасно.
# Политика действий. Краткая форма ниже эквивалентна
# {"default": "<действие>"}; для переопределений по группе и по
# состоянию AA-пользователя используется развёрнутая форма.
AA_DISCORD_AUDIT_POLICY = {
"unknown_guest": "report",
"linked_no_perm": "report",
# "linked_no_perm": {
# "default": "strip",
# "by_state": {"Guest": "report"},
# "by_group": {"Directors": "report"},
# },
}
# Рассылка уведомлений через AA-notify обладателям прав.
AA_DISCORD_AUDIT_NOTIFY_ADMINS = True
# Адрес вебхука Discord для итогов запуска. Считайте его секретом.
AA_DISCORD_AUDIT_WEBHOOK_URL = None
# Список идентификаторов учётных записей ботов, которые надо
# пропускать (в дополнение к таблице BotAccountUid в админке).
AA_DISCORD_AUDIT_BOT_UIDS = []
# Автоматическое распознавание ботов по эвристикам Discord-имени.
# Зарезервировано для v2, в MVP не реализовано. Стартовый валидатор
# выбрасывает ImproperlyConfigured при значении True — оставляйте
# False и ведите явную таблицу BotAccountUid в админке.
AA_DISCORD_AUDIT_AUTO_DISCOVER_BY_NICKNAME = False
# Срок хранения записей о прогонах. 0 отключает очистку, но
# валидатор откажется принять 0, пока не выставлен флаг согласия
# ниже.
AA_DISCORD_AUDIT_RUN_RETENTION_DAYS = 180
AA_DISCORD_AUDIT_RETENTION_OPT_OUT_ACKNOWLEDGED = False
# TTL idempotency-ключа. При положительном значении prune_audit_runs
# освобождает AuditRun.idempotency_key на строках старше отсечки,
# но саму строку оставляет на полный RUN_RETENTION_DAYS. 0 (по
# умолчанию) выключает expiry; ключ погибает вместе со строкой.
AA_DISCORD_AUDIT_IDEMPOTENCY_KEY_TTL_DAYS = 0
# Срок одного прогона. Значение `soft_time_limit` у задачи Celery
# подстраивается под этот лимит.
AA_DISCORD_AUDIT_RUN_DEADLINE_MINUTES = 60
# Скользящее ограничение частоты за 24 часа: сколько принятых
# запусков аудита разрешается одному пользователю в сутки.
# DISABLED — флаг полного отключения; срабатывает только при явной
# установке.
AA_DISCORD_AUDIT_RUN_RATE_LIMIT_PER_DAY = 5
AA_DISCORD_AUDIT_RUN_RATE_LIMIT_DISABLED = False
# Параметры доставки вебхука Discord.
AA_DISCORD_AUDIT_WEBHOOK_TIMEOUT = 10
AA_DISCORD_AUDIT_WEBHOOK_MAX_RETRIES = 3
# Включается явно: групповое снятие ролей одним PATCH-запросом.
# Быстрее на больших гильдиях; пока выключено по умолчанию, потому
# что мы собираем отзывы о том, как себя ведёт ограничение частоты
# со стороны Discord.
AA_DISCORD_AUDIT_USE_BULK_ROLE_STRIP = False
# Периодический (Celery beat) аудит. Разрушительные действия в
# беспилотной периодической задаче требуют этого явного согласия,
# независимо от одноразовой блокировки первого прогона: иначе
# beat-прогон со strip/kick приводится к report, с предупреждением на
# старте при расхождении.
AA_DISCORD_AUDIT_BEAT_ALLOW_DESTRUCTIVE = False
# Грубый глобальный нижний порог между beat-прогонами, в минутах. 0
# (по умолчанию) отключает его — основной регулятор частоты — это
# расписание Celery beat. Страховка от ошибочно частого расписания
# (QueueOnce дедуплицирует только одновременные тики, не идущие
# подряд).
AA_DISCORD_AUDIT_BEAT_MIN_INTERVAL_MINUTES = 0
# Закрепить действующего CLI-пользователя за Django-именем (паттерн
# «сервисного принципала»). У команды управления нет HTTP-запроса,
# поэтому актор обычно выводится из OS-логина; задайте это ради
# чистой стабильной атрибуции ConfigChangeLog как из cron, так и из
# ручного CLI. Разрешается и проверяется на права при каждом вызове.
AA_DISCORD_AUDIT_CLI_ACTOR = None
# Выборка присутствия гильдии. Требует extra-пакета [metrics] и
# отдельного scrape-таргета /audit/discord/metrics.
# PRESENCE_ENABLED независим от [metrics]: даже без extra отключение
# подавляет фоновую задачу-сэмплер.
AA_DISCORD_AUDIT_PRESENCE_ENABLED = True
# Желаемый интервал выборки присутствия в минутах. Значения
# меньше 5 приводятся к значению по умолчанию (10).
AA_DISCORD_AUDIT_PRESENCE_SAMPLE_INTERVAL_MINUTES = 10
# Переключатель для агрегата members_by_group. Отключите, если
# количество AA-групп создаёт неприемлемую кардинальность.
AA_DISCORD_AUDIT_METRICS_MEMBERS_BY_GROUP = True
# Переключатель для агрегата members_by_role. По умолчанию
# выключен — гильдии Discord могут содержать сотни ролей;
# включайте только после оценки числа ролей во избежание взрыва
# кардинальности.
AA_DISCORD_AUDIT_METRICS_MEMBERS_BY_ROLE = False
Долгий audit_discord_roles корректно реагирует на SIGTERM и
SIGINT: прогон переводится в состояние INTERRUPTED, цикл
применения покидает текущую находку на ближайшей границе — частично
выполненная работа остаётся в журнале. Команда audit_discord_roles --resume <run_id> затем подхватывает прогон с остатком находок в
состоянии PENDING.
audit_orphan_members запускает тот же конвейер построения и
применения по расписанию, которое вы заводите в Celery beat (по
умолчанию задача не запланирована). Беспилотный путь огорожен ради
безопасности:
report независимо от политики, пока не выставлен
AA_DISCORD_AUDIT_BEAT_ALLOW_DESTRUCTIVE — снятие одноразовой
блокировки первого прогона ради ручного CLI-запуска не вооружает
beat. На старте печатается предупреждение, если политика
разрушительна, а beat не включён явно.AuditInvocation от системного актора (triggered_by=BEAT), так
что журнал контроля аудитора покрывает и беспилотные запуски.INTERRUPTED. AA_DISCORD_AUDIT_BEAT_MIN_INTERVAL_MINUTES
— грубый порог против ошибочно частого расписания.Пакет регистрирует пять задач с префиксом имени aa_discord_audit.*.
Расписание нужно только beat-задачам; остальные событийные или
запускаются по требованию. Ни одна не планируется за вас.
Чтобы запланировать beat-задачи, добавьте их в CELERYBEAT_SCHEDULE
в вашем local.py, например:
CELERYBEAT_SCHEDULE["aa_discord_audit_sample_guild_presence"] = {
"task": "aa_discord_audit.sample_guild_presence",
"schedule": 600, # секунды; не точнее пола сэмплера
}
Подключается к основному меню Alliance Auth пунктом Discord Audit. Разделы только для чтения, повсюду с цветными бейджами действий и состояний и сортируемыми таблицами. В каждом разделе есть серверный поиск по всем строкам — не только по текущей странице — рядом с выпадающими фильтрами:
ProtectedDiscordMember, блокировка первого
прогона).Операторы с правом aa_discord_audit.run_audit видят кнопку
Launch Audit на странице Audit runs. По клику открывается
модальное окно Bootstrap, в котором отображены режим политики (report-only или
destructive), состояние initial-acknowledgement и кнопка Confirm
Launch. Подтверждение POST'ит на /run-launch/, который создаёт PENDING
AuditRun, ставит process_pending_run в очередь Celery и
редиректит на страницу детализации прогона.
Веб-путь зеркалирует CLI-предохранители:
audit_acknowledge_initial не запускался,
модальное окно показывает блок отказа (без кнопки отправки) вместо Confirm
Launch. POST в обход модального окна (curl) отказывается сервером, отказ
пишется в AuditInvocation, оператор редиректится с flash-
сообщением.was_confirmation_bypassed = user.has_perm(run_audit_destructive) and policy_has_destructive(policy). На UI нет переключателя —
destructive-permission в одиночку определяет намерение. Оператор
только с run_audit, нажавший Confirm Launch при destructive-
политике, получает молчаливо прогон только в режиме REPORT (та же coercion, что
CLI делает без --yes).Пока прогон находится в нетерминальном состоянии (PENDING, RUNNING или
INTERRUPTED), страница детализации опрашивает
/runs/<pk>/state.json каждые пять секунд и обновляет State-карточку
in place. Опрос приостанавливается на скрытых вкладках и
останавливается, как только прогон достигает терминального состояния.
Опциональный слой инструментации Prometheus подключается через
extra-пакет [metrics]. Модуль придерживается принципа
«сотрудничать-не-зависеть»: если установлен django-prometheus,
счётчики и гистограммы аудита регистрируются в общем
prometheus_client.REGISTRY, и эндпоинт /metrics от
django-prometheus отдаёт их вместе со своими сериями. Если
extra-пакет не установлен, каждое обращение к метрике превращается
в пустую заглушку с почти нулевой стоимостью — место вызова
остаётся прежним.
pip install aa-discord-audit[metrics]
Snapshot-гейджи — присутствие гильдии и агрегаты численности по
группам и по ролям — идут вторым путём: они живут в выделенном
реестре, который отдаёт собственный эндпоинт модуля
/audit/discord/metrics. Многопроцессный коллектор django-prometheus
читает только mmap-файлы и пропускает кастомные коллекторы, поэтому
этим гейджам нужен отдельный scrape-таргет. Как и любой /metrics-
таргет, он без аутентификации — ограничьте доступ на уровне
обратного прокси или сети. Обе поверхности бездействуют без
extra-пакета [metrics].
Каталог метрик, словарь меток и рецепты Grafana —
в docs/METRICS.md
(русский перевод:
docs/METRICS.ru.md).
BotAccountUid / AA_DISCORD_AUDIT_BOT_UIDS —
AA_DISCORD_AUDIT_AUTO_DISCOVER_BY_NICKNAME зарезервировано для v2
и не реализовано.1.0.docs/runbook.md
— оперативный справочник: права бота в Discord, чек-лист перед
запуском, снятие блокировки первого запуска, сценарии реагирования
на инциденты, диагностические переключатели.docs/performance.md
— эталонные показатели команды audit_benchmark и выводы по размеру
установки.docs/METRICS.md
/ docs/METRICS.ru.md
— каталог метрик Prometheus, словарь меток, рецепты Grafana.CHANGELOG.mdmake dev # uv sync --all-groups + установка хуков pre-commit
make tests # uv run nox -s tests
make lint # uv run nox -s lint
make typecheck # mypy + basedpyright
make coverage # отчёт о покрытии: терминал + html + xml
make package # uv build
Инструментарий — только uv. Длина строки: 79 для Python, 120 для Markdown.
MIT — см. LICENSE.
| Право | На что влияет |
|---|
aa_discord_audit.run_audit | команда, периодическая задача, удаление запусков |
aa_discord_audit.run_audit_destructive | запуск strip / strip_kick из веб-интерфейса (отдельно от run_audit) |
aa_discord_audit.acknowledge_initial_audit | снятие блокировки первого запуска |
aa_discord_audit.manage_discord_identity | работа с DiscordIdentity в админке |
aa_discord_audit.manage_role_exception | работа с ManagedRoleException в админке |
aa_discord_audit.manage_protected_member | работа с ProtectedDiscordMember в админке |
aa_discord_audit.manage_bot_account_uid | работа с BotAccountUid в админке |
aa_discord_audit.manage_finding_override | работа с FindingActionOverride в админке |
aa_discord_audit.view_auditrun и аналогичные | просмотр журнала на панели Alliance Auth |
| Команда | Назначение |
|---|
audit_discord_roles | Основная точка входа. --action {report,strip,strip_kick}. |
audit_discord_roles --resume <run_id> | Доделать находки в состоянии PENDING для существующего прогона. |
audit_discord_roles --abandon <run_id> | Пометить зависший прогон как ABANDONED. |
audit_discord_roles --diff <run_id> | Сравнить текущее состояние с прошлым прогоном. |
audit_discord_roles --explain <member_id> | Классификация одного участника (без записи). |
audit_discord_roles --policy-preview <json> | Прогнать гипотетическую политику по текущим находкам. |
audit_discord_roles --from-fixture <path> | Воспроизведение по JSON-снимку. |
audit_acknowledge_initial | Снять блокировку первого прогона из консоли. |
audit_benchmark | Бенчмарк под синтетической нагрузкой (см. docs/performance.md). |
prune_audit_runs | Очистка устаревших записей по сроку хранения. |
audit_abandon_stuck_runs | Согласовать зависшие PENDING-прогоны после падения воркера — переводит их в ABANDONED (см. runbook; зависший RUNNING-прогон закрывается через audit_discord_roles --abandon). |
| Задача | Как запускается | Что делает |
|---|
aa_discord_audit.audit_orphan_members | Celery beat — планируете вы | Беспилотный аудит построения и применения, описанный выше. Только отчёт, пока не вооружён; разделяет блокировку discord.user_actions.<uid> с update_groups из AA. |
aa_discord_audit.retry_pending_kicks | Celery beat — планируете вы | Ограниченный проход, повторно отправляющий кики, отложенные временным сбоем Discord, с учётом покейсового кулдауна, чтобы по жёстко падающему участнику не било повторно. |
aa_discord_audit.process_pending_run | Событийная — ставится в очередь веб-запуском | Подхватывает PENDING-прогон, созданный веб-запуском, переводит его в RUNNING и исполняет конвейер с учётом замороженного флага подтверждения. |
aa_discord_audit.prune_audit_runs | Celery beat / cron — планируете вы (также команда управления) | Хранение: истечение ключа идемпотентности, затем удаление строк (см. Команды управления и runbook). |
aa_discord_audit.sample_guild_presence | Celery beat — планируете вы | Лёгкий REST-only замер счётчиков участников / онлайна / бустов в latest-only снимок присутствия для Prometheus-датчиков. Гейтится AA_DISCORD_AUDIT_PRESENCE_ENABLED; страховка-skip ограничивает ошибочно частое расписание (пол AA_DISCORD_AUDIT_PRESENCE_SAMPLE_INTERVAL_MINUTES, по умолчанию 10). Бездействует, пока не установлены extra [metrics] и запись beat (см. docs/METRICS.md). |