Last active
October 28, 2018 13:04
-
-
Save agix/ea1d9f4da35ebf51788e2dcfe4ec75c0 to your computer and use it in GitHub Desktop.
Nginx + redis + lua + authy script to allow access by IP with one touch
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- apt-get install nginx-extras # to get nginx with lua support | |
-- apt-get install luarocks | |
-- apt-get install lua-nginx-redis | |
-- luarocks install lua-requests | |
-- In your nginx config file add two lua dict outside server definition | |
-- | |
-- lua_shared_dict ip_whitelist 1m; | |
-- lua_shared_dict ip_asklist 1m; | |
-- server { | |
-- | |
-- Then in the location you want to protect | |
-- | |
-- location / { | |
-- access_by_lua_file /etc/nginx/lua-scripts/knocky.lua; | |
-- proxy_pass http://localhost:5984; | |
-- } | |
-- | |
local requests = require("requests") | |
local redis = require "nginx.redis"; | |
local authy = "AAAAAAAAAAAAAAAAAAAAAAAAA" | |
local user = "XXXXXXX" | |
local service_name = "ZZZZZZZ" | |
local allowed_time = 60*15 | |
local ipinfo = "BBBBBBBBBBBBBB" | |
local redis_host = "127.0.0.1" | |
local redis_port = 6379 | |
local redis_db = 10 | |
local redis_connection_timeout = 100 | |
local red = redis:new(); | |
local cache_ttl = 1 | |
local ip = ngx.var.remote_addr | |
function askAuthy(approval_request) | |
local headers = {['X-Authy-API-Key'] = authy} | |
local raw_response = requests.get{"https://api.authy.com/onetouch/json/approval_requests/"..approval_request, headers = headers} | |
local response, error = raw_response.json() | |
if response.success then | |
if response.approval_request.status == "approved" then | |
red:setex("white_"..ip, allowed_time, true) | |
return true | |
else | |
ngx.log(ngx.ERR, approval_request.. " is "..response.approval_request.status) | |
end | |
else | |
ngx.log(ngx.ERR, "Error contacting authy : "..raw_response.text); | |
end | |
return false | |
end | |
function updateList(list_type) | |
local ip_list; | |
if list_type == "white" then | |
ip_list = ngx.shared.ip_whitelist; | |
else | |
ip_list = ngx.shared.ip_asklist; | |
end | |
local last_update_time = ip_list:get("last_update_time"); | |
if last_update_time == nil or last_update_time < ( ngx.now() - cache_ttl ) then | |
local name_len = string.len(list_type) + 2; | |
local new_ip_list, err = red:keys(list_type .. "_*"); | |
if err then | |
ngx.log(ngx.ERR, "Redis read error while retrieving ip_list: " .. err); | |
else | |
ip_list:flush_all(); | |
for index, potential_ip in ipairs(new_ip_list) do | |
ip_list:set(string.sub(potential_ip, name_len), true); | |
end | |
ip_list:set("last_update_time", ngx.now()); | |
end | |
end | |
return ip_list | |
end | |
red:set_timeout(redis_connection_timeout); | |
local ok, err = red:connect(redis_host, redis_port); | |
if not ok then | |
ngx.log(ngx.ERR, "Redis connection error: " .. err); | |
return ngx.exit(ngx.HTTP_NOT_FOUND); | |
end | |
local ok, err = red:select(redis_db) | |
if not ok then | |
ngx.log(ngx.ERR, "Redis select error: " .. err); | |
red:quit() | |
return ngx.exit(ngx.HTTP_NOT_FOUND); | |
end | |
local ip_whitelist = updateList("white") | |
if not(ip_whitelist:get(ip)) then | |
local ip_asklist = updateList("ask") | |
if not(ip_asklist:get(ip)) then | |
local query_parameters = {token = ipinfo} | |
local raw_response_ipinfo = requests.get{"https://ipinfo.io/"..ip, params = query_parameters, headers = {['Accept'] = 'application/json'}} | |
ngx.log(ngx.ERR, raw_response_ipinfo.text) | |
local response_ipinfo, error = raw_response_ipinfo.json() | |
local data = {message = "Would you open "..service_name.." for "..ip.." ("..response_ipinfo.city..", "..response_ipinfo.region..", "..response_ipinfo.country..")", seconds_to_expire = allowed_time} | |
local headers = {['Content-Type'] = 'application/json', ['X-Authy-API-Key'] = authy} | |
local raw_response = requests.post{"https://api.authy.com/onetouch/json/users/"..user.."/approval_requests", data = data, headers = headers} | |
local response, error = raw_response.json() | |
if response.success then | |
red:setex("ask_"..ip, allowed_time, response.approval_request.uuid) | |
local attempt = 0 | |
local max_attempt = 10 | |
while attempt < max_attempt do | |
os.execute("sleep 1") | |
attempt = attempt + 1 | |
if askAuthy(response.approval_request.uuid) then | |
red:quit() | |
return | |
end | |
end | |
else | |
ngx.log(ngx.ERR, "Error contacting authy : "..raw_response.text); | |
end | |
else | |
local approval_request, err = red:get("ask_"..ip) | |
if err then | |
ngx.log(ngx.ERR, "Redis read error while retrieving asked_ip: " .. err); | |
else | |
if askAuthy(approval_request) then | |
red:quit() | |
return | |
end | |
end | |
end | |
red:quit() | |
return ngx.exit(ngx.HTTP_NOT_FOUND); | |
else | |
red:quit() | |
return | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment