АйТи Фреш
Главная / Статьи / DevOps и автоматизация
DevOps и автоматизация

После переноса API за nginx пропал заголовок X_API_KEY: где разрешить подчёркивания

Автор: Семёнов Евгений Сергеевич, директор ООО «АйТи-Фреш» · · ~23 мин чтения
После переноса API за nginx пропал заголовок X_API_KEY: где разрешить подчёркивания
Иллюстрация к статье «После переноса API за nginx пропал заголовок X_API_KEY: где разрешить подчёркивания».

Классика: сервис месяцами работал напрямую, его завели за nginx — и авторизация отвалилась. Клиент отправляет X_API_KEY, в curl заголовок виден, а до бэкенда он не доезжает, и разработчик третий день перечитывает свой код. Ошибки в коде нет: заголовок выбросил прокси, молча и по умолчанию. Разбираю, почему nginx так делает, как доказать это за десять минут, какой из трёх способов починки я считаю правильным, почему включение директивы в server-блоке иногда не помогает и почему грабля чаще всего стреляет именно на интеграциях с 1С и онлайн-кассами.

Что происходит на самом деле: две директивы и один молчаливый дефолт

Механика простая, но неочевидная. По документации nginx допустимыми считаются имена полей заголовка, состоящие из английских букв, цифр, дефисов и «возможно знаков подчёркивания». Это «возможно» контролирует директива underscores_in_headers, значение по умолчанию у неё off. Когда подчёркивания запрещены, поле вроде X_API_KEY или access_token помечается как недопустимое и попадает под действие второй директивы, ignore_invalid_headers, которая по умолчанию on. Итог: поле просто игнорируется. Не 400, не пометка в access.log, не строчка в error.log при стандартном уровне error. Запрос доходит до бэкенда целым — только без ключа.

Отсюда и характер жалобы, который я слышу почти дословно одинаково: «в Postman всё есть, в логах nginx запрос есть, приложение возвращает 401». Разработчик идёт разбирать проверку авторизации, потому что со стороны кода это выглядит ровно как «клиент не прислал ключ». А клиент прислал. Между ними стоит прокси, который счёл имя поля недопустимым и вычеркнул его до того, как запрос ушёл в upstream.

Второй момент, который путает следствие: nginx отдаёт заголовки в переменные $http_*, приводя имя к нижнему регистру и заменяя дефисы на подчёркивания. То есть $http_x_api_key заполняется заголовком X-Api-Key. Если вы добавили эту переменную в log_format и увидели там значение — это не доказательство, что подчёркнутый вариант прошёл. Возможно, часть клиентов (новая версия виджета, например) шлёт дефисный вариант, и он честно логируется, а старые клиенты с X_API_KEY молча получают 401. Диагностика по $http_ здесь скорее вредит, чем помогает.

Если API-ключ или токен у вас приходит в заголовке с подчёркиванием, а вы только что поставили перед приложением nginx — не читайте код авторизации. Сначала докажите, что заголовок доехал до upstream.
Памятка: Что происходит на самом деле: две директивы и один молчаливый дефолт — схема
Памятка: Что происходит на самом деле: две директивы и один молчаливый дефолт. Открыть схему в полном размере

Почему nginx так делает и стоит ли на него обижаться

Формально клиент прав. RFC 9110 определяет имя поля как token, а в набор tchar подчёркивание входит наравне с дефисом. То есть X_API_KEY — синтаксически легальное имя HTTP-заголовка, и nginx отбрасывает валидное по стандарту поле. Обидно, но у этого поведения есть внятная причина, и я не считаю его паранойей.

Причина — в CGI-нормализации. Когда заголовок передаётся приложению через переменные окружения (CGI, FastCGI, PHP-FPM, WSGI), имя приводится к виду HTTP_ИМЯ: дефисы заменяются на подчёркивания, буквы переводятся в верхний регистр. И тогда X-Api-Key и X_Api_Key превращаются в одну и ту же переменную HTTP_X_API_KEY. Дальше вопрос в том, кто выиграет коллизию. Если прокси проставляет доверенный X-Forwarded-For, а злоумышленник дополнительно шлёт X_Forwarded_For, то на бэкенде при неудачном порядке разбора может победить подделка. Запрет подчёркиваний по умолчанию этот класс подмен выключает целиком.

