基本思路
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
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)
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
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. 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"