Расширяем возможности Nginx с помощью Lua

2013/11/08

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

Решать я эту задачу стал с помощью Nginx, и почти сразу был слеплен вариант тупой вариант “в лоб”:

:::nginx
set $valid_serial 'false';
if ($ssl_client_serial ~ '01') { set $valid_serial 'true'; }
if ($ssl_client_serial ~ '02') { set $valid_serial 'true'; }
...
if ($ssl_client_serial ~ 'NN') { set $valid_serial 'true'; }
if ($valid_serial ~ false) { return 403; break; }

Поскольку конфиг генерируется из Ansible, то шаблон вышел простой, и для каждого из серийников генерируется нужная строчка с if. Так оно и работала при тестовом списке в 3-5 серийников, но приближалось время, когда список должен был вырасти до 100+, а затем и до 1000+…

Поэтому была проведена изыскательская работа, которая привела меня к модулю Lua для Nginx. Выяснилось, что на нем реализуют весьма сложную логику при приличных нагрузках, о чем есть куча статей, даже на Хабре есть вполне годная статья.

Так что я быстро пересобрал свой пакет с Nginx с модулем Lua, и начал ваять, благо логика у моем случае простейшая:

  1. Загружаем при старте Nginx из файлика с серийниками их список в ngx.shared.DICT

    :::nginx lua_shared_dict serials 1m;

    init_by_lua ' local file = io.open("/etc/nginx/access.list", “r”) local serials = ngx.shared.serials if file then for line in file:lines() do local stripped_line = line:match( “^%s*(.-)%s*$” ) local succ, err, forcible = serials:safe_set( stripped_line, true) if not succ then ngx.log(ngx.ERR,“error populating serials list: " .. err) end end file:close() else ngx.log(ngx.ERR,“access.list file not found”) end ‘; …

  2. При запросе к защищенному контенту проверяем, есть ли такой серийник в списке.

    :::nginx location / { access_by_lua ' local serial = ngx.var.ssl_client_serial local value, flags = ngx.shared.serials:get(serial) if not value then ngx.log(ngx.WARN, “blocked client cert " .. serial) ngx.exit(ngx.HTTP_FORBIDDEN) end ‘; proxy_pass http://upstream; }

Весь Lua-код выполняется в отдельных корутинах-песочницах, так что на работу в Nginx в целом не влияет. И что особенно хорошо, можно будет в дальнейшем список вынести в базу данных, к примеру.

В общем, я был и без того очень впечатлен возможностями Nginx, а с модулем Lua он еще круче.

Tags: Nginx Lua

Categories: IT Russian