fix: MQTTAuth device lookup by Spec.DeviceID + PG15+ GRANT (v0.2.5)

- MQTTAuth: replaced Get(Name=deviceID) with List+filter by Spec.DeviceID
  (K8s object name != deviceID — caused deny for all devices)
- EnsureTenantDB: added GRANT role TO CURRENT_USER before CREATE DATABASE OWNER
  (PG15+ requires SET ROLE privileges for target owner)
- Image: naeel/iot-operator:v0.2.5
- E2E test passed: device create → MQTT publish → SQS → Postgres → REST API
This commit is contained in:
Naeel
2026-04-12 19:00:56 +03:00
parent d5a177e23c
commit f0cc6df9a6
7 changed files with 73 additions and 8 deletions
+1 -1
View File
@@ -38,7 +38,7 @@ spec:
- name: mqtt-bridge
# Тот же образ что и оператор — оба бинаря в одном слое (manager + iot-mqtt-bridge).
# При смене версии оператора — менять тег и здесь.
image: naeel/iot-operator:v0.2.3
image: naeel/iot-operator:v0.2.5
imagePullPolicy: Always
command: ["/mqtt-bridge"]
env:
+2 -2
View File
@@ -3,7 +3,7 @@
#
# Компоненты:
# - ServiceAccount + ClusterRole + ClusterRoleBinding (RBAC для CRD controller)
# - Deployment: naeel/iot-operator:v0.2.3
# - Deployment: naeel/iot-operator:v0.2.5
# - Service: ClusterIP :9090 (REST API, MQTT auth/acl, admin UI)
#
# iot-operator обслуживает:
@@ -85,7 +85,7 @@ spec:
serviceAccountName: iot-operator
containers:
- name: operator
image: naeel/iot-operator:v0.2.3
image: naeel/iot-operator:v0.2.5
imagePullPolicy: Always
ports:
- name: api
+1 -1
View File
@@ -36,7 +36,7 @@ spec:
containers:
- name: sqs-consumer
# Тот же образ что и оператор — все IoT бинари в одном образе.
image: naeel/iot-operator:v0.2.3
image: naeel/iot-operator:v0.2.5
imagePullPolicy: Always
command: ["/sqs-consumer"]
env:
+15
View File
@@ -122,3 +122,18 @@
- [x] doc/architecture/current-v0.2.3.md — актуальная архитектура (без Kafka/RabbitMQ)
- [x] doc/api/endpoints-v0.2.3.md — все API эндпоинты с примерами запросов/ответов
- [x] doc/run-and-test.md — как запустить и протестировать E2E
## 2026-04-12 — v0.2.5: баг-фиксы и E2E тест
### Найдены и исправлены баги:
1. **MQTTAuth: Get по Name=deviceID** — заменено на List + фильтр по Spec.DeviceID
2. **PG15+ GRANT**: добавлен `GRANT role TO CURRENT_USER` перед `CREATE DATABASE ... OWNER`
### E2E тест пройден:
- Устройство создано через REST API (201)
- MQTT CONNECT + PUBLISH через EMQX — OK
- mqtt-bridge → SQS forwarding — OK
- sqs-consumer → Postgres (tenant DB created + telemetry saved) — OK
- REST API GET /telemetry — 2 записи с payload
### Docker Hub: `naeel/iot-operator:v0.2.5`
+32
View File
@@ -205,3 +205,35 @@ E2E тест: создать устройство → MQTT publish → пров
### Осталось
- git commit + push
---
## Агент: GitHub Copilot (Claude Opus 4.6) — E2E тестирование и баг-фиксы
### Баг 1: MQTTAuth ищет IoTDevice по Name=deviceID
**Проблема**: `MQTTAuth` хэндлер вызывал `h.K8s.Get(client.ObjectKey{Name: deviceID})`, но имя K8s объекта IoTDevice (`e2e-test-device`) не равно `Spec.DeviceID` (`e2e-test-01`). Результат — `deny`.
**Диагностика**:
1. Auth endpoint вернул `{"result":"deny"}` при прямом вызове curl
2. Secret `iot-e2e-test-01` существует и пароль совпадает → проблема НЕ в пароле
3. IoTDevice объект называется `e2e-test-device`, а MQTTAuth ищет по `Name: "e2e-test-01"` → NotFound → deny
**Фикс**: Заменил `Get` на `List` + фильтр по `Spec.DeviceID == deviceID`. Это O(n) по количеству устройств в namespace, но для MVP приемлемо. При необходимости можно добавить label-index.
### Баг 2: PG15+ требует GRANT перед CREATE DATABASE ... OWNER
**Проблема**: `EnsureTenantDB` делал `CREATE DATABASE tenant_sless OWNER tenant_sless`, но PG17 (PG15+) требует `SET ROLE` privileges для target owner. Ошибка: `pq: must be able to SET ROLE "tenant_sless" (42501)`.
**Фикс**: Добавил `GRANT {userName} TO CURRENT_USER` перед `CREATE DATABASE ... OWNER`.
### E2E тест v0.2.5 — полный пайплайн
1. `POST /v1/namespaces/sless/iot/devices` → 201, device created
2. `GET /devices/e2e-test-device` → phase=Active, credentials получены
3. `mosquitto_pub` → CONNACK(0), PUBLISH OK
4. mqtt-bridge → `forwarded IoT telemetry to SQS`
5. sqs-consumer → `created tenant DB` + `telemetry saved to Postgres`
6. `GET /v1/namespaces/sless/iot/telemetry?device_id=e2e-test-01` → 2 записи с temperature/humidity
**Все компоненты работают end-to-end.**
+15 -4
View File
@@ -193,10 +193,21 @@ func (h *Handler) MQTTAuth(w http.ResponseWriter, r *http.Request) {
return
}
// Проверяем что IoTDevice активно
device := &iotv1alpha1.IoTDevice{}
if err := h.K8s.Get(r.Context(), client.ObjectKey{Namespace: ns, Name: deviceID}, device); err != nil {
// IoTDevice не найден (или удалён) — deny
// Ищем IoTDevice по Spec.DeviceID (имя K8s объекта может отличаться от deviceID)
deviceList := &iotv1alpha1.IoTDeviceList{}
if err := h.K8s.List(r.Context(), deviceList, client.InNamespace(ns)); err != nil {
writeJSON(w, http.StatusOK, mqttAuthResponse{Result: "deny"})
return
}
var device *iotv1alpha1.IoTDevice
for i := range deviceList.Items {
if deviceList.Items[i].Spec.DeviceID == deviceID {
device = &deviceList.Items[i]
break
}
}
if device == nil {
// IoTDevice с таким deviceID не найден — deny
writeJSON(w, http.StatusOK, mqttAuthResponse{Result: "deny"})
return
}
@@ -126,6 +126,13 @@ func (s *IoTPostgresStore) EnsureTenantDB(ctx context.Context, namespace string)
return fmt.Errorf("iotpg: create user %s: %w", userName, err)
}
// PG15+: GRANT role TO current_user перед CREATE DATABASE ... OWNER
if _, err = s.adminDB.ExecContext(ctx,
fmt.Sprintf(`GRANT %s TO CURRENT_USER`, userName),
); err != nil {
return fmt.Errorf("iotpg: grant role %s: %w", userName, err)
}
// CREATE DATABASE нельзя в транзакции
if _, err = s.adminDB.ExecContext(ctx,
fmt.Sprintf(`CREATE DATABASE %s OWNER %s`, dbName, userName),