Из ΠΆΠΈΠ·Π½ΠΈ с Kubernetes: Как HTTP-сСрвСр испанцСв Π½Π΅ ΠΆΠ°Π»ΠΎΠ²Π°Π»

Из ΠΆΠΈΠ·Π½ΠΈ с Kubernetes: Как HTTP-сСрвСр испанцСв Π½Π΅ ΠΆΠ°Π»ΠΎΠ²Π°Π»

ΠŸΡ€Π΅Π΄ΡΡ‚Π°Π²ΠΈΡ‚Π΅Π»ΡŒ нашСго ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π°, стСк ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΉ ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ³ΠΎ ΠΎΠ±ΠΈΡ‚Π°Π΅Ρ‚ Π² ΠΎΠ±Π»Π°ΠΊΠ΅ ΠΎΡ‚ Microsoft (Azure), обратился с ΠΏΡ€ΠΎΠ±Π»Π΅ΠΌΠΎΠΉ: с Π½Π΅Π΄Π°Π²Π½Π΅Π³ΠΎ Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ Ρ‡Π°ΡΡ‚ΡŒ запросов Π½Π΅ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… ΠΊΠ»ΠΈΠ΅Π½Ρ‚ΠΎΠ² ΠΈΠ· Π•Π²Ρ€ΠΎΠΏΡ‹ стала Π·Π°Π²Π΅Ρ€ΡˆΠ°Ρ‚ΡŒΡΡ ошибкой 400 (Bad Request). ВсС прилоТСния написаны Π½Π° .NET, Ρ€Π°Π·Π²Ρ‘Ρ€Π½ΡƒΡ‚Ρ‹ Π² Kubernetes…

Одно ΠΈΠ· ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΉ β€” API, Ρ‡Π΅Ρ€Π΅Π· ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Π² ΠΊΠΎΠ½Π΅Ρ‡Π½ΠΎΠΌ счётС ΠΏΡ€ΠΈΡ…ΠΎΠ΄ΠΈΡ‚ вСсь Ρ‚Ρ€Π°Ρ„ΠΈΠΊ. Π­Ρ‚ΠΎΡ‚ Ρ‚Ρ€Π°Ρ„ΠΈΠΊ ΡΠ»ΡƒΡˆΠ°Π΅Ρ‚ HTTP-сСрвСр Kestrel, сконфигурированный ΠΊΠ»ΠΈΠ΅Π½Ρ‚ΠΎΠΌ .NET ΠΈ Ρ€Π°Π·ΠΌΠ΅Ρ‰Ρ‘Π½Π½Ρ‹ΠΉ Π² pod’Π΅. Π‘ ΠΎΡ‚Π»Π°Π΄ΠΊΠΎΠΉ Π½Π°ΠΌ ΠΏΠΎΠ²Π΅Π·Π»ΠΎ Π² Ρ‚ΠΎΠΌ смыслС, Ρ‡Ρ‚ΠΎ Π±Ρ‹Π» ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½Ρ‹ΠΉ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒ, Ρƒ ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ³ΠΎ ΡΡ‚Π°Π±ΠΈΠ»ΡŒΠ½ΠΎ Π²ΠΎΡΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΠ»Π°ΡΡŒ ΠΏΡ€ΠΎΠ±Π»Π΅ΠΌΠ°. Однако всё ослоТнялось Ρ†Π΅ΠΏΠΎΡ‡ΠΊΠΎΠΉ Ρ‚Ρ€Π°Ρ„ΠΈΠΊΠ°:

Из ΠΆΠΈΠ·Π½ΠΈ с Kubernetes: Как HTTP-сСрвСр испанцСв Π½Π΅ ΠΆΠ°Π»ΠΎΠ²Π°Π»

Ошибка Π² Ingress выглядСла ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠΌ ΠΎΠ±Ρ€Π°Π·ΠΎΠΌ:

{
   "number_fields":{
      "status":400,
      "request_time":0.001,
      "bytes_sent":465,
      "upstream_response_time":0,
      "upstream_retries":0,
      "bytes_received":2328
   },
   "stream":"stdout",
   "string_fields":{
      "ingress":"app",
      "protocol":"HTTP/1.1",
      "request_id":"f9ab8540407208a119463975afda90bc",
      "path":"/api/sign-in",
      "nginx_upstream_status":"400",
      "service":"app",
      "namespace":"production",
      "location":"/front",
      "scheme":"https",
      "method":"POST",
      "nginx_upstream_response_time":"0.000",
      "nginx_upstream_bytes_received":"120",
      "vhost":"api.app.example.com",
      "host":"api.app.example.com",
      "user":"",
      "address":"83.41.81.250",
      "nginx_upstream_addr":"10.240.0.110:80",
      "referrer":"https://api.app.example.com/auth/login?long_encrypted_header",
      "service_port":"http",
      "user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
      "time":"2019-03-06T18:29:16+00:00",
      "content_kind":"cache-headers-not-present",
      "request_query":""
   },
   "timestamp":"2019-03-06 18:29:16",
   "labels":{
      "app":"nginx",
      "pod-template-generation":"6",
      "controller-revision-hash":"1682636041"
   },
   "namespace":"kube-nginx-ingress",
   "nsec":6726612,
   "source":"kubernetes",
   "host":"k8s-node-55555-0",
   "pod_name":"nginx-v2hcb",
   "container_name":"nginx",
   "boolean_fields":{}
}

