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

Как безопасно размещать несколько веб-сайтов с помощью Nginx и Php-fpm в Ubuntu 14.04


Введение

Хорошо известно, что стек LEMP (Linux, nginx, MySQL, PHP) обеспечивает непревзойденную скорость и надежность для работы PHP-сайтов. Однако другие преимущества этого популярного стека, такие как безопасность и изоляция, менее популярны.

В этой статье мы покажем вам преимущества безопасности и изоляции запуска сайтов на LEMP с разными пользователями Linux. Это будет сделано путем создания разных пулов php-fpm для каждого блока сервера nginx (сайта или виртуального хоста).

Предпосылки

Это руководство было протестировано на Ubuntu 14.04. Описанная установка и настройка будут аналогичны другим ОС или версиям ОС, но команды и расположение файлов конфигурации могут отличаться.

Также предполагается, что у вас уже настроены nginx и php-fpm. Если нет, выполните первый и третий шаги из статьи Как установить стек Linux, nginx, MySQL, PHP (LEMP) в Ubuntu 14.04.

Все команды в этом руководстве должны выполняться от имени пользователя без полномочий root. Если для команды требуется root-доступ, ей будет предшествовать sudo. Если у вас еще нет такой настройки, следуйте этому руководству: Initial Server Setup with Ubuntu 14.04.

Вам также потребуется полное доменное имя (fqdn), указывающее на дроплет для тестирования, в дополнение к localhost по умолчанию. Если у вас его нет под рукой, вы можете использовать site1.example.org. Отредактируйте файл /etc/hosts в своем любимом редакторе, таком как sudo vim /etc/hosts, и добавьте эту строку (замените site1.example.org с вашим полным доменным именем, если вы его используете):

...
127.0.0.1 site1.example.org
... 

Причины для дополнительной защиты LEMP

В обычной настройке LEMP есть только один пул php-fpm, который запускает все скрипты PHP для всех сайтов под одним и тем же пользователем. Это порождает две основные проблемы:

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

Вышеуказанные проблемы решаются в php-fpm путем создания отдельного пула, который работает под разными пользователями для каждого сайта.

Шаг 1. Настройка php-fpm.

Если вы выполнили все необходимые условия, то у вас уже должен быть один работающий веб-сайт в дроплете. Если вы не указали для него собственное полное доменное имя, вы должны иметь возможность получить к нему доступ по полному доменному имени localhost локально или по IP-адресу дроплета удаленно.

Теперь создадим второй сайт (site1.example.org) с собственным пулом php-fpm и пользователем Linux.

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

  1. sudo groupadd site1

Затем создайте пользовательский сайт1, принадлежащий к этой группе:

  1. sudo useradd -g site1 site1

Пока у нового пользователя site1 нет пароля, и он не может войти в Droplet. Если вам нужно предоставить кому-то прямой доступ к файлам этого сайта, то вам следует создать пароль для этого пользователя с помощью команды sudo passwd site1. С новой комбинацией пользователя и пароля пользователь может удаленно войти в систему по ssh или sftp. Дополнительные сведения и сведения о безопасности см. в статье Настройка дополнительного пользователя SSH/SFTP с ограниченным доступом к каталогу.

Затем создайте новый пул php-fpm для site1. Пул php-fpm по своей сути — это обычный процесс Linux, который запускается под определенным пользователем/группой и прослушивает сокет Linux. Он также может прослушивать комбинацию IP: порт, но для этого потребуется больше ресурсов дроплета, и это не предпочтительный метод.

По умолчанию в Ubuntu 14.04 каждый пул php-fpm должен быть настроен в файле внутри каталога /etc/php5/fpm/pool.d. Каждый файл с расширениями .conf в этом каталоге автоматически загружается в глобальную конфигурацию php-fpm.

Итак, для нашего нового сайта давайте создадим новый файл /etc/php5/fpm/pool.d/site1.conf. Вы можете сделать это с помощью своего любимого редактора следующим образом:

  1. sudo vim /etc/php5/fpm/pool.d/site1.conf

Этот файл должен содержать:

[site1]
user = site1
group = site1
listen = /var/run/php5-fpm-site1.sock
listen.owner = www-data
listen.group = www-data
php_admin_value[disable_functions] = exec,passthru,shell_exec,system
php_admin_flag[allow_url_fopen] = off
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
chdir = /

В приведенной выше конфигурации обратите внимание на следующие параметры:

  • [site1] — это имя пула. Для каждого пула необходимо указать уникальное имя.
  • user и group обозначают пользователя Linux и группу, под которой будет работать новый пул.
  • listen должен указывать на уникальное местоположение для каждого пула.
  • listen.owner и listen.group определяют владельца слушателя, то есть сокет нового пула php-fpm. Nginx должен уметь читать этот сокет. Поэтому сокет создается с пользователем и группой, под которыми работает nginx — www-data.
  • php_admin_value позволяет вам устанавливать собственные значения конфигурации php. Мы использовали его для отключения функций, которые могут запускать команды Linux — exec,passthru,shell_exec,system.
  • php_admin_flag похож на php_admin_value, но это просто переключатель для логических значений, то есть включение и выключение. Мы отключим функцию PHP allow_url_fopen, которая позволяет скрипту PHP открывать удаленные файлы и может быть использована злоумышленником.

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

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

