Merge pull request #145 from YunoHost/logging-reloaded

Logging reloaded
This commit is contained in:
Alexandre Aubin 2019-10-15 23:40:26 +02:00 committed by GitHub
commit 5ba7050fd4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 159 additions and 18 deletions

View file

@ -25,6 +25,9 @@ local hlp = require "helpers"
-- Import Perl regular expressions library
local rex = require "rex_pcre"
-- Load logging module
local logger = require("log")
-- Just a note for the client to know that he passed through the SSO
ngx.header["X-SSO-WAT"] = "You've just been SSOed"
@ -47,7 +50,7 @@ if ngx.var.host ~= conf["portal_domain"] and ngx.var.request_method == "GET" the
user = cache:get("CDA|"..cda_key)
if user then
hlp.set_auth_cookie(user, ngx.var.host)
ngx.log(ngx.NOTICE, "Cross-domain authentication: "..user.." connected on "..ngx.var.host)
logger.info("Cross-domain authentication: "..user.." connected on "..ngx.var.host)
cache:delete("CDA|"..cda_key)
end
@ -74,6 +77,7 @@ then
-- Force portal scheme
if ngx.var.scheme ~= conf["portal_scheme"] then
logger.debug("Redirecting to "..conf.portal_url.."Cross-domain authentication: "..user.." connected on "..ngx.var.host)
return hlp.redirect(conf.portal_url)
end
@ -88,6 +92,7 @@ then
-- Logout is also called via a `GET` method
-- TODO: change this ?
if uri_args.action and uri_args.action == 'logout' then
logger.debug("Logging out")
return hlp.logout()
-- If the `r` URI argument is set, it means that we want to
@ -100,7 +105,7 @@ then
-- pass some additional headers
if string.match(back_url, "(.*)\n") then
hlp.flash("fail", hlp.t("redirection_error_invalid_url"))
ngx.log(ngx.ERR, "Redirection url is invalid")
logger.error("Redirection url is invalid")
return hlp.redirect(conf.portal_url)
end
@ -110,7 +115,7 @@ then
for _, domain in ipairs(conf["domains"]) do
local escaped_domain = domain:gsub("-", "%%-") -- escape dash for pattern matching
if string.match(back_url, "^http[s]?://"..escaped_domain.."/") then
ngx.log(ngx.INFO, "Redirection to a managed domain found")
logger.debug("Redirection to a managed domain found")
managed_domain = true
break
end
@ -120,7 +125,7 @@ then
-- redirect to portal home page
if not managed_domain then
hlp.flash("fail", hlp.t("redirection_error_unmanaged_domain"))
ngx.log(ngx.ERR, "Redirection to an external domain aborted")
logger.error("Redirection to an external domain aborted")
return hlp.redirect(conf.portal_url)
end
@ -162,6 +167,7 @@ then
-- If all the previous cases have failed, redirect to portal
else
hlp.flash("info", hlp.t("please_login"))
logger.debug("User should log in to be able to access "..ngx.var.uri)
-- Force the scheme to HTTPS. This is to avoid an issue with redirection loop
-- when trying to access http://main.domain.tld/ (SSOwat finds that user aint
-- logged in, therefore redirects to SSO, which redirects to the back_url, which
@ -180,13 +186,16 @@ then
if hlp.string.ends(ngx.var.uri, conf["portal_path"].."password.html")
or hlp.string.ends(ngx.var.uri, conf["portal_path"].."edit.html")
then
logger.debug("User attempts to edit its information")
return hlp.edit_user()
else
logger.debug("User attempts to log in")
return hlp.login()
end
else
-- Redirect to portal
hlp.flash("fail", hlp.t("please_login_from_portal"))
logger.debug("Invalid POST request not coming from the portal url...")
return hlp.redirect(conf.portal_url)
end
end
@ -228,6 +237,7 @@ if conf["redirected_urls"] then
if url == ngx.var.host..ngx.var.uri..hlp.uri_args_string()
or url == ngx.var.scheme.."://"..ngx.var.host..ngx.var.uri..hlp.uri_args_string()
or url == ngx.var.uri..hlp.uri_args_string() then
logger.debug("Requested URI is in redirected_urls")
detect_redirection(redirect_url)
end
end
@ -238,6 +248,7 @@ if conf["redirected_regex"] then
if match(ngx.var.host..ngx.var.uri..hlp.uri_args_string(), regex)
or match(ngx.var.scheme.."://"..ngx.var.host..ngx.var.uri..hlp.uri_args_string(), regex)
or match(ngx.var.uri..hlp.uri_args_string(), regex) then
logger.debug("Requested URI is in redirected_regex")
detect_redirection(redirect_url)
end
end
@ -264,16 +275,19 @@ function is_protected()
for _, url in ipairs(conf["protected_urls"]) do
if hlp.string.starts(ngx.var.host..ngx.var.uri..hlp.uri_args_string(), url)
or hlp.string.starts(ngx.var.uri..hlp.uri_args_string(), url) then
logger.debug(ngx.var.uri.." is in protected_urls")
return true
end
end
for _, regex in ipairs(conf["protected_regex"]) do
if match(ngx.var.host..ngx.var.uri..hlp.uri_args_string(), regex)
or match(ngx.var.uri..hlp.uri_args_string(), regex) then
logger.debug(ngx.var.uri.." is in protected_regex")
return true
end
end
logger.debug(ngx.var.uri.." is not in protected_urls/regex")
return false
end
@ -291,6 +305,7 @@ if conf["skipped_urls"] then
if (hlp.string.starts(ngx.var.host..ngx.var.uri..hlp.uri_args_string(), url)
or hlp.string.starts(ngx.var.uri..hlp.uri_args_string(), url))
and not is_protected() then
logger.debug("Skipping "..ngx.var.uri)
return hlp.pass()
end
end
@ -301,6 +316,7 @@ if conf["skipped_regex"] then
if (match(ngx.var.host..ngx.var.uri..hlp.uri_args_string(), regex)
or match(ngx.var.uri..hlp.uri_args_string(), regex))
and not is_protected() then
logger.debug("Skipping "..ngx.var.uri)
return hlp.pass()
end
end
@ -327,6 +343,7 @@ end
function serveAsset(shortcut, full)
if string.match(ngx.var.uri, "^"..shortcut.."$") then
logger.debug("Serving static asset "..full)
hlp.serve("/yunohost/sso/assets/"..full, "static_asset")
end
end
@ -378,6 +395,7 @@ if conf["unprotected_urls"] then
if hlp.is_logged_in() then
hlp.set_headers()
end
logger.debug(ngx.var.uri.." is in unprotected_urls")
return hlp.pass()
end
end
@ -391,6 +409,7 @@ if conf["unprotected_regex"] then
if hlp.is_logged_in() then
hlp.set_headers()
end
logger.debug(ngx.var.uri.." is in unprotected_regex")
return hlp.pass()
end
end
@ -416,6 +435,12 @@ if auth_header then
_, _, user, password = string.find(ngx.decode_base64(b64_cred), "^(.+):(.+)$")
user = hlp.authenticate(user, password)
if user then
logger.debug("User got authenticated through basic auth")
-- If user has no access to this URL, redirect him to the portal
if not hlp.has_access(user) then
return hlp.redirect(conf.portal_url)
end
hlp.set_headers(user)
-- If user has no access to this URL, redirect him to the portal
@ -445,5 +470,6 @@ end
-- when trying to access http://main.domain.tld/ (SSOwat finds that user aint
-- logged in, therefore redirects to SSO, which redirects to the back_url, which
-- redirect to SSO, ..)
logger.debug("No rule found for this url. By default, redirecting to portal")
local back_url = "https://" .. ngx.var.host .. ngx.var.uri .. hlp.uri_args_string()
return hlp.redirect(conf.portal_url.."?r="..ngx.encode_base64(back_url))

View file

@ -55,7 +55,8 @@ function get_config()
ldap_attributes = {"uid", "givenname", "sn", "cn", "homedirectory", "mail", "maildrop"},
allow_mail_authentication = true,
default_language = "en",
theme = "default"
theme = "default",
logging = "fatal" -- Only log fatal messages by default (so apriori nothing)
}

View file

@ -9,6 +9,7 @@ module('helpers', package.seeall)
local cache = ngx.shared.cache
local conf = config.get_config()
local logger = require("log")
-- Read a FS stored file
function read_file(file)
@ -158,6 +159,7 @@ function set_auth_cookie(user, domain)
"SSOwAuthHash="..hash..cookie_str,
"SSOwAuthExpire="..expire..cookie_str
}
logger.info("Hash "..hash.." generated for "..user.."@"..ngx.var.remote_addr)
end
@ -220,6 +222,9 @@ function is_logged_in()
authUser..
"|"..expireTime..
"|"..session_key)
if hash ~= authHash then
logger.info("Hash "..authHash.." rejected for "..user.."@"..ngx.var.remote_addr)
end
return hash == authHash
end
end
@ -229,6 +234,15 @@ function is_logged_in()
return false
end
function log_access(user, app)
local key = "ACC|"..user.."|"..app
local block = cache:get(key)
if block == nil then
logger.info("User "..user.."@"..ngx.var.remote_addr.." accesses "..app)
cache:set(key, "block", 60)
end
end
-- Check whether a user is allowed to access a URL using the `users` directive
-- of the configuration file
@ -243,11 +257,12 @@ function has_access(user, url)
-- If there are no `users` directive, or if the user has no ACL set, he can
-- access the URL by default
if not conf["users"] or not conf["users"][user] then
logger.debug("No access rules defined for user "..user..", assuming it can access..")
return true
end
-- Loop through user's ACLs and return if the URL is authorized.
for u, _ in pairs(conf["users"][user]) do
for u, app in pairs(conf["users"][user]) do
-- Replace the original domain by a local one if you are connected from
-- a non-global domain name.
@ -255,8 +270,13 @@ function has_access(user, url)
u = string.gsub(u, conf["original_portal_domain"], conf["local_portal_domain"])
end
if string.starts(url, string.sub(u, 1, -2)) then return true end
if string.starts(url, string.sub(u, 1, -2)) then
logger.debug("Logged-in user can access "..ngx.var.uri)
log_access(user, app)
return true
end
end
logger.debug("Logged-in user cannot access "..ngx.var.uri)
return false
end
@ -279,10 +299,10 @@ function authenticate(user, password)
attrs = {conf["ldap_identifier"]}
} do
if attribs[conf["ldap_identifier"]] then
ngx.log(ngx.NOTICE, "Use email: "..user)
logger.debug("Use email: "..user)
user = attribs[conf["ldap_identifier"]]
else
ngx.log(ngx.ERR, "Unknown email: "..user)
logger.error("Unknown email: "..user)
return false
end
end
@ -306,12 +326,12 @@ function authenticate(user, password)
ensure_user_password_uses_strong_hash(connected, user, password)
end
cache:add(user.."-password", password, conf["session_timeout"])
ngx.log(ngx.NOTICE, "Connected as: "..user)
logger.info("User "..user.." succesfully authenticated from "..ngx.var.remote_addr)
return user
-- Else, the username/email or the password is wrong
else
ngx.log(ngx.ERR, "Connection failed for: "..user)
logger.error("Authentication failure for user "..user.." from "..ngx.var.remote_addr)
return false
end
end
@ -361,13 +381,13 @@ function set_headers(user)
-- If the ldap connection fail (because the password was changed).
-- Logout the user and invalid the password
if not ldap then
ngx.log(ngx.NOTICE, "LDAP connection failed. Disconnect user : ".. user)
logger.debug("LDAP connection failed. Disconnect user : ".. user)
cache:delete(authUser.."-password")
flash("info", t("please_login"))
local back_url = ngx.var.scheme .. "://" .. ngx.var.host .. ngx.var.uri .. uri_args_string()
return redirect(conf.portal_url.."?r="..ngx.encode_base64(back_url))
end
ngx.log(ngx.NOTICE, "Reloading LDAP values for: "..user)
logger.debug("Reloading LDAP values for: "..user)
for dn, attribs in ldap:search {
base = conf["ldap_identifier"].."=".. user ..","..conf["ldap_group"],
scope = "base",
@ -438,6 +458,9 @@ end
-- Takes an URI, and returns file content with the proper HTTP headers.
-- It is used to render the SSOwat portal *only*.
function serve(uri, cache)
logger.debug("Serving portal uri "..uri.." (if the corresponding file exists)")
rel_path = string.gsub(uri, conf["portal_path"], "/")
-- Load login.html as index
@ -932,7 +955,7 @@ function login()
-- pass some additional headers
if string.match(uri_args.r, "(.*)\n") then
flash("fail", t("redirection_error_invalid_url"))
ngx.log(ngx.ERR, "Redirection url is invalid")
logger.debug("Redirection url is invalid")
return redirect(conf.portal_url)
end
@ -971,7 +994,7 @@ end
-- Set cookie and redirect (needed to properly set cookie)
function redirect(url)
ngx.log(ngx.NOTICE, "Redirect to: "..url)
logger.debug("Redirecting to "..url)
return ngx.redirect(url)
end
@ -979,6 +1002,7 @@ end
-- Set cookie and go on with the response (needed to properly set cookie)
function pass()
delete_redirect_cookie()
logger.debug("Allowing to pass through "..ngx.var.uri)
-- When we are in the SSOwat portal, we need a default `content-type`
if string.ends(ngx.var.uri, "/")

View file

@ -8,6 +8,10 @@
-- another.
--
-- Path of the configuration
conf_path = "/etc/ssowat/conf.json"
log_file = "/var/log/nginx/ssowat.log"
-- Remove prepending '@' & trailing 'init.lua'
script_path = string.sub(debug.getinfo(1).source, 2, -9)
@ -23,6 +27,11 @@ local socket = require "socket"
local config = require "config"
lustache = require "lustache"
-- Make sure the log file exists and we can write in it
io.popen("touch "..log_file)
io.popen("chown www-data "..log_file)
io.popen("chmod u+w "..log_file)
-- Persistent shared table
flashs = {}
i18n = {}
@ -54,8 +63,5 @@ for file in lfs.dir(locale_dir) do
end
end
-- Path of the configuration
conf_path = "/etc/ssowat/conf.json"
-- You should see that in your Nginx error logs by default
ngx.log(ngx.INFO, "SSOwat ready")

84
log.lua Normal file
View file

@ -0,0 +1,84 @@
--
-- log.lua
--
-- Copyright (c) 2016 rxi
--
-- This library is free software; you can redistribute it and/or modify it
-- under the terms of the MIT license. See LICENSE for details.
--
local log = { _version = "0.1.0" }
local conf = config.get_config()
log.usecolor = true
log.level = conf.logging
local modes = {
{ name = "trace", color = "\27[34m", },
{ name = "debug", color = "\27[36m", },
{ name = "info", color = "\27[32m", },
{ name = "warn", color = "\27[33m", },
{ name = "error", color = "\27[31m", },
{ name = "fatal", color = "\27[35m", },
}
local levels = {}
for i, v in ipairs(modes) do
levels[v.name] = i
end
local round = function(x, increment)
increment = increment or 1
x = x / increment
return (x > 0 and math.floor(x + .5) or math.ceil(x - .5)) * increment
end
local _tostring = tostring
local tostring = function(...)
local t = {}
for i = 1, select('#', ...) do
local x = select(i, ...)
if type(x) == "number" then
x = round(x, .01)
end
t[#t + 1] = _tostring(x)
end
return table.concat(t, " ")
end
for i, x in ipairs(modes) do
local nameupper = x.name:upper()
log[x.name] = function(...)
-- Return early if we're below the log level
if i < levels[log.level] then
return
end
local msg = tostring(...)
local info = debug.getinfo(2, "Sl")
-- Output to console
print(string.format("%s[%-6s%s]%s %s",
log.usecolor and x.color or "",
nameupper,
os.date("%H:%M:%S"),
log.usecolor and "\27[0m" or "",
msg))
-- Output to log file
local fp = io.open(log_file, "a")
local str = string.format("[%-6s%s] %s\n",
nameupper, os.date(), msg)
fp:write(str)
fp:close()
end
end
return log