基本思路
Grafana开auth.proxy, Nginx+Lua控制cookie及X-WEBAUTH-USER的值 (Enable auth.proxy in Grafana, Then use Nginx+Lua to control cookie and value of X-WEBAUTH-USER).
架构图
环境配置
- Download and install LuaJIT,
- Install Tengine 2.2.1 with configure option
--with-http_lua_module --with-ld-opt="-Wl,-rpath,$LUAJIT_LIB"
- Download and compile lua-cjson, then copy cjson.so to lib dir. Such as
/usr/lib64/lua/5.1/
Nginx配置
Main Configuration
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
upstream grafana { server 127.0.0.1:3000; } server { listen 10.0.0.11:80; server_name grafana.xxx.cn; access_log logs/monitor.proxy.log main; location @client{ proxy_pass http://grafana; } location ^~ /proxy/ { internal; #指定规则为internal规则,防止外部请求命中此规则 rewrite '^/proxy/(http?)/([^/]+)/(\d+)/(.*)' /$4 break; proxy_pass $1://$2:$3; } location / { access_by_lua_file 'conf.d/grafana_sso/sso.lua'; } } |
Access With Out SSO
某种情况下可能不想挑转到sso去认证,比如某个公共的本地账号。这时可以通过Nginx配置绕过SSO认证(Sometimes we may want to login with out SSO, such as a local grafana account for public use. For this situation, we can use Nginx to bypass SSO)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
server { listen 10.0.0.11:3030; server_name grafana.xxx.cn; access_log logs/grafana-nosso.log main; location / { proxy_pass http://127.0.0.1:3000; } location @client { proxy_pass http://127.0.0.1:3000; } # api访问需要被过滤,否则在开启auth.proxy的情况下任意用户都可以操作API # filter api access. if not , everyone can access api when auth.proxy enabled location /api/ { access_by_lua_file 'conf.d/grafana_sso/sso.lua'; } } |
Full Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
local secretkey = 'your secretkey' local timeStr = os.date("%x", os.time()) function getSSOUser() local sso = 'https://sso/login.php' local mysite = 'http://monitor' local login_url = sso .. '?next=' .. mysite local request_method = ngx.var.request_method if "GET" == request_method then args = ngx.req.get_uri_args() elseif "POST" == request_method then ngx.exit(405) end -- get m_tk local m_tk = args['m_tk'] if m_tk == nil then ngx.redirect(login_url) end -- check user token local time = os.time() local check_url = 'http/sso/8080/check.php' -- format used by ngx.location.capture && proxy_pass(a httpclient simulation) local key = 'your local key' local cjson = require("cjson") -- Sign algorithm. (base on your SSO) local sign = ngx.md5(xxx) local checkSign = xxx --[[ location ^~ /proxy/ { internal; //指定规则为internal规则,防止外部请求命中此规则 rewrite ^/proxy/(http?)/([^/]+)/(\d+)/(.*) /$4 break; proxy_pass $1://$2:$3; } --]] checkUrl = '/proxy/' .. check_url .. '?' .. checkSign -- simulate a httpclient use ngx.location.capture && proxy_pass local res = ngx.location.capture(checkUrl, { method = ngx.HTTP_GET, }) if res.body then obj = cjson.decode(res.body) code = obj['respond']['code'] --ngx.say(code) if code ~= '0' then --ngx.say(res.body) ngx.exit(401) end objects = obj['results'] arr = ngx.decode_args(objects) local user = arr['u'] return user else ngx.redirect(login_url) end end function getToken(user) local ctoken = ngx.md5("user:" .. user .. "&key:" .. secretkey .. timeStr) return ctoken end function checkToken() local user = ngx.var.cookie_x_webauth_user local token = ngx.var.cookie_x_webauth_token local ctoken = getToken(user) if ctoken == token then return true else return false end end function isCookieExist() local user = ngx.var.cookie_x_webauth_user local token = ngx.var.cookie_x_webauth_token if user ~= nil and token ~= nil then return user else return false end end function Login(user) --[[ # grafana 配置文件中修改配置项 http_addr 绑定 127.0.0.1,拒绝外部访问,否则任何人都可以通过auth.proxy登录任意账号 # http_addr must bind to 127.0.0.1, otherwise everybody can login as any account(include admin) with auth.proxy # The ip address to bind to, empty will bind to all interfaces http_addr = 127.0.0.1 nginx 添加如下配置 (add Nginx Configuration) location @client { proxy_pass http://127.0.0.1:3000; } --]] ngx.req.set_header("X-WEBAUTH-USER", user) ngx.exec("@client") end -- 管理员用户 function isAdmin(user) if user == "xxx" then return "admin" else return user end end function doLogin() local user = getSSOUser() local user = isAdmin(user) local x_token = getToken(user) ngx.header['Set-Cookie'] = {'x_webauth_user =' .. user , 'x_webauth_token = ' .. x_token} Login(user) end -- 退出 function Logout() if ngx.re.match(ngx.var.request_uri, "logout") then ngx.header['Set-Cookie'] = {'x_webauth_user = '} end end -- api do not use auth.proxy function api() if ngx.re.match(ngx.var.request_uri, "/api/") then ngx.req.clear_header("X-WEBAUTH-USER") ngx.exec("@client") end end api() Logout() local x_user = isCookieExist() if not x_user then doLogin() else if checkToken() then Login(x_user) else doLogin() --ngx.exit(401) end end |
参考资料
1 2 3 4 |
1. https://www.stavros.io/posts/writing-an-nginx-authentication-module-in-lua/ 2. https://blog.raintank.io/authproxy-howto-use-external-authentication-handlers-with-grafana/ 3. http://blog.csdn.net/langeldep/article/details/8629906 4. http://www.cnblogs.com/kyrios/p/ngx_lua-httpclient-using-capture-and-proxy.html |
local secretkey = 'your secretkey'
local key = 'your local key'
这两个变量写的是什么?
随机字符串
使用cas sso怎么单点呢
不了解cas
单点登录认证成功后,仍旧跳到observer login界面,收到去掉网址中的/login就显示已经登录了,这是为什么呢?是grafana配置有什么设置吗
不太明白,observer login界面是什么?
为了使用api,需要在location /前加上 location /api/ 的配置,跳过sso认证location /api/ { proxy_pass http://127.0.0.1:3000; }
这种做法有安全漏洞,正文已修复。在sso.lua里用api()函数来处理
重定向至登录前页面:1. sso的next参数改为 http://grafana/?uri={ngx.var.uri} 来记录跳转到sso之前的请求uri2. 认证过了之后,先设置cookie,然后 ngx.redirect('http://grafana' .. args['uri']),最后调用 Login(user)函数
zabbix 使用http认证,也可以实现类似的 sso 登录。 在header里发送 -H "Authorization:Basic QWxxxxxx"