Files
IoT/legacy/doc/api/endpoints-v0.2.3.md
T

413 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
> ⛔⛔⛔ ЛЕГАСИ (2026-08-16) — СТАРЫЙ IoT (k8s-деплой). НЕ ПРИНИМАТЬ ВО ВНИМАНИЕ. Актуальное: HISTORY/2026-08-16-session-log.md
# IoT REST API — Полная документация (v0.2.3)
> Дата: 2026-04-12
> Базовый URL: https://iot.kube5s.ru (через Ingress) или http://iot-operator.sless.svc:9090 (из кластера)
> Аутентификация: Bearer JWT в заголовке Authorization (authTestMode=true: любой непустой токен)
---
## Содержание
1. [IoT Devices CRUD](#iot-devices-crud)
2. [IoT Telemetry](#iot-telemetry)
3. [MQTT Auth (internal)](#mqtt-auth-internal)
4. [MQTT ACL (internal)](#mqtt-acl-internal)
5. [Admin Stats](#admin-stats)
6. [UI Pages](#ui-pages)
7. [Коды ошибок](#коды-ошибок)
---
## IoT Devices CRUD
### POST /v1/namespaces/{namespace}/iot/devices — Создание устройства
Создаёт IoTDevice CRD. Контроллер асинхронно генерирует MQTT credentials (Secret).
**Headers:**
```
Authorization: Bearer <token>
Content-Type: application/json
```
**Request body:**
```json
{
"name": "sensor-01",
"device_id": "sensor-01",
"enabled": true,
"metadata": {
"model": "DHT22",
"location": "room-1"
}
}
```
| Поле | Тип | Обязательное | Описание |
|------|-----|-------------|----------|
| name | string | да | Имя k8s объекта IoTDevice (уникальное в namespace) |
| device_id | string | да | ID устройства, pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$ |
| enabled | bool | нет | default: true |
| metadata | map[string]string | нет | Произвольные метаданные |
**Response 201 Created:**
```json
{
"name": "sensor-01",
"namespace": "tenant-abc",
"device_id": "sensor-01",
"enabled": true,
"phase": "",
"metadata": {"model": "DHT22", "location": "room-1"},
"created_at": "2026-04-12 14:30:00 UTC"
}
```
**Response 409 Conflict:** `{"error": "iot device already exists"}`
---
### GET /v1/namespaces/{namespace}/iot/devices — Список устройств
Возвращает все IoTDevice в namespace. **Пароли НЕ включены** (security by design).
**Headers:**
```
Authorization: Bearer <token>
```
**Response 200 OK:**
```json
[
{
"name": "sensor-01",
"namespace": "tenant-abc",
"device_id": "sensor-01",
"enabled": true,
"phase": "Active",
"mqtt_username": "tenant-abc_sensor-01",
"secret_name": "iot-sensor-01",
"topic_prefix": "tenant-abc/",
"metadata": {"model": "DHT22"},
"created_at": "2026-04-12 14:30:00 UTC"
}
]
```
---
### GET /v1/namespaces/{namespace}/iot/devices/{name} — Получение устройства
Возвращает устройство **включая mqtt_password** из Secret.
Используется для конфигурации физического устройства.
**Headers:**
```
Authorization: Bearer <token>
```
**Response 200 OK:**
```json
{
"name": "sensor-01",
"namespace": "tenant-abc",
"device_id": "sensor-01",
"enabled": true,
"phase": "Active",
"mqtt_username": "tenant-abc_sensor-01",
"mqtt_password": "a1b2c3d4...hex64chars",
"secret_name": "iot-sensor-01",
"topic_prefix": "tenant-abc/",
"last_connected": "2026-04-12T14:35:00Z",
"metadata": {"model": "DHT22"},
"created_at": "2026-04-12 14:30:00 UTC"
}
```
**Response 404:** `{"error": "iot device not found"}`
> Примечание: mqtt_password будет пустым если Secret ещё не создан (phase=Pending).
---
### PATCH /v1/namespaces/{namespace}/iot/devices/{name} — Обновление устройства
Включает/отключает устройство.
**Headers:**
```
Authorization: Bearer <token>
Content-Type: application/json
```
**Request body:**
```json
{
"enabled": false
}
```
| Поле | Тип | Обязательное | Описание |
|------|-----|-------------|----------|
| enabled | bool | да | true=Active, false=Disabled |
**Response 200 OK:** полный объект устройства (без пароля).
**Response 404:** `{"error": "iot device not found"}`
---
### DELETE /v1/namespaces/{namespace}/iot/devices/{name} — Удаление устройства
Удаляет IoTDevice CRD. Контроллер через finalizer удаляет Secret каскадно.
**Headers:**
```
Authorization: Bearer <token>
```
**Response 204 No Content** (пустое тело)
**Response 404:** `{"error": "iot device not found"}`
---
## IoT Telemetry
### GET /v1/namespaces/{namespace}/iot/telemetry — Чтение телеметрии
Возвращает записи телеметрии из per-tenant Postgres.
**Headers:**
```
Authorization: Bearer <token>
```
**Query parameters:**
| Параметр | Тип | Default | Описание |
|----------|-----|---------|----------|
| device | string | (все) | Фильтр по device_id |
| limit | int | 50 | Макс. кол-во записей (max 1000) |
**Response 200 OK:**
```json
{
"items": [
{
"id": 1,
"device_id": "sensor-01",
"payload": {"temperature": 22.5, "humidity": 65},
"received_at": "2026-04-12T14:35:00Z",
"created_at": "2026-04-12T14:35:01Z"
}
],
"count": 1
}
```
**Response 503:** `{"error": "IoT telemetry storage not configured"}` (IOT_PG_DSN не задан)
---
## MQTT Auth (internal)
### POST /internal/mqtt/auth — Аутентификация MQTT клиента
Вызывается EMQX при каждом MQTT CONNECT. **Без JWT.** Доступен только из кластера.
**Request body (от EMQX):**
```json
{
"username": "tenant-abc_sensor-01",
"password": "a1b2c3d4...hex64chars",
"clientid": "mqtt-client-123",
"peerhost": "10.0.1.5"
}
```
**Логика:**
1. Если username == BridgeUsername → проверить BridgePassword (constant-time) → allow/deny
2. Парсить username по первому "_" → namespace + deviceId
3. Найти Secret `iot-{deviceId}` в namespace
4. `crypto/subtle.ConstantTimeCompare(password, secret["mqtt-password"])`
5. Проверить IoTDevice существует и enabled=true
6. Обновить status.lastConnected (best-effort)
7. Вернуть ACL правила для клиента
**Response 200 (allow с ACL):**
```json
{
"result": "allow",
"acl": [
{"permission": "allow", "action": "publish", "topic": "tenant-abc/telemetry/sensor-01"},
{"permission": "allow", "action": "subscribe", "topic": "tenant-abc/telemetry/sensor-01"},
{"permission": "deny", "action": "all", "topic": "#"}
]
}
```
**Response 200 (bridge allow):**
```json
{
"result": "allow"
}
```
**Response 200 (deny):**
```json
{
"result": "deny"
}
```
> Всегда HTTP 200. EMQX игнорирует non-200 ответы.
---
## MQTT ACL (internal)
### POST /internal/mqtt/acl — Авторизация pub/sub
Вызывается EMQX для каждого publish/subscribe. **Без JWT.**
**Request body:**
```json
{
"username": "tenant-abc_sensor-01",
"clientid": "mqtt-client-123",
"action": "publish",
"topic": "tenant-abc/telemetry/sensor-01"
}
```
**Логика:**
- Bridge (clientid=sless-iot-bridge): только subscribe → allow. Publish → deny.
- Device: action на topic `{ns}/telemetry/{deviceId}` → allow. Всё остальное → deny.
**Response 200:** `{"result": "allow"}` или `{"result": "deny"}`
---
## Admin Stats
### GET /iot-admin/stats — Статистика администратора
Защищён токеном ADMIN_STATS_TOKEN (env). Не проходит через JWT middleware.
**Headers:**
```
Authorization: Bearer <ADMIN_STATS_TOKEN>
```
**Response 200 OK:**
```json
{
"collected_at": "2026-04-12T14:40:00Z",
"postgres": {
"reachable": true,
"tenants": [
{
"namespace": "tenant-abc",
"total_count": 150,
"last_1h_count": 42,
"last_24h_count": 130,
"latest_rows": [...]
}
]
},
"sqs": {
"approximate_messages": 5,
"approximate_messages_not_visible": 2
},
"pods": {
"iot-mqtt-bridge": {
"name": "iot-mqtt-bridge-xxx",
"phase": "Running",
"ready": true,
"restarts": 0,
"age": "3h"
},
"iot-sqs-consumer": {
"name": "iot-sqs-consumer-yyy",
"phase": "Running",
"ready": true,
"restarts": 0,
"age": "3h"
}
}
}
```
**Response 401:** `{"error": "unauthorized"}`
**Response 503:** `{"error": "admin stats not configured: ADMIN_STATS_TOKEN not set"}`
---
## UI Pages
### GET /console — IoT Консоль
HTML-страница (go:embed) для управления устройствами и просмотра телеметрии.
Включает MQTT WebSocket клиент для реального времени.
### GET /iot-admin — IoT Admin Panel
HTML-страница (go:embed) администратора с графиками и мониторингом.
---
## Коды ошибок
| Код | Значение | Когда |
|-----|---------|-------|
| 200 | OK | Успешные GET, PATCH, MQTT auth/acl |
| 201 | Created | Успешный POST (создание устройства) |
| 204 | No Content | Успешный DELETE |
| 400 | Bad Request | Невалидный JSON, отсутствуют обязательные поля |
| 401 | Unauthorized | Невалидный/отсутствующий Bearer token |
| 404 | Not Found | Устройство не найдено |
| 409 | Conflict | Устройство уже существует |
| 500 | Internal Server Error | Ошибка k8s API или БД |
| 503 | Service Unavailable | IoTPG не сконфигурирован или AdminToken не задан |
---
## Curl примеры
```bash
# Создать устройство
curl -X POST https://iot.kube5s.ru/v1/namespaces/test-ns/iot/devices \
-H "Authorization: Bearer test-token" \
-H "Content-Type: application/json" \
-d '{"name":"sensor-01","device_id":"sensor-01","enabled":true}'
# Список устройств
curl https://iot.kube5s.ru/v1/namespaces/test-ns/iot/devices \
-H "Authorization: Bearer test-token"
# Получить устройство с паролем
curl https://iot.kube5s.ru/v1/namespaces/test-ns/iot/devices/sensor-01 \
-H "Authorization: Bearer test-token"
# Включить/отключить
curl -X PATCH https://iot.kube5s.ru/v1/namespaces/test-ns/iot/devices/sensor-01 \
-H "Authorization: Bearer test-token" \
-H "Content-Type: application/json" \
-d '{"enabled":false}'
# Удалить
curl -X DELETE https://iot.kube5s.ru/v1/namespaces/test-ns/iot/devices/sensor-01 \
-H "Authorization: Bearer test-token"
# Телеметрия (последние 100)
curl "https://iot.kube5s.ru/v1/namespaces/test-ns/iot/telemetry?limit=100" \
-H "Authorization: Bearer test-token"
# Телеметрия по устройству
curl "https://iot.kube5s.ru/v1/namespaces/test-ns/iot/telemetry?device=sensor-01&limit=50" \
-H "Authorization: Bearer test-token"
```