Расширяем возможности 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
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
';
...
  1. При запросе к защищенному контенту проверяем, есть ли такой серийник в списке.
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