Практический вывод такой. Включать подчёркивания можно, но тогда подчёркнутые «двойники» доверенных заголовков нужно явно вычищать на прокси. Важная деталь, которую часто упускают: proxy_set_header X-Forwarded-For не удаляет присланное клиентом поле X_Forwarded_For — для nginx это разные заголовки, и второй уйдёт в upstream как есть. Удалить поле можно только отдельной строкой с пустым значением: по документации proxy_set_header поле с пустой строкой в upstream не передаётся.

И ещё одно наблюдение из миграций. Проблема почти всегда всплывает при смене или добавлении звена в цепочке: приложение, слушающее порт напрямую, подчёркнутые заголовки получает, облачный конструктор сайта или прежний хостинг могли их тоже пропускать. Поэтому «раньше работало» — не иллюзия заказчика, а точное описание фактов. Изменился не код и не клиент, изменился путь запроса.

$proxy_add_x_forwarded_for не перезаписывает, а дописывает адрес клиента к тому, что клиент прислал сам. На первом прокси, который смотрит в интернет, ставьте proxy_set_header X-Forwarded-For $remote_addr; — иначе бэкенд может поверить подделанной цепочке адресов.
После переноса API за nginx пропал заголовок X_API_KEY: где разрешить подчёркивания — схема
Схема к статье. Открыть схему в полном размере

Почему это вылезает на интеграциях с 1С, онлайн-кассами и API

Браузер сам по себе заголовков с подчёркиванием не придумывает, поэтому сайт после переезда за nginx «открывается нормально». Ломается то, где имя поля выбирал человек: самописный обмен 1С с сайтом, модуль уведомлений от облачной кассы или платёжного сервиса, виджет онлайн-записи, мобильное приложение подрядчика. В учебных примерах и старых обработках имена вроде access_token, api_key, X_API_KEY встречаются постоянно — разработчик назвал заголовок так же, как переменную в коде, и нигде до nginx это не мешало.

В 1С заголовки HTTP-запроса задаются обычным соответствием, и платформа ничего не проверяет — какое имя вставили, такое и уйдёт в сеть. Типичный фрагмент из обработки обмена выглядит так, и ровно этот access_token nginx потом выбрасывает:

Заголовки = Новый Соответствие;
Заголовки.Вставить("Content-Type", "application/json");
Заголовки.Вставить("access_token", ТокенДоступа);
Запрос = Новый HTTPЗапрос("/api/v1/requests", Заголовки);
Соединение = Новый HTTPСоединение("zapis.example.com", 443, , , , 30, Новый ЗащищенноеСоединениеOpenSSL);
Ответ = Соединение.Получить(Запрос);

С онлайн-кассами и платёжными сервисами ситуация неприятнее: уведомление об оплате (webhook) формирует чужая сторона, и если в её протоколе подпись или токен лежат в поле с подчёркиванием, переименовать его вы не можете в принципе. Остаётся только разрешить подчёркивания на своём nginx. Второй риск — такие уведомления обычно ретраятся ограниченное число раз, и если прокси несколько суток отдавал бэкенду запросы без подписи, часть оплат придётся сверять с личным кабинетом кассы вручную.

Отдельно про публикацию самой 1С за nginx. Веб-клиент и тонкий клиент ходят со стандартными заголовками, им эта настройка безразлична. Но если на той же публикации живут HTTP-сервисы, которые дёргают внешние системы, у них те же шансы получить токен в подчёркнутом поле. Проверять надо не «открывается ли база», а конкретный вызов конкретной интеграции.

Перед переносом сайта или публикации 1С за nginx выпишите все внешние интеграции и имена заголовков, которыми они авторизуются. Если в списке есть подчёркивание — решение нужно принять до переезда, а не в понедельник утром.

Разбор из практики: музыкальная школа «Форте», 8 рабочих мест, онлайн-запись за nginx

Заказчик — частная музыкальная школа «Форте»: восемь рабочих мест (директор, два администратора, бухгалтер, методист и три общих компьютера в учительской), остальные преподаватели работают с личных устройств. У школы небольшой VPS с сайтом и сервисом онлайн-записи на пробные занятия, написанным фрилансером на Node.js. К сервису ходят двое: виджет записи на сайте с ключом в заголовке X_API_KEY и обработка в 1С у бухгалтера, которая раз в час забирает новые заявки с заголовком access_token. Сервис слушал порт 8080 напрямую, TLS не было вовсе.

