[Kimchi-devel] [PATCH v3] [Wok] Bug fix #147: Block authentication request after too many failures
Ramon Medeiros
ramonn at linux.vnet.ibm.com
Mon Jan 30 12:55:02 UTC 2017
Just start the discussion about JSON validation here:
http://lists.ovirt.org/pipermail/kimchi-devel/2017-January/017435.html
On 1/26/17 9:04 AM, Aline Manera wrote:
>
>
> On 01/26/2017 08:42 AM, Ramon Medeiros wrote:
>>
>>
>> On 1/25/17 3:46 PM, Aline Manera wrote:
>>>
>>>
>>> On 01/24/2017 06:12 PM, ramonn at linux.vnet.ibm.com wrote:
>>>> From: Ramon Medeiros <ramonn at linux.vnet.ibm.com>
>>>>
>>>> To prevent brute force attack, creates a mechanism to allow 3 tries
>>>> first. After that, a timeout will start and will be added 30
>>>> seconds for
>>>> each failed try in a row.
>>>>
>>>> Signed-off-by: Ramon Medeiros <ramonn at linux.vnet.ibm.com>
>>>> ---
>>>> Changes:
>>>>
>>>> v3:
>>>> Improve error handling on login page
>>>>
>>>> v2:
>>>> Set timeout by user, ip and session id. This will avoid trouble with
>>>> users using the same ip, like NAT.
>>>>
>>>> src/wok/i18n.py | 2 ++
>>>> src/wok/root.py | 66
>>>> ++++++++++++++++++++++++++++++++++++++++++++----
>>>> ui/js/src/wok.login.js | 21 +++++++++------
>>>> ui/pages/i18n.json.tmpl | 5 +++-
>>>> ui/pages/login.html.tmpl | 6 ++---
>>>> 5 files changed, 82 insertions(+), 18 deletions(-)
>>>>
>>>> diff --git a/src/wok/i18n.py b/src/wok/i18n.py
>>>> index e454e31..7d595b8 100644
>>>> --- a/src/wok/i18n.py
>>>> +++ b/src/wok/i18n.py
>>>> @@ -41,7 +41,9 @@ messages = {
>>>> "WOKAUTH0001E": _("Authentication failed for user
>>>> '%(username)s'. [Error code: %(code)s]"),
>>>> "WOKAUTH0002E": _("You are not authorized to access Wok.
>>>> Please, login first."),
>>>> "WOKAUTH0003E": _("Specify %(item)s to login into Wok."),
>>>> + "WOKAUTH0004E": _("You have failed to login in too much
>>>> attempts. Please, wait for %(seconds)s seconds to try again."),
>>>> "WOKAUTH0005E": _("Invalid LDAP configuration: %(item)s :
>>>> %(value)s"),
>>>> + "WOKAUTH0006E": _("The username or password you entered is
>>>> incorrect. Please try again."),
>>>>
>>>> "WOKLOG0001E": _("Invalid filter parameter. Filter parameters
>>>> allowed: %(filters)s"),
>>>> "WOKLOG0002E": _("Creation of log file failed: %(err)s"),
>>>> diff --git a/src/wok/root.py b/src/wok/root.py
>>>> index 080b7f0..d314d25 100644
>>>> --- a/src/wok/root.py
>>>> +++ b/src/wok/root.py
>>>> @@ -1,7 +1,7 @@
>>>> #
>>>> # Project Wok
>>>> #
>>>> -# Copyright IBM Corp, 2015-2016
>>>> +# Copyright IBM Corp, 2015-2017
>>>> #
>>>> # Code derived from Project Kimchi
>>>> #
>>>> @@ -21,7 +21,9 @@
>>>>
>>>> import cherrypy
>>>> import json
>>>> +import re
>>>> import os
>>>> +import time
>>>> from distutils.version import LooseVersion
>>>>
>>>> from wok import auth
>>>> @@ -31,7 +33,7 @@ from wok.config import paths as wok_paths
>>>> from wok.control import sub_nodes
>>>> from wok.control.base import Resource
>>>> from wok.control.utils import parse_request
>>>> -from wok.exception import MissingParameter
>>>> +from wok.exception import MissingParameter, UnauthorizedError
>>>> from wok.reqlogger import log_request
>>>>
>>>>
>>>> @@ -48,7 +50,8 @@ class Root(Resource):
>>>> super(Root, self).__init__(model)
>>>> self._handled_error = ['error_page.400', 'error_page.404',
>>>> 'error_page.405', 'error_page.406',
>>>> - 'error_page.415', 'error_page.500']
>>>> + 'error_page.415', 'error_page.500',
>>>> + 'error_page.403', 'error_page.401']
>>>>
>>>> if not dev_env:
>>>> self._cp_config = dict([(key,
>>>> self.error_production_handler)
>>>> @@ -146,6 +149,7 @@ class WokRoot(Root):
>>>> self.domain = 'wok'
>>>> self.messages = messages
>>>> self.extends = None
>>>> + self.failed_logins = {}
>>>>
>>>> # set user log messages and make sure all parameters are
>>>> present
>>>> self.log_map = ROOT_REQUESTS
>>>> @@ -153,6 +157,13 @@ class WokRoot(Root):
>>>>
>>>> @cherrypy.expose
>>>> def login(self, *args):
>>>> + def _raise_timeout(user_id):
>>>> + length = self.failed_logins[user_ip_sid]["count"]
>>>> + timeout = (length - 3) * 30
>>>> + details = e = UnauthorizedError("WOKAUTH0004E",
>>>> + {"seconds": timeout})
>>>> + log_request(code, params, details, method, 403)
>>>> + raise cherrypy.HTTPError(403, e.message)
>>>> details = None
>>>> method = 'POST'
>>>> code = self.getRequestMessage(method, 'login')
>>>> @@ -161,17 +172,62 @@ class WokRoot(Root):
>>>> params = parse_request()
>>>> username = params['username']
>>>> password = params['password']
>>>> +
>>>
>>>
>>> It would be good to use strip() around username and password to
>>> avoid multiple white spaces.
>>>
>>>> + # no data passed: raise error
>>>> + if len(username + password) == 0:
>>>> + raise KeyError
>>>> +
>>>
>>> What is this for? You are summing 'username' and 'password' and get
>>> the length of it. I am not sure is that what you want.
>>>
>>> If it is to identify a missing parameter you should check them
>>> individually. As username AND password should be a string with
>>> length != 0
>> Well,
>>
>> when parse_request run, it never get a missing parameters, so i need
>> to check if the var is empty. I will check each one alone.
>
> Hrm... could you point me the code on where it is done?
>
> From what I remember, the request parameters validation is done
> through API.json on which each parameter is specified with a required
> flag.
> I don't remember we have it for login request. Do we have? Otherwise,
> there is no way to control knows which parameters are required or not.
>
> Maybe it is time to add a entry for login on API.json to make username
> and password required fields and non-empty ones. And then you make
> sure to have only valid values when the request gets to backend
> (model) code.
>
>>>
>>>> except KeyError, item:
>>>> details = e = MissingParameter('WOKAUTH0003E',
>>>> {'item': str(item)})
>>>> log_request(code, params, details, method, 400)
>>>> raise cherrypy.HTTPError(400, e.message)
>>>>
>>>> + # get authentication info
>>>> + remote_ip = cherrypy.request.remote.ip
>>>> + session_id = str(cherrypy.session.originalid)
>>>> + user_ip_sid = re.escape(username + remote_ip + session_id)
>>>> +
>>>> + # check for repetly
>>>> + count = self.failed_logins.get(user_ip_sid, {"count":
>>>> 0}).get("count")
>>>> + if count >= 3:
>>>> +
>>>> + # verify if timeout is still valid
>>>> + last_try = self.failed_logins[user_ip_sid]["time"]
>>>> + if time.time() < (last_try + ((count - 3) * 30)):
>>>> + _raise_timeout(user_ip_sid)
>>>
>>> else:
>>> # remove the entry from the dict
>>>
>> ok.
>>>> +
>>>> try:
>>>> status = 200
>>>> user_info = auth.login(username, password)
>>>> +
>>>> + # user logged sucessfuly: reset counters
>>>> + if self.failed_logins.get(user_ip_sid) != None:
>>>> + self.failed_logins.remove(user_ip_sid)
>>>> except cherrypy.HTTPError, e:
>>>> - status = e.status
>>>> - raise
>>>> +
>>>> + # store time and prevent too much tries
>>>> + if self.failed_logins.get(user_ip_sid) == None:
>>>> + self.failed_logins[user_ip_sid] = {"time":
>>>> time.time(),
>>>> + "ip": remote_ip,
>>>> + "session_id": session_id,
>>>> + "username": username,
>>>> + "count": 1}
>>>> + else:
>>>> +
>>>> + # tries take more than 30 seconds between each
>>>> one: do not
>>>> + # increase count
>>>> + if (time.time() -
>>>> + self.failed_logins[user_ip_sid]["time"]) < 30:
>>>> +
>>>> + self.failed_logins[user_ip_sid]["time"] =
>>>> time.time()
>>>> + self.failed_logins[user_ip_sid]["count"] += 1
>>>> +
>>>> + # more than 3 fails: raise error
>>>> + if self.failed_logins[user_ip_sid]["count"] > 3:
>>>> + _raise_timeout(user_ip_sid)
>>>> +
>>>
>>>> + details = e = MissingParameter('WOKAUTH0006E')
>>>> + raise cherrypy.HTTPError(401, e.message)
>>>> finally:
>>>> log_request(code, params, details, method, status)
>>>>
>>>> diff --git a/ui/js/src/wok.login.js b/ui/js/src/wok.login.js
>>>> index fa2a98a..d9daacf 100644
>>>> --- a/ui/js/src/wok.login.js
>>>> +++ b/ui/js/src/wok.login.js
>>>> @@ -1,7 +1,7 @@
>>>> /*
>>>> * Project Wok
>>>> *
>>>> - * Copyright IBM Corp, 2015-2016
>>>> + * Copyright IBM Corp, 2015-2017
>>>> *
>>>> * Code derived from Project Kimchi
>>>> *
>>>> @@ -19,6 +19,10 @@
>>>> */
>>>> wok.login_main = function() {
>>>> "use strict";
>>>> + var i18n;
>>>> + wok.getI18n(function(i18nObj){
>>>> + i18n = i18nObj;
>>>> + }, false, "i18n.json", true);
>>>>
>>>> // verify if language is available
>>>> var selectedLanguage = wok.lang.get();
>>>> @@ -50,7 +54,8 @@ wok.login_main = function() {
>>>> var query = window.location.search;
>>>> var error = /.*error=(.*?)(&|$)/g.exec(query);
>>>> if (error && error[1] === "sessionTimeout") {
>>>> - $("#messSession").show();
>>>> + $("#errorArea").html(i18n["WOKAUT0001E"]);
>>>> + $("#errorArea").show();
>>>> }
>>>>
>>>> var userNameBox = $('#username');
>>>> @@ -82,13 +87,13 @@ wok.login_main = function() {
>>>> window.location.replace(window.location.pathname.replace(/\/+login.html/,
>>>> '') + next_url);
>>>> }, function(jqXHR, textStatus, errorThrown) {
>>>> if (jqXHR.responseText == "") {
>>>> - $("#messUserPass").hide();
>>>> - $("#missServer").show();
>>>> - } else {
>>>> - $("#missServer").hide();
>>>> - $("#messUserPass").show();
>>>> + $("#errorArea").html(i18n["WOKAUT0002E"]);
>>>> + $("#errorArea").show();
>>>> + } else if ((jqXHR.responseJSON != undefined) &&
>>>> + ! (jqXHR.responseJSON["reason"] ==
>>>> undefined)) {
>>>> + $("#errorArea").html(jqXHR.responseJSON["reason"]);
>>>> + $("#errorArea").show();
>>>> }
>>>> - $("#messSession").hide();
>>>> $("#logging").hide();
>>>> $("#login").show();
>>>> });
>>>> diff --git a/ui/pages/i18n.json.tmpl b/ui/pages/i18n.json.tmpl
>>>> index ba29532..4329ad0 100644
>>>> --- a/ui/pages/i18n.json.tmpl
>>>> +++ b/ui/pages/i18n.json.tmpl
>>>> @@ -1,7 +1,7 @@
>>>> #*
>>>> * Project Wok
>>>> *
>>>> - * Copyright IBM Corp, 2014-2016
>>>> + * Copyright IBM Corp, 2014-2017
>>>> *
>>>> * Code derived from Project Kimchi
>>>> *
>>>> @@ -39,6 +39,9 @@
>>>>
>>>> "WOKHOST6001M": "$_("Max:")",
>>>>
>>>> + "WOKAUT0001E": "$_("Session timeout, please re-login.")",
>>>> + "WOKAUT0002E": "$_("Server unreachable")",
>>>> +
>>>> "WOKSETT0001M": "$_("Application")",
>>>> "WOKSETT0002M": "$_("User")",
>>>> "WOKSETT0003M": "$_("Request")",
>>>> diff --git a/ui/pages/login.html.tmpl b/ui/pages/login.html.tmpl
>>>> index f5a4b2d..6f967cf 100644
>>>> --- a/ui/pages/login.html.tmpl
>>>> +++ b/ui/pages/login.html.tmpl
>>>> @@ -1,7 +1,7 @@
>>>> #*
>>>> * Project Wok
>>>> *
>>>> - * Copyright IBM Corp, 2014-2016
>>>> + * Copyright IBM Corp, 2014-2017
>>>> *
>>>> * Code derived from Project Kimchi
>>>> *
>>>> @@ -104,9 +104,7 @@
>>>> <div class="container">
>>>> <div id="login-window" class="login-area row">
>>>> <div class="err-area">
>>>> - <div id="messUserPass" class="alert
>>>> alert-danger" style="display: none;">$_("The username or password
>>>> you entered is incorrect. Please try again.")</div>
>>>> - <div id="messSession" class="alert
>>>> alert-danger" style="display: none;">$_("Session timeout, please
>>>> re-login.")</div>
>>>> - <div id="missServer" class="alert
>>>> alert-danger" style="display: none;">$_("Server unreachable.")</div>
>>>> + <div id="errorArea" class="alert alert-danger"
>>>> style="display: none;"></div>
>>>> </div>
>>>> <form id="form-login" class="form-horizontal"
>>>> method="post">
>>>> <div class="form-group">
>>>
>>
>
More information about the Kimchi-devel
mailing list