Files
tf_provider/HISTORY/SONNET/0107.md
T

151 lines
7.9 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.
Вот, отправь Соннету:
---
**Контекст**: Пишем Terraform Provider для Nubes Cloud (ColdFusion API). Провайдер на Go, версия 5.0.66, опубликован в registry `terra.k8c.ru/nubes-test/nubes`.
**Суть проблемы**: `terraform apply` для создания S3-бакета падает с ошибкой. Прошли путь от 403 до EOF.
**Хронология**:
1. `HTTP 403` — DDoS-Guard блокирует Go `crypto/tls` (JA3 fingerprint). curl с того же сервера работает.
2. Поменяли URL с `index.cfm/instances` на `index.cfm?endpoint=/instances` — 403 остался.
3. Добавили `utls.HelloFirefox_120`**403 ушёл**, но стал `EOF` (сервер закрывает соединение без ответа).
4. Убрали utls, оставили `InsecureSkipVerify: true``EOF`.
5. Вернули REST-путь (`index.cfm/instances`) + `InsecureSkipVerify: true``EOF`.
**Что работает**: curl с токеном напрямую с того же сервера:
```bash
curl -H "Authorization: Bearer $TOKEN" -H "User-Agent: Mozilla/5.0" \
"https://deck-api-test.ngcloud.ru/api/v1/index.cfm?endpoint=/instances&page=1&size=100"
# → JSON, 200 OK
```
**Код**: `doRequest()` в client.go (строка 831). Использует `http.DefaultTransport.Clone()` с `TLSClientConfig{InsecureSkipVerify: true, MinVersion: TLS1.2}`.
**Вопросы**:
1. Почему Go HTTP клиент (с идентичными заголовками) получает EOF там, где curl получает JSON?
2. Может ли ColdFusion-сервер требовать определённый порядок заголовков или HTTP-метод (POST вместо GET)?
3. Как заставить Go `http.Client` отправлять точно такие же запросы как curl? Может, через `net/http/httputil` дампить запрос?
**Файлы**:
- client.go
- provider.go
Список задач обновлен
Searched for files matching `**/client.go`, 2 matches
Read memory [](file:///home/naeel/.vscode-server/data/User/workspaceStorage/358cf615179442f06eab7245b983b753/GitHub.copilot-chat/memory-tool/memories/repo/tf_provider_inventory.md)
Searched for text `doRequest|DoRequest` (`**/universal_rebuild/internal/core/client.go`), 20 results
Searched for text `doRequest|DoRequest` (`**/internal/core/client.go`), 20 results
Read [](file:///home/naeel/tf_provider/universal_rebuild/internal/core/client.go#800-800), lines 800 to 900
Read [](file:///home/naeel/tf_provider/internal/core/client.go#177-177), lines 177 to 260
Searched for text `HttpClient|NewClient|InsecureSkipVerify|TLSClientConfig|DefaultTransport` (`**/universal_rebuild/internal/core/client.go`), 5 results
Searched for text `HttpClient|NewClient|InsecureSkipVerify|TLSClientConfig|DefaultTransport` (`**/internal/core/client.go`), 7 results
Searched for text `HttpClient|NewUniversalClient|InsecureSkipVerify|TLSClientConfig|DefaultTransport|http.Client{` (`**/universal_rebuild/internal/provider/**`), 11 results
Searched for text `HttpClient|NewUniversalClient|InsecureSkipVerify|TLSClientConfig|DefaultTransport|http.Client{` (`**/internal/provider/**`), 20 results
Read [](file:///home/naeel/tf_provider/universal_rebuild/internal/provider/provider.go#105-105), lines 105 to 165
Теперь посмотрю на S3-ресурс и полный URL, чтобы понять что именно запрашивается:
Searched for text `s3|bucket|S3|Bucket` (`**/universal_rebuild/internal/core/client.go`), 8 results
Read [](file:///home/naeel/tf_provider/universal_rebuild/internal/core/client.go#1-1), lines 1 to 50
Теперь у меня достаточно контекста. Сформирую план.
Created memory file [](file:///home/naeel/.vscode-server/data/User/workspaceStorage/358cf615179442f06eab7245b983b753/GitHub.copilot-chat/memory-tool/memories/MjE3NzcyMzEtOGRkYy00ODA4LWI0OGItOGEyZDliNmU5YjFl/plan.md)
## Анализ и план
### Корневая причина EOF
**Главный виновник — HTTP/2.**
`http.DefaultTransport.Clone()` копирует ссылку на `TLSNextProto` map, в которой зарегистрирован HTTP/2 (`h2`) handler. При TLS-хендшейке Go предлагает `h2` в ALPN-расширении. ColdFusion + DDoS-Guard не поддерживают HTTP/2 — сервер принимает TLS-соединение, но сразу закрывает его без HTTP-ответа → **EOF**.
Именно поэтому curl работает: он по умолчанию использует HTTP/1.1 и не предлагает `h2` в ALPN.
`InsecureSkipVerify` здесь роли не играет — он влияет только на проверку сертификата, не на TLS-хендшейк.
---
### Ответы на вопросы
**1. Почему Go получает EOF, а curl — нет?**
Go через ALPN предлагает `["h2", "http/1.1"]`. Сервер видит `h2` и закрывает соединение (ColdFusion не понимает HTTP/2). curl предлагает только `["http/1.1"]` — сервер отвечает нормально.
**2. Может ли ColdFusion требовать особый порядок заголовков?**
Маловероятно причина. Порядок заголовков важен для WAAP/DDoS-Guard fingerprinting, но если 403 ушёл, то DDoS-Guard вас пропускает, а EOF — это именно HTTP/2 на уровне приложения.
**3. Как заставить Go отправлять то же, что curl?**
Двумя действиями ниже.
---
### Plan: Fix EOF (2 изменения)
**Файлы к изменению:**
- provider.go — строка ~124
- client.go — строка ~836 (`doRequest`)
**Шаг 1 — Отключить HTTP/2** (приоритет #1, наиболее вероятный фикс)
В provider.go после `transport := http.DefaultTransport.(*http.Transport).Clone()` добавить две строки:
```go
transport := http.DefaultTransport.(*http.Transport).Clone()
transport.TLSHandshakeTimeout = 60 * time.Second
// Отключаем HTTP/2: ColdFusion + DDoS-Guard не поддерживают h2 → EOF
transport.TLSNextProto = make(map[string]func(authority string, c *tls.Conn) http.RoundTripper)
transport.ForceAttemptHTTP2 = false // запрещаем upgrade
```
> `ForceAttemptHTTP2 = false` одного недостаточно — нужен именно пустой `TLSNextProto`, иначе `h2` всё равно остаётся в ALPN.
**Шаг 2 — Убрать Content-Type на GET** (приоритет #2, возможная причина WAF-блокировки)
В `doRequest()` в client.go заголовок `Content-Type` выставлять только если payload != nil:
```go
if payload != nil {
req.Header.Set("Content-Type", "application/json")
}
```
**Шаг 3 — Debug dump** (если 1+2 не помогают)
Добавить через env-флаг `NUBES_DEBUG_HTTP=1`:
```go
if os.Getenv("NUBES_DEBUG_HTTP") == "1" {
dump, _ := httputil.DumpRequestOut(req, true)
fmt.Fprintf(os.Stderr, ">>> REQUEST:\n%s\n", dump)
}
```
Потом сравнить вывод с `curl -v --http1.1 -H "Authorization: Bearer ..." ...`.
---
**Верификация:**
1. `terraform apply` для S3-бакета — нет EOF
2. `curl -v --http2 <url>` — проверить что с `--http2` curl тоже получает EOF (подтвердит гипотезу)
3. `terraform plan` после успешного apply
---
Ждать «делай» для реализации?
Создано задач: 3