В пятницу вечером мы завели его за nginx: TLS-сертификат, единый вход, ограничение частоты запросов к форме, нормальные логи. nginx из стабильной ветки репозитория nginx.org, конфиг стандартный: server с listen 443 ssl, location / с proxy_pass на http://127.0.0.1:8080. Проверили в браузере — сайт открывается, health-check отвечает 200. Разошлись. В понедельник в 9:40 администратор написала: «за выходные ни одной заявки, а родители звонят и говорят, что форма ругается». У бухгалтера обработка в 1С с субботы падала с 401 на каждом запуске.

Первые полчаса ушли впустую, и это моя вина: я поверил access.log. В логе были аккуратные строки с 401 от upstream, то есть запросы доходили, сервис их обрабатывал и отвергал. Логика подсказывала искать в приложении — может, фрилансер что-то выкатил. Не выкатывал, последний коммит был двухмесячной давности. Тогда я остановил сервис и повесил на 8080 socat, который просто печатает всё, что ему прислали. Запрос от виджета приехал с Host, User-Agent, X-Forwarded-For, Content-Type — а X_API_KEY не было вообще. Дальше всё стало понятно за минуту.

Починили в два шага. Сразу — underscores_in_headers on; в http-блоке и вычистка подчёркнутых двойников служебных заголовков, nginx -t и reload. Виджет ожил мгновенно, обработка 1С отработала на следующем часовом запуске и подтянула заявки, которые всё-таки успели лечь в базу до переезда. Планово, за две недели — фрилансер перевёл виджет на X-Api-Key, обработку в 1С я поправил сам (одна строка в Соответствии), сервис месяц принимал оба варианта, потом подчёркнутый убрали. Заодно сервис научили писать в лог список полученных имён заголовков при отказе в авторизации.

Что я вынес из этого случая. Простой формы записи — около 60 часов календарных, и в масштабах маленькой школы это ощутимо: администратор по звонкам и сообщениям насчитала одиннадцать семей, которые пытались записаться на пробное занятие в выходные — именно на выходные у школы приходится основная часть заявок. Всех обзвонили в понедельник, но двое уже записались в другую школу. С тех пор любое изменение пути запроса я проверяю не браузером, а прогоном настоящего клиентского вызова с реальным набором заголовков — и никогда не переключаю такие вещи в пятницу вечером.

Проверка «сайт открывается, health-check зелёный» ничего не говорит о судьбе прикладных заголовков. После постановки прокси перед API прогоняйте настоящий запрос настоящего клиента — с ключом, с кастомными полями, тем же методом. И не делайте этого в пятницу вечером.
Цифры и версии: Разбор из практики: музыкальная школа «Форте», 8 рабочих мест, онлайн-запись за nginx — схема
Цифры и версии: Разбор из практики: музыкальная школа «Форте», 8 рабочих мест, онлайн-запись за nginx. Открыть схему в полном размере

Три способа починить, и какой я выбираю

Способ первый и правильный: переименовать заголовок в дефисный вид — X_API_KEY становится X-Api-Key, access_token становится Access-Token. Это лечит причину, а не симптом: такой заголовок пройдёт через любой прокси и любой CGI-шлюз без единой настройки. Минус ровно один и он тяжёлый — нужно править клиентов. Если клиенты ваши и обновляются централизованно (своя обработка 1С, свой виджет), делайте так. Если это чужой webhook, мобильное приложение со своим циклом обновлений или интеграция, к которой у вас нет доступа, — переходите к способу два, а переименование ставьте в план там, где оно возможно.

Способ второй, рабочая лошадка: разрешить подчёркивания на уровне http. Одна строка, применяется ко всем виртуальным серверам сразу, никаких сюрпризов с выбором сервера. Так я делаю в большинстве случаев — сначала эта строка и reload, чтобы прекратить простой, потом спокойный разговор про переименование.

Минимальный рабочий конфиг, который я ставлю в такой ситуации, выглядит так — директива именно в http, а подчёркнутые двойники служебных заголовков явно вычищаются:

