Понимание и реализация прокси FastCGI в Nginx

Вступление

Nginx стал одним из самых гибких и мощных доступных решений для веб-серверов. Однако, с точки зрения дизайна, это в первую очередь прокси-сервер. Это означает, что Nginx очень эффективен при работе с запросами к другим серверам.

Nginx может прокси-запросы с использованием http, FastCGI, uwsgi, SCGI или memcached. В этом руководстве мы обсудим прокси FastCGI, который является одним из наиболее распространенных протоколов прокси.

Зачем использовать FastCGI Proxying?

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

Один из основных вариантов использования прокси FastCGI в Nginx - для обработки PHP. В отличие от Apache, который может обрабатывать PHP-обработку напрямую с использованием модуляmod_php, Nginx должен полагаться на отдельный PHP-процессор для обработки PHP-запросов. Чаще всего эта обработка выполняется с помощьюphp-fpm, процессора PHP, который был тщательно протестирован для работы с Nginx.

Nginx с FastCGI можно использовать с приложениями, использующими другие языки, если есть доступный компонент, настроенный для ответа на запросы FastCGI.

Основы прокси FastCGI

Как правило, запросы на проксирование включают прокси-сервер, в данном случае Nginx, переадресацию запросов от клиентов на внутренний сервер. Директива, которую Nginx использует для определения фактического сервера для прокси-сервера с использованием протокола FastCGI, -fastcgi_pass.

Например, для пересылки любых совпадающих запросов на PHP в бэкэнд, посвященный обработке PHP с использованием протокола FastCGI, базовый блок местоположения может выглядеть примерно так:

# server context

location ~ \.php$ {
    fastcgi_pass 127.0.0.1:9000;
}

. . .

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

В то время как прокси http-to-http в основном включает в себя расширение заголовков http, чтобы гарантировать, что бэкэнд имеет информацию, необходимую для ответа прокси-серверу от имени клиента, FastCGI - это отдельный протокол, который не может читать заголовки http. В связи с этим любая соответствующая информация должна передаваться бэкэнду другими способами.

Основной метод передачи дополнительной информации при использовании протокола FastCGI - с параметрами. Фоновый сервер должен быть настроен на их чтение и обработку, изменяя его поведение в зависимости от того, что он находит. Nginx может устанавливать параметры FastCGI с помощью директивыfastcgi_param.

Минимальная конфигурация, которая действительно будет работать в сценарии прокси FastCGI для PHP, выглядит примерно так:

# server context

location ~ \.php$ {
    fastcgi_param REQUEST_METHOD $request_method;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_pass 127.0.0.1:9000;
}

. . .

В приведенной выше конфигурации мы устанавливаем два параметра FastCGI, называемыеREQUEST_METHOD иSCRIPT_FILENAME. Они оба необходимы для того, чтобы внутренний сервер мог понять природу запроса. Первый сообщает ему, какой тип операции он должен выполнять, а второй сообщает вышестоящему, какой файл выполнять.

В этом примере мы использовали некоторые переменные Nginx, чтобы установить значения этих параметров. Переменная$request_method всегда будет содержать http-метод, запрошенный клиентом.

ПараметрSCRIPT_FILENAME устанавливается как комбинация переменной$document_root и переменной$fastcgi_script_name. $document_root будет содержать путь к базовому каталогу, установленному директивойroot. Переменная$fastcgi_script_name будет установлена ​​на URI запроса. Если URI запроса заканчивается косой чертой (/), значение директивыfastcgi_index будет добавлено в конец. Этот тип самоопределенных определений местоположения возможен, потому что мы запускаем процессор FastCGI на той же машине, что и наш экземпляр Nginx.

Давайте посмотрим на другой пример:

# server context
root /var/www/html;

location /scripts {
    fastcgi_param REQUEST_METHOD $request_method;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_index index.php;
    fastcgi_pass unix:/var/run/php5-fpm.sock;
}

. . .

Если для обработки запроса/scripts/test/ выбрано указанное выше место, значениеSCRIPT_FILENAME будет представлять собой комбинацию значений директивыroot, URI запроса иfastcgi_index директива. В этом примере параметр будет установлен на/var/www/html/scripts/test/index.php.

Мы внесли еще одно существенное изменение в приведенную выше конфигурацию, указав, что бэкэнд FastCGI использует сокет Unix вместо сетевого сокета. Nginx может использовать любой тип интерфейса для подключения к FastCGI. Если процессор FastCGI находится на одном хосте, обычно для безопасности рекомендуется сокет Unix.

Преодоление конфигурации FastCGI

Ключевое правило для поддерживаемого кода - стараться следовать принципу СУХОЙ («Не повторяй себя»). Это помогает уменьшить количество ошибок, повысить возможность повторного использования и улучшить организацию. Учитывая, что одна из основных рекомендаций по администрированию Nginx - всегда устанавливать директивы в их самой широкой применимой области, эти фундаментальные цели также применимы к конфигурации Nginx.

При работе с конфигурациями прокси FastCGI большинство случаев использования будет иметь большую часть конфигурации. Из-за этого и из-за того, как работает модель наследования Nginx, почти всегда выгодно объявлять параметры в общем объеме.

Объявление деталей конфигурации FastCGI в родительском контексте

Один из способов уменьшить количество повторений - объявить детали конфигурации в более высоком родительском контексте. Все параметры за пределами фактическогоfastcgi_pass могут быть указаны на более высоких уровнях. Они будут каскадно спускаться в место, где происходит проход. Это означает, что несколько мест могут использовать одну и ту же конфигурацию.

Например, мы могли бы изменить последний фрагмент конфигурации из приведенного выше раздела, чтобы сделать его полезным в нескольких местах:

# server context
root /var/www/html;

fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_index index.php;

location /scripts {
    fastcgi_pass unix:/var/run/php5-fpm.sock;
}

location ~ \.php$ {
    fastcgi_pass 127.0.0.1:9000;
}

. . .

В приведенном выше примере оба объявленияfastcgi_param и директиваfastcgi_index доступны в обоих последующих блоках местоположения. Это один из способов удаления повторяющихся объявлений.

Однако приведенная выше конфигурация имеет один серьезный недостаток. Если какой-либоfastcgi_param объявлен в нижнем контексте,none значенийfastcgi_param из родительского контекста будет унаследован. Вы либо используетеonly унаследованные значения, либо не используете ни одно из них.

Директиваfastcgi_param - это директиваarray на языке Nginx. С точки зрения пользователя, директива массива - это, по сути, любая директива, которую можно использовать более одного раза в одном контексте. Каждое последующее объявление будет добавлять новую информацию к тому, что Nginx знает из предыдущих объявлений. Директиваfastcgi_param была разработана как директива массива, чтобы пользователи могли устанавливать несколько параметров.

Директивы Array наследуют дочерние контексты иначе, чем некоторые другие директивы. Информация из директив массива будет унаследована дочерним контекстамonly if they are not present at any place in the child context. Это означает, что если вы используетеfastcgi_param в своем местоположении, он полностью очистит значения, унаследованные от родительского контекста.

Например, мы могли бы немного изменить вышеуказанную конфигурацию:

# server context
root /var/www/html;

fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_index index.php;

location /scripts {
    fastcgi_pass unix:/var/run/php5-fpm.sock;
}

location ~ \.php$ {
    fastcgi_param QUERY_STRING $query_string;
    fastcgi_pass 127.0.0.1:9000;
}

. . .

На первый взгляд, вы можете подумать, что параметрыREQUEST_METHOD иSCRIPT_FILENAME будут унаследованы во втором блоке местоположения, причем параметрQUERY_STRING будет дополнительно доступен для этого конкретного контекста.

На самом деле происходит то, чтоall родительских значенийfastcgi_param стирается во втором контексте, и устанавливается только параметрQUERY_STRING. ПараметрыREQUEST_METHOD иSCRIPT_FILENAME останутся неустановленными.

Примечание о нескольких значениях для параметров в одном контексте

Одна вещь, которую определенно стоит упомянуть на данном этапе, это последствия установки нескольких значений для одних и тех же параметров в одном контексте. Давайте возьмем следующий пример для обсуждения:

# server context

location ~ \.php$ {
    fastcgi_param REQUEST_METHOD $request_method;
    fastcgi_param SCRIPT_FILENAME $request_uri;

    fastcgi_param DOCUMENT_ROOT initial;
    fastcgi_param DOCUMENT_ROOT override;

    fastcgi_param TEST one;
    fastcgi_param TEST two;
    fastcgi_param TEST three;

    fastcgi_pass 127.0.0.1:9000;
}

. . .

В приведенном выше примере мы установили параметрыTEST иDOCUMENT_ROOT несколько раз в одном контексте. Посколькуfastcgi_param - это директива массива, каждое последующее объявление добавляется во внутренние записи Nginx. ПараметрTEST будет иметь объявления в массиве, устанавливающие для него значенияone,two иthree.

На этом этапе важно понимать, что все они будут переданы бэкэнду FastCGI без дальнейшей обработки Nginx. Это означает, что именно от выбранного процессора FastCGI зависит, как обрабатывать эти значения. К сожалению, разные процессоры FastCGI обрабатывают переданные значенияcompletely differently.

Например, если указанные выше параметры были получены PHP-FPM, значениеfinal будет интерпретировано как переопределение любого из предыдущих значений. Таким образом, в этом случае параметрTEST будет установлен наthree. Точно так же параметрDOCUMENT_ROOT будет установлен наoverride.

Тем не менее, если указанное выше значение передается в нечто вроде FsgiWrap, значения интерпретируются очень по-разному. Сначала он делает начальный проход, чтобы решить, какие значения использовать для запуска скрипта. Он будет использовать значениеDOCUMENT_ROOT изinitial для поиска сценария. Однако, когда он передает фактические параметры в скрипт, он передает окончательные значения, как PHP-FPM.

Эта непоследовательность и непредсказуемость означает, что вы не можете и не должны полагаться на бэкэнд для правильной интерпретации ваших намерений при установке одного и того же параметра более одного раза. Единственное безопасное решение - объявить каждый параметр только один раз. Это также означает, что не существует такой вещи, как безопасное переопределение значения по умолчанию с помощью директивыfastcgi_param.

Использование «Включить в исходную конфигурацию FastCGI из отдельного файла»

Есть еще один способ выделить ваши общие элементы конфигурации. Мы можем использовать директивуinclude для чтения содержимого отдельного файла в место объявления директивы.

Это означает, что мы можем хранить все наши общие элементы конфигурации в одном файле и включать его в любом месте нашей конфигурации, где нам это нужно. Поскольку Nginx будет размещать фактическое содержимое файла там, где вызываетсяinclude, мы не будем наследовать вниз от родительского контекста к дочернему. Это предотвратит стирание значенийfastcgi_param, позволяя при необходимости устанавливать дополнительные параметры.

Во-первых, мы можем установить наши общие значения конфигурации FastCGI в отдельном файле в нашем каталоге конфигурации. Назовем этот файлfastcgi_common:

fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

Теперь мы можем читать в этом файле везде, где мы хотим использовать эти значения конфигурации:

# server context
root /var/www/html;

location /scripts {
    include fastcgi_common;

    fastcgi_index index.php;
    fastcgi_pass unix:/var/run/php5-fpm.sock;
}

location ~ \.php$ {
    include fastcgi_common;
    fastcgi_param QUERY_STRING $query_string;
    fastcgi_param CONTENT_TYPE $content_type;
    fastcgi_param CONTENT_LENGTH $content_length;

    fastcgi_index index.php;
    fastcgi_pass 127.0.0.1:9000;
}

. . .

Здесь мы переместили некоторые общие значенияfastcgi_param в файл с именемfastcgi_common в нашем каталоге конфигурации Nginx по умолчанию. Затем мы получаем этот файл, когда хотим вставить значения, объявленные внутри.

Есть несколько вещей, которые стоит отметить в этой конфигурации.

