Ошибки 404 при конфигурации нескольких шлюзов с одним и тем же TLS-сертификатом

Содержание

Описание проблемы

Симптомы

При обращении к сервисам через Istio Ingress Gateway с использованием протокола HTTP/2 возникают ошибки 404. Это известная проблема в сообществе Istio.

Корневая причина

Конфигурация нескольких шлюзов с одним и тем же TLS-сертификатом приводит к тому, что браузеры HTTP/2 генерируют ошибки 404 при обращении к вторичным хостам после установления первоначального соединения. Это происходит из-за повторного использования HTTP/2 соединения в браузерах.

Пример сценария:

  • Домены a.example.com и b.example.com используют один и тот же TLS-сертификат
  • Настроены в отдельных ресурсах Gateway
  • Браузер обращается сначала к a.example.com, затем к b.example.com по одному и тому же соединению

Устранение неполадок

Скрипт для проверки

Выполните этот скрипт на мастер-узле кластера, в котором развернут Istio Ingress Gateway:

#!/bin/bash
nslist=$(kubectl get ns -o jsonpath='{.items[*].metadata.name}')
declare -A cred_map

echo "begin to check gw"
for ns in $nslist; do
  gateways=$(kubectl get gw -n $ns -o jsonpath='{.items[*].metadata.name}')
  for gateway in $gateways; do
    gateway_yaml=$(kubectl get gw -n $ns $gateway -o yaml)
    secname=$(echo "$gateway_yaml" | grep 'credentialName:' | awk '{print $2}')

    if [[ -n "$secname" ]]; then
      if [[ -n "${cred_map[$secname]}" ]]; then
        echo -e "\033[31m Duplicate TLS detected in gateway: ${cred_map[$secname]} \033[0m"
        hosts=$(kubectl get gw -n $ns $gateway -o json | jq -r '.spec.servers[] | .hosts[]')
        echo -e "\033[31m Conflict gateway: $gateway in $ns \033[0m"
        echo "Affected hosts: $hosts"
      else
        cred_map["$secname"]="$gateway~$ns"
      fi
    fi
  done
done

Ожидаемый вывод:

Duplicate TLS detected in gateway: drawdb-gateway~drawdb
Conflict gateway: authory-gateway in nm-edu-authory
Affected hosts: rzzx-test.jiaxiurc.com

Решение корневой причины 1: Объединение ресурсов Gateway

Особенности

  • Рекомендуемое сообществом решение
  • Сохраняет преимущества производительности HTTP/2
  • Требует изменения существующих конфигураций Gateway

Предварительные условия

  1. Установлен jq версии 1.7 и выше на узлах кластера
  2. Доступ к кластеру с правами kubectl

Шаги

  1. Определить конфликтующие Gateways с помощью скрипта проверки
  2. Объединить конфигурации Host:
    apiVersion: networking.istio.io/v1beta1
    kind: Gateway
    metadata:
      name: unified-gateway
      namespace: istio-system
    spec:
      selector:
        istio: ingressgateway
      servers:
      - hosts:
          - "a.example.com"
          - "b.example.com"
        tls:
          mode: SIMPLE
          credentialName: shared-cert
        port:
          name: https
          number: 443
          protocol: HTTPS
  3. Обновить VirtualServices для ссылки на объединённый Gateway
  4. Удалить избыточные Gateways
  5. Проверить конфигурацию:
    kubectl get gw -A
    istioctl analyze

Решение корневой причины 2: Код ответа 421

Особенности

  • Требуется поддержка клиентом кода статуса 421
  • Совместимо с Chrome/Firefox/Safari 15.1 и выше

Предварительные условия

  1. Версия Istio ≥ 1.12
  2. Права администратора кластера

Шаги

  1. Применить EnvoyFilter:
    apiVersion: networking.istio.io/v1alpha3
    kind: EnvoyFilter
    metadata:
      name: misdirected-request
      namespace: istio-system
    spec:
      configPatches:
        - applyTo: HTTP_FILTER
          match:
            context: GATEWAY
            listener:
              filterChain:
                filter:
                  name: envoy.filters.network.http_connection_manager
          patch:
            operation: INSERT_BEFORE
            value:
              name: envoy.lua
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
                inlineCode: |
                  function envoy_on_request(request_handle)
                    local authority = request_handle:headers():get(":authority")
                    local sni = request_handle:streamInfo().requestedServerName
    
                    if sni ~= "" and sni ~= authority:match("([^:]+)") then
                      request_handle:respond({[":status"] = "421"}, "Misdirected Request")
                    end
                  end
  2. Проверить реализацию:
    curl -I -H "Host: b.example.com" https://gateway-ip

Профилактические меры

  1. Управление сертификатами:
    • Использовать wildcard-сертификаты (*.example.com)
    • Избегать повторного использования сертификатов в разных окружениях
  2. Проектирование Gateway:
    • Реализовать один шлюз на шаблон домена
    • Использовать изоляцию сертификатов по namespace
  3. Регулярные аудиты:
    istioctl experimental precheck
    kubectl get secret --all-namespaces -o json | jq '.items[].metadata.name' | sort | uniq -d

Связанный контент

Механизм повторного использования HTTP/2 соединений:

  • Одно TLS-соединение обрабатывает несколько запросов
  • Сервер использует SNI для маршрутизации запросов
  • Несовпадение заголовков SNI вызывает сбои маршрутизации

Ссылка на документацию Istio:
Istio Common Problems - 404 Errors