[Kimchi-devel] [PATCH v5] [Wok] Bug fix #147: Block authentication request after too many failures

Aline Manera alinefm at linux.vnet.ibm.com
Fri Feb 3 19:45:50 UTC 2017


Ramon,

On 02/03/2017 05:39 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:
>
> v5:
> Friendly error message
> Set maximum tries to 3
>
> v4:
> Use API.json for input validation
>
> 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/API.json         | 25 +++++++++++++++++-
>   src/wok/i18n.py          |  7 +++--
>   src/wok/root.py          | 69 +++++++++++++++++++++++++++++++++++++++++-------
>   ui/js/src/wok.login.js   | 19 ++++++++-----
>   ui/pages/i18n.json.tmpl  |  5 +++-
>   ui/pages/login.html.tmpl |  6 ++---
>   6 files changed, 107 insertions(+), 24 deletions(-)
>
> diff --git a/src/wok/API.json b/src/wok/API.json
> index 8965db9..3f7bfd7 100644
> --- a/src/wok/API.json
> +++ b/src/wok/API.json
> @@ -2,5 +2,28 @@
>       "$schema": "http://json-schema.org/draft-03/schema#",
>       "title": "Wok API",
>       "description": "Json schema for Wok API",
> -    "type": "object"
> +    "type": "object",
> +    "properties": {
> +        "wokroot_login": {
> +            "type": "object",
> +            "properties": {
> +                "username": {
> +                    "description": "Username",
> +                    "required": true,
> +                    "type": "string",
> +                    "minLength": 1,
> +                    "error": "WOKAUTH0003E"
> +                },
> +                "password": {
> +                    "description": "Password",
> +                    "required": true,
> +                    "type": "string",
> +                    "minLength": 1,
> +                    "error": "WOKAUTH0006E"
> +                }
> +            },
> +            "additionalProperties": false,
> +            "error": "WOKAUTH0007E"
> +        }
> +    }
>   }
> diff --git a/src/wok/i18n.py b/src/wok/i18n.py
> index 935c9c1..3cf669e 100644
> --- a/src/wok/i18n.py
> +++ b/src/wok/i18n.py
> @@ -38,10 +38,13 @@ messages = {
>       "WOKASYNC0003E": _("Timeout of %(seconds)s seconds expired while running task '%(task)s."),
>       "WOKASYNC0004E": _("Unable to kill task due error: %(err)s"),
>
> -    "WOKAUTH0001E": _("Authentication failed for user '%(username)s'. [Error code: %(code)s]"),
> +    "WOKAUTH0001E": _("The username or password you entered is incorrect. Please try again"),

Create a new error code to handle the above message and keep the former 
one as is as it is used in auth.py

Then:

>       "WOKAUTH0002E": _("You are not authorized to access Wok. Please, login first."),
> -    "WOKAUTH0003E": _("Specify %(item)s to login into Wok."),
> +    "WOKAUTH0003E": _("Specify username 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": _("Specify password to login into Wok."),
> +    "WOKAUTH0007E": _("You need to specify username and password to login into Wok."),
>
>       "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..c75a44a 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 @@
>       def login(self, *args):
> +        def _raise_timeout(user_id):
> +            length = self.failed_logins[user_ip_sid]["count"]
> +            timeout = (length - 2) * 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')
>
>           try:
>               params = parse_request()
> +            validate_params(params, self, "login")
>               username = params['username']
>               password = params['password']
> -        except KeyError, item:
> -            details = e = MissingParameter('WOKAUTH0003E', {'item': str(item)})
> -            log_request(code, params, details, method, 400)
> -            raise cherrypy.HTTPError(400, e.message)
> +        except WokException, e:
> +            details = e
> +            status = e.getHttpStatusCode()
> +            raise cherrypy.HTTPError(status, 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 - 2) * 30)):
> +                    _raise_timeout(user_ip_sid)
> +                else:
> +                    self.failed_logins.pop(user_ip_sid)
>
>           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.pop(user_ip_sid)
>           except cherrypy.HTTPError, e:
> -            status = e.status
> +
> +            # 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)

>               raise

raise AuthorizationError(<new error code>)

>           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 666a339..9e2a392 100644
>



More information about the Kimchi-devel mailing list