New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[REF] auth_brute_force: Cover all auth entrypoints #1219
[REF] auth_brute_force: Cover all auth entrypoints #1219
Conversation
According to odoo/odoo#24187 (comment), I monkey-patched Odoo to get this safely working. As you might know, monkey patches affect all databases in the same instance, which can be bad in a SaaS environment. However, the patch is completely harmless in such case. |
Using |
Yikes, tricky tests! It's all ✔️ now, ready to merge IMHO. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes! Very important in terms of GDPR 👍 :
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With users behind the same IP (or bad configured proxies): if a user gets banned, would it put the other's sessions (already logged) down?
@chienandalu Those already logged in would retain their sessions, although new login would get locked, yes. This is a side effect of this addon, docummented in Known Issues. Do you think there would be a better approach on the matter? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it's ok like that :)
I'm gonna think some more on the subject. It could be really problematic. Imagine:
So, the problem is quite big. I think it should be blocked just by user. Then maybe a notification could be sent to him. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just code review, great code!
If someone tested this functionally, considering also the unittest, for me it's ok to merge.
last_ok = self.search( | ||
[('result', '=', 'successfull'), ('remote', '=', remote)], | ||
[('result', '=', 'successfull'), ('remote', '=', remote), | ||
("attempt_date", "<=", until)], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe at least some of the fields used in this search should be setted as index=True
? On a medium/big installation running for years there could be potentially thousands (or even tens or hundreds of thousands) of login attempts to search in..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the other hand, I don't see any interest to keep attempts with a date > X monthes. So maybe a cron to clean this table should be great. (I think there is a generic module that does that task for any table in server-tools.)
But Indeed, set an index=True is a good idea !
I fully understand your use case. But if what you say is implemented, it should be optional. When I wrote this module, it was not designed to block user that doesn't remember their password, but to prevent attack and the only thing we have is the IP. (it's not perfect, it doesn't prevent Distributed attack, but it's a first step). If it is only blocked by user and not by IP (as you suggest), it will generate the following use case : I want to attack a company that use odoo. I see the implementation of the "block by login" as a big regression. Solution 0 (no dev)
-> I don't see any users trying to log in 30 times. After some tries, he will contact the administrator. solution 1
solution 2
-> only one browser is blocked.
solution 3
what do you think ? kind regards. |
Yes, that could be a problem. Adding the user on login attempts is in my opinion an ok solution; it would just mean that a potential attacker could try 10 passwords per user instead of 10 in general; which shouldn't be a problem. |
I think @yajo meant to block just login attempts for that specific user to that IP, not to block the user in general. So the IP is still considered, and you can't use this to block access to odoo (unless you have the same IP as the real user, but in that case it's an internal problem and can't really be avoided..) |
Yep, I think I'm gonna go that path: just block the user+IP combination instead of just the IP. |
How ! I didn't understood ! Yes indeed. An option still is a good implementation. like
It can be a problem. if you have a company with 1000 users, you let an attacker to make an attack with up to 10.000 attempts. he has more chance to succeed, statisticaly. |
Yes, 10000 in total, but still 10 per user. But I get what you mean: if an attacker tries the 10 most common passwords, the probability of at least 1 user in 1000 using it is much higher than the probability of a single specific user using it.
So, for example, block ip 1.1.1.1 for user 'admin' after 5 incorrect attempts for user 'admin', and block 1.1.1.1 in general after 50 consecutive invalid attempts between all users. |
@yajo I was thinking it could be useful to save and show the login attempt channel (web - xmlrpc -jsonrpc). But it's ok to do it in a future PR as to not overload this one. |
You'd be interested in v12 approach, currently in RD phase: odoo/odoo#22212 |
OK Travis is ✔️ now. I digged a lot and couldn't discover the conflict, so I just triggered separate test suites. Shall we squash+merge now? |
We need here the last approval of a PSC 👍 |
* If some users are behind the same Internet Service Provider, if a user is | ||
banned, all the other users will be banned too; | ||
|
||
* If the Odoo server is behind an Apache / NGinx proxy and it is not properly |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incorrect indentation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's because it's a sub-point for previous point.
<data> | ||
|
||
<record id="max_attempt_qty" model="ir.config_parameter"> | ||
<field name="key">auth_brute_force.max_attempt_qty</field> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do we control this now? No change in README?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I updated the README.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@yajo thank you so much for the effort, can't wait to test it out!
Added my 2¢, hopefully some of it will be helpful.
try: | ||
res = json.loads(urlopen(url, timeout=5).read()) | ||
except Exception: | ||
_logger.error( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can use _logger.exception
instead of _logger.error
with exc_info=True
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't want to give so much importance to this failure, which is not such a big problem. In fact, I think I'm gonna lower it to a warning instead 🤔
("remote", "=", remote), | ||
] | ||
last_ok = self.search(domain_good, order='create_date desc', limit=1) | ||
recent_failures = self.search( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: you may also use the .search_count()
shortcut.
auth_brute_force/models/res_users.py
Outdated
_inherit = "res.users" | ||
|
||
# HACK https://github.com/odoo/odoo/issues/24183 | ||
# TODO Remvove when fixed, and use normal odoo.http.request to get details |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: Remvove
auth_brute_force/models/res_users.py
Outdated
"""Update a given auth attempt if we still ignore its result.""" | ||
auth_id = getattr(current_thread(), "auth_attempt_id", False) | ||
if not auth_id: | ||
return dict() # No running auth attempt; nothing to do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: {}
is faster
# Some tests could retain environ from last test and produce fake | ||
# results without this patch | ||
# HACK https://github.com/odoo/odoo/issues/24183 | ||
# TODO Remvove when fixed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: Remvove.
auth_brute_force/views/view.xml
Outdated
<record id="view_res_authentication_attempt_tree" model="ir.ui.view"> | ||
<field name="model">res.authentication.attempt</field> | ||
<field name="arch" type="xml"> | ||
<tree colors="orange: result == 'failed';red: result == 'banned'"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: colors
attribute has been deprecated since v9.
exc_info=True, | ||
) | ||
else: | ||
item.metadata = "\n".join( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't it be remote_metadata
?
from odoo import api, fields, models | ||
from odoo.osv.expression import TRUE_LEAF | ||
|
||
GEOLOCALISATION_URL = u"http://ip-api.com/json/{}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure about pros/cons of existing online service alternatives, but maybe it would be better to use a service which has HTTPS in their free plan, eg. https://ipapi.co/json?
Also, I have tested http://ip-api.com/json/ locally and after a couple of hundreds of requests I got banned (I guess timeout based), so in a real world case I believe this could potentially break if the limit is reached within the time window, resulting in empty metadata?
IIUC this would also be stored upon successful logins? In that case, each new res.authentication.attempt
record could potentially take ~5 seconds? What if we try to fill the metadata asynchronously afterwards, eg. with Cron or connector? This way you could also group records by remote address and reduce the number of requests in this way (now it is repeated for the same IP).
Also, I am wondering why an external service is used here instead of eg. GeoIP?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi. I implemented the call of ip-api some years ago, but I did'nt made a benchmark about all the solutions, so maybe it's not the best solution.
Fill the metadata via a cron could be a great improvment indeed. And using GeoIP too ! (but maybe not the objective of this PR).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with both: this should be improved, but this PR targets other purpose. I added it to known issues.
), | ||
) | ||
# Attempts recorded | ||
with self.cursor() as cr: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Readabillty: These test cases seem quite repetitive, maybe it would be possible to factor out the common parts to reduce code repetition?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly, but the little details make it quite difficult to refactor, and after all repeating code in test cases is not so bad as in other parts of the code.
login, | ||
) | ||
return False | ||
# Check if remote + login combination is banned |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Readability: The # Check if remote is banned
and # Check if remote + login combination is banned
parts share much of the code, might be worth factoring out into separate helper function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
auth_brute_force/__manifest__.py
Outdated
'author': "GRAP, " | ||
"Tecnativa, " | ||
"Odoo Community Association (OCA)", | ||
'website': 'http://www.grap.coop', | ||
'website': 'https://github.com/OCA/server-auth', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But this PR is in serve-tools 🤔
auth_brute_force/README.rst
Outdated
@@ -69,14 +69,15 @@ For further information, please visit: | |||
Known issues / Roadmap | |||
====================== | |||
|
|||
* The ID used to identify a remote request is the IP provided in the request | |||
(key 'REMOTE_ADDR'). | |||
* Remove 🐒 patch for https://github.com/odoo/odoo/issues/24183 when fixed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This issue was closed. Solved for v12
@yajo which is the state of this? OCA need this great work! |
Updated with all remarks attended. |
…1219) To fix OCA/server-tools#1125 I needed to refactor the addon. To whitelist IPs now you use a config parameter, which renders res.banned.remote model unneeded. The fix is affected by odoo/odoo#24183 and will not work until it gets fixed upstream due to the technical limitations implied. [FIX] auth_brute_force: Small typos (OCA/server-tools#1250) - The `whitelisted` field needs to exist in view to be usable. - The correct class is `decoration-danger` for tree views.
…1219) To fix OCA/server-tools#1125 I needed to refactor the addon. To whitelist IPs now you use a config parameter, which renders res.banned.remote model unneeded. The fix is affected by odoo/odoo#24183 and will not work until it gets fixed upstream due to the technical limitations implied. [FIX] auth_brute_force: Small typos (OCA/server-tools#1250) - The `whitelisted` field needs to exist in view to be usable. - The correct class is `decoration-danger` for tree views. [FIX] auth_brute_force: Fix addon requirement computation (OCA/server-tools#1251) Include HACK for odoo/odoo#24833, which explains the false positive problem we were having here: an addon being importable doesn't mean it is installed.
…1219) To fix OCA/server-tools#1125 I needed to refactor the addon. To whitelist IPs now you use a config parameter, which renders res.banned.remote model unneeded. The fix is affected by odoo/odoo#24183 and will not work until it gets fixed upstream due to the technical limitations implied.
To fix OCA#1125 I needed to refactor the addon. To whitelist IPs now you use a config parameter, which renders res.banned.remote model unneeded. The fix is affected by odoo/odoo#24183 and will not work until it gets fixed upstream due to the technical limitations implied.
* '10.0' of https://github.com/OCA/server-tools: (47 commits) [UPD] addons table in README.md [ci skip] [UPD] README.rst module_auto_update: readme fragments module_auto_update: sbidoul as maintainer [FIX] rst syntax errors [10.0][FIX] module_auto_update: Don't set 'to upgrade' on void recordset modules [UPD] addons table in README.md [ci skip] [FIX] auth_brute_force: Avoid storing false login attempts (OCA#1258) [UPD] addons table in README.md [ci skip] [FIX] password_security: Create user as admin in tests [UPD] addons table in README.md [ci skip] [FIX] auth_brute_force: Fix addon requirement computation (OCA#1251) [UPD] addons table in README.md [ci skip] [FIX] auth_brute_force: Small typos [UPD] addons table in README.md [ci skip] [REF] auth_brute_force: Cover all auth entrypoints (OCA#1219) [UPD] addons table in README.md [ci skip] module_auto_update: bump version module_auto_update: check a post condition [UPD] addons table in README.md [ci skip] ...
To fix OCA#1125 I needed to refactor the addon. To whitelist IPs now you use a config parameter, which renders res.banned.remote model unneeded. The fix is affected by odoo/odoo#24183 and will not work until it gets fixed upstream due to the technical limitations implied.
To fix OCA#1125 I needed to refactor the addon. To whitelist IPs now you use a config parameter, which renders res.banned.remote model unneeded. The fix is affected by odoo/odoo#24183 and will not work until it gets fixed upstream due to the technical limitations implied.
To fix OCA#1125 I needed to refactor the addon. To whitelist IPs now you use a config parameter, which renders res.banned.remote model unneeded. The fix is affected by odoo/odoo#24183 and will not work until it gets fixed upstream due to the technical limitations implied.
To fix OCA#1125 I needed to refactor the addon. To whitelist IPs now you use a config parameter, which renders res.banned.remote model unneeded. The fix is affected by odoo/odoo#24183 and will not work until it gets fixed upstream due to the technical limitations implied.
To fix OCA#1125 I needed to refactor the addon. To whitelist IPs now you use a config parameter, which renders res.banned.remote model unneeded. The fix is affected by odoo/odoo#24183 and will not work until it gets fixed upstream due to the technical limitations implied.
…1219) To fix OCA/server-tools#1125 I needed to refactor the addon. To whitelist IPs now you use a config parameter, which renders res.banned.remote model unneeded. The fix is affected by odoo/odoo#24183 and will not work until it gets fixed upstream due to the technical limitations implied.
…1219) To fix OCA/server-tools#1125 I needed to refactor the addon. To whitelist IPs now you use a config parameter, which renders res.banned.remote model unneeded. The fix is affected by odoo/odoo#24183 and will not work until it gets fixed upstream due to the technical limitations implied.
…1219) To fix OCA/server-tools#1125 I needed to refactor the addon. To whitelist IPs now you use a config parameter, which renders res.banned.remote model unneeded. The fix is affected by odoo/odoo#24183 and will not work until it gets fixed upstream due to the technical limitations implied.
…1219) To fix OCA/server-tools#1125 I needed to refactor the addon. To whitelist IPs now you use a config parameter, which renders res.banned.remote model unneeded. The fix is affected by odoo/odoo#24183 and will not work until it gets fixed upstream due to the technical limitations implied.
To fix #1125 I needed to refactor the addon.
The fix is affected by odoo/odoo#24183 and will not work until it gets fixed upstream due to the technical limitations implied.
This PR is implemented assuming odoo/odoo#24187 will be merged and backported.@Tecnativa