良い一日。 この記事では、nginxとluaスクリプトを使用した簡単な認証について説明します。
プレックスと送信機能を備えたubuntuのホームサーバーを取得し、これを大きな世界にもたらしたドメインを取得したので、単一の認証ポイントを取得するのがいいと思いました。 特にnginxはすでにインストールされています(ngax-extrasでさえ、luaがあるので重要です)。
彼の考えを集めて、彼は要件を策定しました:
- 追加のソフトウェアをインストールする必要はありません
- 個別の認証ページ
- nginxのすべてのサービスのエンドツーエンド認証
- 少なくともバスティングに対する最小限の保護
nginxの基本認証を使用したバリアントは列挙に対する保護がないため機能しませんでした。nginxauth PAMを使用したバリアントはOSログイン/パスワードを使用した認証が原因で不信感を引き起こしました。 また、両方のオプションでは、個別のフォームによる認証は許可されません。
認証アルゴリズムは非常に簡単です。
さあ、始めましょう。
最初に、将来必要になるいくつかの関数を含むluaスクリプトを作成します。
/etc/nginx/lua/secure.lua
-- ip/32 User-Agent local ip_ua_max = 10 -- ip/32 local ip_4_max = 50 -- ip/16 local ip_3_max = 100 -- ip/8 local ip_2_max = 500 -- ip/0 local ip_1_max = 1000 counters = {} counters["ip_ua"] = {} counters["ip_4"] = {} counters["ip_3"] = {} counters["ip_2"] = {} counters["ip_1"] = {} -- (is_cnt=false) (is_cnt=true) function is_secure(ip, user_agent, is_cnt) local md5_ip_ua = ngx.md5(ip..user_agent) local md5_ip_4 = ngx.md5(ip) local md5_ip_3 = "" local md5_ip_2 = "" local md5_ip_1 = "" local cnt = 0 for i in string.gmatch(ip, "%d+") do cnt = cnt + 1 if cnt < 4 then md5_ip_3 = md5_ip_3.."."..i end if cnt < 3 then md5_ip_2 = md5_ip_2.."."..i end if cnt < 2 then md5_ip_1 = md5_ip_1.."."..i end end md5_ip_3 = ngx.md5(md5_ip_3) md5_ip_2 = ngx.md5(md5_ip_2) md5_ip_1 = ngx.md5(md5_ip_1) if is_cnt then -- counters["ip_ua"][md5_ip_ua] = (counters["ip_ua"][md5_ip_ua] or 0) + 1 counters["ip_4"][md5_ip_4] = (counters["ip_4"][md5_ip_4] or 0) + 1 counters["ip_3"][md5_ip_3] = (counters["ip_3"][md5_ip_3] or 0) + 1 counters["ip_2"][md5_ip_2] = (counters["ip_2"][md5_ip_2] or 0) + 1 counters["ip_1"][md5_ip_1] = (counters["ip_1"][md5_ip_1] or 0) + 1 -- log_file = io.open("/var/log/nginx/access.log", "a") log_file:write(ip.." "..(counters["ip_ua"][md5_ip_ua] or 0).." "..(counters["ip_4"][md5_ip_4] or 0).." "..(counters["ip_3"][md5_ip_3] or 0).." "..(counters["ip_2"][md5_ip_2] or 0).." "..(counters["ip_1"][md5_ip_1] or 0).." "..user_agent.."\n") log_file:close() else -- if (counters["ip_ua"][md5_ip_ua] or 0) > ip_ua_max or (counters["ip_4"][md5_ip_4] or 0) > ip_4_max or (counters["ip_3"][md5_ip_3] or 0) > ip_3_max or (counters["ip_2"][md5_ip_2] or 0) > ip_2_max or (counters["ip_1"][md5_ip_1] or 0) > ip_1_max then return false else return true end end end -- / -- , / ( ) function sing_in(log, pass) local auth_file = io.open("/etc/nginx/auth/pass","r") for line in io.lines("/etc/nginx/auth/pass") do if line == log..":"..ngx.md5(pass) then auth_file:close() return true end end auth_file:close() return false end -- secure local secure = ngx.shared.secure secure:set("sing_in", sing_in) secure:set("is_secure", is_secure)
このスクリプトの初期化をグローバルnginx構成に追加します。
/etc/nginx/nginx.conf
• • • http { • • • # lua_shared_dict secure 10m; # init_by_lua_file /etc/nginx/lua/secure.lua; • • • include /etc/nginx/conf.d/*.conf; }
Cookieを確認するluaスクリプトを作成します(ステップ2、2.1、3):
/etc/nginx/lua/access.lua
-- local req_url_err = "https://auth.somedomain.ru" -- User-Agent local ua = ngx.req.get_headers()["User-Agent"] -- cookie local auth_str = ngx.var.cookie_sv_auth local auth_token = "" local life_time = "" if auth_str ~= nil and auth_str:find("|") ~= nil then local divider = auth_str:find("|") auth_token = auth_str:sub(0,divider-1) life_time = auth_str:sub(divider+1) -- 2. if auth_token == ngx.encode_base64(ngx.hmac_sha1("______-_32-_",ua.."|"..life_time)) and tonumber(life_time) >= ngx.time() then -- return end end -- -- 2.1. coockie url ngx.header["Set-Cookie"] = "sv_req_url="..ngx.req.get_headers()["Host"].."; path=/; domain=.somedomain.ru; Expires="..ngx.cookie_time(ngx.time()+60*60).."; Secure; HttpOnly" -- return ngx.redirect(req_url_err)
このスクリプトを使用して、内部サービスの構成にチェックを追加します。
/etc/nginx/conf.d/plex.conf
server { listen 443 ssl; server_name plex.somedomain.ru; access_by_lua_file /etc/nginx/lua/access.lua; location / { proxy_pass http://localhost:32400; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } ssl on; • • • }
認証ページを作成します。
/var/www/html/auth.html
<!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <title>somedomain</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> body{ height: 100%; background-color: rgb(64, 64, 64); text-align:center; align:center; vertical-align: middle; } form { display: inline-block; text-align: center; vertical-align: middle; position:absolute; top:50%; right:0; left:0; } input{ color: rgb(0, 255, 0); text-align: center; border: 2px solid; border-color: rgb(0, 255, 0); background-color: rgb(64, 64, 64); } ::-webkit-input-placeholder{ color:rgb(0, 255, 0); text-align: center; } ::-moz-placeholder{ color:rgb(0, 255, 0); text-align: center; } :-moz-placeholder{ color:rgb(0, 255, 0); text-align: center; } :-ms-input-placeholder{ color:rgb(0, 255, 0); text-align: center; } br{ display: block; margin: 7px 0; line-height: 7px; content: " "; } </style> </head> <body> <form method="post"> <input type="text" name="login" placeholder="login" autocomplete="off"> <br> <input type="password" name="password" placeholder="password" autocomplete="off"> <br> <input type="submit" value="sign in"> </form> </body> </html>
そして、そのためのnginx設定を追加します:
/etc/nginx/conf.d/auth.conf
server { listen 443 ssl; server_name auth.somedomain.ru; access_by_lua_file /etc/nginx/lua/auth_access.lua; location / { default_type 'text/html'; root /var/www/html/; index auth.html; if ($request_method = POST ) { content_by_lua_file /etc/nginx/lua/auth.lua; } } ssl on; • • • }
この構成では、「auth_access.lua」を使用して認証の試行回数を確認します(ステップ4、4.2)
/etc/nginx/lua/auth_access.lua
-- secure local secure = ngx.shared.secure is_secure = secure:get("is_secure") -- ip local ip = ngx.var.remote_addr -- User-Agent local ua = ngx.req.get_headers()["User-Agent"] -- 4. if is_secure(ip,ua,false) then -- , ngx.header["Set-Cookie"] = {"sv_auth=; path=/; domain=.somedomain.ru; Expires="..ngx.cookie_time(ngx.time()-60).."; Secure; HttpOnly"} return end -- 4.2. , HTTP 403 ngx.exit(ngx.HTTP_FORBIDDEN)
「auth.lua」を使用してログイン/パスワードを確認します(ステップ5、5.1、2.2)
/etc/nginx/lua/auth.lua
-- secure local secure = ngx.shared.secure sing_in = secure:get("sing_in") is_secure = secure:get("is_secure") -- ip local ip = ngx.var.remote_addr -- User-Agent local ua = ngx.req.get_headers()["User-Agent"] -- local req_url_err = "https://auth.somedomain.ru" -- cookie , cookie local req_url = "https://"..(ngx.var.cookie_sv_req_url or "somedomain.ru") -- POST- ngx.req.read_body() local args, err = ngx.req.get_post_args() if args then -- 4.1. POST- local log local pass for key, val in pairs(args) do if key == "login" then log = val elseif key == "password" then pass = val end end -- , if log ~= nil and pass ~= nil then -- 5. if sing_in(log, pass) then -- -- () local life_time = ngx.time()+86400 -- local auth_str = ngx.encode_base64(ngx.hmac_sha1("______-_32-_",ua.."|"..life_time)).."|"..life_time -- 5.1. cookie url ngx.header["Set-Cookie"] = {"sv_auth="..auth_str.."; path=/; domain=.somedomain.ru; Expires="..ngx.cookie_time(ngx.time()+60*60*24).."; Secure; HttpOnly","sv_req_url="..ngx.req.get_headers()["Host"].."; path=/; domain=.somedomain.ru; Expires="..ngx.cookie_time(ngx.time()-60).."; Secure; HttpOnly"} -- 2.2. return ngx.redirect(req_url) end -- 5.2. / , is_secure(ip,ua,true) end end -- 3. , ngx.redirect(req_url_err)
次に、ユーザー名とパスワードを使用してファイルを作成します。
md5="`echo -n "PASSWORD" | md5sum`";echo -e "LOGIN"":`sed 's/^\([^ ]\+\) .*$/\1/' <<< "$md5"`" > ~/pass; sudo mv ~/pass /etc/nginx/auth/pass; sudo chown nginx:nginx /etc/nginx/auth/pass
「LOGIN」の代わりにログインを、「PASSWORD」の代わりにパスワードに置き換えます。
それだけです、認証が実装されています。
サービスを追加するときは、設定で「access.lua」のチェックを指定するだけで十分です。
access_by_lua_file /etc/nginx/lua/access.lua;
ご清聴ありがとうございました。
UPD 03/26/2018 ( YourChiefに感謝):
-不要なnvl関数を削除しました
-md5は、トークンの生成時にHMACに置き換えられます
-彼の人生の時間がトークンに追加されます
-md5およびHMACは組み込みのnginxを使用します