Commit 94e38f43 authored by Renato Figueiro Maia's avatar Renato Figueiro Maia
Browse files

[OPENBUS-2502] Nova configuração para permitir limitar o número de...

[OPENBUS-2502] Nova configuração para permitir limitar o número de autenticações inválidas junto aos validadores de senha


git-svn-id: https://subversion.tecgraf.puc-rio.br/engdist/openbus/core/branches/02_00_00@151274 ae0415b3-e90b-0410-900d-d0be9363c56b
parent 912a5ec7
......@@ -72,6 +72,7 @@ local ICredentialObserver = idl.types.access_control_service.ICredentialObserver
local msg = require "openbus.core.services.messages"
local Logins = require "openbus.core.services.LoginDB"
local PasswordAttempts = require "openbus.core.services.PasswordAttempts"
local coreutil = require "openbus.core.services.util"
local assertCaller = coreutil.assertCaller
......@@ -189,63 +190,6 @@ local function checkaccesskey(pubkey)
end
local PasswordAttempts = class()
function PasswordAttempts:__init()
self.attemptsOf = {}
end
function PasswordAttempts:check(sourceid)
local attemptsOf = self.attemptsOf
local attempts = attemptsOf[sourceid]
if attempts ~= nil then
local deadline = attempts.deadline
if deadline < time() then
attemptsOf[sourceid] = nil
elseif attempts.count >= self.maxattempts then
log:exception(msg.TooManyFailedLogins:tag{
sourceid = sourceid,
deadline = os.date(log.timeformat, deadline),
})
-- TODO: move minor to IDL
NO_RESOURCES{ completed = "COMPLETED_YES", minor = 0x42555000 }
end
end
end
function PasswordAttempts:denied(sourceid)
local penalty = self.penaltytime
if penalty > 0 then
local now = self:clean()
local deadline = now+penalty
local attemptsOf = self.attemptsOf
local attempts = attemptsOf[sourceid]
if attempts == nil then
attempts = { deadline = deadline, count = 1}
attemptsOf[sourceid] = attempts
else
attempts.deadline = deadline
attempts.count = attempts.count + 1
end
end
end
function PasswordAttempts:granted(sourceid)
self.attemptsOf[sourceid] = nil
end
function PasswordAttempts:clean()
local now = time()
local attemptsOf = self.attemptsOf
for sourceid, attempts in pairs(attemptsOf) do
if attempts.deadline < now then
attemptsOf[sourceid] = nil
end
end
return now
end
local LoginProcess = class{ __type = LoginProcessType }
function LoginProcess:cancel()
......@@ -305,9 +249,14 @@ function AccessControl:__init(data)
self.passwordValidators = data.validators
self.leaseTime = data.leaseTime
self.expirationGap = data.expirationGap
self.passwordAttempts = PasswordAttempts{
maxattempts = data.passwordTries,
penaltytime = data.passwordPenaltyTime,
self.loginAttempts = PasswordAttempts{
limit = data.passwordTries,
period = data.passwordPenaltyTime,
}
self.validationAttempts = PasswordAttempts{
mode = PasswordAttempts.modes.LeakyBucket,
limit = data.passwordFailureLimit,
period = data.passwordFailureLimit/data.passwordFailureRate,
}
self.activeLogins = Logins{ database = database }
......@@ -414,10 +363,24 @@ function AccessControl:loginByPassword(entity, pubkey, encrypted)
local decoder = access.orb:newdecoder(decrypted)
local decoded = decoder:get(self.LoginAuthInfo)
if decoded.hash == sha256(pubkey) then
local sourceid = access.callerAddressOf[running()]
if sourceid ~= nil then sourceid = sourceid.host end
local attempts = self.passwordAttempts
attempts:check(sourceid)
local sourceid = access.callerAddressOf[running()].host
local loginAttempts = self.loginAttempts
local allowed, wait = loginAttempts:allow(sourceid)
if not allowed then
log:exception(msg.TooManyFailedLogins:tag{sourceid=sourceid,wait=wait})
NO_RESOURCES{ completed = "COMPLETED_YES", minor = 0x42555000 }
end
allowed, wait = loginAttempts:allow(entity)
if not allowed then
log:exception(msg.TooManyFailedEntityLogins:tag{entity=entity,wait=wait})
NO_RESOURCES{ completed = "COMPLETED_YES", minor = 0x42555000 }
end
local validationAttempts = self.validationAttempts
allowed, wait = validationAttempts:allow("validators")
if not allowed then
log:exception(msg.TooManyFailedValidations:tag{entity=entity,wait=wait})
NO_RESOURCES{ completed = "COMPLETED_YES", minor = 0x42555000 }
end
for _, validator in ipairs(self.passwordValidators) do
local valid, errmsg = validator.validate(entity, decoded.data)
if valid then
......@@ -428,7 +391,7 @@ function AccessControl:loginByPassword(entity, pubkey, encrypted)
validator = validator.name,
})
renewLogin(self, login)
attempts:granted(sourceid)
loginAttempts:granted(entity)
return login, self.leaseTime
elseif errmsg ~= nil then
log:exception(msg.FailedPasswordValidation:tag{
......@@ -438,7 +401,9 @@ function AccessControl:loginByPassword(entity, pubkey, encrypted)
})
end
end
attempts:denied(sourceid)
loginAttempts:denied(sourceid)
loginAttempts:denied(entity)
validationAttempts:denied("validators")
else
log:exception(msg.WrongPublicKeyHash:tag{ entity = entity })
end
......
local _G = require "_G"
local pairs = _G.pairs
local os = require "os"
local date = os.date
local cothread = require "cothread"
local time = cothread.now
local oo = require "openbus.util.oo"
local class = oo.class
local modes = {
AngryDog = function (self, now, before)
local wait = self.period-(now-before.time)
if wait > 0 then
return wait
end
end,
LeakyBucket = function (self, now, before)
local rate = self.limit/self.period
count = count - (now-before.time)*rate -- leak the bucket
before.time = now
if count > 0 then
before.count = count
return 1/rate
end
end,
}
local PasswordAttempts = class{
modes = modes,
mode = modes.AngryDog,
}
function PasswordAttempts:__init()
self.attemptsOf = {}
end
function PasswordAttempts:allow(sourceid)
local attemptsOf = self.attemptsOf
local attempts = attemptsOf[sourceid]
if attempts ~= nil then
local blocked = self:mode(time(), attempts)
if blocked == nil then
attemptsOf[sourceid] = nil
else
if attempts.count >= self.limit then
return false, wait
end
end
end
return true
end
function PasswordAttempts:denied(sourceid)
if self.period > 0 then
local now = self:clean()
local attemptsOf = self.attemptsOf
local attempts = attemptsOf[sourceid]
if attempts == nil then
attempts = { time = now, count = 1}
attemptsOf[sourceid] = attempts
else
attempts.time = now
attempts.count = attempts.count + 1
end
end
end
function PasswordAttempts:granted(sourceid)
self.attemptsOf[sourceid] = nil
end
function PasswordAttempts:clean()
local now = time()
local attemptsOf = self.attemptsOf
for sourceid, attempts in pairs(attemptsOf) do
local count = self:mode(now, attempts.time, attempts.count)
if count == nil then
attemptsOf[sourceid] = nil
else
attempts.count = count
end
end
return now
end
return PasswordAttempts
\ No newline at end of file
......@@ -10,6 +10,9 @@ local setmetatable = _G.setmetatable
local io = require "string"
local format = io.format
local math = require "math"
local inf = math.huge
local io = require "io"
local stderr = io.stderr
......@@ -59,6 +62,8 @@ return function(...)
passwordpenalty = 3*60,
passwordtries = 3,
validationburst = inf,
validationrate = inf,
admin = {},
validator = {},
......@@ -99,6 +104,8 @@ Options:
-passwordpenalty <seconds> perodo com tentativas de login limitadas aps falha de senha
-passwordtries <number> nmero de tentativas durante o perodo de 'passwordpenalty'
-validationburst <number> nmero mximo de validaes de senha simultneas
-validationrate <number> frequncia mxima de validaes de senha (validao/segundo)
-admin <user> usurio com privilgio de administrao
-validator <name> nome de pacote de validao de login
......@@ -159,8 +166,18 @@ Options:
msg.InvalidExpirationGap:tag{value=Configs.expirationgap})
assert(Configs.passwordpenalty >= 0,
msg.InvalidPasswordPenaltyTime:tag{value=Configs.passwordpenalty})
assert(Configs.passwordtries > 0,
assert(Configs.passwordtries > 0 and Configs.passwordtries%1 == 0,
msg.InvalidNumberOfPasswordLimitedTries:tag{value=Configs.passwordtries})
assert((Configs.validationburst ~= inf) == (Configs.validationrate ~= inf),
msg.MissingPasswordValidationParameter:tag{
missing = (Configs.validationburst == inf)
and "validationburst"
or "validationrate"
})
assert(Configs.validationburst >= 1,
msg.InvalidPasswordValidationLimit:tag{value=Configs.validationburst})
assert(Configs.validationrate > 0,
msg.InvalidPasswordValidationRate:tag{value=Configs.validationrate})
-- create a set of admin users
local adminUsers = {}
......@@ -231,6 +248,8 @@ Options:
expirationGap = Configs.expirationgap,
passwordPenaltyTime = Configs.passwordpenalty,
passwordTries = Configs.passwordtries,
passwordFailureLimit = Configs.validationrate,
passwordFailureRate = Configs.validationrate,
admins = adminUsers,
validators = validators,
enforceAuth = not Configs.noauthorizations,
......
......@@ -32,6 +32,7 @@ LUASRC= \
$(LUADIR)/openbus/core/services/LoginDB.lua \
$(LUADIR)/openbus/core/services/main.lua \
$(LUADIR)/openbus/core/services/messages.lua \
$(LUADIR)/openbus/core/services/PasswordAttempts.lua \
$(LUADIR)/openbus/core/services/PropertyIndex.lua \
$(LUADIR)/openbus/core/services/OfferRegistry.lua \
$(LUADIR)/openbus/core/services/util.lua \
......
......@@ -98,9 +98,7 @@ for _, userpat in ipairs({"%s", "%s%d"}) do
assert(ok == false)
assert(ex._repid == logintypes.AccessDenied)
-- reseting failed login attempts
encrypted = encodeLogin(bus.key, password, pubkey)
login = ac:loginByPassword(user, pubkey, encrypted)
doLogout(login.id)
sleep(passwordpenalty)
end
do -- login with wrong password max - 1 tries
......@@ -120,9 +118,7 @@ for _, userpat in ipairs({"%s", "%s%d"}) do
assert(ok == false)
assert(ex._repid == logintypes.AccessDenied)
-- reseting failed login attempts
encrypted = encodeLogin(bus.key, password, pubkey)
login = ac:loginByPassword(user, pubkey, encrypted)
doLogout(login.id)
sleep(passwordpenalty)
end
end
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment