Возникают ошибки 404 при конфигурации нескольких шлюзов с одним TLS-сертификатом
Содержание
Описание проблемы
Симптом
При доступе через Istio Ingress Gateway с использованием протокола HTTP/2 возникает ошибка 404.
Эта проблема известна в сообществе Istio. Для подробностей смотрите 404 errors occur when multiple gateways configured with same TLS certificate.
Анализ
Конфигурация более одного шлюза с использованием одного и того же TLS-сертификата приводит к тому, что браузеры, использующие повторное использование HTTP/2 соединений (то есть большинство браузеров), при обращении ко второму хосту после установления соединения с другим хостом выдают ошибку 404.
Пример: Если домены a.example.com
и b.example.com
используют один TLS-сертификат и доступны через один Istio Ingress Gateway, но настроены в двух разных ресурсах Gateway, клиент HTTP/2 браузера получит ошибку 404 при обращении к b.example.com
после посещения a.example.com
. Это связано с повторным использованием HTTP/2 соединения браузером.
Метод устранения
Вы можете использовать следующий скрипт для быстрой проверки, есть ли в вашей среде конфигурации Gateway, соответствующие описанной проблеме. Скрипт необходимо запускать на мастер-узле бизнес-кластера, где расположен Istio Ingress Gateway.
NOTE
- Скрипт зависит от утилиты
jq
. Если на узле кластера нет jq
, установите его перед запуском скрипта. Ссылка для скачивания: jq download.
- Версия
jq
должна быть 1.7 или выше.
#!/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
# Get gw resources
#echo "begin to list gw in $ns"
gateways=$(kubectl get gw -n $ns -o jsonpath='{.items[*].metadata.name}')
# Get the YAML file of the Gateway resource
for gateway in $gateways; do
gateway_yaml=$(kubectl get gw -n $ns $gateway -o yaml)
gateway_json=$(kubectl get gw -n $ns $gateway -o json)
tls_lines=$(echo "$gateway_yaml" | grep 'credentialName:')
secname=$(echo "$gateway_yaml" | grep 'credentialName:'|awk '{print $2}')
if [[ -n "$tls_lines" ]]; then
found=false
for key in "${!cred_map[@]}"; do
if [[ "$key" == "$secname" ]]; then
found=true
break
fi
done
if [[ $found == true ]]; then
echo -e "\033[31m cred already exist in other gw resource ,please must merge hosts in the gw resource ${cred_map[$secname]} ,and delete this gw! \033[0m"
hosts=$(echo "$gateway_json" | jq -r '.spec.servers[] | .hosts[]')
# Output Gateway name and hosts information
echo -e "\033[31m invalid gw name namespace: $gateway , $ns \033[0m"
echo "Hosts: $hosts"
else
echo "first get secret name $secname the gw is $gateway $ns"
cred_map["$secname"]="$gateway~$ns"
fi
#for key in "${!cred_map[@]}"; do
#echo "Key: $key, Value: ${cred_map[$key]}"
#done
echo ""
fi
done
done
Пример вывода скрипта:
[root@idp-lihuang-w9x9w-9n9jv-cluster0-dt2n4 gwtls]# sh check.sh
begin to check gw
first get secret name jiaxiurc-com the gw is drawdb-gateway drawdb
first get secret name gyssg-com the gw is ec jxb-ec
first get secret name nexus the gw is nexus-gateway nexus
cred already exist in other gw resource, please must merge hosts in the gw resource drawdb-gateway~drawdb, and delete this gw!
invalid gw name namespace: authory-gateway, nm-edu-authory
Hosts: rzzx-test.jiaxiurc.com
rzzx-test.jiaxiurc.com
Если в выводе вы видите похожее сообщение: «cred already exist in other gw resource, please must merge hosts in the gw resource drawdb-gateway~drawdb, and delete this gw!», это означает, что у вас описанная в документе проблема.
Обзор решений
Для решения данной проблемы мы предлагаем два варианта. Вы можете ознакомиться с их сравнением ниже и выбрать подходящий для вашей среды.
Сравнение решений
Решение | Преимущества | Недостатки |
---|
(Рекомендуемое) Решение 1: Объединение ресурсов Gateway | - Рекомендуется сообществом, сохраняет обратную совместимость. - Поддерживает производительность HTTP/2 для клиента. | - При большом количестве связанных ресурсов Gateway в кластере операция объединения может быть неудобной. |
Решение 2: Код ответа 421 | - Не требует изменения существующих ресурсов Gateway и VirtualService. - Поддерживает производительность HTTP/2. | - Зависит от поддержки клиентом кода ответа 421. Большинство популярных браузеров поддерживают 421, например Chrome, Firefox и Safari (версия Safari должна быть 15.1 или выше, то есть macOS Monterey). - Перед обновлением Istio необходимо проверить совместимость EnvoyFilter. |
Решение 1: Объединение ресурсов Gateway
Описание решения
Объединить несколько ресурсов Gateway, использующих один TLS-сертификат, в один.
Шаги реализации
- Объединить несколько ресурсов Gateway в одну конфигурацию Gateway, используя общий список
spec.servers.hosts
или конфигурацию с подстановочным доменом (wildcard).
- Изменить связанные ресурсы VirtualService, чтобы они ссылались на объединённый Gateway.
Например, в исходной конфигурации два Gateway используют один TLS-сертификат testhl
:
# Gateway Error Example 1: Two Gateways use the same TLS certificate
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: default2
namespace: istio-system
spec:
selector:
istio: ingressgateway
servers:
- hosts:
- "asm2.test.com"
tls:
mode: SIMPLE
credentialName: "testhl"
port:
name: https
number: 443
protocol: HTTPS
---
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: default
namespace: istio-system
spec:
selector:
istio: ingressgateway
servers:
- hosts:
- "asm1.test.com"
tls:
mode: SIMPLE
credentialName: "testhl"
port:
name: https
number: 443
protocol: HTTPS
---
# Gateway Error Example 2: The same Gateway uses the same TLS certificate in different Hosts sections
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: error-3
namespace: istio-system
spec:
selector:
istio: ingressgateway
servers:
- hosts:
- "asm1.test.com"
tls:
mode: SIMPLE
credentialName: "testhl"
port:
name: https-2
number: 443
protocol: HTTPS
- hosts:
- "asm2.test.com"
tls:
mode: SIMPLE
credentialName: "testhl"
port:
name: https
number: 443
protocol: HTTPS
---
# VirtualService Example
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: default2
namespace: bus-system
spec:
gateways:
- istio-system/default2
hosts:
- asm2.test.com
http:
- route:
- destination:
host: asm-0.testhl.svc.cluster.local
port:
number: 80
...
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: default
namespace: bus-system
spec:
gateways:
- istio-system/default
hosts:
- asm1.test.com
...
Правильная конфигурация после объединения:
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: default2
namespace: istio-system
spec:
selector:
istio: ingressgateway
servers:
- hosts:
- "asm2.test.com"
- "asm1.test.com"
tls:
mode: SIMPLE
credentialName: "testhl"
port:
name: https
number: 443
protocol: HTTPS
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: default1
namespace: istio-system
spec:
gateways:
- istio-system/default2
hosts:
- asm2.test.com
...
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: default2
namespace: istio-system
spec:
gateways:
- istio-system/default2
hosts:
- asm1.test.com
...
Также можно использовать запись с подстановочным знаком:
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: default2
namespace: istio-system
spec:
selector:
istio: ingressgateway
servers:
- hosts:
- "*.test.com"
tls:
mode: SIMPLE
credentialName: "testhl"
port:
name: https
number: 443
protocol: HTTPS
Итоговые шаги
- Объединить
spec.servers.hosts
ресурсов Gateway, собрав все Gateway, использующие один сертификат, в одну конфигурацию server
.
- Изменить VirtualService, чтобы они ссылались на объединённый Gateway.
- Убедиться, что в VirtualService в поле
destination
используется формат FQDN Kubernetes.
Важное примечание: После выполнения указанных шагов повторно запустите скрипт проверки, чтобы убедиться, что проблема устранена.
Решение 2: Код ответа 421
Описание решения
Возврат кода ответа 421 в случае проблемы позволяет клиенту повторно установить соединение, которое будет направлено на правильный целевой хост.
Шаги реализации
Примените следующую конфигурацию 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
subFilter:
name: envoy.filters.http.router
patch:
operation: INSERT_BEFORE
value:
name: envoy.lua
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
inlineCode: |
local function get_host_from_authority(authority)
local colon_pos = authority:find(":", 1, true)
return colon_pos and authority:sub(1, colon_pos - 1) or authority
end
function envoy_on_request(request_handle)
local streamInfo = request_handle:streamInfo()
local requestedServerName = streamInfo:requestedServerName()
if requestedServerName ~= "" then
local host = get_host_from_authority(request_handle:headers():get(":authority"))
local isWildcard = string.sub(requestedServerName, 1, 2) == "*."
if isWildcard and not string.find(host, string.sub(requestedServerName, 3)) then
request_handle:respond({[":status"] = "421"}, "Misdirected Request")
elseif not isWildcard and requestedServerName ~= host then
request_handle:respond({[":status"] = "421"}, "Misdirected Request")
end
end
end