
조정 감사: Alliance Auth의 Discord 통합을 위한 것으로, Auth가 부여하지 않은 AA 관리 역할을 보유한 길드 멤버를 찾아 운영자 제어 정책 하에 제거하거나 추방합니다. 독립형 커뮤니티 Django 앱.
Alliance Auth의 Discord 통합에 대한 조정 감사입니다. 설정된 Discord 길드의 실제 역할 할당을 Alliance Auth에 표현된 상태(사용자별 그룹 + 상태)와 비교하고, 중재자가 AA가 모르는 사용자에게 AA 명명된 역할을 수동으로 할당할 수 있는 격차를 해소합니다.
상태: alpha (
0.1.x). 공개 API와 설정은1.0이전에 변경될 수 있습니다.
감사는 기본적으로 안전합니다:
InitialAuditAcknowledgement (관리자 또는 셸 전용)가 필요합니다.report입니다 — 파괴적 작업은 선택 사항입니다.AuditRun, AuditFinding, AuditInvocation, ConfigChangeLog)은 관리자 및 인스턴스 계층에서 추가 전용입니다; 대량 update() / bulk_update()는 차단됩니다.각 길드 구성원은 하나의 범주로 분류됩니다:
| Category | Meaning |
|---|---|
unknown_guest | AA가 아무것도 모르는 Discord 구성원 |
linked_no_perm | AA에 알려진 신원이지만 discord.access_discord가 없음 |
bot_filtered | 구성된 봇 계정 — 절대 조치하지 않음 |
운영자는 각 범주를 하나의 작업에 매핑합니다:
| Action | Behaviour |
|---|---|
report | 결과 기록; Discord 측 변경 없음 |
strip | AA 관리 역할 제거 |
strip_kick | AA 관리 역할 제거 후 길드에서 추방 |
매핑은 AA_DISCORD_AUDIT_POLICY 설정입니다; 그룹별 및 상태별 재정의는 각 범주 내에 중첩됩니다.
allianceauth.services.modules.discord) installed and configured (bot token + guild)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
`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_* 코드명은 blast radius(영향 범위)별로 분리되어 있어, 주니어가 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
## 관리 명령
| Command | Purpose |
|--------------------------|------------------------------------------------------------------|
| `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`](https://gitlab.com/eveo7/aa-discord-audit/-/blob/main/docs/performance.md) 참조). |
| `prune_audit_runs` | 보존 정리. |
| `audit_abandon_stuck_runs` | 충돌한 작업자가 유출한 멈춘 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에 설정한 일정에 따라 동일한 빌드 + 적용 파이프라인을 실행합니다 (기본적으로 예약되지 않음). 무인 경로는 안전을 위해 차단되어 있습니다:
- **무장되지 않으면 보고서 전용.** `AA_DISCORD_AUDIT_BEAT_ALLOW_DESTRUCTIVE`가 설정되지 않으면 정책과 관계없이 beat 실행이 `report`로 강제됩니다. 수동 CLI 실행을 위한 일회성 첫 실행 잠금 해제는 beat를 무장시키지 않습니다. 정책이 파괴적이지만 beat가 옵트인되지 않은 경우 부팅 시 경고가 발생합니다.
- **귀속됨.** 모든 beat 실행은 수락된 시스템 액터 `AuditInvocation` (`triggered_by=BEAT`)을 기록하므로 감사-감사인 추적이 무인 실행도 포함합니다.
- **자가 복구.** 소프트 시간 제한 충돌 또는 작업자 충돌로 중단된 실행은 다음 틱에서 재개 가능한 `INTERRUPTED` 상태로 조정됩니다. `AA_DISCORD_AUDIT_BEAT_MIN_INTERVAL_MINUTES`는 잘못 구성된 빠른 일정에 대한 대략적인 최소값입니다.
## Celery 작업
패키지는 `aa_discord_audit.*` 이름 접두사 아래에 5개의 작업을 등록합니다. 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` | 이벤트 기반 — 웹 실행에 의해 대기열에 추가됨 | 웹 실행이 생성한 `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]` extra와 beat 항목이 모두 있을 때까지 비활성 상태입니다 ( [`docs/METRICS.md`](https://gitlab.com/eveo7/aa-discord-audit/-/blob/main/docs/METRICS.md) 참조). |
beat 작업을 예약하려면 `local.py`의 `CELERYBEAT_SCHEDULE`에 추가하세요. 예:```python
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
}
Auth 메인 내비게이션 아래 Discord Audit으로 마운트됩니다. 읽기 전용 뷰이며, 각 목록에는 드롭다운 필터와 함께 보이는 페이지뿐만 아니라 모든 행을 검색하는 서버 측 검색 상자가 있습니다.
ProtectedDiscordMember, 첫 실행 잠금)가 결과를 결정했는지.aa_discord_audit.run_audit 권한이 있는 운영자는 Audit runs 페이지에 Launch Audit 버튼을 볼 수 있습니다. 클릭하면 Bootstrap 모달이 열리며, 구성된 정책 모드(보고 전용 또는 파괴적), 초기 확인 상태, Confirm Launch 버튼이 표시됩니다. /run-launch/에 POST를 제출하면 AuditRun이 PENDING 상태로 생성되고, Celery를 통해 process_pending_run이 큐에 추가되며, 실행 세부 페이지로 리디렉션됩니다.
웹 경로는 CLI의 안전 게이트를 미러링합니다:
audit_acknowledge_initial가 실행되지 않은 경우, 모달은 Confirm Launch 작업 대신 거부 블록(제출 버튼 없음)을 표시합니다. 모달을 우회하는 POST(예: curl)는 서버 측에서 거부되며, 거부는 AuditInvocation에 기록되고, 운영자는 플래시 메시지와 함께 리디렉션됩니다.was_confirmation_bypassed = user.has_perm(run_audit_destructive) and policy_has_destructive(policy). UI에는 토글이 없으며 — 파괴적 권한만이 의도를 결정합니다. 파괴적 정책에 대해 실행을 트리거하는 run_audit 전용 운영자는 자동으로 REPORT-only 실행을 받습니다(CLI가 --yes 없이 적용하는 것과 동일한 강제 적용).실행이 종료되지 않은 상태(PENDING, RUNNING, 또는 INTERRUPTED)에 있는 동안, 실행 세부 페이지는 5초마다 /runs/<pk>/state.json을 폴링하여 State 카드를 업데이트합니다. 폴링은 숨겨진 탭에서 일시 중지되며, 실행이 종료 상태에 도달하자마자 중지됩니다.
[metrics] 추가 항목 뒤에 선택적 Prometheus 계측이 있습니다. 없으면 모든 메트릭 호출이 거의 비용이 없는 no-op 스텁으로 해결됩니다. 모듈은 협력-비의존 계층을 제공합니다: django-prometheus가 설치되면 감사 카운터와 히스토그램이 기본 prometheus_client.REGISTRY에 등록되고 django-prometheus의 /metrics 뷰가 자체 시리즈와 함께 이를 내보냅니다.```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용으로 예약되어 있으며 구현되지 않았습니다.
- **알파 단계.** 설정 이름과 공개 API는 `1.0` 이전에 변경될 수 있습니다.
## 문서
- [`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)입니다.
MIT — LICENSE 참조.
| 코드명 | 게이트 |
|---|
aa_discord_audit.run_audit | 관리 명령, 비트 태스크, 삭제 실행 |
aa_discord_audit.run_audit_destructive | strip / strip_kick용 웹 실행 게이트 (run_audit와 별개) |
aa_discord_audit.acknowledge_initial_audit | 첫 실행 dry-run 잠금 해제 |
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 (및 관련 권한) | Auth 대시보드의 읽기 전용 감사 이력 |