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 | Discord 成员 AA 完全不知道 |
linked_no_perm | 身份已知于 AA 但缺乏 discord.access_discord |
bot_filtered | 配置的机器人账户 — 从不处理 |
操作员将每个类别映射到一个操作:
| 操作 | 行为 |
|---|---|
report | 记录发现;不对 Discord 端进行更改 |
strip | 移除 AA 管理的角色 |
strip_kick | 移除 AA 管理的角色,然后从服务器踢出 |
映射由 AA_DISCORD_AUDIT_POLICY 设置决定;每个组和每个状态的覆盖嵌套在每个类别内。
allianceauth.services.modules.discord)已安装并配置(机器人令牌 + 服务器)pip install aa-discord-audit
在你的 Auth `local.py` 中:```python
# `aa_discord_audit` must appear AFTER
# `allianceauth.services.modules.discord` so the discord module's
# models load first; `apps.ready()` raises `ImproperlyConfigured`
# otherwise.
INSTALLED_APPS += ["aa_discord_audit"]
MIDDLEWARE += [
"aa_discord_audit.current_user.CurrentUserMiddleware",
]
然后运行迁移:```sh python manage.py migrate aa_discord_audit
The `CurrentUserMiddleware` 是必需的 — 如果缺少它,`apps.ready()` 会抛出 `ImproperlyConfigured` 异常。正是它让 `ConfigChangeLog` 信号处理器能够将管理员编辑归因于真实用户,而不是 `<system>`。
## 快速开始
1. 将 `aa_discord_audit.run_audit` 授予执行审计的操作员角色。
2. 运行一次试运行审计: ```sh
python manage.py audit_discord_roles --action report
InitialAuditAcknowledgement 行,或运行 python manage.py audit_acknowledge_initial。两者都需要 aa_discord_audit.run_audit 和 aa_discord_audit.acknowledge_initial_audit。manage_* 代号按影响范围拆分,因此拥有 manage_bot_account_uid 的初级管理员不能通过编辑 ManagedRoleException 来削弱审计。
所有设置均为可选。默认值是安全的。```python
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_DISCORD_AUDIT_NOTIFY_ADMINS = True
AA_DISCORD_AUDIT_WEBHOOK_URL = None
AA_DISCORD_AUDIT_BOT_UIDS = []
AA_DISCORD_AUDIT_AUTO_DISCOVER_BY_NICKNAME = False
AA_DISCORD_AUDIT_RUN_RETENTION_DAYS = 180 AA_DISCORD_AUDIT_RETENTION_OPT_OUT_ACKNOWLEDGED = False
AA_DISCORD_AUDIT_IDEMPOTENCY_KEY_TTL_DAYS = 0
AA_DISCORD_AUDIT_RUN_DEADLINE_MINUTES = 60
AA_DISCORD_AUDIT_RUN_RATE_LIMIT_PER_DAY = 5 AA_DISCORD_AUDIT_RUN_RATE_LIMIT_DISABLED = False
AA_DISCORD_AUDIT_WEBHOOK_TIMEOUT = 10 AA_DISCORD_AUDIT_WEBHOOK_MAX_RETRIES = 3
AA_DISCORD_AUDIT_USE_BULK_ROLE_STRIP = False
AA_DISCORD_AUDIT_BEAT_ALLOW_DESTRUCTIVE = False
AA_DISCORD_AUDIT_BEAT_MIN_INTERVAL_MINUTES = 0
AA_DISCORD_AUDIT_CLI_ACTOR = None
AA_DISCORD_AUDIT_PRESENCE_ENABLED = True
AA_DISCORD_AUDIT_PRESENCE_SAMPLE_INTERVAL_MINUTES = 10
AA_DISCORD_AUDIT_METRICS_MEMBERS_BY_GROUP = True
AA_DISCORD_AUDIT_METRICS_MEMBERS_BY_ROLE = False
## 管理命令
| 命令 | 用途 |
|------------------------------------------|------------------------------------------------------------|
| `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` | 从控制台释放首次运行的 dry-run 锁。 |
| `audit_benchmark` | 合成负载性能基准测试(参见 [`docs/performance.md`](https://gitlab.com/eveo7/aa-discord-audit/-/blob/main/docs/performance.md))。 |
| `prune_audit_runs` | 保留期清理。 |
| `audit_abandon_stuck_runs` | 协调因崩溃 worker 漏掉的卡住 PENDING 运行——将其翻转为 ABANDONED(参见运行手册;卡住的 RUNNING 运行使用 `audit_discord_roles --abandon`)。 |
长时间运行的 `audit_discord_roles` 能干净地响应 `SIGTERM` 和 `SIGINT`:运行被翻转为 `INTERRUPTED`,应用循环在下一个发现边界退出,从而部分工作在审计追踪中持久化。`audit_discord_roles --resume <run_id>` 从剩余的 `PENDING` 发现处恢复运行。
## 定期审计(Celery beat)
`audit_orphan_members` 按照你配置到 Celery beat 的调度执行相同的构建+应用流水线(默认不调度)。无人值守路径带安全门控:
- **除非武装,否则仅报告。** 无论策略如何,beat 运行都会被强制为 `report`,除非设置了 `AA_DISCORD_AUDIT_BEAT_ALLOW_DESTRUCTIVE`——释放首次运行的 one-shot 锁并不会武装 beat。当策略具有破坏性但 beat 未选择加入时,启动时会发出警告。
- **归属。** 每次 beat 运行都会写入一个接受了的系统参与者 `AuditInvocation`(`triggered_by=BEAT`),因此审计审计者的追踪也覆盖了无人值守运行。
- **自愈。** 因软超时限制命中或 worker 崩溃而滞留的运行会在下一个节拍中协调为可恢复的 `INTERRUPTED` 状态。`AA_DISCORD_AUDIT_BEAT_MIN_INTERVAL_MINUTES` 是针对错误配置的快速调度的粗略下限。
## Celery 任务
该包在 `aa_discord_audit.*` 名称前缀下注册了五个任务。只有 beat 任务需要调度;其余任务由事件驱动或按需运行。它们不会为你预先调度。
| 任务 | 运行方式 | 功能 |
|------|----------|------|
| `aa_discord_audit.audit_orphan_members` | Celery beat — 由你调度 | 上述无人值守的构建+应用审计。除非武装,否则仅报告;与 AA 的 `update_groups` 共享 `discord.user_actions.<uid>` 锁。 |
| `aa_discord_audit.retry_pending_kicks` | Celery beat — 由你调度 | 有界扫描,重新调度因瞬时 Discord 故障而延迟的踢出操作,尊重每行冷却时间,避免对硬失败的成员重复操作。 |
| `aa_discord_audit.process_pending_run` | 事件驱动 — 由 web 启动入队 | 获取 web 启动创建的 `PENDING` 运行,将其翻转为 `RUNNING`,并针对运行的冻结确认标志驱动流水线。 |
| `aa_discord_audit.prune_audit_runs` | Celery beat / cron — 由你调度(也是一个管理命令) | 保留:幂等键过期后删除行(参见**管理命令**和运行手册)。 |
| `aa_discord_audit.sample_guild_presence` | Celery beat — 由你调度 | 轻量、仅 REST 的采样,获取公会成员/在线/助推计数,存入最新一次的存在快照,用于 Prometheus 存在指标。由 `AA_DISCORD_AUDIT_PRESENCE_ENABLED` 门控;跳过守卫限制错误配置的快速调度(下限 `AA_DISCORD_AUDIT_PRESENCE_SAMPLE_INTERVAL_MINUTES`,默认 10)。直到 `[metrics]` 附加组件和 beat 条目同时存在时才生效(参见 [`docs/METRICS.md`](https://gitlab.com/eveo7/aa-discord-audit/-/blob/main/docs/METRICS.md))。 |
要调度 beat 任务,请将它们添加到 `local.py` 的 `CELERYBEAT_SCHEDULE` 中,例如:
(此处保留原代码块,不翻译)
CELERYBEAT_SCHEDULE["aa_discord_audit_sample_guild_presence"] = {
"task": "aa_discord_audit.sample_guild_presence",
"schedule": 600, # seconds; honoured no finer than the sampler floor
}
```
## Operator dashboard
Mounted under the Auth main nav as **Discord Audit**. Read-only views;
each list carries a server-side search box that matches across every
row — not just the page in view — alongside its dropdown filters:
- **Audit runs** — a summary card with fleet-wide audit posture above a
badged, sortable list; per-run detail splits into cards around the
per-finding table.
- **Per-finding explain** — classification, resolved action (with the
resolved policy as JSON and an as-of marker), and which override
layer (per-uid override, per-group / per-state policy,
`ProtectedDiscordMember`, first-run lock) decided the outcome.
- **Invocations** — every CLI / beat trigger including refused
attempts, with the run state surfaced and the full argv expandable.
Surfaces rate-limit usage.
- **Config change log** — every operator edit to the operator-managed
config tables, rendered as a field-level before/after diff for
updates (pretty-printed JSON for create / delete) with a
case-insensitive actor filter. The audit-the-auditor trail.
### Launching an audit from the web UI
Operators with the **`aa_discord_audit.run_audit`** permission see a
**Launch Audit** button on the *Audit runs* page. Clicking it opens a
Bootstrap modal that surfaces the configured policy mode (report-only
or destructive), the initial-acknowledgement state, and a *Confirm
Launch* button. Submitting POSTs to `/run-launch/`, which creates a
PENDING `AuditRun`, enqueues `process_pending_run` via Celery, and
redirects to the run-detail page.
The web path mirrors the CLI's safety gates:
- **First-run lock.** If `audit_acknowledge_initial` has not been
run, the modal shows a refusal block (no submit button) instead of
the *Confirm Launch* action. A POST that bypasses the modal (e.g.
curl) is refused server-side, the refusal is recorded in
`AuditInvocation`, and the operator is redirected with a flash
message.
- **Destructive intent.** Whether a launch may strip roles or kick
members is decided server-side from the operator's permissions and
the policy: `was_confirmation_bypassed = user.has_perm(run_audit_destructive)
and policy_has_destructive(policy)`. The UI has **no toggle** — the
destructive permission alone determines intent. A `run_audit`-only
operator who triggers a launch against a destructive policy gets a
silent REPORT-only run (the same coercion the CLI applies without
`--yes`).
- **Per-user rate limit.** The same daily quota as the CLI; the modal
still opens but the POST returns a flash + redirect once the quota
is spent.
While a run is in a non-terminal state (`PENDING`, `RUNNING`, or
`INTERRUPTED`), the run-detail page polls `/runs/<pk>/state.json`
every five seconds and updates the State card in place. Polling
pauses on hidden tabs and stops as soon as the run reaches a terminal
state.
## Observability
Optional Prometheus instrumentation behind the `[metrics]` extra —
absent, every metric call resolves to a no-op stub at near-zero cost.
The module ships a cooperate-don't-depend layer: when `django-prometheus`
is installed the audit's counters and histograms register into the
default `prometheus_client.REGISTRY` and `django-prometheus`'
`/metrics` view exports them alongside its own series.```sh
pip install aa-discord-audit[metrics]
```
即时指标——公会出现情况以及按小组/按角色的成员聚合数据——走另一条路径:它们存放在模块自身 `/audit/discord/metrics` 端点导出的专用注册表中。django-prometheus 的多进程收集器仅读取 mmap 文件,跳过自定义收集器,因此这些指标需要单独的抓取目标。与任何 `/metrics` 目标一样,它是未经身份验证的——请在反向代理或网络层加以限制。如果没有 `[metrics]` 附加依赖,这两个表面都不会激活。
指标目录、标签词汇表和 Grafana 使用指南位于
[`docs/METRICS.md`](https://gitlab.com/eveo7/aa-discord-audit/-/blob/main/docs/METRICS.md)
(俄语翻译:
[`docs/METRICS.ru.md`](https://gitlab.com/eveo7/aa-discord-audit/-/blob/main/docs/METRICS.ru.md))。
## 限制
- **单个公会。** 审计仅与 AA 配置的那个 Discord 公会进行核对;不跨多个公会。
- **仅限 AA 管理的角色。** 剥离/踢出操作针对 AA 命名的角色和公会成员;AA 不管理的角色永远不会被触及。
- **无昵称自动发现。** 机器人账号仅通过显式的 `BotAccountUid` 表 / `AA_DISCORD_AUDIT_BOT_UIDS` 识别——`AA_DISCORD_AUDIT_AUTO_DISCOVER_BY_NICKNAME` 保留给 v2 且尚未实现。
- **Alpha 阶段。** 在 `1.0` 之前,设置名称和公共 API 可能发生变化。
## 文档
- [`docs/runbook.md`](https://gitlab.com/eveo7/aa-discord-audit/-/blob/main/docs/runbook.md)
— 运维手册:机器人 Discord 权限、预飞行检查清单、释放首次运行锁、事件处理剧本、诊断开关。
- [`docs/performance.md`](https://gitlab.com/eveo7/aa-discord-audit/-/blob/main/docs/performance.md)
— `audit_benchmark` 参考数值及规模影响。
- [`docs/METRICS.md`](https://gitlab.com/eveo7/aa-discord-audit/-/blob/main/docs/METRICS.md)
/ [`docs/METRICS.ru.md`](https://gitlab.com/eveo7/aa-discord-audit/-/blob/main/docs/METRICS.ru.md)
— Prometheus 指标目录、标签词汇表、Grafana 使用指南。
- 源代码:<https://gitlab.com/eveo7/aa-discord-audit>
- 问题跟踪:<https://gitlab.com/eveo7/aa-discord-audit/-/issues>
- 更新日志:
[`CHANGELOG.md`](https://gitlab.com/eveo7/aa-discord-audit/-/blob/main/CHANGELOG.md)
## 开发```sh
make dev # uv sync --all-groups + pre-commit install
make tests # uv run nox -s tests
make lint # uv run nox -s lint
make typecheck # mypy + basedpyright
make coverage # term + html + xml report
make package # uv build
```
Toolchain 仅使用 uv。行长度为 79(Python)/ 120(Markdown)。
## 翻译
- [Русский](https://gitlab.com/eveo7/aa-discord-audit/-/blob/main/README.ru.md)
## 许可证
MIT — 详见 [`LICENSE`](https://gitlab.com/eveo7/aa-discord-audit/-/blob/main/LICENSE)。
| Codename | Gates |
|---|
aa_discord_audit.run_audit | 管理命令、定期任务、运行删除 |
aa_discord_audit.run_audit_destructive | strip / strip_kick 的 Web 启动门控(独立于 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 (and friends) | Auth 仪表板中的只读审计追踪 |