Встала некоторое время назад передо мной задача - фильтровать по полю 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, и начал ваять, благо логика у моем случае простейшая:
- Загружаем при старте 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
';
...
- При запросе к защищенному контенту проверяем, есть ли такой серийник в списке.
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 он еще круче.