Во-первых, мы не поместили в файл, который мы планируем, источник значений, которые, возможно, пожелаем настроить для каждого местоположения. Из-за проблемы с интерпретацией, которую мы упоминали выше, которая возникает при установке нескольких значений для одного и того же параметра, и поскольку директивы, не являющиеся массивами, могут быть установлены только один раз для контекста, размещайте элементы только в общем файле, который вы не захотите изменять. Каждая директива (или ключ параметра), которую мы можем захотеть настроить для каждого контекста, должна быть исключена из общего файла.

Другая вещь, которую вы, возможно, заметили, это то, что мы установили некоторые дополнительные параметры FastCGI во втором блоке местоположения. Это та способность, которую мы надеялись достичь. Мы смогли установить дополнительные параметрыfastcgi_param по мере необходимости, не стирая общие значения.

Использование файла fastcgi_params или файла fastcgi.conf

Имея в виду вышеуказанную стратегию, разработчики Nginx и многие команды по распространению пакетов работали над тем, чтобы обеспечить разумный набор общих параметров, которые вы можете включить в свои местоположения проходов FastCGI. Они называютсяfastcgi_params илиfastcgi.conf.

Эти два файла в основном одинаковы, с той лишь разницей, что на самом деле является следствием проблемы, о которой мы говорили ранее о передаче нескольких значений для одного параметра. Файлfastcgi_params не содержит объявления для параметраSCRIPT_FILENAME, в то время как файлfastcgi.conf содержит.

Файлfastcgi_params был доступен гораздо дольше. Чтобы избежать нарушения конфигураций, которые полагались наfastcgi_params, когда было принято решение предоставить значение по умолчанию дляSCRIPT_FILENAME, необходимо было создать новый файл. Невыполнение этого требования могло привести к тому, что этот параметр был установлен как в общем файле, так и в месте прохождения FastCGI. Это подробно описано вMartin Fjordvald’s excellent post on the history of these two files.

Многие разработчики пакетов для популярных дистрибутивов решили включить только один из этих файлов или точно отразить их содержимое. Если у вас есть только один из них, используйте тот, который у вас есть. Не стесняйтесь изменять его в соответствии с вашими потребностями.

Если у вас есть оба этих файла, для большинства мест прохода FastCGI, вероятно, лучше включить файлfastcgi.conf, так как он включает объявление для параметраSCRIPT_FILENAME. Обычно это желательно, но в некоторых случаях вы можете настроить это значение.

Они могут быть включены путем ссылки на их расположение относительно корневого каталога конфигурации Nginx. Корневой каталог конфигурации Nginx обычно выглядит как/etc/nginx, если Nginx был установлен с менеджером пакетов.

Вы можете включить файлы как это:

# server context

location ~ \.php$ {
    include fastcgi_params;
    # You would use "fastcgi_param SCRIPT_FILENAME . . ." here afterwards

    . . .

}

Или вот так:

# server context

location ~ \.php$ {
    include fastcgi.conf;

    . . .

}

Важные директивы, параметры и переменные FastCGI

В вышеприведенных разделах мы установили достаточное количество параметров, часто равных переменным Nginx, для демонстрации других концепций. Мы также ввели некоторые директивы FastCGI без особых пояснений. В этом разделе мы обсудим некоторые общие директивы для установки, параметры, которые вам может потребоваться изменить, и некоторые переменные, которые могут содержать необходимую информацию.

Общие директивы FastCGI

Ниже представлены некоторые наиболее полезные директивы для работы с проходами FastCGI:

  • fastcgi_pass: фактическая директива, которая передает запросы в текущем контексте в бэкэнд. Это определяет местоположение, где процессор FastCGI может быть достигнут.

  • fastcgi_param: директива массива, которая может использоваться для установки значений параметров. Чаще всего это используется вместе с переменными Nginx для установки параметров FastCGI в значения, специфичные для запроса.

  • try_files: не специфическая для FastCGI директива, а общая директива, используемая в местах прохождения FastCGI. Это часто используется как часть процедуры очистки запроса, чтобы убедиться, что запрошенный файл существует, прежде чем передать его процессору FastCGI.

  • include: Опять же, это не директива, специфичная для FastCGI, а та, которая активно используется в контекстах прохода FastCGI. Чаще всего это используется для включения общих общих параметров конфигурации в нескольких местах.

  • fastcgi_split_path_info: эта директива определяет регулярное выражение с двумя захваченными группами. Первая захваченная группа используется как значение для переменной$fastcgi_script_name. Вторая захваченная группа используется как значение для переменной$fastcgi_path_info. Оба они часто используются для правильного анализа запроса, чтобы процессор знал, какие части запроса являются файлами для запуска, а какие - дополнительной информацией для передачи в сценарий.

  • fastcgi_index: определяет индексный файл, который должен быть добавлен к значениям$fastcgi_script_name, оканчивающимся косой чертой (/). Это часто бывает полезно, если для параметраSCRIPT_FILENAME установлено значение$document_root$fastcgi_script_name, а блок местоположения настроен на прием запросов с информацией после файла.

  • fastcgi_intercept_errors: эта директива определяет, должны ли ошибки, полученные от сервера FastCGI, обрабатываться Nginx или передаваться напрямую клиенту.

Приведенные выше директивы представляют большую часть того, что вы будете использовать при разработке типичного прохода FastCGI. Вы не можете использовать все это все время, но мы можем начать видеть, что они довольно тесно взаимодействуют с параметрами и переменными FastCGI, о которых мы поговорим далее.

Общие переменные, используемые с FastCGI

Прежде чем мы сможем поговорить о параметрах, которые вы, вероятно, будете использовать с проходами FastCGI, мы должны немного поговорить о некоторых общих переменных Nginx, которые мы воспользуемся при установке этих параметров. Некоторые из них определены модулем FastCGI в Nginx, но большинство из них из модуля Core.

  • $query_string or $args: аргументы, указанные в исходном клиентском запросе.

  • $is_args: равно «?» если в запросе есть аргументы, в противном случае будет установлена ​​пустая строка. Это полезно при построении параметров, которые могут иметь или не иметь аргументы.

  • $request_method: указывает исходный метод запроса клиента. Это может быть полезно при определении того, должна ли операция быть разрешена в текущем контексте.

  • $content_type: устанавливается в заголовок запросаContent-Type. Эта информация необходима прокси-серверу, если запрос пользователя является POST для правильной обработки следующего содержимого.

  • $content_length: устанавливается равным значению заголовкаContent-Length от клиента. Эта информация требуется для любых клиентских запросов POST.

  • $fastcgi_script_name: он будет содержать файл сценария для запуска. Если запрос заканчивается косой чертой (/), значение директивыfastcgi_index будет добавлено в конец. В случае использования директивыfastcgi_split_path_info для этой переменной будет установлена ​​первая захваченная группа, определенная этой директивой. Значение этой переменной должно указывать фактический скрипт, который должен быть запущен.

  • $request_filename: эта переменная будет содержать путь к запрошенному файлу. Он получает это значение, беря значение текущего корня документа с учетом директивroot иalias, а также значение$fastcgi_script_name. Это очень гибкий способ назначения параметраSCRIPT_FILENAME.

  • $request_uri: весь запрос, полученный от клиента. Это включает в себя сценарий, любую дополнительную информацию о пути, а также любые строки запроса.

  • $fastcgi_path_info: эта переменная содержит дополнительную информацию о пути, которая может быть доступна после имени сценария в запросе. Это значение иногда содержит другое местоположение, о котором должен знать скрипт для выполнения. Эта переменная получает свое значение из второй захваченной группы регулярных выражений при использовании директивыfastcgi_split_path_info.

  • $document_root: эта переменная содержит значение корня текущего документа. Это будет установлено в соответствии с директивамиroot илиalias.

  • $uri: эта переменная содержит текущий URI с примененной нормализацией. Поскольку определенные директивы, которые переписывают или внутренне перенаправляют, могут влиять на URI, эта переменная будет выражать эти изменения.

Как вы можете видеть, при выборе способа установки параметров FastCGI вам доступно довольно много переменных. Многие из них похожи, но имеют некоторые тонкие различия, которые повлияют на выполнение ваших скриптов.

Общие параметры FastCGI