http {
    underscores_in_headers on;

    server {
        listen 443 ssl;
        server_name zapis.example.com;

        location / {
            proxy_pass http://127.0.0.1:8080;
            proxy_set_header Host              $host;
            proxy_set_header X-Real-IP         $remote_addr;
            proxy_set_header X-Forwarded-For   $remote_addr;
            proxy_set_header X-Forwarded-Proto $scheme;
            # подчёркнутые двойники от клиента в upstream не пропускаем
            proxy_set_header X_Real_IP         "";
            proxy_set_header X_Forwarded_For   "";
            proxy_set_header X_Forwarded_Proto "";
        }
    }
}

Способ третий, крайний: ignore_invalid_headers off. Он выключает выбрасывание полей с недопустимыми именами вообще — не только с подчёркиваниями, но и с любыми другими символами вне букв, цифр и дефисов. Применять его стоит только тогда, когда действительно нужно пропустить имя, которое nginx считает недопустимым по другой причине, и вы понимаете, что отправляете на бэкенд. Для задачи «дошёл X_API_KEY» это стрельба из пушки: underscores_in_headers решает её точнее.

И отдельно — то, о чём забывают в половине конфигов. По документации proxy_set_header наследуется с предыдущего уровня только при условии, что на текущем уровне не описано ни одной своей директивы proxy_set_header. Написали одну строку в location — весь набор из server или http для этого location пропал. Неявные значения по умолчанию (Host $proxy_host и Connection close) при этом продолжают действовать, а вот ваши X-Real-IP, X-Forwarded-For и строки с пустыми значениями — нет. Держите полный набор в одном файле и подключайте его через include в каждом месте, где он нужен.

Помните про наследование: как только вы написали хотя бы один proxy_set_header внутри location, набор с уровня выше для него перестаёт действовать — включая строки, вычищающие подчёркнутых двойников. Держите полный список в одном месте и подключайте через include.

Ловушка default server: включил в server-блоке, а не помогло

Это второй акт истории, на котором спотыкаются те, кто уже прочитал документацию и добавил нужную строку. Директива underscores_in_headers допускается в контекстах http и server. Логично поставить её только в тот server, где живёт API, чтобы не менять поведение остальных сайтов на том же сервере. И тут документация прямо предупреждает: если директива указана на уровне server, может использоваться значение из сервера по умолчанию. То же сказано и про ignore_invalid_headers.

Причина — в порядке выбора виртуального сервера. По разделу «Выбор виртуального сервера» соединение сначала создаётся в контексте сервера по умолчанию для данного listen, затем имя сервера уточняется: предварительно при SSL handshake по SNI, после обработки строки запроса и после обработки поля Host. Для ignore_invalid_headers, large_client_header_buffers и underscores_in_headers, которые участвуют в разборе полей заголовка, документация отдельно указывает: выбор сервера дополнительно зависит от того, была ли конфигурация уже обновлена согласно строке запроса или полю Host. Клиенты с корректным SNI, скорее всего, попадут в нужный server; обращение по IP, по HTTP без TLS или клиент, у которого Host идёт не первым полем, — и часть заголовков будет разобрана с настройками default server, то есть с off.

Практический вывод: не мучайтесь с тонкой настройкой. Ставьте underscores_in_headers on; в http-блоке — один раз и для всех. Риск от глобального включения закрывается вычисткой подчёркнутых двойников на прокси, а отладка плавающего «у половины клиентов работает» съедает часы. Если глобально нельзя, продублируйте директиву и в целевом server, и в сервере по умолчанию для того же listen (том, что помечен default_server, или первом в списке) — иначе результат будет зависеть от того, как именно клиент подключился.

«Работает у одних клиентов и не работает у других» при включённой в server-блоке директиве — почти всегда именно это: часть запросов разбирается с настройками сервера по умолчанию.

Диагностика за десять минут: последовательность, которая не врёт

Порядок действий такой, чтобы каждый шаг что-то доказывал, а не создавал ощущение работы. Сначала фиксируем, что отправляет клиент. Потом — что доходит до бэкенда. Разница между этими двумя фактами и есть ответ, и на этом расследование обычно заканчивается.

Отправляем контрольный запрос с обоими вариантами имени сразу — так за один вызов видно, режется ли именно подчёркнутый:

curl -sv -H 'X_API_KEY: test-123' -H 'X-Api-Key: test-123' \
     https://zapis.example.com/api/v1/ping

