Вопрос или проблема
Что у меня есть
У меня настроен Nginx для обработки файлов .php для всех виртуальных хостов, и все работает хорошо.
location ~ \.php {
include snippets/fastcgi-php.conf;
keepalive_timeout 0;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/php7.4-fpm.sock;
}
Вышеизложенное находится в snippets/common.conf
и включается в каждую конфигурацию виртуального хоста. Упомянутый fastcgi-php.conf
находится рядом с ним.
Что мне нужно
Для данного виртуального хоста мне нужно запретить доступ ко всем файлам, кроме /example.php
.
Что я пробовал
-
location ~ ^/example { allow all; } location ~ ^/ { allow 123.123.123.123; deny all; }
Само ограничение работает хорошо, но PHP файл теперь возвращается как текст исходного кода, вместо обработки. Это означает, что вышеупомянутое каким-то образом перезаписывает предыдущий
location ~ \.php
, даже если я здесь не включал расширение.php
. -
Создать
common-php.conf
include snippets/fastcgi-php.conf; keepalive_timeout 0; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass unix:/run/php/php7.4-fpm.sock;
Повторно использовать его в
common.conf
:location ~ \.php { include snippets/common-php.conf; }
И в моей фактической конфигурации виртуального хоста:
location ~ ^/example { allow all; include snippets/common-php.conf; } location ~ ^/ { allow 123.123.123.123; deny all; include snippets/common-php.conf; }
Проблемы с вышеизложенным:
- Это значительно усложняет настройку
- Казалось бы избыточным настраивать
/example.php
дважды - Это не полностью работает, так как я получаю 403, например, на
/plugins/Feedback/angularjs/feedback-popup/feedback-popup.directive.html?cb=abcd1234
по какой-то причине. CMS, которую я использую, сложна, и по предыдущим двум причинам я не проводил дальнейшее расследование.
Я хотел бы настроить доступ к файлу /example.php
, не перезаписывая существующий обработчик PHP, прикрепленный к нему. Это возможно? Или включения — это мой единственный вариант?
Пожалуйста, дайте мне знать, если вы можете придумать лучшую конфигурацию для этой задачи. Спасибо!
Обновление @ 2025.02.26
Исходная версия этого ответа (можно просмотреть через историю редактирования) не работала по причине того, что директива geo
не позволяет использовать переменные для значения производной переменной. При цепочке блоков map
и geo
блок geo
всегда должен быть последним звеном в цепи.
То есть, очень распространенная ошибка заключается в следующем
location / {
... некоторые настройки
}
location /admin {
... дополнительные настройки
}
и ожидании, что запрос /admin/...
использует комбинированные настройки. Этого не произойдет, в зависимости от местоположения, где запрос будет в конечном итоге обработан, будет применен только один набор настроек. Однако, как я уже говорил, каждый запрос может проходить через несколько блоков местоположения, и некоторые трюки можно выполнить, используя манипуляции с переменными. Определенной переменной можно изменить значение несколько раз, пока запрос проходит через разные местоположения, и ее фактическое значение можно использовать в конечном местоположении в качестве параметра директивы. Решение данного вопроса не будет использовать эту технику, но это возможно в некоторых других случаях.
Многие вещи (например, условные заголовки) могут быть реализованы с использованием (очень мощной) функции map
в nginx. Альтернатива правилам allow
/deny
может быть достигнута, комбинируя блоки map
и geo
, чтобы оценить переменную, действующую как флаг, который указывает, должен ли запрос быть заблокирован или нет (некоторый более общий пример можно найти здесь):
geo $restricted {
123.123.123.123 0;
default 1;
}
map $uri $deny {
/example.php 0;
default $restricted;
}
Теперь, чтобы принять решение, должен ли запрос быть заблокирован, вы можете использовать следующий блок if
:
if ($deny) { return 403; }
Этот блок может быть размещен либо на уровне server
, либо на уровне обработчика PHP. Однако размещение его на уровне server
будет более оптимальным, так как оно будет выполнено на более раннем этапе NGX_HTTP_SERVER_REWRITE_PHASE
, вместо NGX_HTTP_REWRITE_PHASE
.
Если по какой-то причине вы захотите добавить базовую аутентификацию вместо фильтрации по IP, это можно сделать, используя следующий трюк:
geo $restricted {
123.123.123.123 off;
default "Protected area";
}
map $uri $realm {
/example.php off;
default $restricted;
}
location ~ \.php$ {
auth_basic $realm;
auth_basic_user_file /path/to/.htpasswd;
...
}
.
Ответ или решение
Теория
При конфигурировании Nginx для управления различными специфическими запросами к вашему серверу, важно понимать, как Nginx обрабатывает директивы location
. В Nginx, порядок очередности и специфичность правил играет ключевую роль. Запросы сопоставляются с location
в порядке строгой иерархии, начиная с более специфичных и заканчивая более общими совпадениями.
Например, location
с регулярным выражением (location ~ \.php$
) будет обрабатывать все файлы с расширением .php
. Если есть специфичные требования для пары запросов, для которых нужен специальный доступ, можно использовать более конкретные совпадения, такие как location = /example.php
. Важно также помнить, что внутри одного location
блока можно управлять процессом с использованием переменных, которые определяют, как должны обработаться запросы.
Пример
Предположим, требуется настроить сервер так, чтобы доступ был разрешен только к одному конкретному файлу example.php
, но при этом остались активными все остальные существующие настройки для других PHP-файлов. Описанная вами проблема заключается в том, что более общие настройки location ~ \.php$
перезаписываются, когда вы добавляете новые более специфичные location
блоки, что приводит к тому, что PHP-код возвращается в открытом виде (в виде текста), вместо того чтобы обрабатываться FastCGI как должно.
Применение
Ваша задача может быть решена посредством использования более специфичных директив location
, а также map
и geo
блоков для динамического управления доступом.
-
Использование более специфичного
location
блока:
Для определения специфичной логики обработкиexample.php
, вам стоит использовать точное совпадение:location = /example.php { include snippets/fastcgi-php.conf; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass unix:/run/php/php7.4-fpm.sock; allow all; }
Это гарантирует, что
example.php
будет обрабатываться как PHP скрипт и доступ будет открыт для всех. -
Управление доступом через IP-адреса:
Если необходимо ограничение на доступ к другим файлам через IP-адреса, можно использоватьgeo
иmap
для более гибкого управления:geo $restricted { 123.123.123.123 0; default 1; } map $uri $deny { /example.php 0; default $restricted; } # использование if на серверном уровне server { # другие серверные конфигурации if ($deny) { return 403; } }
В этом примере мы заранее определяем, к каким файлам может быть открыт доступ, а к каким доступ будет ограничен, исходя из IP-адреса.
-
Базовая аутентификация:
Если нужно реализовать базовую аутентификацию на основе URI, можно реализовать это следующим образом:geo $restricted { 123.123.123.123 off; default "Protected Area"; } map $uri $realm { /example.php off; default $restricted; } location ~ \.php$ { auth_basic $realm; auth_basic_user_file /path/to/.htpasswd; }
Этот метод позволяет установить уникальные параметры аутентификации для различных файловых путей.
Заключение
Таким образом, используя комбинацию точных location
директив и механизмов geo
и map
, можно добиться более гибкого и надежного решения задачи, избегая избыточности и нежелательного перекрытия настроек. Для гибкой настройки доступа это подход обеспечит полноценную конфигурацию и помощи в соблюдении всех требований безопасности и производительности.