Параметры FastCGI представляют информацию о значении ключа, которую мы хотим сделать доступной для процессора FastCGI, которому мы отправляем запрос. Не каждому приложению понадобятся одинаковые параметры, поэтому вам часто нужно обращаться к документации приложения.

Некоторые из этих параметров необходимы процессору для правильного определения сценария для запуска. Другие становятся доступными для сценария, возможно, изменяя его поведение, если он настроен на использование заданных параметров.

  • QUERY_STRING: этот параметр должен быть установлен для любой строки запроса, предоставленной клиентом. Обычно это будут пары ключ-значение, поставляемые после «?» В URI. Как правило, этот параметр устанавливается либо на переменные$query_string, либо на$args, обе из которых должны содержать одни и те же данные.

  • REQUEST_METHOD: этот параметр указывает процессору FastCGI, какой тип действия был запрошен клиентом. Это один из немногих параметров, которые необходимо установить для правильной работы прохода.

  • CONTENT_TYPE: Если установленный выше метод запроса - «POST», этот параметр должен быть установлен. Указывает тип контента, который должен ожидать процессор FastCGI. Это почти всегда просто устанавливается в переменную$content_type, которая устанавливается в соответствии с информацией в исходном запросе.

  • CONTENT_LENGTH: если метод запроса - «POST», этот параметр должен быть установлен. Это указывает на длину контента. Это почти всегда просто устанавливается в$content_length, переменная, которая получает свое значение из информации в исходном клиентском запросе.

  • SCRIPT_NAME: этот параметр используется для указания имени основного скрипта, который будет запущен. Это чрезвычайно важный параметр, который можно установить различными способами в соответствии с вашими потребностями. Часто устанавливается значение$fastcgi_script_name, которое должно быть URI запроса, URI запроса с добавленнымfastcgi_index, если он заканчивается косой чертой, или первой захваченной группой, если используетсяfastcgi_fix_path_info.

  • SCRIPT_FILENAME: этот параметр указывает фактическое местоположение на диске сценария для запуска. Из-за его связи с параметромSCRIPT_NAME в некоторых руководствах предлагается использовать$document_root$fastcgi_script_name. Другой альтернативой, имеющей много преимуществ, является использование$request_filename.

  • REQUEST_URI: он должен содержать полный немодифицированный URI запроса, вместе со сценарием для запуска, дополнительной информацией о пути и любыми аргументами. Некоторые приложения предпочитают анализировать эту информацию самостоятельно. Этот параметр дает им информацию, необходимую для этого.

  • PATH_INFO: Еслиcgi.fix_pathinfo установлен в «1» в файле конфигурации PHP, он будет содержать любую дополнительную информацию о пути, добавленную после имени скрипта. Это часто используется для определения аргумента файла, с которым должен работать скрипт. Установкаcgi.fix_pathinfo на «1» может иметь последствия для безопасности, если запросы сценария не обрабатываются другими способами (мы обсудим это позже). Иногда это устанавливается в переменную$fastcgi_path_info, которая содержит вторую захваченную группу из директивыfastcgi_split_path_info. В других случаях потребуется использовать временную переменную, поскольку это значение иногда перекрывается другой обработкой.

  • PATH_TRANSLATED: этот параметр отображает информацию о пути, содержащуюся вPATH_INFO, в фактический путь файловой системы. Обычно для этого параметра устанавливается что-то вроде$document_root$fastcgi_path_info, но иногда более поздняя переменная должна быть заменена временной сохраненной переменной, как указано выше.

Проверка запросов перед переходом на FastCGI

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

Чтобы решить эту проблему, мы должны убедиться, что мы отправляем только законные запросы нашим процессорам FastCGI. Мы можем сделать это различными способами в зависимости от потребностей нашей конкретной установки и от того, находится ли процессор FastCGI в той же системе, что и наш экземпляр Nginx.

Одно основное правило, которое должно информировать нас о том, как мы проектируем нашу конфигурацию, состоит в том, что мы никогда не должны допускать никакой обработки и интерпретации пользовательских файлов. Злоумышленникам относительно легко внедрить действительный код в кажущиеся невинными файлы, такие как изображения. После того, как такой файл загружен на наш сервер, мы должны убедиться, что он никогда не попадет на наш процессор FastCGI.