ΠŸΡ€ΠΈ этом Kestrel ΠΎΡ‚Π΄Π°Π²Π°Π»:

HTTP/1.1 400 Bad Request
Connection: close
Date: Wed, 06 Mar 2019 12:34:20 GMT
Server: Kestrel
Content-Length: 0

Π”Π°ΠΆΠ΅ ΠΏΡ€ΠΈ максимальном verbosity ошибка Kestrel содСрТала ΠΊΡ€Π°ΠΉΠ½Π΅ ΠΌΠ°Π»ΠΎ ΠΏΠΎΠ»Π΅Π·Π½ΠΎΠΉ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ:

{
   "number_fields":{"ThreadId":76},
   "stream":"stdout",
   "string_fields":{
      "EventId":"{"Id"=>17, "Name"=>"ConnectionBadRequest"}",
      "SourceContext":"Microsoft.AspNetCore.Server.Kestrel",
      "ConnectionId":"0HLL2VJSST5KV",
      "@mt":"Connection id "{ConnectionId}" bad request data: "{message}"",
      "@t":"2019-03-07T13:06:48.1449083Z",
      "@x":"Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException: Malformed request: invalid headers.n   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1Connection.TryParseRequest(ReadResult result, Boolean& endConnection)n   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.<ProcessRequestsAsync>d__185`1.MoveNext()",
      "message":"Malformed request: invalid headers."
   },
   "timestamp":"2019-03-07 13:06:48",
   "labels":{
      "pod-template-hash":"2368795483",
      "service":"app"
   },
   "namespace":"production",
   "nsec":145341848,
   "source":"kubernetes",
   "host":"k8s-node-55555-1",
   "pod_name":"app-67bdcf98d7-mhktx",
   "container_name":"app",
   "boolean_fields":{}
}

Казалось Π±Ρ‹, Ρ‚ΠΎΠ»ΡŒΠΊΠΎ tcpdump ΠΏΠΎΠΌΠΎΠΆΠ΅Ρ‚ Π² Ρ€Π΅ΡˆΠ΅Π½ΠΈΠΈ этой проблСмы… Π½ΠΎ ΠΏΠΎΠ²Ρ‚ΠΎΡ€ΡŽ ΠΏΡ€ΠΎ Ρ†Π΅ΠΏΠΎΡ‡ΠΊΡƒ Ρ‚Ρ€Π°Ρ„ΠΈΠΊΠ°:

Из ΠΆΠΈΠ·Π½ΠΈ с Kubernetes: Как HTTP-сСрвСр испанцСв Π½Π΅ ΠΆΠ°Π»ΠΎΠ²Π°Π»

РасслСдованиС

ΠžΡ‡Π΅Π²ΠΈΠ΄Π½ΠΎ, Ρ‡Ρ‚ΠΎ ΠΏΠΎΡΠ»ΡƒΡˆΠ°Ρ‚ΡŒ Ρ‚Ρ€Π°Ρ„ΠΈΠΊ Π»ΡƒΡ‡ΡˆΠ΅ Π½Π° Ρ‚ΠΎΠΌ ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½ΠΎΠΌ ΡƒΠ·Π»Π΅, Π³Π΄Π΅ Kubernetes Ρ€Π°Π·Π²Π΅Ρ€Π½ΡƒΠ» pod: ΠΎΠ±ΡŠΡ‘ΠΌ Π΄Π°ΠΌΠΏΠ° Π±ΡƒΠ΄Π΅Ρ‚ Ρ‚Π°ΠΊΠΎΠΉ, Ρ‡Ρ‚ΠΎ получится довольно быстро Π½Π°ΠΉΡ‚ΠΈ Ρ…ΠΎΡ‚ΡŒ Ρ‡Ρ‚ΠΎ-Ρ‚ΠΎ. И Π΄Π΅ΠΉΡΡ‚Π²ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎ, ΠΏΡ€ΠΈ Π΅Π³ΠΎ рассмотрСнии Π±Ρ‹Π» Π·Π°ΠΌΠ΅Ρ‡Π΅Π½ Ρ‚Π°ΠΊΠΎΠΉ Ρ„Ρ€Π΅ΠΉΠΌ:

