На прошлой неделе была опубликована заметка "Как передать IP адрес, который вызвал HTTP запрос в 1C (для веб-сервера Apache)". Судя по "звездопаду", обрушевшемуся на неё, тема определения в приложении IP-адреса клиента является насущной для сообщества.
В упомянутой статье дан рецепт проброса клиентского IP-адреса в 1С (равно как и в любое другое приложение) с помощью кастомного http-заголовка для веб-сервера Apache.
В комментариях было задано два вопроса, оставшихся без ответа:
1. А как быть если перед сервером стоит какой-нибудь прокси, например фронтенд веб-сервер nginx?
2. Как сделать подобное для веб-сервера IIS, если это вообще возможно?
Постараюсь ответить на первый и предложить свое решение для второго. Развёрнуто и с картинками. Итак:
Фронтенд веб-сервер
Если впереди стоит фронтенд веб-сервер, например nginx, то задача решается совсем просто. Не понадобится даже использование решения, предлагаемого в вышеупомянутой статье.
Всё, что требуется, это донастроить фронтенд сервер, включив в его настройках заполнение заголовка X-Forwarded-For, который является дефакто-стандартом для передачи реального IP-адреса исходного запроса.
После включения заголовка X-Forwarded-For на фронт-сервере, этот заголовок будет доступен как в рабочем веб-сервере, так и в коде 1С. То есть мы можем обрабатывать его и извлекать необходимый IP-адрес.
Для nginx эта настройка будет выглядеть так:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
http://nginx.org/ru/docs/http/ngx_http_proxy_module.html
Встроенные переменные
В модуле ngx_http_proxy_module
есть встроенные переменные, которые можно использовать для формирования заголовков с помощью директивы proxy_set_header:
$proxy_host
$proxy_port
$proxy_add_x_forwarded_for
$remote_addr
. Если же поля “X-Forwarded-For” в заголовке запроса клиента нет, то переменная $proxy_add_x_forwarded_for
равна переменной $remote_addr
.
Проброс IP-адреса клиента на веб-сервере IIS
Для того, чтобы реализовать кастомный заголовок, содержащий IP-адрес клиента, на веб-сервере IIS, понадобится установить модуль URL Rewrite. Скачать его можно на официальном сайте: https://www.iis.net/downloads/microsoft/url-rewrite.
Модуль предназначен для версий IIS 7, IIS 7.5, IIS 8, IIS 8.5, IIS 10
Скриншоты примеров сделаны на инсталляции с версией IIS 7.5.
После установки иконка модуля появится в секции IIS при выделенном в левой панели сайте.
Для создания и заполнения заголовка нам нужно будет создать правило.
Выделяем Default Web Site и в средней панели даблкликом открываем модуль URL Rewrite.
В правой панели жмем ссылку "View Server Variables...".
Там же, в правой панели жмем "Add..." и пишем желаемое название заголовка с префиксом "HTTP_" (обязательно в верхнем регистре). У меня http-заголовок будет называться Client-IP-Address, поэтому моя переменная зовется "HTTP_Client-IP-Address"
Префикс "HTTP_" обеспечит нам создание нового http-заголовка.
После создания переменной, возвращаемся к списку правил, нажав в правой панели на ссылку "Back to rules".
Создаем новое правило нажатием на ссылку "Add Rule(s)..." и выбираем "Blank rule" в категории "Inbound rules"
Даем нашему правилу какое-нибудь содержательное название. У меня это будет "Add Client-IP-Address header".
В секции Match URL выбираем Matches the Pattern, Regular Expressions и указываем выражение, которому должен соответствовать URL для того, чтобы наше правило сработало. Нам нужно, чтобы оно работало для всех URL, поэтому в поле Pattern пишем ".*"
Секция Conditions в этом правиле нам не нужна, пропускаем её.
В секции Server Variables добавляем запись. В форме записи выбираем ранее созданную переменную "HTTP_Client-IP-Address", а в качестве значения указываем "{REMOTE_ADDR}" - серверную переменную, содержащую IP-адрес, с которого поступил запрос.
В секции Action выберем None и сохраним правило, нажав в правой панели ссылку "Apply"
Перезагружать или перезапускать что-либо нет необходимости. Правило начинает действовать сразу после применения.
Проверяем. Для проверки вызываю из браузера HTTP-сервис типовой Бухгалтерии https://<Хост:Порт>/<ОпубликованнаяИБ>/ru_RU/hs/api/v1/kpi/
Ура, работает! Мы получили в 1С созданный нами заголовок "Client-IP-Address" и он содержит IP-адрес, с которого я выполнял проверку.
Вместо выполнения всех вышеописанных интерактивных действий можно разместить в корне файлового расположения дефолтного сайта ("C:\inetpub\wwwroot", если не менялись настройки по умолчанию) файл web.config со следующим содержанием. Или добавить в секцию <system.webServer> файла, если он уже существует.
<?xml version="1.0" encoding="UTF-8"?> <configuration> <system.webServer> <rewrite> <rules> <rule name="Add Client-IP-Address header"> <match url=".*" /> <serverVariables> <set name="HTTP_Client-IP-Address" value="{REMOTE_ADDR}" /> </serverVariables> <action type="None" /> </rule> </rules> </rewrite> </system.webServer> </configuration>
Но, пожалуй, это еще не всё.
Используем заголовок X-Forwarded-For
Научим наш веб-сервер заполнять IP-адрес клиента в зависимости от того, есть ли в запросе заголовок X-Forwarded-For. Этот заголовок мы получим как результат настройки собственного фронтенд-вебсервера, или если запрос прошел через какие-то сторонние прокси-серверы до попадания на наш веб-сервер.
В общем случае, в заголовке X-Forwarded-For может быть список IP-адресов, разделенный запятыми. При прохождении запроса через цепочку прокси-серверов, каждый сервер добавляет в этот заголовок IP-адрес источника. Таким образом, самым первым в списке будет IP-адрес клиента, инициировавшего запрос. Соответственно, нам нужен именно этот первый IP-адрес.
Доработаем правила модуля URL Rewrite на веб-сервере.
В случае, если есть заполненный заголовок X-Forvarded-For, первым IP-адресом из него нужно обновить созданный нами ранее заголовок Client-IP-Address.
Для этого в модуле URL rewrite добавим еще одно правило. Назовем его "X_Forwarded_For to Client-IP-Address header"
Секцию Match URL заполняем так же как и у первого правила. Так же поступаем и с секцией Action.
Раскрываем секцию Conditions. Значение Logical grouping можем оставить любое, т.к. у нас будет только одно условие.
Добавляем условие.
Значение Condition input: "{HTTP_X_Forwarded_For}"
Check if input string: Matches the Pattern
Pattern: "(([0-9]{1,3}\.){3}[0-9]{1,3})"
В данном условии проверяем есть ли в значении заголовка X_Forwarded_For строка, удовлетворяющая шаблону IP-адреса и сохраняем эту строку для дальнейшего использования, обрамляя её скобками.
Регулярное выражение "(([0-9]{1,3}\.){3}[0-9]{1,3})", использованное мною в примере, не является идеальным для проверки IP-адреса, т.к. вычленит невалидное "934.448.784.432" из строки 4266934.448.784.43254325, и приведено в демонстрационных целях, чтобы не усложнять пример. Оно выделяет содержимое исходя из шаблона "три цифры - точка - три цифры - точка - три цифры - точка - три цифры"
Принимая во внимание, что вряд ли в этом заголовке будут присутствовать строки, не являющиеся IP-адресами, такое выражение вполне можно использовать.
Сторонникам перфекционизма могу предложить такой вариант регулярного выражения, учитывающий что числа в IP-адресе не могут превышать 255:
\b((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\b
Переходим к секции Server Variables и добавляем новую строку.
Выбираем ту же переменную "HTTP_Client-IP-Address", что добавляли для первого условия.
В поле Value пишем "{C:0}", что означает использование первого совпадения из секции Conditions, т.е. полученный из заголовка IP-адрес.
В итоге получаем следующий алгоритм:
Правила срабатывают в порядке их размещения.
Первым правилом наш заголовок заполняется значением серверной переменной REMOTE_ADDR, содержащей IP-адрес клиента, от которого получен запрос.
Затем второе правило, при существовании заголовка X_Forwarded_For и условии что в его содержимом находятся удовлетворяющие выражению данные, т.е. IP-адрес, обновляет наш кастомный заголовок.
У меня сейчас нет инсталляции с фронтенд-сервером, на которой можно было бы организовать реальную проверку, поэтому я буду использовать расширение для Chrome, позволяющее добавить к запросам такой заголовок с произвольным заполнением https://chrome.google.com/webstore/detail/x-forwarded-for-header/hkghghbnihliadkabmlcmcgmffllglin.
Для проверки вызываю из браузера тот же самый HTTP-сервис типовой Бухгалтерии https://<Хост:Порт>/<ОпубликованнаяИБ>/ru_RU/hs/api/v1/kpi/
Но теперь в http-заголовках присутствует X_Forwarded_For, заполненный значением "40.112.72.205,78.129.196.11".
Как видим, в нашем кастомном заголовке Client-IP-Address оказался первый IP-адрес из цепочки присутствующих в X-Forwarded-For.
Содержимое файла web.config после настройки второго правила будет иметь следующий вид:
<?xml version="1.0" encoding="UTF-8"?> <configuration> <system.webServer> <rewrite> <rules> <rule name="REMOTE_ADDR to Client-IP-Address header"> <match url=".*" /> <serverVariables> <set name="HTTP_Client-IP-Address" value="{REMOTE_ADDR}" /> </serverVariables> <action type="None" /> <conditions logicalGrouping="MatchAny"> </conditions> </rule> <rule name="X_Forwarded_For to Client-IP-Address header"> <match url=".*" /> <conditions logicalGrouping="MatchAny"> <add input="{HTTP_X_Forwarded_For}" pattern="(([0-9]{1,3}\.){3}[0-9]{1,3})" /> </conditions> <serverVariables> <set name="HTTP_Client-IP-Address" value="{C:0}" /> </serverVariables> <action type="None" /> </rule> </rules> </rewrite> </system.webServer> </configuration>
Пара слов о синтаксисе URL Rewrite
На официальном сайте с документацией приведено немало примеров, но найти информацию об используемом синтаксисе оказалось непросто. Поэтому позволю себе продублировать некоторые моменты здесь.
Секция Server Variables (элемент <serverVariables>) предназначена для определения серверных переменных и http-заголовков, которые требуется установить или изменить. Действие будет произведено только если запрос отвечает шаблону, установленному в секции Match URL и выполняются условия, назначенные в секции Conditions.
В поле Value можно использовать как простую строку, так и "ссылки" на серверные переменные, http-заголовки, и обратные ссылки на результаты регулярных выражений, использованных в других секциях.
Для использования "ссылки" необходимо обрамить имя переменной, заголовка или обратной ссылки в фигурные кавычки.
Имя серверной переменной указывается "как есть". В приведенном примере использовались указание серверной переменной REMOTE_ADDR - {REMOTE_ADDR}.
Для указания http-заголовка используется префикс HTTP_
Для указания заголовка http-ответа используется префикс RESPONSE_
Обратные ссылки на результаты работы регулярных выражений секции Conditions обозначаются {C:n}, секции Match URL - {R:n}
Более подробно - в документации: URL Rewrite Module 2.0 Configuration Reference (english).