Параметр chdir должен быть /, который является корнем файловой системы. Это не следует менять, если вы не используете другую важную опцию chroot.

Опция chroot не включена в приведенную выше конфигурацию намеренно. Это позволит вам запустить пул в закрытой среде, то есть запертой внутри каталога. Это отлично подходит для безопасности, потому что вы можете заблокировать пул внутри корневого веб-узла сайта. Однако эта максимальная безопасность вызовет серьезные проблемы для любого нормального PHP-приложения, которое использует системные двоичные файлы и приложения, такие как Imagemagick, которые будут недоступны. Если вам интересна эта тема, прочтите статью Как использовать Firejail для настройки установки WordPress в среде с тюрьмой.

После того, как вы закончите с приведенной выше конфигурацией, перезапустите php-fpm, чтобы новые настройки вступили в силу, с помощью команды:

  1. sudo service php5-fpm restart

Убедитесь, что новый пул работает правильно, выполнив поиск его процессов следующим образом:

  1. ps aux |grep site1

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

site1   14042  0.0  0.8 133620  4208 ?        S    14:45   0:00 php-fpm: pool site1
site1   14043  0.0  1.1 133760  5892 ?        S    14:45   0:00 php-fpm: pool site1

Красным цветом отмечен пользователь, под которым запущен процесс или пул php-fpm — site1.

Кроме того, мы отключим кеширование php по умолчанию, предоставляемое opcache. Это конкретное расширение кэширования может быть отличным для производительности, но не для безопасности, как мы увидим позже. Чтобы отключить его, отредактируйте файл /etc/php5/fpm/conf.d/05-opcache.ini с правами суперпользователя и добавьте строку:

opcache.enable=0

Затем снова перезапустите php-fpm (sudo service php5-fpm restart), чтобы настройка вступила в силу.

Шаг 2. Настройка nginx

После того, как мы настроим пул php-fpm для нашего сайта, мы настроим блок сервера в nginx. Для этого создайте новый файл /etc/nginx/sites-available/site1 в своем любимом редакторе, например:

  1. sudo vim /etc/nginx/sites-available/site1

Этот файл должен содержать:

server {
    listen 80;

    root /usr/share/nginx/sites/site1;
    index index.php index.html index.htm;

    server_name site1.example.org;

    location / {
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php5-fpm-site1.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

В приведенном выше коде показана общая конфигурация блока сервера в nginx. Обратите внимание на интересные выделенные части:

  • Корневой веб-сайт — /usr/share/nginx/sites/site1.
  • Имя сервера использует полное доменное имя site1.example.org, упомянутое в предварительных требованиях к этой статье.
  • fastcgi_pass указывает обработчик файлов php. Для каждого сайта вы должны использовать разные сокеты Unix, такие как /var/run/php5-fpm-site1.sock.

Создайте корневой веб-каталог:

  1. sudo mkdir /usr/share/nginx/sites
  2. sudo mkdir /usr/share/nginx/sites/site1

Чтобы включить вышеуказанный сайт, вы должны создать символическую ссылку на него в каталоге /etc/nginx/sites-enabled/. Это можно сделать с помощью команды:

  1. sudo ln -s /etc/nginx/sites-available/site1 /etc/nginx/sites-enabled/site1

Наконец, перезапустите nginx, чтобы изменения вступили в силу следующим образом:

  1. sudo service nginx restart

Шаг 3. Тестирование

Для запуска тестов мы будем использовать известную функцию phpinfo, которая предоставляет подробную информацию о среде php. Создайте новый файл с именем info.php, содержащий только строку <?php phpinfo(); ?>. Сначала вам понадобится этот файл на сайте nginx по умолчанию и его корневой веб-странице /usr/share/nginx/html/. Для этой цели вы можете использовать такой редактор:

  1. sudo vim /usr/share/nginx/html/info.php

После этого скопируйте файл в корневой каталог другого сайта (site1.example.org) следующим образом:

  1. sudo cp /usr/share/nginx/html/info.php /usr/share/nginx/sites/site1/

Теперь вы готовы запустить самый простой тест для проверки пользователя сервера. Вы можете выполнить тест с помощью браузера или из терминала Droplet и lynx, браузера командной строки. Если у вас еще нет lynx в дроплете, установите его с помощью команды sudo apt-get install lynx.

Сначала проверьте файл info.php на вашем сайте по умолчанию. Он должен быть доступен под локальным хостом следующим образом:

  1. lynx --dump http://localhost/info.php |grep 'SERVER\["USER"\]'

В приведенной выше команде мы фильтруем вывод с помощью grep только для переменной SERVER[USER], которая обозначает пользователя сервера. Для сайта по умолчанию выходные данные должны отображать пользователя www-data по умолчанию следующим образом:

_SERVER["USER"]                 www-data

Точно так же проверьте пользователя сервера на site1.example.org:

  1. lynx --dump http://site1.example.org/info.php |grep 'SERVER\["USER"\]'

На этот раз вы должны увидеть в выводе пользователя site1:

_SERVER["USER"]                 site1

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

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

В своем любимом редакторе создайте новый файл на основном сайте /usr/share/nginx/html/config.php. Этот файл должен содержать:

<?php
$pass = 'secret';
?>

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

Чтобы изменить права на 400, выполните команду:

  1. sudo chmod 400 /usr/share/nginx/html/config.php

Кроме того, наш основной сайт работает под пользователем www-data, который должен иметь возможность читать этот файл. Таким образом, измените право собственности на файл на этого пользователя следующим образом:

  1. sudo chown www-data:www-data /usr/share/nginx/html/config.php

В нашем примере мы будем использовать другой файл с именем /usr/share/nginx/html/readfile.php, чтобы прочитать секретную информацию и распечатать ее. Этот файл должен содержать следующий код:

<?php
include('/usr/share/nginx/html/config.php');
print($pass);
?>

Также измените владельца этого файла на www-data:

  1. sudo chown www-data:www-data /usr/share/nginx/html/readfile.php

Чтобы подтвердить правильность всех разрешений и прав собственности в корневом каталоге веб-сайта, выполните команду ls -l /usr/share/nginx/html/. Вы должны увидеть вывод, похожий на:

-r-------- 1 www-data www-data  27 Jun 19 05:35 config.php
-rw-r--r-- 1 www-data www-data  68 Jun 21 16:31 readfile.php

Теперь получите доступ к последнему файлу на вашем сайте по умолчанию с помощью команды lynx --dump http://localhost/readfile.php. В выводе должен быть напечатан secret, который показывает, что файл с конфиденциальной информацией доступен на том же сайте, что является ожидаемым правильным поведением.

Теперь скопируйте файл /usr/share/nginx/html/readfile.php на свой второй сайт site1.example.org следующим образом:

  1. sudo cp /usr/share/nginx/html/readfile.php /usr/share/nginx/sites/site1/

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

  1. sudo chown site1:site1 /usr/share/nginx/sites/site1/readfile.php

Чтобы подтвердить, что вы установили правильные разрешения и права собственности на файл, укажите содержимое корневого веб-сайта site1 с помощью команды ls -l /usr/share/nginx/sites/site1/. Тебе следует увидеть:

-rw-r--r-- 1 site1 site1  80 Jun 21 16:44 readfile.php

Затем попробуйте получить доступ к тому же файлу с сайта site1.example.com с помощью команды lynx --dump http://site1.example.org/readfile.php. Вы увидите только пустое пространство. Кроме того, если вы будете искать ошибки в журнале ошибок nginx с помощью команды grep sudo grep error /var/log/nginx/error.log, вы увидите:

2015/06/30 15:15:13 [error] 894#0: *242 FastCGI sent in stderr: "PHP message: PHP Warning:  include(/usr/share/nginx/html/config.php): failed to open stream: Permission denied in /usr/share/nginx/sites/site1/readfile.php on line 2

Примечание. Вы также увидите аналогичную ошибку в выводе lynx, если для параметра display_errors установлено значение On в файле конфигурации php-fpm /etc/php5/fpm/. php.ini.

Предупреждение показывает, что скрипт с сайта site1.example.org не может прочитать конфиденциальный файл config.php с основного сайта. Таким образом, сайты, работающие под разными пользователями, не могут поставить под угрозу безопасность друг друга.

Если вы вернетесь к концу этой статьи, посвященному настройке, вы увидите, что мы отключили кэширование по умолчанию, предоставляемое opcache. Если вам интересно, почему, попробуйте снова включить opcache, установив с привилегиями суперпользователя opcache.enable=1 в файле /etc/php5/fpm/conf.d/05-opcache. ini и перезапустите php5-fpm с помощью команды sudo service php5-fpm restart.

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

Заключение

С точки зрения безопасности важно использовать пулы php-fpm с разными пользователями для каждого сайта на одном и том же веб-сервере Nginx. Даже если это приведет к небольшому снижению производительности, преимущество такой изоляции может предотвратить серьезные нарушения безопасности.

Идея, описанная в этой статье, не уникальна и присутствует в других подобных технологиях изоляции PHP, таких как SuPHP. Однако производительность всех других альтернатив намного хуже, чем у php-fpm.