Commit 4cdb1b4b authored by Renato Figueiro Maia's avatar Renato Figueiro Maia
Browse files

[OPENBUS-2494] Impedir que um número muito grande de tentativas de...

[OPENBUS-2494] Impedir que um número muito grande de tentativas de autenticação por senha sem sucesso sejam feitas de uma mesma máquina (IP)
[OPENBUS-2495] Exibir o IP e porta de origem de cada requisição que resulta numa entrada no log do barramento

git-svn-id: https://subversion.tecgraf.puc-rio.br/engdist/openbus/core/branches/02_00_00@151008 ae0415b3-e90b-0410-900d-d0be9363c56b
parent 266004e7
......@@ -7,6 +7,9 @@ local rawset = _G.rawset
local array = require "table"
local unpack = array.unpack or _G.unpack
local coroutine = require "coroutine"
local running = coroutine.running
local hash = require "lce.hash"
local sha256 = hash.sha256
......@@ -31,6 +34,7 @@ local setNoPermSysEx = access.setNoPermSysEx
local Context = access.Context
local Interceptor = access.Interceptor
local receiveBusRequest = Interceptor.receiverequest
local sendBusReply = Interceptor.sendreply
......@@ -69,6 +73,7 @@ function BusInterceptor:__init()
end,
}
end, "k")
self.callerAddressOf = setmetatable({}, {__mode = "k"})
do
local forAllOps = Everybody
......@@ -139,6 +144,7 @@ function BusInterceptor:signChainFor(target, chain)
end
function BusInterceptor:receiverequest(request)
self.callerAddressOf[running()] = request.channel_address
if request.servant ~= nil then -- servant object does exist
local op = request.operation_name
if op:find("_", 1, true) ~= 1
......@@ -178,6 +184,11 @@ function BusInterceptor:receiverequest(request)
end
end
function BusInterceptor:sendreply(...)
self.callerAddressOf[running()] = nil
return sendBusReply(self, ...)
end
function BusInterceptor:setGrantedUsers(interface, operation, users)
local accessByIface = self.grantedUsers
local accessByOp = rawget(accessByIface, interface)
......
......@@ -39,6 +39,7 @@ local oo = require "openbus.util.oo"
local class = oo.class
local sysex = require "openbus.util.sysex"
local BAD_PARAM = sysex.BAD_PARAM
local NO_RESOURCES = sysex.NO_RESOURCES
local idl = require "openbus.core.idl"
local assert = idl.serviceAssertion
......@@ -188,6 +189,62 @@ 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 }
......@@ -248,6 +305,10 @@ 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.activeLogins = Logins{ database = database }
-- initialize access
......@@ -345,13 +406,18 @@ end
function AccessControl:loginByPassword(entity, pubkey, encrypted)
if entity ~= self.login.entity then
checkaccesskey(pubkey)
local decrypted, errmsg = self.access.prvkey:decrypt(encrypted)
local access = self.access
local decrypted, errmsg = access.prvkey:decrypt(encrypted)
if decrypted == nil then
WrongEncoding{entity=entity,message=errmsg or "no error message"}
end
local decoder = self.access.orb:newdecoder(decrypted)
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)
for _, validator in ipairs(self.passwordValidators) do
local valid, errmsg = validator.validate(entity, decoded.data)
if valid then
......@@ -362,6 +428,7 @@ function AccessControl:loginByPassword(entity, pubkey, encrypted)
validator = validator.name,
})
renewLogin(self, login)
attempts:granted(sourceid)
return login, self.leaseTime
elseif errmsg ~= nil then
log:exception(msg.FailedPasswordValidation:tag{
......@@ -371,13 +438,14 @@ function AccessControl:loginByPassword(entity, pubkey, encrypted)
})
end
end
attempts:denied(sourceid)
else
log:exception(msg.WrongPublicKeyHash:tag{ entity = entity })
end
else
log:exception(msg.RefusedLoginOfBusEntity:tag{ entity = entity })
end
AccessDenied{entity=entity}
AccessDenied{ entity = entity }
end
function AccessControl:startLoginByCertificate(entity)
......
......@@ -5,6 +5,10 @@ local assert = _G.assert
local ipairs = _G.ipairs
local require = _G.require
local select = _G.select
local setmetatable = _G.setmetatable
local io = require "string"
local format = io.format
local io = require "io"
local stderr = io.stderr
......@@ -14,6 +18,7 @@ local getenv = os.getenv
local table = require "loop.table"
local copy = table.copy
local memoize = table.memoize
local cothread = require "cothread"
local running = cothread.running
......@@ -52,6 +57,9 @@ return function(...)
leasetime = 30*60,
expirationgap = 10,
passwordpenalty = 3*60,
passwordtries = 3,
admin = {},
validator = {},
......@@ -62,6 +70,7 @@ return function(...)
noauthorizations = false,
nolegacy = false,
logaddress = false,
}
-- parse configuration file
......@@ -88,6 +97,9 @@ Options:
-leasetime <seconds> tempo de lease dos logins de acesso
-expirationgap <seconds> tempo que os logins ficam vlidas aps o lease
-passwordpenalty <seconds> perodo com tentativas de login limitadas aps falha de senha
-passwordtries <number> nmero de tentativas durante o perodo de 'passwordpenalty'
-admin <user> usurio com privilgio de administrao
-validator <name> nome de pacote de validao de login
......@@ -98,6 +110,7 @@ Options:
-noauthorizations desativa o suporte a autorizaes de oferta
-nolegacy desativa o suporte verso antiga do barramento
-logaddress exibe o endereo IP do requisitante no log do barramento
-configs <path> arquivo de configuraes adicionais do barramento
......@@ -106,6 +119,32 @@ Options:
end
end
local logaddress = Configs.logaddress and {}
if logaddress then
local function writeCallerAddress(verbose)
local viewer = verbose.viewer
local output = viewer.output
local address = logaddress[running()]
if address == nil then
output:write(" ")
else
output:write(format("%15s:%5d ", address.host, address.port))
end
return true
end
local backup = log.custom
log.custom = memoize(function(tag)
local custom = backup[tag]
if custom ~= nil then
return function (self, ...)
writeCallerAddress(self)
return custom(self, ...)
end
end
return writeCallerAddress
end)
end
-- setup log files
setuplog(log, Configs.loglevel, Configs.logfile)
log:version(msg.CopyrightNotice)
......@@ -118,6 +157,10 @@ Options:
msg.InvalidLeaseTime:tag{value=Configs.leasetime})
assert(Configs.expirationgap > 0,
msg.InvalidExpirationGap:tag{value=Configs.expirationgap})
assert(Configs.passwordpenalty >= 0,
msg.InvalidPasswordPenaltyTime:tag{value=Configs.passwordpenalty})
assert(Configs.passwordtries > 0,
msg.InvalidNumberOfPasswordLimitedTries:tag{value=Configs.passwordtries})
-- create a set of admin users
local adminUsers = {}
......@@ -148,14 +191,15 @@ Options:
local ACS = require "openbus.core.legacy.AccessControlService"
legacy = ACS.IAccessControlService
end
local iceptor = access.Interceptor{
iceptor = access.Interceptor{
prvkey = assert(readprivatekey(Configs.privatekey)),
orb = orb,
legacy = legacy,
}
orb:setinterceptor(iceptor, "corba")
loadidl(orb)
logaddress = logaddress and iceptor.callerAddressOf
-- prepare facets to be published as CORBA objects
local facets = {}
do
......@@ -185,14 +229,18 @@ Options:
database = assert(opendb(Configs.database)),
leaseTime = Configs.leasetime,
expirationGap = Configs.expirationgap,
passwordPenaltyTime = Configs.passwordpenalty,
passwordTries = Configs.passwordtries,
admins = adminUsers,
validators = validators,
enforceAuth = not Configs.noauthorizations,
}
log:config(msg.LoadedBusDatabase:tag{path=Configs.database})
log:config(msg.LoadedBusPrivateKey:tag{path=Configs.privatekey})
log:config(msg.SetupLoginLeaseTime:tag{value=params.leaseTime})
log:config(msg.SetupLoginExpirationGap:tag{value=params.expirationGap})
log:config(msg.SetupLoginLeaseTime:tag{seconds=params.leaseTime})
log:config(msg.SetupLoginExpirationGap:tag{seconds=params.expirationGap})
log:config(msg.WrongPasswordPenaltyTime:tag{seconds=Configs.passwordpenalty})
log:config(msg.WrongPasswordLimitedTries:tag{maxtries=Configs.passwordtries})
if not params.enforceAuth then
log:config(msg.OfferAuthorizationDisabled)
end
......
......@@ -21,6 +21,9 @@ local CredentialContextId = idl.const.credential.CredentialContextId
local loginconst = idl.const.services.access_control
local logintypes = idl.types.services.access_control
local giop = require "oil.corba.giop"
local sysex = giop.SystemExceptionIDs
local server = require "openbus.util.server"
local readfrom = server.readfrom
......@@ -36,6 +39,37 @@ local shortkey = newkey(EncryptedBlockSize-1):encode("public")
local longkey = newkey(EncryptedBlockSize+1):encode("public")
local otherkey = newkey(EncryptedBlockSize)
-- local function --------------------------------------------------------------
local function doLogout(login)
-- create an invalid credential
local credential = {
opname = "logout",
bus = bus.id,
login = login,
session = 0,
ticket = 0,
secret = "",
chain = NullChain,
}
putreqcxt(CredentialContextId, encodeCredential(credential))
-- request cresential reset
local ok, ex = pcall(ac.logout, ac)
assert(ok == false)
assert(ex._repid == "IDL:omg.org/CORBA/NO_PERMISSION:1.0")
assert(ex.completed == "COMPLETED_NO")
assert(ex.minor == loginconst.InvalidCredentialCode)
local reset = decodeReset(assert(getrepcxt(CredentialContextId)), prvkey)
-- update credential with credential reset information
credential.session = reset.session
credential.ticket = 1
credential.secret = reset.secret
putreqcxt(CredentialContextId, encodeCredential(credential))
-- perform bus call
ac:logout()
return credential
end
-- login by password -----------------------------------------------------------
do -- login using reserved entity
......@@ -46,13 +80,55 @@ do -- login using reserved entity
assert(ex._repid == logintypes.AccessDenied)
end
do -- login with wrong password
local encrypted = encodeLogin(bus.key, "WrongPassword", pubkey)
local ok, ex = pcall(ac.loginByPassword, ac, user, pubkey, encrypted)
assert(ok == false)
assert(ex._repid == logintypes.AccessDenied)
for _, userpat in ipairs({"%s", "%s%d"}) do
do -- login with wrong password max tries
local encrypted = encodeLogin(bus.key, "WrongPassword", pubkey)
local ok, ex
for i = 1, passwordtries do
local entity = userpat:format(user, i)
ok, ex = pcall(ac.loginByPassword, ac, entity, pubkey, encrypted)
assert(ok == false)
assert(ex._repid == logintypes.AccessDenied)
end
ok, ex = pcall(ac.loginByPassword, ac, user, pubkey, encrypted)
assert(ok == false)
assert(ex._repid == sysex.NO_RESOURCES)
sleep(passwordpenalty)
ok, ex = pcall(ac.loginByPassword, ac, user, pubkey, encrypted)
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)
end
do -- login with wrong password max - 1 tries
local encrypted = encodeLogin(bus.key, "WrongPassword", pubkey)
local ok, ex
for i = 1, passwordtries - 1 do
local entity = userpat:format(user, i)
ok, ex = pcall(ac.loginByPassword, ac, entity, pubkey, encrypted)
assert(ok == false)
assert(ex._repid == logintypes.AccessDenied)
end
encrypted = encodeLogin(bus.key, password, pubkey)
local login = ac:loginByPassword(user, pubkey, encrypted)
doLogout(login.id)
encrypted = encodeLogin(bus.key, "WrongPassword", pubkey)
ok, ex = pcall(ac.loginByPassword, ac, user, pubkey, encrypted)
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)
end
end
-- TODO: login with wrong password max tries with different IP addresses
do -- login with wrong access key hash
local encrypted = encodeLogin(bus.key, password, "WrongKey")
local ok, ex = pcall(ac.loginByPassword, ac, user, pubkey, encrypted)
......@@ -198,31 +274,7 @@ end
-- logout ----------------------------------------------------------------------
do -- logout
-- create an invalid credential
local credential = {
opname = "logout",
bus = bus.id,
login = syslogin,
session = 0,
ticket = 0,
secret = "",
chain = NullChain,
}
putreqcxt(CredentialContextId, encodeCredential(credential))
-- request cresential reset
local ok, ex = pcall(ac.logout, ac)
assert(ok == false)
assert(ex._repid == "IDL:omg.org/CORBA/NO_PERMISSION:1.0")
assert(ex.completed == "COMPLETED_NO")
assert(ex.minor == loginconst.InvalidCredentialCode)
local reset = decodeReset(assert(getrepcxt(CredentialContextId)), prvkey)
-- update credential with credential reset information
credential.session = reset.session
credential.ticket = 1
credential.secret = reset.secret
putreqcxt(CredentialContextId, encodeCredential(credential))
-- perform bus call
ac:logout()
local credential = doLogout(syslogin)
-- update credential with new ticket
credential.ticket = credential.ticket+1
putreqcxt(CredentialContextId, encodeCredential(credential))
......
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