Второй шаг — смотрим, что приехало в upstream. Самый честный способ: на время остановить приложение и повесить на его порт перехватчик, который печатает сырой запрос. Никаких допущений, вы видите ровно те байты, которые отдал nginx. Если приложение останавливать нельзя, поднимите перехватчик на соседнем порту и временно переключите proxy_pass на него:

systemctl stop zapis-api
socat -v TCP-LISTEN:8080,fork,reuseaddr STDOUT
# повторяем клиентский запрос и читаем вывод: заголовка X_API_KEY в нём не будет

Третий шаг, если хочется подтверждения от самого nginx. Отдельная отладочная сборка для этого не нужна: при включённом ignore_invalid_headers nginx пишет отброшенное поле в error_log сообщением «client sent invalid header line» с уровнем info. На стандартном уровне error этих строк не видно, поэтому временно понизьте уровень лога для нужного server, повторите запрос и верните как было:

server {
    # временно, на время проверки
    error_log /var/log/nginx/zapis-debug.log info;
    ...
}

Debug-лог (сборка с --with-debug, в пакетах nginx.org — отдельный бинарник nginx-debug) нужен только если info ничего не показал, и включать его стоит точечно через debug_connection для своего адреса. После любой правки конфига — nginx -t, и только потом systemctl reload nginx. И проверьте итоговую конфигурацию целиком через nginx -T: на серверах с долгой историей я регулярно нахожу второй экземпляр директивы, приехавший из подключённого сниппета.

Не начинайте с debug-лога. Сначала два факта: что отправил клиент и что дошло до upstream. Если нужен третий — хватит уровня info в error_log. Оставлять info на постоянной основе не стоит: интернет-сканеры шлют мусорные заголовки, и лог быстро разбухает.
Порядок действий: Диагностика за десять минут: последовательность, которая не врёт — схема
Порядок действий: Диагностика за десять минут: последовательность, которая не врёт. Открыть схему в полном размере

Та же грабля за пределами nginx: ingress, Envoy, PHP-FPM

Маленькой школе Kubernetes не нужен, но сайт с онлайн-записью нередко живёт у подрядчика на его платформе, и там история повторяется. В контроллере ingress-nginx поведение управляется ключом ConfigMap enable-underscores-in-headers со значением по умолчанию "false« — заголовки с подчёркиванием точно так же выбрасываются. Рядом живёт ignore-invalid-headers со значением по умолчанию »true". Лечится добавлением ключа в ConfigMap контроллера, после чего он сам перегенерирует nginx.conf; это глобальная настройка контроллера, а не отдельного Ingress.

Если у подрядчика на пути стоит Envoy (в том числе в составе Istio), проверьте и его: за подчёркивания отвечает параметр headers_with_underscores_action в HttpProtocolOptions. По умолчанию там ALLOW, но платформа может выставить DROP_HEADER (заголовок молча удаляется) или REJECT_REQUEST (HTTP/1 получает 400, HTTP/2 — сброс потока). Тогда вы увидите ту же картину на шаг глубже. Итог для многозвенных контуров: заголовок может умереть на любом хопе, и искать надо снаружи внутрь, отсекая по одному звену.

И последнее, про PHP-FPM и всё, что ходит по FastCGI. Даже когда nginx подчёркивания пропустил, приложение видит заголовки как переменные HTTP_*, где дефис и подчёркивание неразличимы. Если в одном запросе окажутся и X_API_KEY, и X-Api-Key, в $_SERVER останется только одно значение, и какое именно — вопрос порядка обработки, а не вашей логики. Ещё один довод в пользу того, чтобы в итоге прийти к единому дефисному имени и не держать два варианта дольше переходного периода.

Что я советую при любой миграции, где меняется путь запроса: до переезда снять полный список заголовков, которыми реально пользуются ваши интеграции. Пять минут с tcpdump или логом приложения на старом контуре — и вы заранее знаете, какие поля проверить после. Это дешевле, чем выходные без онлайн-записи.

Перед переносом API за новый прокси зафиксируйте реальный набор заголовков от каждой интеграции. Список из десяти строк экономит выходные.

Частые вопросы

Почему nginx не возвращает ошибку, а просто выбрасывает заголовок?