Главная проблема, которую мы пытаемся решить, это та, которая фактически указана в спецификации CGI. Спецификация позволяет вам указать файл сценария для запуска, а затем дополнительную информацию о пути, которая может использоваться сценарием. Эта модель выполнения позволяет пользователям запрашивать URI, который может выглядеть как законный скрипт, в то время как фактическая часть, которая будет выполнена, будет раньше в пути.

Рассмотрим запрос на/test.jpg/index.php. Если ваша конфигурация просто передает каждый запрос, заканчивающийся на.php, вашему процессору без проверки его легитимности, процессор, если он следует спецификации, проверит это местоположение и выполнит его, если это возможно. Еслиdoes not найдет файл, он будет следовать спецификации и попытается выполнить файл/test.jpg, пометив/index.php как дополнительную информацию о пути для сценария. Как видите, это может привести к некоторым очень нежелательным последствиям в сочетании с идеей пользовательских загрузок.

Есть несколько способов решить эту проблему. Самый простой способ, если ваше приложение не использует эту дополнительную информацию о пути для обработки, - просто отключить ее в своем процессоре. Для PHP-FPM вы можете отключить это в вашем файлеphp.ini. Например, в системах Ubuntu вы можете отредактировать этот файл:

sudo nano /etc/php5/fpm/php.ini

Просто найдите параметрcgi.fix_pathinfo, раскомментируйте его и установите значение «0», чтобы отключить эту «функцию»:

cgi.fix_pathinfo=0

Перезапустите процесс PHP-FPM, чтобы внести изменения:

sudo service php5-fpm restart

Это заставит PHP только когда-либо пытаться выполнить последний компонент пути. Итак, в нашем примере выше, если файл/test.jpg/index.php не существует, PHP корректно выдаст ошибку вместо попытки выполнить/test.jpg.

Другой вариант, если наш процессор FastCGI находится на той же машине, что и наш экземпляр Nginx, это просто проверить наличие файлов на диске перед их передачей в процессор. Если файл/test.jgp/index.php не существует, выдается ошибка. Если это так, отправьте его на сервер для обработки. На практике это приведет к тому же поведению, что и выше:

location ~ \.php$ {
        try_files $uri =404;

        . . .

}

Если ваше приложениеdoes полагается на поведение информации о пути для правильной интерпретации, вы все равно можете безопасно разрешить такое поведение, выполнив проверки перед принятием решения, отправлять ли запрос на бэкэнд.

Например, мы могли бы специально сопоставить каталоги, в которых мы разрешаем ненадежные загрузки, и гарантировать, что они не будут переданы нашему процессору. Например, если каталог загрузок нашего приложения -/uploads/, мы могли бы создать подобный блок местоположения, который соответствует до того, как будут оцениваться какие-либо регулярные выражения:

location ^~ /uploads {
}

Внутри мы можем отключить любую обработку PHP-файлов:

location ^~ /uploads {
    location ~* \.php$ { return 403; }
}

Родительское расположение будет соответствовать любому запросу, начинающемуся с/uploads, и любой запрос, касающийся файлов PHP, вернет ошибку 403 вместо отправки ее на бэкэнд.

Вы также можете использовать директивуfastcgi_split_path_info, чтобы вручную определить часть запроса, которая должна интерпретироваться как сценарий, и часть, которая должна быть определена как дополнительная информация о пути, используя регулярные выражения. Это позволяет вам по-прежнему полагаться на функциональность информации о пути, но точно определить, что вы считаете сценарием, а что - путем.

Например, мы можем настроить блок местоположения, который рассматривает первый экземпляр компонента пути, заканчивающийся на.php, как сценарий для запуска. Остальные будут считаться дополнительной информацией о пути. Это будет означать, что в случае запроса/test.jpg/index.php весь путь может быть отправлен процессору как имя сценария без дополнительной информации о пути.

Это местоположение может выглядеть примерно так:

