Документ для IT-администратора. Пошаговая диагностика от симптома к решению. Каждый раздел — независимый сценарий, искать через TOC.
Содержание
- Устройство не enrolled
- Политика не применяется на устройство
- Kiosk-режим ломается после перезагрузки
- Lost mode не работает
- Remote Assist не подключается
- MQTT disconnect storm
- Dashboard 500 / tile 403 / CSP report overflow
- Где смотреть логи
1. Устройство не enrolled
1.1. Сценарии
QR-сканирование не переходит в установку DPC.
- Устройство запускает встроенный браузер вместо DPC-процесса.
- На экране appears Ошибка конфигурации DPC.
DPC устанавливается, но в /devices не появляется.
- DPC активен, видно в шторке уведомлений, но в admin UI устройство не видно.
Устройство было enrolled, но пропало из списка онлайн.
1.2. Диагностика
Шаг 1 — определить, какой тип provisioning используется
| Тип | Как запускается | Признак |
|---|---|---|
| QR | Factory reset → 6 тапов по welcome | com.android.managedprovisioning перехватывает QR |
| DPC Identifier | afw#komendant в первом логине Google |
Требует Play Services |
| Zero-touch (ZT) | Устройство пред-зарегистрировано у вендора | Автоматически при первом старте |
| NFC bump | Прикоснуться NFC-тегом | Редко, для крупных развёртываний |
Если клиент не использует Google Play (ТСД на AOSP), только QR работает.
Шаг 2 — проверить содержимое QR
Расшифруйте QR-код любым сканером. Должен содержать JSON:
{
"android.app.extra.PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME":
"ru.komendant.dpc/.receivers.AdminReceiver",
"android.app.extra.PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM":
"<base64-sha256>",
"android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION":
"https://mdm.example.ru/downloads/dpc.apk",
"android.app.extra.PROVISIONING_ADMIN_EXTRAS_BUNDLE": {
"server_url": "https://api.mdm.example.ru",
"enroll_token": "<jwt>"
}
}
Частые проблемы QR:
| Проблема | Проверка | Решение |
|---|---|---|
| checksum не совпадает с APK | apksigner verify --print-certs dpc.apk и base64(sha256) первого cert |
Перегенерировать QR в /enroll/qr после rebuild APK |
URL http:// вместо https:// |
смотрите QR-JSON | enrollment требует TLS, исправить в /settings/enroll |
enroll_token истёк |
JWT exp < now | QR валиден только 24 часа, перегенерировать |
| Чужой tenant | enroll_token.tenant_id |
очистить кэш, генерировать из корректного tenant |
Шаг 3 — DPC установился, но не подключается к MQTT
На устройстве включить USB-debug, получить логи:
adb logcat -s KomendantDPC MQTT
Ищем строки:
| Лог | Диагноз |
|---|---|
MqttException: Unable to find valid certification path |
MQTT-broker отдаёт non-trusted cert. DPC pinned на конкретный CA. Проверить, что mqtt.mdm.example.ru использует letsencrypt или указанный в сборке DPC CA |
Connection refused на 8883 |
firewall блокирует исходящий 8883 со стороны клиента (корп-WiFi?). Проверить nc -vz mqtt.mdm.example.ru 8883 |
BadUserNameOrPassword |
DPC username-token истёк или подпись отвалилась. Перегенерировать enroll-token |
ConnectionLost after CONNACK |
ACL EMQX отвергает подписку. Проверить /ops/emqx → ACL logs, обычно причина — mismatch client_id vs device_id в JWT |
Шаг 4 — устройство подключено к MQTT, но нет в /devices
# на сервере
journalctl -u komendant-api --since "10 min ago" | grep -i enroll
Ищем строки enroll.register. Частые ошибки:
ErrTenantQuotaExceeded— превышенmax_devicesлицензии → увеличить лицензиюErrFingerprintMismatch— устройство заявляет другой hardware-fingerprint чем при первом enroll → re-enroll через factory resetErrTenantMismatch— токен для tenant A, но broker routes в tenant B → проверить/settings/enroll/codesна корректность
1.3. Edge cases
- Xiaomi/Realme/OPPO — у этих OEM агрессивное killing фоновых процессов. В onboarding-инструкции для пользователя:
Настройки → Батарея → Без ограничений для DPC. - Huawei без GMS — DPC работает, но FCM-пуши не доступны; используется pure MQTT. Убедитесь, что MQTT-keepalive ≤ 60 сек.
- Samsung Knox — для полного контроля нужен Knox-E-FOTA. В пилоте Триола — стандартный DPC через Android Enterprise.
2. Политика не применяется на устройство
2.1. Симптом
В admin UI /devices/<id> → Policy Applied показывает desired_version=5, applied_version=3 и висит так > 10 минут.
2.2. Диагностика
Шаг 1 — проверить, получило ли устройство policy push
/devices/<id>/timeline
Событие policy.push.sent должно быть не старше 2 минут. Если нет — MQTT disconnected, перейти в раздел 6.
Шаг 2 — проверить audit events
/audit?device_id=<id>&event=policy.apply.*
| Event | Значение |
|---|---|
policy.apply.started |
устройство приняло команду |
policy.apply.completed |
успех |
policy.apply.failed |
ошибка, см. reason |
| отсутствует | устройство не получило → MQTT |
Шаг 3 — типовые причины policy.apply.failed
| reason | Диагноз | Решение |
|---|---|---|
DPM_SET_APPLICATION_RESTRICTIONS_failed |
приложение не установлено или заблокировано Play Protect | в policy убрать ссылку на несуществующий package |
permission_denied_kiosk |
DPC не device-owner, а только profile-owner | factory reset + re-enroll как device-owner |
ca_cert_install_failed |
сертификат в policy повреждён или уже установлен | пересобрать сертификат через /settings/certificates |
password_complexity_not_supported |
требования paroля выше чем поддерживает OS | снизить password_quality или обновить OS |
system_app_missing |
policy ссылается на package, которого нет на устройстве | в target_devices отфильтровать по OS version |
Шаг 4 — RLS / tenant mismatch на бэке
Если /audit пуст вообще — возможно, вы смотрите из-под super-admin без tenant_id:
-- внутри /admin/sql (super-admin only)
SELECT COUNT(*) FROM audit_events WHERE device_id = '<id>' AND tenant_id = '<tenant>';
Если 0 — device не принадлежит вашему tenant, это проблема enrollment; см. раздел 1.
Шаг 5 — DPM API fallback
Некоторые прошивки (MIUI 13, ColorOS 12) периодически сбрасывают policy после OTA. DPC при каждом старте делает DevicePolicyManager.isProfileOwnerApp() → если false, посылает событие dpc.degraded серверу, и сервер показывает устройство в статусе degraded.
Решение: инструктаж пользователя → factory reset и re-enroll.
3. Kiosk-режим ломается после перезагрузки
3.1. Симптом
Устройство в kiosk-policy с lock_task_enabled=true. После reboot — возвращается launcher OS, kiosk теряется до ручного открытия приложения.
3.2. Диагностика
Шаг 1 — проверить BootReceiver
DPC регистрирует BOOT_COMPLETED receiver. Проверить в logcat после перезагрузки:
adb logcat -s KomendantDPC | grep BootReceiver
Должно быть: BootReceiver.onReceive: restoring kiosk mode.
Если нет — receiver не вызван. Причины:
- OEM battery optimization бъёт BOOT_COMPLETED до того, как app'у дают CPU. Решение: в policy добавить
request_battery_optimization_exempt=true. - App hibernation (Android 12+) — ОС «усыпила» app за неделю без запуска. Решение: policy
app_hibernation_exemptчерезDevicePolicyManager.setApplicationExemptions(требует Android 13+).
Шаг 2 — Foreground Service permission
DPC держит FGS (notification "Управление устройством активно"). Если FGS убита — kiosk тоже. Проверка:
adb shell dumpsys activity services | grep komendant
Должен быть active service. Если AnrScreen: fgs_timeout — приложение превысило лимит FGS без interaction.
Решение: в AndroidManifest.xml DPC используется foregroundServiceType="systemExempted" + REQUEST_COMPANION_RUN_IN_BACKGROUND permission, подписывается через policy fgs_always_on.
Шаг 3 — Persistent notification
/devices/<id>/policy → kiosk → show_persistent_notification=true. Без этого некоторые Android (13+) могут убить foreground service через 6 часов.
4. Lost mode не работает
4.1. Симптом
Нажимаем /devices/<id> → Lost Mode. Экран устройства не блокируется, координаты не приходят.
4.2. Диагностика
Шаг 1 — проверить delivery
/audit?device_id=<id>&event=lostmode.*
Ищем:
- lostmode.command.sent — сервер послал команду
- lostmode.overlay.shown — устройство приняло и наложило overlay
- lostmode.location.report — первая локация
Если command.sent есть, а overlay.shown нет — устройство offline либо DPC degraded.
Шаг 2 — Lock Screen Overlay permission
Overlay использует TYPE_APPLICATION_OVERLAY. На Android 11+ требует runtime-permission SYSTEM_ALERT_WINDOW. У device-owner даётся автоматически, но на некоторых OEM (Xiaomi) нужно отдельно включить в Настройки → Приложения → DPC → Отображение поверх других.
Проверить:
adb shell appops get ru.komendant.dpc SYSTEM_ALERT_WINDOW
Должно быть allow. Если default — DPC policy auto_grant_overlay=true.
Шаг 3 — Location permission
Lost mode шлёт координаты раз в 60 сек. Для этого нужен ACCESS_BACKGROUND_LOCATION + FGS_TYPE_LOCATION (Android 14+).
adb shell dumpsys package ru.komendant.dpc | grep permission | grep granted=true
Должны быть:
- android.permission.ACCESS_FINE_LOCATION: granted=true
- android.permission.ACCESS_BACKGROUND_LOCATION: granted=true
- android.permission.FOREGROUND_SERVICE_LOCATION: granted=true
Если нет — обновить DPC до версии с foregroundServiceType="location" в manifest.
Шаг 4 — Google Play Services
На AOSP без GMS location работает через FusedLocationProvider fallback на LocationManager (GPS+NETWORK). На Huawei без HMS — только GPS, точность ниже. Это ожидаемое поведение.
5. Remote Assist не подключается
5.1. Симптом
В /remote-assist → New Session → Device admin и устройство показывают Connecting..., сессия не устанавливается.
5.2. Диагностика
Шаг 1 — TURN-сервер
Remote Assist использует WebRTC с принудительным iceTransportPolicy: "relay" (всё через TURN). Проверить:
curl -X POST https://api.mdm.example.ru/api/v1/remote-assist/turn-credentials \
-H "Authorization: Bearer <admin-token>"
Должен вернуть JSON с urls, username, credential. Далее проверка самого TURN:
# С клиентской машины
turnutils_uclient -v -u <username> -w <credential> turn.mdm.example.ru -p 3478
Должны быть allocation и refresh успешными.
Частые причины отказа:
| Ошибка | Решение |
|---|---|
401 Unauthorized на TURN |
ephemeral-credentials истекли; сервер выдаёт с lifetime 60 мин, генерировать новые через API |
Connection timeout на UDP 3478 |
корп-firewall режет UDP; включить TCP 3478 в coturn (listening-ip=...,relay-ip=... в turnserver.conf + no-udp-relay=false) |
443 relay over TLS недоступен |
если сеть вообще без UDP, нужен TURN-TLS на 5349 (по умолчанию выключен), включить в /settings/remote-assist → TURN over TLS |
Шаг 2 — RELAY-only ICE
Если на сети клиента жёсткий NAT и нет UDP — даже TURN не поможет без 443/TCP. Fallback:
/settings/remote-assist
ice_servers:
- turns:turn.mdm.example.ru:5349?transport=tcp
Шаг 3 — сигналинг не достиг
WebRTC offer/answer идут через WebSocket к API. Проверка:
/devices/<id>/timeline → ищем ra.session.invite.received
Если событие есть на сервере, но DPC не показал UI — значит WebSocket разорван. Проверить в DPC logs adb logcat -s KomendantDPC-WS:
WS connection lost, code=1006, reason=
1006 — сеть прервалась. DPC переподключается автоматически через exp-backoff (1с, 2, 4, 8, 16, 32, 60).
Шаг 4 — Permission SYSTEM_CAPTURE_VIDEO_OUTPUT
Для share-screen через MediaProjection устройство покажет диалог Разрешить Комендант DPC записывать экран?. Это одноразовое согласие пользователя, skipped быть не может (требование Android security). Пользователь должен нажать «Разрешить».
6. MQTT disconnect storm
6.1. Симптом
В /ops/grafana → Komendant Overview → MQTT видно пик emqx_mqtt_disconnects_total > 50/минута. Устройства флапают online ⇄ offline.
6.2. Диагностика
Шаг 1 — проверить EMQX logs
docker logs emqx --tail 500 | grep -E 'disconn|acl|auth'
Ищем паттерны:
| Паттерн | Диагноз |
|---|---|
acl_deny: subscribe denied on topic |
устройства пытаются подписаться не на свой topic (bad JWT tenant_id) |
keep_alive_timeout |
сетевые проблемы клиентов; проверить, не упал ли провайдер |
connection_rejected: not_authorized |
истёк enroll-token; DPC должен сам запросить refresh через /api/v1/dpc/token/refresh |
max_connections |
достигнут лимит EMQX max_conn=1000 на listener; увеличить в emqx.conf |
Шаг 2 — ACL rules
Каждый device подписан только на devices/<device_id>/cmd/# и publish только в devices/<device_id>/ack/#. Проверить:
docker exec emqx emqx ctl acl list | grep <device_id>
Если нет записи → команда komendant-cli mqtt sync-acl --device <id>.
Шаг 3 — Token refresh loop
DPC раз в 6 часов делает refresh. Если API возвращает 401 — DPC повторяет → storm. Проверка:
journalctl -u komendant-api --since "1 hour ago" | grep -c 'token refresh failed'
Если > 50 → на сервере истёк JWT signing key rotation, нужно komendant-cli jwt rotate-key. После — все DPC переконнектятся автоматически.
Шаг 4 — Clean session
Если в policy случайно выставлен mqtt_clean_session=true — DPC при каждом reconnect теряет in-flight messages, что вызывает дубликаты команд и новые disconnects. Фикс: /settings/mqtt → clean_session=false (default).
7. Dashboard 500 / tile 403 / CSP report overflow
7.1. Dashboard 500
В admin UI /dashboard показывает 500 Internal Server Error.
Проверка:
journalctl -u komendant-api --since "5 min ago" | grep ERROR | tail -20
Частые причины:
| ERROR | Решение |
|---|---|
pgx: connection refused |
PostgreSQL упал; systemctl status postgresql@17-main |
redis: LOADING Redis is loading dataset in memory |
Redis после рестарта прогревает RDB; подождать 30-60 сек |
context deadline exceeded: query running 30s |
медленный запрос; EXPLAIN ANALYZE и добавить индекс |
panic: runtime error: invalid memory address |
баг; собрать goroutine-dump через curl http://127.0.0.1:6060/debug/pprof/goroutine?debug=2 и в поддержку |
7.2. Tile 403
Отдельный дашборд-плиток возвращает 403. Причина — RLS: пользователь смотрит данные не из своего tenant.
Типовой случай: super-admin переключился в tenant A, но в URL остался tenant_id=B. Очистить cookie komendant_active_tenant и выбрать нужный в /admin/tenants.
7.3. CSP report overflow
В /ops/prometheus видно рост komendant_csp_reports_total > 1000/min.
Причина: браузер пользователей рапортует нарушения CSP (inline scripts, сторонние trackers). Это не критично, но забивает storage.
Решение:
- Проверить
/admin/security/csp-reports— какие директивы флапают. - Если это какой-то легитимный ресурс (например, Яндекс.Метрика в landing) — добавить в whitelist
connect-srcв nginx. - Включить CSP rate-limit в nginx:
limit_req zone=csp burst=5 nodelayна/csp-report.
8. Где смотреть логи
8.1. Серверные логи
| Сервис | Команда |
|---|---|
| API | journalctl -u komendant-api -f |
| Admin UI | journalctl -u komendant-admin -f |
| PostgreSQL | journalctl -u postgresql@17-main -f |
| EMQX | docker logs emqx -f |
| Redis | journalctl -u redis-server -f |
| MinIO | docker logs minio -f |
| nginx access | tail -f /var/log/nginx/access.log |
| nginx error | tail -f /var/log/nginx/error.log |
| Backup | journalctl -u komendant-backup.service |
8.2. Grafana + Loki
Если observability развернут: https://mdm.example.ru/ops/grafana (basic-auth из .env).
Dashboards: - Komendant Overview — устройства, policy-apply, MQTT, SLA - API SLO — p50/p95/p99 latency, error rate - Licensing — active devices vs limit - Backup — last success timestamp, S3 offsite
8.3. Audit log (для IT-админа tenant-а)
/admin/audit — фильтры по user, device, event type, время. Экспорт в CSV и JSON. Хранится 7 лет (152-ФЗ + наш SLA).
8.4. Export в SIEM
Если у клиента MaxPatrol/Kaspersky KUMA — настройте syslog-forwarder:
/settings/integrations/siem
type: syslog
host: siem.example.ru
port: 514
protocol: tcp+tls
format: CEF | LEEF
Теги и поля — см. docs/security-log-tags.md.
Версия документа: 1.0 (2026-04-18). Для ошибок, не описанных здесь: support@komendant-mdm.ru, приложить journalctl --since "1 hour ago" -u komendant-api > api.log.