Так работает связка двух директив. underscores_in_headers off помечает поле с подчёркиванием как недопустимое, а ignore_invalid_headers on предписывает недопустимые поля игнорировать. Запрос считается корректным, просто в нём стало на один заголовок меньше. Единственный след — строка «client sent invalid header line» в error_log, и то только при уровне info. Поэтому со стороны приложения симптом выглядит как «клиент не прислал ключ».

Опасно ли включать underscores_in_headers on глобально?

Риск невелик, но не нулевой. При передаче заголовков через CGI/FastCGI имена X-Forwarded-For и X_Forwarded_For схлопываются в одну переменную HTTP_X_FORWARDED_FOR, и клиент может подсунуть свой вариант. Перезапись X-Forwarded-For через proxy_set_header этого не закрывает — подчёркнутое поле для nginx другое и уйдёт в upstream. Закрывается строками proxy_set_header X_Forwarded_For ""; и аналогичными для других доверенных полей.

Добавил директиву в нужный server-блок, но у части клиентов заголовок всё ещё пропадает. Почему?

Потому что разбор полей заголовка может начаться до того, как окончательно выбран виртуальный сервер. Документация прямо предупреждает, что для underscores_in_headers на уровне server может использоваться значение из сервера по умолчанию. Клиенты с корректным SNI попадут в ваш блок, а обращения по IP или по HTTP без TLS могут быть разобраны с настройками default server, где стоит off. Решение — вынести директиву в http-блок.

Чем underscores_in_headers on отличается от ignore_invalid_headers off?

Первая расширяет определение допустимого имени, добавляя к буквам, цифрам и дефисам подчёркивание. Вторая вообще отключает отбрасывание полей с недопустимыми именами — любых, не только подчёркнутых. Для задачи «пропустить X_API_KEY» нужна первая: она точнее и не пропускает на бэкенд ничего лишнего.

Как быстро доказать, что заголовок режет именно nginx, а не клиент?

Отправьте один запрос с двумя вариантами имени сразу: curl -sv -H 'X_API_KEY: test' -H 'X-Api-Key: test' на ваш адрес. Если приложение видит только дефисный вариант, вопрос закрыт. Для полной уверенности остановите бэкенд и повесьте на его порт socat -v TCP-LISTEN:8080,fork,reuseaddr STDOUT — увидите сырой запрос ровно в том виде, в каком его отдаёт nginx. Либо временно выставьте error_log с уровнем info и найдите строку «client sent invalid header line».

Обмен 1С с сайтом перестал работать после переезда за nginx. Что поменять в 1С?

Посмотрите, как в обработке заполняется соответствие заголовков для HTTPЗапрос. Если там строка вроде Заголовки.Вставить("access_token", Токен), переименуйте поле в дефисный вид (Access-Token) и одновременно научите сайт принимать новое имя. Пока обе стороны не обновлены, временно включите underscores_in_headers on в http-блоке nginx.

Онлайн-касса шлёт уведомления с подчёркиванием в заголовке — что делать?

Протокол уведомлений задаёт сервис, переименовать поле вы не можете. Включите underscores_in_headers on в http-блоке, вычистите подчёркнутых двойников служебных заголовков и после починки сверьте оплаты за период простоя с личным кабинетом кассы — повторные отправки у таких сервисов обычно ограничены.

В Kubernetes та же проблема лечится так же?

Да, только настройка живёт в ConfigMap контроллера ingress-nginx: enable-underscores-in-headers со значением по умолчанию "false". Это глобальный параметр контроллера. Если на пути ещё и Envoy, проверьте headers_with_underscores_action: по умолчанию там ALLOW, но платформа может выставить DROP_HEADER или REJECT_REQUEST.

Столкнулись с похожей задачей? Обращайтесь — решим

Если у вас происходит что-то из описанного в этой статье — или любая другая проблема с ИТ-инфраструктурой, — обращайтесь в любое время. Мои специалисты и я лично разберём ситуацию, найдём настоящую причину и доведём до решения.

Возьмёмся и за разовую задачу, и за постоянное обслуживание. Первичная консультация — бесплатно и без обязательств.

📞 +7 903 729-62-41 💬 MAX: +7 903 729-62-41 ✈ Telegram @ITfresh_Boss

С уважением, Семёнов Евгений Сергеевич, директор «АйТи Фреш» — IT-аутсорсинг для компаний до 50 рабочих мест, 15+ лет практики

Источники

© ООО «АйТи-Фреш» · Москва · Все статьи