Nginx рдореЗрдВ LUA: рдереЛрдбрд╝рд╛ рдмреБрджреНрдзрд┐рдорд╛рди рдлрд╝рд╛рдпрд░рд╡реЙрд▓


рдпрд╣ рдкреЛрд╕реНрдЯ рдирдЧрдиреЗрдХреНрд╕ рдореЗрдВ рд▓реБрдЖ рдХреЗ рдЙрдкрдпреЛрдЧ рдХреА рдирд┐рд░рдВрддрд░рддрд╛ рд╣реИ ред

рдЙрдиреНрд╣реЛрдВрдиреЗ рд╕реНрдореГрддрд┐ рдореЗрдВ рдХреИрд╢рд┐рдВрдЧ рдкрд░ рдЪрд░реНрдЪрд╛ рдХреА, рдФрд░ рдпрд╣рд╛рдБ рдиреБрдЖрдиреЗрдХреНрд╕-рдмреИрд▓реЗрдиреНрд╕рд░ рдкрд░ рдПрдХ рдкреНрд░рдХрд╛рд░ рдХреЗ рдлрд╝рд╛рдпрд░рд╡реЙрд▓ рдХреЗ рд░реВрдк рдореЗрдВ рдЖрдиреЗ рд╡рд╛рд▓реЗ рдЕрдиреБрд░реЛрдзреЛрдВ рдХреЛ рдлрд╝рд┐рд▓реНрдЯрд░ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд▓реБрдЖ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЬрд╛рдПрдЧрд╛ред 2GIS рдореЗрдВ рднреА рдХреБрдЫ рдРрд╕рд╛ рд╣реА рдерд╛ ред рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдЕрдкрдиреА рдмрд╛рдЗрдХ рд╣реИ :) рдЬрд┐рд╕рдореЗрдВ рд╣рдо рдЧрддрд┐рд╢реАрд▓рддрд╛ рдФрд░ рд╕реНрдЯреИрдЯрд┐рдХреНрд╕ рд╕рд╛рдЭрд╛ рдХрд░рддреЗ рд╣реИрдВ, рд╣рдо NAT рдФрд░ рд╕рдлреЗрдж рд╕реВрдЪреА рдХреЛ рдзреНрдпрд╛рди рдореЗрдВ рд░рдЦрдиреЗ рдХреА рдХреЛрд╢рд┐рд╢ рдХрд░рддреЗ рд╣реИрдВред рдФрд░, рдЬрд╝рд╛рд╣рд┐рд░ рд╣реИ, рдЖрдк рд╣рдореЗрд╢рд╛ рдХреБрдЫ рдФрд░ рд╡рд┐рд╢рд┐рд╖реНрдЯ рддрд░реНрдХ рдкреНрд░рд╛рдкреНрдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ рдЬреЛ рддреИрдпрд╛рд░ рдХрд┐рдП рдЧрдП рдореЙрдбреНрдпреВрд▓ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╕рдордп рдХрд╛рдо рдирд╣реАрдВ рдХрд░реЗрдВрдЧреЗред
рдпрд╣ рдпреЛрдЬрдирд╛ рдЕрдм рдЪреБрдкрдЪрд╛рдк рдФрд░ рдЕрдирд╛рдпрд╛рд╕ (рд╡реНрдпрд╛рд╡рд╣рд╛рд░рд┐рдХ рд░реВрдк рд╕реЗ рд╕реАрдкреАрдпреВ рдХреЗ рдЙрдкрдпреЛрдЧ рдХреЛ рдкреНрд░рднрд╛рд╡рд┐рдд рдирд╣реАрдВ рдХрд░рддреА рд╣реИ), рдпрд╣ рд▓рдЧрднрдЧ 1200 рдЕрдиреБрд░реЛрдзреЛрдВ / рд╕реЗрдХрдВрдб рдХреА рдкреНрд░рдХреНрд░рд┐рдпрд╛ рдХрд░рддреА рд╣реИред рд╕реАрдорд╛ рдорд╛рдиреЛрдВ рдХрд╛ рдкрд░реАрдХреНрд╖рдг рдирд╣реАрдВ рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛ред рд╢рд╛рдпрдж, рд╕реМрднрд╛рдЧреНрдп рд╕реЗ :)


рдореИрдВ рд░рд╕реАрдж рдкрд░ рддреБрд░рдВрдд рдЖрдиреЗ рд╡рд╛рд▓реЗ рд╕рднреА рдЕрдиреБрд░реЛрдзреЛрдВ рдХреЛ рд╕рдВрд╕рд╛рдзрд┐рдд рдХрд░рдирд╛ рдЪрд╛рд╣рддрд╛ рд╣реВрдВ, рдФрд░ рдПрдХреНрд╕реЗрд╕_рд▓реЙрдЧ рдореЗрдВ рдПрдХ рдкрдВрдХреНрддрд┐ рдХреЗ рддрдереНрдп рдкрд░ рдирд╣реАрдВ (рдЬреЛ рдЕрднреА рднреА рдЙрд╕реА рд╕реНрдЯреИрдЯрд┐рдХреНрд╕ рдХреЗ рд▓рд┐рдП рдмрдВрдж рдорд╛рдирд╛ рдЬрд╛рддрд╛ рд╣реИ)ред рдХреЛрдИ рд╕рд╡рд╛рд▓ рдирд╣реАрдВ, рд╣рдо рдкреВрд░реЗ http рдХреЗ рд▓рд┐рдП рд╡рд┐рд╢реНрд╡ рд╕реНрддрд░ рдкрд░ рд╣реИрдВрдбрд▓рд░ рдХреЛ рд▓рдЯрдХрд╛рддреЗ рд╣реИрдВ:
http { include lua/req.conf; } #  lua/req.conf #      ( ,      LRU ) lua_shared_dict req_limit 1024m; #      (   ,    ) lua_shared_dict ban_list 128m; #  .   ,     geo $lua_req_whitelist { default 0; 12.34.56.78/24 1; } #  init_by_lua ' --      lua_req_priv_key = "secretpassphrase" --    lua_req_cookie_name = "reqcookiename" --      lua_req_ban_log = "/path/to/log/file" --     ( ) --     lua_req_d_one = 42 --    URI lua_req_d_mul = 84 --    URI lua_req_s_one = 100 --    URI lua_req_s_mul = 200 --    URI lua_req_d_ip = 200 --    IP lua_req_s_ip = 400 --    IP --   10  lua_req_ban_ttl = 600 --  math.randomseed(math.floor(ngx.now()*1000)) '; #   ,   access    access_by_lua_file /path/to/nginx/lua/req.lua; 

рдЕрдм nginx рдкрд░ рдЖрдиреЗ рд╡рд╛рд▓реЗ рд╕рднреА рдЕрдиреБрд░реЛрдз рд╣рдорд╛рд░реЗ req.lua рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдЬрд╛рдПрдВрдЧреЗред
рдЗрд╕реА рд╕рдордп, рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдЕрдиреБрд░реЛрдзреЛрдВ рдХреЗ рдЗрддрд┐рд╣рд╛рд╕ рдФрд░ рдкрд╣рд▓реЗ рд╕реЗ рдкреНрд░рддрд┐рдмрдВрдзрд┐рдд рдХреА рдЧрдИ рд╕реВрдЪреА (рдиреАрдЪреЗ рдЕрдзрд┐рдХ рд╡рд┐рд╡рд░рдг) рдХреА рд╕реВрдЪреА рдХреЛ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рджреЛ рдЯреЗрдмрд▓ req_limit рдФрд░ ban_list рд╣реИрдВред
рдФрд░ рдЖрдИрдкреА рдкрд░ рд╢реНрд╡реЗрддрд╕реВрдЪреА рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╕рд╛рдЗрдХрд┐рд▓ рдХреЗ рдмрдЬрд╛рдп, рдЬрд┐рдпреЛ рдирдЧрдиреЗрдХреНрд╕ рдореЙрдбреНрдпреВрд▓ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛, рдЬрд┐рд╕рдореЗрдВ lua_req_whitelist рдЪрд░ рдХрд╛ рдореВрд▓реНрдп рдиреАрдЪреЗ рд░рдЦрд╛ рдЧрдпрд╛ рд╣реИ, рдЬрд┐рд╕рдХрд╛ рдЙрдкрдпреЛрдЧ рдХреБрдЫ рдЗрд╕ рддрд░рд╣ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ:
 if ngx.var.lua_req_whitelist ~= '1' then -- IP    ,   end 


