Поиск по сайту:

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


Введение

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

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

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

Проксирование 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

Ключевым правилом поддерживаемого кода является попытка следовать принципу DRY («Не повторяйся»). Это помогает уменьшить количество ошибок, повысить возможность повторного использования и обеспечить лучшую организацию. Учитывая, что одной из основных рекомендаций по администрированию 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 объявлен в нижнем контексте, ни одно значение fastcgi_param из родительского контекста не будет унаследовано. Вы либо используете только унаследованные значения, либо не используете ни одно из них.

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

Директивы массива наследуют дочерние контексты иначе, чем некоторые другие директивы. Информация из директив массива будет наследоваться дочерним контекстам, только если они не присутствуют ни в каком месте дочернего контекста. Это означает, что если вы используете 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 будут унаследованы во второй блок location, а параметр QUERY_STRING будет дополнительно доступен для этого конкретного контекста.

На самом деле происходит следующее: все родительские значения 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 будет иметь объявления в массиве, устанавливающие для него значения один, два и три.

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

Например, если вышеуказанные параметры были получены PHP-FPM, значение final будет интерпретировано как переопределяющее любое из предыдущих значений. Таким образом, в этом случае для параметра TEST будет установлено значение три. Точно так же для параметра 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. Это очень подробно описано в превосходной статье Мартина Фьордвальда об истории этих двух файлов.

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

Если у вас есть оба этих файла, для большинства местоположений проходов 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 или $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, вашему процессору, не проверяя его легитимность, процессор, если он следует спецификации, проверит это местоположение и, если возможно, выполнит его. Если он не найдет файл, он будет следовать спецификации и попытается выполнить файл /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;

        . . .

}

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

Например, мы могли бы специально сопоставить каталоги, в которые мы разрешаем ненадежные загрузки, и гарантировать, что они не будут переданы нашему процессору. Например, если каталог загрузки нашего приложения — /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 работать с большим количеством приложений в производительных и безопасных конфигурациях.