location ~ [^/]\.php(/|$) {

    fastcgi_split_path_info ^(.+?\.php)(.*)$;
    set $orig_path $fastcgi_path_info;

    try_files $fastcgi_script_name =404;

    fastcgi_pass unix:/var/run/php5-fpm.sock;
    fastcgi_index index.php;
    include fastcgi_params;

    fastcgi_param SCRIPT_FILENAME $request_filename;
    fastcgi_param PATH_INFO $orig_path;
    fastcgi_param PATH_TRANSLATED $document_root$orig_path;
}

Вышеупомянутый блок должен работать для конфигураций PHP, гдеcgi.fix_pathinfo установлено в «1», чтобы разрешить дополнительную информацию о пути. Здесь наш блок местоположения соответствует не только запросам, заканчивающимся на.php, но также и запросам с.php непосредственно перед косой чертой (/), указывающей, что следует дополнительный компонент каталога.

Внутри блока директиваfastcgi_split_path_info определяет две захваченные группы с регулярными выражениями. Первая группа соответствует части URI от начала до первого экземпляра.php и помещает ее в переменную$fastcgi_script_name. Затем он помещает любую информацию с этой точки во вторую захваченную группу, которую сохраняет в переменной с именем$fastcgi_path_info.

Мы используем директивуset, чтобы сохранить значение, содержащееся в$fastcgi_path_info в этот момент, в переменной с именем$orig_path. Это связано с тем, что переменная$fastcgi_path_info будет немедленно уничтожена нашей директивойtry_files.

Мы проверяем имя сценария, которое мы записали выше, используяtry_files. Это файловая операция, которая гарантирует, что скрипт, который мы пытаемся запустить, находится на диске. Однако это также имеет побочный эффект очистки переменной$fastcgi_path_info.

После выполнения обычного прохода FastCGI мы устанавливаемSCRIPT_FILENAME как обычно. Мы также устанавливаемPATH_INFO на значение, которое мы выгрузили в переменную$orig_path. Хотя наш$fastcgi_path_info был очищен, его исходное значение сохраняется в этой переменной. Мы также устанавливаем параметрPATH_TRANSLATED, чтобы сопоставить дополнительную информацию о пути с местом, где она существует на диске. Мы делаем это, комбинируя переменную$document_root с переменной$orig_path.

Это позволяет нам создавать запросы типа/index.php/users/view, чтобы наш файл/index.php мог обрабатывать информацию о каталоге/users/view, избегая ситуаций, когда/test.jpg/index.php будет запущен. Он всегда будет устанавливать сценарий на самый короткий компонент, заканчивающийся на.php, что позволяет избежать этой проблемы.

Мы могли бы даже сделать это с помощью директивы псевдонима, если нам нужно изменить расположение наших файлов скриптов. Нам просто нужно учесть это как в нашем заголовке местоположения, так и в определенииfastcgi_split_path_info:

location ~ /test/.+[^/]\.php(/|$) {

    alias /var/www/html;

    fastcgi_split_path_info ^/test(.+?\.php)(.*)$;
    set $orig_path $fastcgi_path_info;

    try_files $fastcgi_script_name =404;

    fastcgi_pass unix:/var/run/php5-fpm.sock;
    fastcgi_index index.php;
    include fastcgi_params;

    fastcgi_param SCRIPT_FILENAME $request_filename;
    fastcgi_param PATH_INFO $orig_path;
    fastcgi_param PATH_TRANSLATED $document_root$orig_path;
}

Это позволит вам безопасно запускать приложения, использующие параметрPATH_INFO. Помните, что вам нужно будет изменить параметрcgi.fix_pathinfo в файлеphp.ini на «1», чтобы это работало правильно. Возможно, вам также придется отключитьsecurity.limit_extensions в вашем файлеphp-fpm.conf.

Заключение

Надеемся, что теперь вы лучше понимаете возможности прокси-прокси FastCGI в Nginx. Эта способность позволяет Nginx проявлять свои сильные стороны в быстрой обработке соединений и обслуживании статического контента, в то же время перекладывая ответственность за динамический контент на более подходящее программное обеспечение. FastCGI позволяет Nginx работать с большим количеством приложений в конфигурациях, которые являются производительными и безопасными.

Related