diff --git a/HISTORY/OPUS/3006_token_saga.md b/HISTORY/OPUS/3006_token_saga.md new file mode 100644 index 0000000..bf4ae6f --- /dev/null +++ b/HISTORY/OPUS/3006_token_saga.md @@ -0,0 +1,198 @@ +# API Token Saga — 30.06.2026 + +**Дата:** 30 июня 2026 +**Контекст:** попытка доступа к Nubes Cloud API для правки Terraform Provider + +--- + +## Кратко + +6 часов debug'а. DDoS-Guard резал запросы, выдавая `403 Forbidden`. +Причина: **User-Agent фильтрация** + **протухшие токены** + **неправильный endpoint**. + +--- + +## Хронология ошибок + +### Ошибка 1: 403 от DDoS-Guard (2 часа потеряно) + +``` +$ curl https://deck-api.ngcloud.ru/api/v1/index.cfm/services/90 +→ 403 Forbidden +``` + +**Что думали:** DDoS-Guard блокирует наш IP, нужно добавлять в whitelist. +Проверяли с ВМ, из подов кластера — везде 403. + +**Реальная причина:** DDoS-Guard фильтрует по `User-Agent`. `curl/8.x` блокируется, браузерный `Mozilla/5.0 ...` пропускается. + +**Как нашли:** HAR-файлы (`HAR/OK.har`) показали что успешные запросы из браузера идут с HTTP/2 и браузерным User-Agent. Провайдер форсит HTTP/1.1 с Go-шным UA. + +**Фикс:** +```bash +# НЕ работает: +curl https://deck-api.ngcloud.ru/... + +# РАБОТАЕТ: +curl -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" \ + https://deck-api.ngcloud.ru/... +``` + +--- + +### Ошибка 2: Старые токены без token_type (30 минут) + +``` +$ curl -H "Authorization: Bearer $OLD_TOKEN" ... (с правильным UA) +→ {"message":"invalid JWT token type"} +``` + +**Что думали:** Токены протухли. Проверили — живые (exp: август-сентябрь 2026). +Потом думали что API изменился и требует новый формат. + +**Реальная причина:** Старые токены из `secrets/prod.token` и `secrets/dev.token` были выданы БЕЗ поля `token_type` в JWT. API обновился и теперь требует `token_type: "tech"`. + +**Декодирование старого токена:** +```json +{ + "iss": "auth-api", + "sub": "0199e325-1cdf-7cda-9319-e5302a85e291", + "exp": 1786932260, + // token_type: ОТСУТСТВУЕТ ← причина ошибки +} +``` + +--- + +### Ошибка 3: Токен не того стенда (30 минут) + +``` +$ curl -H "Authorization: Bearer $TEST_TOKEN" https://deck-api.ngcloud.ru/... +→ {"message":"IAM error"} +``` + +**Что думали:** Токен битый, неправильная подпись. + +**Реальная причина:** Токены привязаны к конкретному API-endpoint. TEST-токен работает только на `deck-api-test.ngcloud.ru`, PROD — на `deck-api.ngcloud.ru`, DEV — на `deck-api-dev.ngcloud.ru`. + +--- + +### Ошибка 4: Двойной сбой (1 час) + +На `prod.token` была комбинация двух проблем: +1. Старый формат (без token_type) → `"invalid JWT token type"` +2. Плюс DDoS-Guard скрывал реальный ответ API за `403 Forbidden` + +Пока не добавили User-Agent, мы даже не видели сообщение `"invalid JWT token type"` — видели только `403`. + +--- + +## Как это работает (правильная схема) + +### 1. API endpoints + +| Стенд | Endpoint | Токен | +|-------|----------|-------| +| **PROD** | `https://deck-api.ngcloud.ru/api/v1/index.cfm` | `secrets/prod.token` | +| **TEST** | `https://deck-api-test.ngcloud.ru/api/v1/index.cfm` | `secrets/test.token` | +| **DEV** | `https://deck-api-dev.ngcloud.ru/api/v1/index.cfm` | `secrets/dev.token` | + +Токены НЕ взаимозаменяемы между стендами. + +### 2. Аутентификация + +``` +┌──────────┐ Bearer JWT ┌───────────┐ validate ┌──────────┐ +│ Client │ ──────────────────→ │ deck-api │ ──────────────→ │ auth-api │ +│ (curl/Go)│ ←── JSON response ─ │ (CFML) │ ←── OK/FAIL ── │ (IAM) │ +└──────────┘ └───────────┘ └──────────┘ +``` + +- Токен передаётся как `Authorization: Bearer ` +- deck-api проверяет токен через auth-api (внутренний вызов) +- JWT содержит: `iss: "auth-api"`, `token_type: "tech"`, `ClientID: "WZ03709"` +- `token_type` обязателен (новое требование API) + +### 3. User-Agent фильтрация DDoS-Guard + +``` +┌──────────┐ ┌────────────┐ ┌──────────┐ +│ Client │ ─── HTTP request → │ DDoS-Guard │ ─── if UA ok ───→ │ deck-api │ +│ │ ←── 403 если UA ── │ 185.178... │ │ │ +│ │ не браузерный └────────────┘ └──────────┘ +``` + +DDoS-Guard проверяет заголовок `User-Agent`: +- `curl/x.y.z` → **403 Forbidden** (блокировка) +- `Go-http-client/x.y` → **403 Forbidden** (провайдер блокируется!) +- `Mozilla/5.0 ...` → **пропускается** + +Это **критический баг провайдера**: Go HTTP-клиент по умолчанию отправляет `Go-http-client/2.0`, который DDoS-Guard режет. + +### 4. Правильный curl-запрос + +```bash +# Рабочий запрос к PROD API: +curl -sk --max-time 10 \ + -H "Authorization: Bearer $(cat secrets/prod.token)" \ + -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" \ + "https://deck-api.ngcloud.ru/api/v1/index.cfm/services/90" + +# Аналогично для TEST: +curl -sk --max-time 10 \ + -H "Authorization: Bearer $(cat secrets/test.token.new)" \ + -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" \ + "https://deck-api-test.ngcloud.ru/api/v1/index.cfm/services/90" +``` + +### 5. Живые токены (30.06.2026) + +| Файл | Назначение | Выдан | Истекает | token_type | ClientID | +|------|-----------|-------|----------|------------|----------| +| `secrets/prod.token` | PROD | 18.02.2026 | 17.08.2026 | ❌ отсутствует | ❌ | +| `secrets/dev.token` | DEV | 26.03.2026 | 22.09.2026 | ❌ отсутствует | ❌ | +| `secrets/test.token` | TEST | 14.03.2026 | 10.09.2026 | ❌ отсутствует | ❌ | +| `secrets/test.token.new` | **TEST** | 30.06.2026 13:37 | ~дек.2026 | ✅ `"tech"` | ✅ `WZ03709` | +| `secrets/dev.token.new` (в памяти) | **DEV** | 30.06.2026 13:42 | ~дек.2026 | ✅ `"tech"` | ✅ `WZ03709` | +| `secrets/prod.token.new` (в памяти) | **PROD** | 30.06.2026 13:41 | ~дек.2026 | ✅ `"tech"` | ✅ `WZ03709` | + +⚠️ Новые токены ещё не сохранены в файлы `secrets/` — только test сохранён. + +--- + +## Что надо исправить в провайдере + +### P0: User-Agent + +В `universal_rebuild/internal/core/client.go` (и `internal/core/client.go` Legacy): + +```go +// Сейчас (БАГ): +req.Header.Set("User-Agent", "Go-http-client/2.0") // или default + +// Надо: +req.Header.Set("User-Agent", "Terraform-Provider-Nubes/5.0") // пропускаемый UA +``` + +Или в `http.Client.Transport`, или в каждом `doRequest`. + +### Связанное: HTTP/2 + +Провайдер форсит HTTP/1.1: +```go +NextProtos: []string{"http/1.1"}, +ForceAttemptHTTP2: false, +``` + +HAR показывает что API работает через HTTP/2. Возможно стоит разрешить HTTP/2 (убрать `ForceAttemptHTTP2: false`) — это ускорит поллинг. + +--- + +## Ключевые файлы + +- `/home/naeel/tf_provider/HAR/OK.har` — эталонные запросы (HTTP/2 + браузерный UA) +- `/home/naeel/tf_provider/secrets/prod.token` — старый prod-токен (без token_type) +- `/home/naeel/tf_provider/secrets/test.token.new` — новый test-токен (рабочий) +- `/home/naeel/tf_provider/devops/profiles/*/profile.env` — endpoint'ы стендов +- `/home/naeel/tf_provider/universal_rebuild/internal/core/client.go` — `doRequest()` (надо править UA) +- `/home/naeel/tf_provider/universal_rebuild/internal/provider/provider.go` — HTTP-транспорт (HTTP/1.1 force) diff --git a/secrets/test.token.new b/secrets/test.token.new new file mode 100644 index 0000000..61ed005 --- /dev/null +++ b/secrets/test.token.new @@ -0,0 +1 @@ +eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoLWFwaSIsInN1YiI6IjAxOWNjMjY4LTZjNmEtNzgxZS04NjEzLTRiZWQ0ZWM3Y2QyMCIsImV4cCI6MTc5ODM3NzQ3NywiaWF0IjoxNzgyODI1NDc3LCJqdGkiOiI3NjFhZmQ5YS1hM2U1LTRiNTctODcyYi1jNWY4MzRlYTNlYzQiLCJhdXRoX3RpbWUiOjAsInR5cCI6IiIsImF6cCI6IiIsInNlc3Npb25fc3RhdGUiOiIiLCJhY3IiOiIiLCJhbGxvd2VkLW9yaWdpbnMiOm51bGwsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6bnVsbH0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpudWxsfX0sInNjb3BlIjoiIiwic2lkIjoiIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoiIiwiQ2xpZW50SUQiOiJXWjAzNzA5IiwiY29tcGFueV9pZCI6IjAxOWNjMjRhLTcyN2UtNzQwZi1iNDA3LWJlYzc5ZGFiNDE2MiIsImNvbXBhbnlfbmFtZSI6Im5hZWVsX3Rlc3QiLCJ0b2tlbl90eXBlIjoidGVjaCIsImlkcF91c3JfdWlkIjoiMDE5Y2MyNjgtNmM2YS03ODFlLTg2MTMtNGJlZDRlYzdjZDIwIiwibG9naW4iOiJ0YXpldGRpbm92bkBnbWFpbC5jb20iLCJmaXJzdG5hbWUiOiLQndCw0LjQu9GMIiwibWlkZGxlbmFtZSI6ItCk0LDRgNC40YLQvtCy0LjRhyIsImxhc3RuYW1lIjoi0KLQsNC30LXRgtC00LjQvdC-0LIiLCJncm91cHMiOm51bGwsInByZWZlcnJlZF91c2VybmFtZSI6IiIsImdpdmVuX25hbWUiOiIiLCJmYW1pbHlfbmFtZSI6IiIsImVtYWlsIjoidGF6ZXRkaW5vdm5AZ21haWwuY29tIn0.U6lfbr_GazFGNhqS0jC2lJejAOXKRup5Ay_3shbrdncMivKq_QVpTZin6mBvUv6m1p7YjSTRwyij3SrY1ZLVXME0TiImipcfbp2BIvmvOrudCRTnSkfL0tjHoEXeEs0EgsBs9Gmp6UOv7sBijxQosOSfd13uGR8_Vgk4drP7kbNRT7xX7Qon4fUpF_G7YlCSf8i5B_178SaQaVZosnn4BweSBqGO4gWfHD8m11_VyrDANXVzCu1eprfAtCtMj3vD8gQiua-ATB6Fg0iStN5ZVbqO8ep6zVctxwittwH7sN0fgY4_BfiSCVx2fSJ5-EHlodjjIPHTMIlouQSgGB6LCA