Files
tf_provider/HISTORY/SONNET/0107.md
T

7.9 KiB
Raw Blame History

Вот, отправь Соннету:


Контекст: Пишем 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_120403 ушёл, но стал EOF (сервер закрывает соединение без ответа).
  4. Убрали utls, оставили InsecureSkipVerify: trueEOF.
  5. Вернули REST-путь (index.cfm/instances) + InsecureSkipVerify: trueEOF.

Что работает: 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}.

Вопросы:

  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

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 , lines 800 to 900

Read , 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 , lines 105 to 165

Теперь посмотрю на S3-ресурс и полный URL, чтобы понять что именно запрашивается:

Searched for text s3|bucket|S3|Bucket (**/universal_rebuild/internal/core/client.go), 8 results

Read , lines 1 to 50

Теперь у меня достаточно контекста. Сформирую план.

Created memory file

Анализ и план

Корневая причина 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 ..." ....


Верификация:

  1. terraform apply для S3-бакета — нет EOF
  2. curl -v --http2 <url> — проверить что с --http2 curl тоже получает EOF (подтвердит гипотезу)
  3. terraform plan после успешного apply

Ждать «делай» для реализации?

Создано задач: 3