7.9 KiB
Вот, отправь Соннету:
Контекст: Пишем Terraform Provider для Nubes Cloud (ColdFusion API). Провайдер на Go, версия 5.0.66, опубликован в registry terra.k8c.ru/nubes-test/nubes.
Суть проблемы: terraform apply для создания S3-бакета падает с ошибкой. Прошли путь от 403 до EOF.
Хронология:
HTTP 403— DDoS-Guard блокирует Gocrypto/tls(JA3 fingerprint). curl с того же сервера работает.- Поменяли URL с
index.cfm/instancesнаindex.cfm?endpoint=/instances— 403 остался. - Добавили
utls.HelloFirefox_120— 403 ушёл, но сталEOF(сервер закрывает соединение без ответа). - Убрали utls, оставили
InsecureSkipVerify: true—EOF. - Вернули REST-путь (
index.cfm/instances) +InsecureSkipVerify: true—EOF.
Что работает: curl с токеном напрямую с того же сервера:
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}.
Вопросы:
- Почему Go HTTP клиент (с идентичными заголовками) получает EOF там, где curl получает JSON?
- Может ли ColdFusion-сервер требовать определённый порядок заголовков или HTTP-метод (POST вместо GET)?
- Как заставить Go
http.Clientотправлять точно такие же запросы как curl? Может, черезnet/http/httputilдампить запрос?
Файлы:
- client.go
- provider.go
Список задач обновлен
Searched for files matching **/client.go, 2 matches
Searched for text doRequest|DoRequest (**/universal_rebuild/internal/core/client.go), 20 results
Searched for text doRequest|DoRequest (**/internal/core/client.go), 20 results
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
Теперь посмотрю на S3-ресурс и полный URL, чтобы понять что именно запрашивается:
Searched for text s3|bucket|S3|Bucket (**/universal_rebuild/internal/core/client.go), 8 results
Теперь у меня достаточно контекста. Сформирую план.
Анализ и план
Корневая причина 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() добавить две строки:
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:
if payload != nil {
req.Header.Set("Content-Type", "application/json")
}
Шаг 3 — Debug dump (если 1+2 не помогают)
Добавить через env-флаг NUBES_DEBUG_HTTP=1:
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 ..." ....
Верификация:
terraform applyдля S3-бакета — нет EOFcurl -v --http2 <url>— проверить что с--http2curl тоже получает EOF (подтвердит гипотезу)terraform planпосле успешного apply
Ждать «делай» для реализации?
Создано задач: 3