Re: limit_except и proxy_pass

Hennadii Makhomed gmm на csdoc.com
Вс Сен 15 14:12:40 UTC 2024


On 15.09.2024 12:25, Slawa Olhovchenkov wrote:

>>> А как лучше всео скрещивать limit_except и proxy_pass?
>>> Ну вот то есть надо запетить, например, POST на /.
>>> И как, если у нас дальше надо куда-то через proxy_pass прокинуть?
>>
>>       location = / {
>>           limit_except GET { deny all; }
>>           proxy_pass http://backend;
>>       }
>>
>>
>> В документации https://nginx.org/r/limit_except/ru явно указано,
>> директивы только из каких именно трех модулей nginx разрешено
>> использовать внутри блока директивы limit_except.
> 
> для GET / вообще-то гораздо больше директив и их не хочется
> дублировать тут, как и в блоке location /

тогда или использовать директиву include и общие части
конфига выносить в отдельные файлы, или написать свой
генератор конфига, который будет на основании своего
"высокоуровневого" описания конфига в виде своего DSL
(domain-specific language) генерировать конфиг nginx
в котором дублирование кода не будет проблемой, потому
что этот конфиг уже никто не будет потом править вручную.

хороший вариант для создания своих генераторов конфига
- это язык программирования Python плюс библиотека Jinja2
- для удобных шаблонов плюс библиотека Invoke - для того,
чтобы выполнить команду run('systemctl reload nginx')
после обновления конфга nginx.

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

например:

def atomic_write_text(content, filename):
     assert isinstance(content, str)
     assert isinstance(filename, Path)
     tmp_filename = filename.with_name(filename.name + '.tmp.' + 
secrets.token_hex() + '.tmp')
     tmp_filename.write_text(content)
     tmp_filename.rename(filename)

как я это делал в скрипте https://github.com/makhomed/nginx-cloudflare

или если совсем не интересно автоматизировать и программировать -
тогда можно сделать такой запрет с использованием директивы map:

map $request_method$uri $post_to_root {
     default 0;
     "POST/" 1;
}

       location / {
           if ( $post_to_root ) { return 403; }
           proxy_pass http://backend;
       }

здесь неудобство в том, что директиву map необходимо выносить
за пределы блока server { ... }  в контекст http, поэтому -
необходимо будет следить самостоятельно за тем, чтобы
в разных директивах map были разные имена паеременных,
потому что nginx самостоятельно эту ошибку не отслеживает
и такой конфиг считает полностью валидным:

map $request_method$uri $post_to_root {
     default 0;
     "POST/" 1;
}


map $request_method$request_uri $post_to_root {
     default 0;
     "POST/" 1;
}

# nginx -v ; nginx -t
nginx version: nginx/1.27.1
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

поэтому - могут быть очень интересные глюки, если внутри каталога
/etc/nginx/conf.d будут файлы *.conf в которых за пределами блоков
server { ... } будут находиться директивы map с одинаковыми именами
переменных - для читателя конфига это может выглядеть так, словно бы
каждая директива map локальна для своего конфигурационного файла,
но в действительности - все директивы map задаются только в глобальном
контексте http { ... } и если имена переменных одинаковы - они будут
просто перезатирать друг друга, вызывая неочевидные глюки в работе.

идеальным решением этой проблемы с директивой map (с моей точки зрения)
было бы разрешить задавать директиву map не только в контекстах
http и stream, но и в контексте server. тогда - каждый блок
server { ... } был бы полностью независимым от других
блоков server { ... } и не надо было бы читать весь
конфиг nginx, пытаясь понять, почему что-то не работает.

Именно такая проблема была у httpd Apache, и именно эту
проблему когда-то и хотел избежать Игорь Сысоев, создавая
nginx, чтобы конфигурация nginx была бы легко масштабируемой:

Масштабируемая конфигурация nginx / Игорь Сысоев
https://www.youtube.com/watch?v=jf3wIN-FwW4

003. Масштабируемая конфигурация nginx - Игорь Сысоев
https://www.youtube.com/watch?v=fcG-7k20oG8

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

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

или - просто стараться не использовать директиву map
без крайней на то необходимости, как и директиву if,
потому что, map - это по своей сути, почти то же самое,
что и директива if - только без известных глюков
в стиле "if is evil" - https://habr.com/ru/articles/74135/
https://mailman.nginx.org/pipermail/nginx-ru/2013-September/051898.html

нельзя сказать, что "map is evil", потому что директива map
работает именно так, как написано в документации на сайте,
и такм нет никаких "сорпризов" в этом плане.

просто map - это просто уже неудобно, когда на сервере
есть больше одного блока server { ... } и тогда, например,
надо придумывать различные "костыли", чтоыб не было проблем,
например, включать "имя" сервера в переменную второго аргумента:

map $request_method$uri $example_com_post_to_root {
     default 0;
     "POST/" 1;
}


map $request_method$request_uri $example_net_post_to_root {
     default 0;
     "POST/" 1;
}

директива map хороша еще и тем, что можно вообще любую условную
логику с ее помощью закодировать - and, or, not и любые их комбинации,
в том числе и включая скобки любой вложенности. Но то, что это можно
сделать не означает, что это надо делать, потому что разбирать
такие цепочки из map потом неудобно и это может отрицательно
сказываться  на производительности. Но такая возможность
есть, и на самом деле, директива map - очень мощная.

например:

     geo $remote_addr_block {
         default 1;
         10.0.0.0/8 0;
         172.16.0.0/12 0;
         2001:DB8:11:22::/64 0;
         2001:DB8:99:77::/64 0;
     }

     map $http_cf_ipcountry $country_block {
         default 1;
         RU 0;
         BY 0;
     }

     map $remote_addr_block$country_block $block {
         11 1;
         10 0;
         01 0;
         00 0;
     }

и потом уже, где нужна блокировка: if ( $block ) { return 403; }


-- 
Best regards,
  Gena


Подробная информация о списке рассылки nginx-ru