рд╕реНрдЯреЗрдЯрд┐рдХреНрд╕ / рдбрд╛рдпрдирд╛рдорд┐рдХреНрд╕ (рдбрд┐рд╕реНрдХ / рдмреИрдХрдПрдВрдб рд╕рд░реНрд╡рд░ рдкрд░ рдлрд╝рд╛рдЗрд▓ рдХреЗ рд▓рд┐рдП рдЕрдиреБрд░реЛрдз) рдХреА рдЬрд╛рдВрдЪ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдо рдЕрдиреБрд░реЛрдзрд┐рдд рдлрд╝рд╛рдЗрд▓ рдХреЗ рдирд╛рдо рдкрд░ рдПрдХ рд╕рд╛рдзрд╛рд░рдг рдЬрд╛рдВрдЪ рдХрд░рддреЗ рд╣реИрдВ (рдпрд╣рд╛рдВ рдЖрдк рдЕрдкрдиреЗ рд╡реНрдпрд╛рд╡рд╕рд╛рдпрд┐рдХ рддрд░реНрдХ рдХреЛ рд╕реНрд╡реАрдХрд╛рд░ рдХрд░рдХреЗ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХреЛ рдЬрдЯрд┐рд▓ рдмрдирд╛ рд╕рдХрддреЗ рд╣реИрдВ):
 function string.endswith(haystack, needle) return (needle == '') or (needle == string.sub(haystack, -string.len(needle))) end local function path_is_static(path) local exts = {'js', 'css', 'png', 'jpg', 'jpeg', 'gif', 'xml', 'ico', 'swf'} path = path:lower() for _,ext in ipairs(exts) do if path:endswith(ext) then return true end end return false end local uri_path = ngx.var.request_uri if ngx.var.is_args == '?' then uri_path = uri_path:gsub('^([^?]+)\\?.*$', '%1') end local is_static = path_is_static(uri_path) 


рдХрдо рд╕реЗ рдХрдо рдХреБрдЫ NAT рдкреНрд░реЛрд╕реЗрд╕рд┐рдВрдЧ рдХреЗ рд▓рд┐рдП, IP рдХреНрд▓рд╛рдЗрдВрдЯ рдХреЗ рдЕрд▓рд╛рд╡рд╛, рдЙрдирдХреЗ UserAgent рдХреЛ рднреА рдзреНрдпрд╛рди рдореЗрдВ рд░рдЦрд╛ рдЬрд╛рддрд╛ рд╣реИ рдФрд░ рдПрдХ рд╡рд┐рд╢реЗрд╖ рдХреБрдХреА рдЬреЛрдбрд╝реА рдЬрд╛рддреА рд╣реИред рдПрдХ рдкреВрд░реЗ рдХреЗ рд░реВрдк рдореЗрдВ рд╕рднреА рддреАрди рддрддреНрд╡ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдЖрдИрдбреА рдмрдирд╛рддреЗ рд╣реИрдВред рдпрджрд┐ рдХреБрдЫ рдЦрд▓рдирд╛рдпрдХ рд╕рд░реНрд╡рд░ рдХреЛ рдЦреЛрдЦрд▓рд╛ рдХрд░ рджреЗрддреЗ рд╣реИрдВ, рд╕рдВрдЪрд╛рд░рд┐рдд рдХреБрдХреА рдХреА рдЕрдирджреЗрдЦреА рдХрд░рддреЗ рд╣реИрдВ, рддреЛ рд╕рдмрд╕реЗ рдЦрд░рд╛рдм рд╕реНрдерд┐рддрд┐ рдореЗрдВ, рдЗрд╕рдХреЗ рдЖрдИрдкреА / рд╕рдмрдиреЗрдЯ рдкрд░ рдкреНрд░рддрд┐рдмрдВрдз рд▓рдЧрд╛ рджрд┐рдпрд╛ рдЬрд╛рдПрдЧрд╛ред рдЗрд╕реА рд╕рдордп, рдЗрд╕ рд╕рдмрдиреЗрдЯ рд╕реЗ рдЙрди рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЛ рдЬреЛ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рдХреБрдХреА рдкреНрд░рд╛рдкреНрдд рдХрд░ рдЪреБрдХреЗ рд╣реИрдВ, рдЪреБрдкрдЪрд╛рдк рдЖрдЧреЗ рдХрд╛рдо рдХрд░реЗрдВрдЧреЗ (рдЖрдИрдкреА рдкрд░ рдкреНрд░рддрд┐рдмрдВрдз рдХреЗ рдорд╛рдорд▓реЗ рдХреЛ рдЫреЛрдбрд╝рдХрд░)ред рд╕рдорд╛рдзрд╛рди рд╕рд╣реА рдирд╣реАрдВ рд╣реИ, рд▓реЗрдХрд┐рди рдЕрднреА рднреА рдЖрдзреЗ рджреЗрд╢ / рдореЛрдмрд╛рдЗрд▓ рдСрдкрд░реЗрдЯрд░ рдХреЛ рдПрдХ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЗ рд░реВрдк рдореЗрдВ рдЧрд┐рдирдиреЗ рд╕реЗ рдмреЗрд╣рддрд░ рд╣реИред
рдХреБрдХреАрдЬрд╝ рдХрд╛ рд╕реГрдЬрди рдФрд░ рд╕рддреНрдпрд╛рдкрди:
 local function gen_cookie_rand() return tostring(math.random(2147483647)) end local function gen_cookie(prefix, rnd) return ngx.encode_base64( --       IP    UserAgent,     ngx.sha1_bin(ngx.today() .. prefix .. lua_req_priv_key .. rnd) ) end local uri = ngx.var.request_uri --  URI local host = ngx.var.http_host --      (   nginx   ) local ip = ngx.var.remote_addr local user_agent = ngx.var.http_user_agent or '' if user_agent:len() > 0 then user_agent = ngx.encode_base64(ngx.sha1_bin(user_agent)) end local key_prefix = ip .. ':' .. user_agent --    local user_cookie = ngx.unescape_uri(ngx.var['cookie_' .. lua_req_cookie_name]) or '' local rnd = gen_cookie_rand() local p = user_cookie:find('_') if p then rnd = user_cookie:sub(p+1) user_cookie = user_cookie:sub(1, p-1) end local control_cookie = gen_cookie(key_prefix, rnd) if user_cookie ~= control_cookie then user_cookie = '' rnd = gen_cookie_rand() control_cookie = gen_cookie(key_prefix, rnd) end key_prefix = key_prefix .. ':' .. user_cookie ngx.header['Set-Cookie'] = string.format('%s=%s; path=/; expires=%s', lua_req_cookie_name, ngx.escape_uri(control_cookie .. '_' .. rnd), ngx.cookie_time(ngx.time()+24*3600) ) 

рдЕрдм key_prefix рдореЗрдВ рдХреНрд▓рд╛рдЗрдВрдЯ рдХрд╛ рдкрд╣рдЪрд╛рдирдХрд░реНрддрд╛ рд╣реЛрддрд╛ рд╣реИ рдЬрд┐рд╕рдХрд╛ рдЕрдиреБрд░реЛрдз рд╣рдо рдкреНрд░реЛрд╕реЗрд╕ рдХрд░ рд░рд╣реЗ рд╣реИрдВред рдпрджрд┐ рдЗрд╕ рдЧреНрд░рд╛рд╣рдХ рдкрд░ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рдкреНрд░рддрд┐рдмрдВрдз рд╣реИ, рддреЛ рдЖрдЧреЗ рдХреА рдкреНрд░рдХреНрд░рд┐рдпрд╛ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдирд╣реАрдВ рд╣реИ:
 local ban_key = key_prefix..':ban' if ban_list:get(ban_key) or ban_list:get(ip..':ban') then --          IP return ngx.exit(ngx.HTTP_FORBIDDEN) end 

рд╣рдореЗрдВ рдХреБрдВрдЬреА рдорд┐рд▓реА, рд╣рдордиреЗ рдкреНрд░рддрд┐рдмрдВрдз рдХреА рдЬрд╛рдВрдЪ рдХреА, рдЕрдм рд╣рдо рдпрд╣ рдЧрдгрдирд╛ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ рдХрд┐ рдХреНрдпрд╛ рдпрд╣ рдЕрдиреБрд░реЛрдз рд╕реАрдорд╛ рд╕реЗ рдЕрдзрд┐рдХ рдирд╣реАрдВ рд╣реИ:
 --   :   URI    URI local limits = { [false] = { [false] = lua_req_d_mul, --    URI [true] = lua_req_d_one, --    URI }, [true] = { [false] = lua_req_s_mul, --    URI [true] = lua_req_s_one, --    URI } } for _,one_path in ipairs({true, false}) do local limit = limits[is_static][one_path] local key = {key_prefix} --        if is_static then table.insert(key, 'S') else table.insert(key, 'D') end --          (  API   ) if one_path then table.insert(key, host..uri) end --    "12.34.56.78:useragentsha1base64:cookiesha1base64:S:site.com/path/to/file" key = table.concat(key, ':') local exhaust = check_limit_exhaust(key, limit, ban_ttl) if exhaust then return ngx.exit(ngx.HTTP_FORBIDDEN) end end 

