Отключение аутентификации Nginx для заданных ip-диапазонов

Удобный инструмент для защиты разного рода тестовых/разработческих сайтов — аутентификация на уровне веб-сервера по паролю. Она закрывает сайты от ненамеренного индексирования поисковиками и от входа третьих лиц. В Nginx такая делается директивой auth_basic, которая дополняется файлом с пользователями и паролями. Кто-то из разработчиков заходит со статических адресов, поэтому в им аутентификация не нужна и её можно отключить. Делается это следующим образом:

satisfy  any;
allow XX.XX.XX.XX/ZZ;
allow YY.YY.YY.YY/WW;
allow ...
deny   all;

auth_basic            "restricted area";
auth_basic_user_file  htpasswd/example.com;

Таким образом пользователи со статических адресов могут заходить на сайт без ввода паролей. Таким же образом можно открыть сайт для интернет-тестеров разных возможностей сайта, в частности оптимизации (google pagespeed, gtmetrix), безопасности и т.п.

Поддомены по маске в CORS

В веб-проектах в последнее время всё активнее и активнее используется размещение разного контента по разным поддоменам. Например, типична ситуация, когда статические файлы размещают на отдельных поддоменах для работы с CDN или использование поддоменов для разных языковых версий сайта, или же для разных географических локаций. Основная часть ресурсов обычно едина для всех отдельных сайтов, как правило это картинки css/js и прочая статика. Чтобы кросс-запросы между поддоменами корректно принимались браузером в плане безопасности, необходимо настроить для CORS заголовки в ответе веб-сервера.

Домены с которых разрешено загружать контент указываются на ресурсе-источнике в заголовке Access-Control-Allow-Origin. В нём можно перечислить явно домены, с которых можно запрашивать контент, или разрешить со всех астериском ‘*’. К сожалению, разрешить поддомены по маске, например *.example.com, простым укаазанием этой маски в заголовке не удастся. Т.е. можно разрешить или всё или перечислять все домены. Что делать если этих доменов много или их неопределённый список и логичнее включить доступ по маске?

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

  set $cors "";
  if ($http_origin ~* (\.example\.com|\.example\.ru)) {
      set $cors "true";
  }

  if ($cors = "true") {
    add_header 'Access-Control-Allow-Origin' "$http_origin";
    add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, OPTIONS, DELETE';
    add_header 'Access-Control-Allow-Credentials' 'true';
    add_header 'Access-Control-Allow-Headers' 'User-Agent,Keep-Alive,Content-Type';
  }

  proxy_pass http://backend:8080/;

Его целесообразно вынести в отдельный snippet и включать в те location, с которых берёрется контент для остальных поддоменов. При этом мы не затрагиваем бэкэнд, что экономит производительность.

Реальный адрес через обратный прокси nginx

Nginx в последнее время повсеместно используется как обратный веб-прокси. Среди параметров, которые он передаёт бэкенду есть и реальный ip-адрес пользователя, который нередко используется в приложении. Кроме приложения тот же адрес отображается в логах и для целей отладки целесообразно в них писать также действительный адрес. Задача передачи этого параметра (и некоторых других — протокола, хоста) со стороны обратного прокси обычно решается добавлением заголовков:

    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

Файл с этими заголовками в виде сниппета есть даже в поставке nginx «из коробки», поэтому со его стороны всё решается предельно просто: инклюдом этого сниппета в конфигурации виртуальных хостов.

По-разному задача решается со стороны бэкендов. Так, в apache2 для этого подключается модуль remoteip и добавляется для него (стандартный) конфигурационный файл:

    RemoteIPHeader X-Real-IP
    RemoteIPInternalProxy 127.0.0.1

И после этого всё работает. В лог-файлах для корректного отображения адреса также стоит заменить «%h» -> «%a», что есть не лучшее, но рабочее решение.