GET /back/user HTTP/1.1
Host: api.app.example.com
X-Request-ID: 27ceb14972da8c21a8f92904b3eff1e5
X-Real-IP: 83.41.81.250
X-Forwarded-For: 83.41.81.250
X-Forwarded-Host: api.app.example.com
X-Forwarded-Port: 443
X-Forwarded-Proto: https
X-Original-URI: /front/back/user
X-Scheme: https
X-Original-Forwarded-For: 83.41.81.250
X-Nginx-Geo-Client-Country: Spain
X-Nginx-Geo-Client-City: M.laga
Accept-Encoding: gzip
CF-IPCountry: ES
CF-RAY: 4b345cfd1c4ac691-MAD
CF-Visitor: {"scheme":"https"}
pragma: no-cache
cache-control: no-cache
accept: application/json, text/plain, */*
origin: https://app.example.com
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36
referer: https://app.example.com/auth/login
accept-language: en-US,en;q=0.9,en-GB;q=0.8,pl;q=0.7
cookie: many_encrypted_cookies; .AspNetCore.Identity.Application=something_encrypted; 
CF-Connecting-IP: 83.41.81.250
True-Client-IP: 83.41.81.250
CDN-Loop: cloudflare

HTTP/1.1 400 Bad Request
Connection: close
Date: Wed, 06 Mar 2019 12:34:20 GMT
Server: Kestrel
Content-Length: 0

ΠŸΡ€ΠΈ Π²Π½ΠΈΠΌΠ°Ρ‚Π΅Π»ΡŒΠ½ΠΎΠΌ рассмотрСнии Π΄Π°ΠΌΠΏΠ° Π±Ρ‹Π»ΠΎ Π·Π°ΠΌΠ΅Ρ‡Π΅Π½ΠΎ слово M.laga. Π›Π΅Π³ΠΊΠΎ Π΄ΠΎΠ³Π°Π΄Π°Ρ‚ΡŒΡΡ, Ρ‡Ρ‚ΠΎ Π² Испании Π½Π΅Ρ‚ Π³ΠΎΡ€ΠΎΠ΄Π° M.laga (Π·Π°Ρ‚ΠΎ Π΅ΡΡ‚ΡŒ MΓ‘laga). Π£Ρ…Π²Π°Ρ‚ΠΈΠ²ΡˆΠΈΡΡŒ Π·Π° эту идСю, ΠΌΡ‹ посмотрСли ΠΊΠΎΠ½Ρ„ΠΈΠ³ΠΈ Ingress, Π³Π΄Π΅ ΡƒΠ²ΠΈΠ΄Π΅Π»ΠΈ вставлСнный мСсяц Π½Π°Π·Π°Π΄ (ΠΏΠΎ запросу ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π°) Β«Π±Π΅Π·ΠΎΠ±ΠΈΠ΄Π½Ρ‹ΠΉΒ» snippet:

    ingress.kubernetes.io/configuration-snippet: |
      proxy_set_header X-Nginx-Geo-Client-Country $geoip_country_name;
      proxy_set_header X-Nginx-Geo-Client-City $geoip_city;

ΠŸΡ€ΠΈ ΠΎΡ‚ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΈ проброса этих Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠΎΠ² всё стало Ρ…ΠΎΡ€ΠΎΡˆΠΎ! (ВскорС ΠΈ вовсС Π²Ρ‹ΡΡΠ½ΠΈΠ»ΠΎΡΡŒ, Ρ‡Ρ‚ΠΎ эти Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠΈ самому ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡŽ большС Π½Π΅ Ρ‚Ρ€Π΅Π±ΠΎΠ²Π°Π»ΠΈΡΡŒ.)

Π’Π΅ΠΏΠ΅Ρ€ΡŒ посмотрим Π½Π° ΠΏΡ€ΠΎΠ±Π»Π΅ΠΌΡƒ Π² Π±ΠΎΠ»Π΅Π΅ ΠΎΠ±Ρ‰Π΅ΠΌ Π²ΠΈΠ΄Π΅. Π•Ρ‘ Π»Π΅Π³ΠΊΠΎ воспроизвСсти Π²Π½ΡƒΡ‚Ρ€ΠΈ прилоТСния, Ссли ΡΠ΄Π΅Π»Π°Ρ‚ΡŒ telnet-запрос Π½Π° localhost:80:

GET /back/user HTTP/1.1
Host: api.app.example.com
cache-control: no-cache
accept: application/json, text/plain, */*
origin: https://app.example.com
Cookie: test=Desiree

