v5.0.71: rawQuery (no %2F) + Accept: */* (DDoS-Guard POST fix)

This commit is contained in:
“Naeel”
2026-07-01 15:52:50 +04:00
parent 3f9b02c7f7
commit f2d2b02e45
4 changed files with 156 additions and 13 deletions
+142
View File
@@ -148,3 +148,145 @@ if os.Getenv("NUBES_DEBUG_HTTP") == "1" {
Создано задач: 3
Вопрос Соннету:
---
**Контекст**: Terraform Provider на Go. API: ColdFusion за DDoS-Guard.
**Что сделали**:
- `TLSNextProto = make(map[string]...)` — GET работает (200 JSON)
- `?endpoint=` формат URL — как у генератора
- `User-Agent: Mozilla/5.0`
**Проблема**: GET → 200. POST → 403. curl (GET и POST) → оба 200.
**Код POST**:
```go
func (c *UniversalClient) doRequest(ctx context.Context, method, path string, payload interface{}) ([]byte, http.Header, error) {
// payload — json-сериализованная структура
req, _ := http.NewRequestWithContext(ctx, method, c.ApiEndpoint, body)
// ?endpoint=/instances
q := req.URL.Query()
q.Set("endpoint", path)
req.URL.RawQuery = q.Encode()
req.Header.Set("User-Agent", "Mozilla/5.0")
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
req.Header.Set("Authorization", "Bearer "+token)
}
```
**Работающий curl**:
```bash
curl -X POST \
-H "User-Agent: Mozilla/5.0" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"serviceId":13}' \
"https://deck-api-test.ngcloud.ru/api/v1/index.cfm?endpoint=/instances"
```
**Вопрос**: Что в Go `http.Client` (c `TLSNextProto = make(...)`) может вызывать 403 на POST, при том что GET работает, и curl POST тоже работает? Может ли `http.NewRequestWithContext` добавлять заголовки/байты, которые триггерят DDoS-Guard на POST но не на GET?
Список задач обновлен
Прочитаю актуальный код `doRequest` и инициализацию transport, чтобы ответить точно, а не по памяти:
Read [](file:///home/naeel/tf_provider/universal_rebuild/internal/core/client.go#804-804), lines 804 to 880
Read [](file:///home/naeel/tf_provider/universal_rebuild/internal/provider/provider.go#122-122), lines 122 to 155
## Диагноз: 3 кандидата, #1 самый вероятный
Код виден полностью. Вот что реально отличает Go POST от curl POST.
---
### #1 (главный подозреваемый): `url.Values.Encode()` кодирует `/` как `%2F`
```go
q.Set("endpoint", endpointPath) // endpointPath = "/instances"
req.URL.RawQuery = q.Encode() // → endpoint=%2Finstances
```
curl отправляет: `?endpoint=/instances`
Go отправляет: `?endpoint=%2Finstances`
**Почему GET проходит, а POST нет?** DDoS-Guard WAF для POST-запросов почти всегда имеет **allowlist эндпоинтов** — проверяет, что `endpoint` соответствует разрешённым путям. `/instances` совпадает, `%2Finstances` — нет → 403. GET-запросы проверяются слабее (read-only, меньший риск).
---
### #2: Нет заголовка `Accept`
curl всегда добавляет `Accept: */*` автоматически.
Ваш Go-код его не ставит.
DDoS-Guard fingerprinting видит: POST с телом + нет `Accept` → нет `Accept` = нет браузера = потенциальный бот.
---
### #3: `Accept-Encoding: gzip` добавляет transport
Т.к. `DisableCompression` не выставлен, transport добавляет `Accept-Encoding: gzip` ко всем запросам. Для GET это тоже есть → GET проходит → вероятно, не причина. Но на всякий случай стоит проверить.
---
### Как проверить немедленно
`DumpRequestOut` уже в коде:
```bash
NUBES_DEBUG_HTTP=1 terraform apply 2>&1 | grep -A 30 ">>> REQ POST"
```
И сравнить с:
```bash
curl -v -X POST -H "User-Agent: Mozilla/5.0" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"serviceId":13}' \
"https://deck-api-test.ngcloud.ru/api/v1/index.cfm?endpoint=/instances" 2>&1 | grep -E "^>|^<"
```
---
### Что менять
**Fix #1 — не использовать `q.Encode()` для endpoint:**
```go
// Вместо q.Set("endpoint",...) + q.Encode()
rawQuery := "endpoint=" + endpointPath // слэш не кодируется
if extraQuery != "" {
rawQuery += "&" + extraQuery
}
req.URL.RawQuery = rawQuery
```
**Fix #2 — добавить `Accept: */*`** (как curl):
```go
req.Header.Set("Accept", "*/*")
```
**Fix #3 (опционально) — отключить автоматическое gzip в transport:**
```go
transport.DisableCompression = true
```
---
### Итог
| Причина | Почему GET ок | Почему POST 403 |
|---|---|---|
| `%2Finstances` в URL | WAF для GET мягче | WAF для POST проверяет allowlist путей |
| Нет `Accept` | То же | Browser fingerprint не проходит |
| `Connection: close` | - | (здесь `req.Close = true` нет, уже убрано) |
Начинать с Fix #1 — вероятность ~70% что это именно оно.
Создано задач: 4