В nginx-бэкенде чтобы не менять стандартный log_format в окружение server виртуального хоста добавляется

    set_real_ip_from  192.168.1.4;
    real_ip_header    X-Forwarded-For;

После этого в логах отображается правильный исходный клиентский адрес.

Странности nginx ssl

Параметры SSL в конфигурации nginx могут быть описаны как в глобальной http-секции, так и в настройках локальных серверов server. В контексте server по логике настройки должны применяться только на этот сервер.

Интересно, что поддержка TLS v1.2 включается только если её включить в server-секциях всех виртуальных хостов, а не только нужного.

nginx HTTP/2

Отлично! В энжинксе сделали полноценную поддержку http2, а если точнее, то заменили SPDY на HTTP/2. Поддержка есть в последних версиях (>=1.9.5), проверяем

$ nginx -V

и если в ответе есть --with-http_v2_module, то можно включать. Включается, как и все в nginx, очень просто — дописываем в конфигурации SSL:

listen 443 ssl http2;

или меняем spdy, если оно там стояло ранее.

PS: Если поддержка https на вашем сайте не настроена, то скорее настраиваем.

Confluence + nginx

Стандартная установка Atlassian Confluence вешает его по умолчанию на 8090 http-порт. Софтину в целях совместимости и безопасности целесообразно ставить в виртуалку и обращаться к ней по http, при этом сама виртуалка может находится в недоступной извне локальной сети. Поэтому, возникает задача проксировать траффик 8090 порта конфлюенса вовне. Ниже описывается, как это меньшими средствами сделать с помощью nginx.

В документации Atlassian рассматривается вариант проксирования траффика, в том числе энжинксом. Предлагаемый в документации способ предполагает правки в xml-конфиге самого Confluence, что есть не лучшее решение. Можно обойтись настройкой только nginx, без правок оригинального конфига конфлюенса, что обычно намного разумнее. Вот пример проксирования:

	location / {
		proxy_set_header X-Forwarded-Host $host;
		proxy_set_header X-Forwarded-Server $host;
		proxy_set_header X-Real-IP $remote_addr;
		proxy_set_header X-Forwarded-For $remote_addr;
		port_in_redirect off;
		proxy_pass http://confluence-vhost:8090;
		proxy_redirect http://confluence-vhost:8090/ /;
		proxy_connect_timeout 600;
		#proxy_redirect off;
	}

Аналогичным способом можно завернуть траффик в SSL и отдавать его через https, что существенно повышает безопасность работы.

Минимум настроек apache2 и nginx веб-серверов для повышения безопасности

Рассмотрим типовую схему веб-сервера: nginx используется в качестве фронтэнда (front-end), apache2 — бэкэнда (back-end). Установка пакетов из репозитариев дает стандартную конфигурацию, которая применима для сервера разработки и тестирования. Однако, стоковая конфигурация изначально подстроена под большой класс задач и требует доработки. В частности, в плане безопасности. Опишем минимум настроек, которые необходимо сделать, если настраиваемый сервер выходит в интернет для широкого использования.

apache2

Отключаем потенциально опасные и просто неиспользуемые модули, а также неиспользуемые подконфиги. Почти всегда можно (и нужно) отключить:

  • модуль autoindex, позволяющий просматривать содержимое директории при отсутствии индексного файла,
  • обработку cgi-bin, которая в deb-based системах лежит в конфиге serve-cgi-bin.conf и используется очень редко,
  • Другие неиспользуемые модули (список которых зависит от выкладываемого кода). Т.е. необходимо перешерстить директории mods-enabled, conf-enabled

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

Apache/2.4.10 (Debian) Server at ...

Этот параметр, как и некоторые другие настройки безопасности в debian-системах обычно находится в конфиге /etc/apache2/conf-available/security.conf. Переключаем в менее информативную и, соответственно, более безопасную версию:

#ServerTokens Minimal
#ServerTokens OS
#ServerTokens Full
ServerTokens Prod