… возвращаСтся 401 Unauthorized, ΠΊΠ°ΠΊ ΠΈ оТидаСтся. А Ρ‡Ρ‚ΠΎ случится, Ссли ΠΌΡ‹ сдСлаСм:

GET /back/user HTTP/1.1
Host: api.app.example.com
cache-control: no-cache
accept: application/json, text/plain, */*
origin: https://app.example.com
Cookie: test=DΓ©sirΓ©e

?

ВСрнётся 400 Bad request β€” Π² Π»ΠΎΠ³Π΅ прилоТСния ΠΏΠΎΠ»ΡƒΡ‡ΠΈΠΌ ΡƒΠΆΠ΅ Π·Π½Π°ΠΊΠΎΠΌΡƒΡŽ Π½Π°ΠΌ ΠΎΡˆΠΈΠ±ΠΊΡƒ:

{
   "@t":"2019-03-31T12:59:54.3746446Z",
   "@mt":"Connection id "{ConnectionId}" bad request data: "{message}"",
   "@x":"Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException: Malformed request: invalid headers.n   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1Connection.TryParseRequest(ReadResult result, Boolean& endConnection)n   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.<ProcessRequestsAsync>d__185`1.MoveNext()",
   "ConnectionId":"0HLLLR1J974L9",
   "message":"Malformed request: invalid headers.",
   "EventId":{
      "Id":17,
      "Name":"ConnectionBadRequest"
   },
   "SourceContext":"Microsoft.AspNetCore.Server.Kestrel",
   "ThreadId":71
}

Π˜Ρ‚ΠΎΠ³ΠΈ

ΠšΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½ΠΎ Kestrel Π½Π΅ ΠΌΠΎΠΆΠ΅Ρ‚ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎ ΠΎΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Ρ‚ΡŒ HTTP-Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠΈ с ΠΏΡ€Π°Π²ΠΈΠ»ΡŒΠ½Ρ‹ΠΌΠΈ символами Π² UTF-8, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ содСрТатся Π² названиях довольно большого количСства Π³ΠΎΡ€ΠΎΠ΄ΠΎΠ².

Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ Ρ„Π°ΠΊΡ‚ΠΎΡ€ Π² нашСм случаС β€” ΠΌΠ΅Π½ΡΡ‚ΡŒ Ρ€Π΅Π°Π»ΠΈΠ·Π°Ρ†ΠΈΡŽ Kestrel Π² ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΈ ΠΊΠ»ΠΈΠ΅Π½Ρ‚ Π² Π΄Π°Π½Π½Ρ‹ΠΉ ΠΌΠΎΠΌΠ΅Π½Ρ‚ Π½Π΅ ΠΏΠ»Π°Π½ΠΈΡ€ΡƒΠ΅Ρ‚. Π’ΠΏΡ€ΠΎΡ‡Π΅ΠΌ, issues Π² самом AspNetCore (β„–4318, β„–7707) говорят ΠΎ Ρ‚ΠΎΠΌ, Ρ‡Ρ‚ΠΎ это ΠΈ Π½Π΅ помоТСт…

ΠŸΠΎΠ΄Ρ‹Ρ‚ΠΎΠΆΠΈΠ²Π°Ρ: Π·Π°ΠΌΠ΅Ρ‚ΠΊΠ° большС Π½Π΅ ΠΎ спСцифичСских ΠΏΡ€ΠΎΠ±Π»Π΅ΠΌΠ°Ρ… Kestrel ΠΈΠ»ΠΈ UTF-8 (Π² 2019-Ρ‚ΠΎ Π³ΠΎΠ΄Ρƒ?!), Π° ΠΎ Ρ‚ΠΎΠΌ, Ρ‡Ρ‚ΠΎ Π²Π½ΠΈΠΌΠ°Ρ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ ΠΈ ΠΏΠΎΡΠ»Π΅Π΄ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎΠ΅ ΠΈΠ·ΡƒΡ‡Π΅Π½ΠΈΠ΅ ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ шага Π²ΠΎ врСмя поиска ΠΏΡ€ΠΎΠ±Π»Π΅ΠΌΡ‹ Ρ€Π°Π½ΠΎ ΠΈΠ»ΠΈ ΠΏΠΎΠ·Π΄Π½ΠΎ принСсут свои ΠΏΠ»ΠΎΠ΄Ρ‹. УспСхов!

P.S.

Π§ΠΈΡ‚Π°ΠΉΡ‚Π΅ Ρ‚Π°ΠΊΠΆΠ΅ Π² нашСм Π±Π»ΠΎΠ³Π΅:

Π˜ΡΡ‚ΠΎΡ‡Π½ΠΈΠΊ: habr.com