refactor: старые артефакты IoT перенесены в legacy/ (git mv, история сохранена); активное дерево очищено, go build OK

This commit is contained in:
“Naeel”
2026-08-16 09:48:34 +04:00
parent bf32394def
commit f0c36a11ee
30 changed files with 31 additions and 9 deletions
+412
View File
@@ -0,0 +1,412 @@
> ⛔⛔⛔ ЛЕГАСИ (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"
```
+56
View File
@@ -0,0 +1,56 @@
> ⛔⛔⛔ ЛЕГАСИ (2026-08-16) — СТАРЫЙ IoT (k8s-деплой). НЕ ПРИНИМАТЬ ВО ВНИМАНИЕ. Актуальное: HISTORY/2026-08-16-session-log.md
# IoT API Endpoints
## Публичные (без auth)
| Метод | Путь | Описание |
|-------|------|----------|
| GET | `/console` | IoT Консоль (HTML SPA) |
| GET | `/iot-admin` | Страница администратора (HTML) |
## Защищённые JWT (/v1/)
| Метод | Путь | Описание |
|-------|------|----------|
| POST | `/v1/namespaces/{ns}/iot/devices` | Создать IoT устройство |
| GET | `/v1/namespaces/{ns}/iot/devices` | Список устройств (без паролей) |
| GET | `/v1/namespaces/{ns}/iot/devices/{name}` | Устройство + MQTT password |
| DELETE | `/v1/namespaces/{ns}/iot/devices/{name}` | Удалить устройство |
| PATCH | `/v1/namespaces/{ns}/iot/devices/{name}` | Обновить (enabled) |
| GET | `/v1/namespaces/{ns}/iot/telemetry` | Телеметрия (?device=&limit=) |
## Внутренние (без JWT, только из кластера)
| Метод | Путь | Описание |
|-------|------|----------|
| POST | `/internal/mqtt/auth` | MQTT auth для EMQX |
| POST | `/internal/mqtt/acl` | MQTT ACL для EMQX |
## Администратор (ADMIN_STATS_TOKEN)
| Метод | Путь | Описание |
|-------|------|----------|
| GET | `/iot-admin/stats` | JSON статистика (PG, Kafka lag, pods) |
---
## Обновление 2026-04-12: admin stats → SQS
> Kafka lag заменён на SQS queue stats. Endpoint тот же, формат ответа изменён.
### `/iot-admin/stats` — актуальный формат ответа
```json
{
"sqs": {
"queue_url": "http://us-east-1.goaws.com:4100/t-96afe7e9f781f6ca/iot-telemetry",
"approximate_messages": "0",
"approximate_messages_not_visible": "0"
},
"sqs_consumer_pods": [...],
"pg_tenants": [...]
}
```
Вместо `kafka_lag` теперь `sqs.approximate_messages` — количество сообщений в очереди, ожидающих обработки.