Аналогичным образом стоит поступить с подписью сервера на «ошибочных» страницах

ServerSignature Off
#ServerSignature On

и трассировкой

TraceEnable Off

Последняя обычно уже отключена, но в этом необходимо убедиться.

Код некоторых выкладываемых сайтов может содержать скрытые файлы, вроде .gitignore, .svn и других, раскрывающих стуктуру кода. Их желательно отключить здесь же, в апаче, или в фронт-энде. Можно отключить сразу все скрытые файлы:

<DirectoryMatch "/\.">
	Require all denied
</DirectoryMatch>

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

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

Отключим доступ к скрытым файлам — в секции виртуалхоста добавляем:

RedirectMatch 404 /\..*$

Такие файлы нередко встречаются в проектах. В частности, системы контроля версий вроде git, svn. Эта метаинформация не должна попадать в прошакш, тем не менее следует превентивно защититься от такой возможности.

Если VirtualHost на сервере не один, то также не плохо будет раскомментировать секцию


   AllowOverride None
   Order Deny,Allow
   Deny from all

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


	AllowOverride All
	Order Deny,Allow
	Allow from all

Я не описываю настройку прав доступа (пользователь:группа) на уровне ОС и файловой системы и использование модуля mpm_itk с директивой AssignUserID. Это тоже должно быть сделано для серверов, на которых такое разделение требуется.

nginx

Отключим отображение скрытых файлов:

location ~ /\. {
	deny all;
}

nginx используется как frontend, и если на нем отдается контент по https, то повышаем его безопасность. Для начала отключаем SSLv3 и другие небезопасные протоколы. Если nginx свежий, то в нем это уже и так отключено, поэтому лучше просто обновиться.
Генерируем параметры обмена ключами Diffie-Hellman (которые могут генерироваться достаточно долго, запаситесь временем)

openssl dhparam -out /etc/nginx/ssl/dhparam.pem 4096

и затем подключаем их в энжинксе

ssl_dhparam /etc/nginx/ssl/dhparam.pem;

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

ssl_ciphers 'AES256+EECDH:AES256+EDH:!aNULL';

Также имеет смысл включить кеширование

ssl_session_cache shared:SSL:10m;

После всех настроек желательно проверить их корректность в каком-нибудь сервисе, например, этом.

PHP

Как и в случае апача скрываем версию

expose_php = Off

В зависимости от задач можно отключить вывод ошибок:

error_reporting = 0
display_errors = Off

 

Проверки

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

HEAD example.com

Также побродим по сайту, в том числе по страницам, которые генерируют 4xx и 5xx ошибки, проверим, что версии софта не раскрываются и тем более не раскрывается внутренняя структура сервиса.

Завершающие замечания

Если на сервере хостится несколько сайтов, то многие из приведённых выше настроек повышают безопасность лишь частично. Сделать хорошую защиту между виртуалхостами на одной машине принципиально будет достаточно сложно. Если такое требуется — лучше смотреть в сторону виртуализации уровня операционной системы, по образцу OpenVZ или lxc.

Со временем пост будет пополняться.

phpmyadmin за SSL-фронтендом nginx

Администрировать БД MySQL часто бывает удобно с помощью phpmyadmin. Естественно, пользоваться им необходимо безопасно, т.е. через https.

Простой вариант решения заключается в указании явного URL для пхпмайадмина в его конфиге:

$cfg['PmaAbsoluteUri'] = 'https://admin.example.com/pma';

Это полностью рабочий и безопасный для других сайтов вариант, но требует правки конфига phpmyadmin.

Альтернативный вариант — передавать порт, по которому идет соединение на nginx front-end от пользователя. Для этого в стандартных настройках прокси для энжикса меняем

proxy_set_header Host $host;

на

proxy_set_header Host $host:$server_port;

Однако, последний способ выдает некоторые глюки на другом коде, который исполняется на том же домене.