рд╣рдо рдХрд╛рдЙрдВрдЯрд░реЛрдВ рдХреЗ 4 рд╕рдВрд╕реНрдХрд░рдгреЛрдВ рдХреА рдЬрд╛рдВрдЪ рдХрд░рддреЗ рд╣реИрдВ: рд╕реНрдерд┐рд░ / рдЧрддрд┐рд╢реАрд▓рддрд╛, рдПрдХ рдкрде / рдЕрд▓рдЧред рд╕реАрдзреА рдЬрд╛рдБрдЪ check_limit_exerate () рдореЗрдВ рдХреА рдЬрд╛рддреА рд╣реИ:
 local function check_limit_exhaust(key, limit, cnt_ttl) local key_ts = key..':ts' local cnt, _ = req_limit:incr(key, 1) --   ,     --        if cnt == nil then if req_limit:add(key, 1, cnt_ttl) then req_limit:set(key_ts, ngx.now(), cnt_ttl) end return false end --     (    ) if cnt <= limit then return false end --     (  ), --              local key_lock = key..':lock' local key_lock_ttl = 0.5 local ts local try_until = ngx.now() + key_lock_ttl local locked while true do locked = req_limit:add(key_lock, 1, key_lock_ttl) cnt = req_limit:get(key) ts = req_limit:get(key_ts) if locked or (try_until < ngx.now()) then break end ngx.sleep(0.01) end --            - , ,  . --      IP  blacklist --       if (not locked) and ((not cnt) or (not ts)) then return true, 'lock_failed' end --    ( )   local ts_diff = math.max(0.001, ngx.now() - ts) --      local cnt_norm = math.floor(cnt / ts_diff) --        if cnt_norm <= limit then --  ts  cnt (    set'  -        ) req_limit:set(key, cnt_norm, cnt_ttl) req_limit:set(key_ts, ngx.now() - 1, cnt_ttl) --  ;  blacklist  ;    if locked then req_limit:delete(key_lock) end return false end --  . ,  ,    req_limit:delete(key) req_limit:delete(key_ts) if locked then req_limit:delete(key_lock) end return true, cnt_norm end 

Lua_req_ban_ttl рд╕реЗрдХрдВрдб рдХреЗ рд▓рд┐рдП рд╕реАрдзреЗ рдкреНрд░рддрд┐рдмрдВрдз рдХреЗ рдЕрд▓рд╛рд╡рд╛, рдЖрдк рд╕реНрдерд╛рдпреА рднрдВрдбрд╛рд░рдг рдХреЛ рд▓рд╛рдЧреВ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ, рдФрд░ рдПрдХ рд╣реА рд╕рдордп рдореЗрдВ IP рджреНрд╡рд╛рд░рд╛ iptables / analogues рдкрд░ рд▓реЙрдЧрд┐рдВрдЧ рдФрд░ рдЕрдЧреНрд░реЗрд╖рдг рдХреЛ рдкреНрд░рддрд┐рдмрдВрдзрд┐рдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред рдпрд╣ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рд╡рд┐рд╖рдп рд╣реИред

рдпрд╣ рд╕рдм, рдЬрд╝рд╛рд╣рд┐рд░ рд╣реИ, рдХреЗрд╡рд▓ рдПрдХ рдЙрджрд╛рд╣рд░рдг рд╣реИ, рди рдХрд┐ рдПрдХ рдЪрд╛рдВрджреА рдХреА рдХреЙрдкреА-рдкреЗрд╕реНрдЯ рдмреБрд▓реЗрдЯред рдЗрд╕рдХреЗ рдЕрд▓рд╛рд╡рд╛, рд╕реАрдорд╛ рдХреА рджреА рдЧрдИ рд╕рдВрдЦреНрдпрд╛ рдЫрдд рд╕реЗ рдЗрдВрдЧрд┐рдд рдХреА рдЬрд╛рддреА рд╣реИред

рд╣реЗрдбрд░ рдореЗрдВ рдЫрд╡рд┐ рдпрд╣рд╛рдВ рд╕реЗ рд▓реА рдЧрдИ рд╣реИ ред

Source: https://habr.com/ru/post/In215235/


All Articles