From 19fe73b388e7a6110c2a0d64ab17049a6540865e Mon Sep 17 00:00:00 2001 From: kaiyou Date: Sun, 29 Oct 2017 18:42:35 +0100 Subject: [PATCH] Implement authentication rate limit, fixes #116 --- .env.dist | 3 +++ admin/mailu/__init__.py | 4 ++++ admin/mailu/internal/views.py | 6 +++++- admin/requirements-prod.txt | 3 +++ admin/requirements.txt | 2 ++ 5 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.env.dist b/.env.dist index 96272524..3d4a82b6 100644 --- a/.env.dist +++ b/.env.dist @@ -33,6 +33,9 @@ POSTMASTER=admin # Choose how secure connections will behave (value: letsencrypt, cert, notls) TLS_FLAVOR=cert +# Authentication rate limit (per source IP address) +AUTH_RATELIMIT=10/minute;1000/hour + ################################### # Optional features ################################### diff --git a/admin/mailu/__init__.py b/admin/mailu/__init__.py index 2aa35b62..6b557a7e 100644 --- a/admin/mailu/__init__.py +++ b/admin/mailu/__init__.py @@ -5,6 +5,7 @@ import flask_login import flask_script import flask_migrate import flask_babel +import flask_limiter import os import docker @@ -35,6 +36,8 @@ default_config = { 'CERTS_PATH': '/certs', 'PASSWORD_SCHEME': 'SHA512-CRYPT', 'WEBMAIL': 'none', + 'AUTH_RATELIMIT': '10/minute;1000/hour', + 'RATELIMIT_STORAGE_URL': 'redis://redis' } # Load configuration from the environment if available @@ -45,6 +48,7 @@ for key, value in default_config.items(): flask_bootstrap.Bootstrap(app) db = flask_sqlalchemy.SQLAlchemy(app) migrate = flask_migrate.Migrate(app, db) +limiter = flask_limiter.Limiter(app, key_func=lambda: current_user.username) # Debugging toolbar if app.config.get("DEBUG"): diff --git a/admin/mailu/internal/views.py b/admin/mailu/internal/views.py index 179b0cf5..dc893b66 100644 --- a/admin/mailu/internal/views.py +++ b/admin/mailu/internal/views.py @@ -1,10 +1,14 @@ -from mailu import db, models +from mailu import db, models, app, limiter from mailu.internal import internal, nginx import flask @internal.route("/auth/email") +@limiter.limit( + app.config["AUTH_RATELIMIT"], + lambda: flask.request.headers["Client-Ip"] +) def nginx_authentication(): """ Main authentication endpoint for Nginx email server """ diff --git a/admin/requirements-prod.txt b/admin/requirements-prod.txt index 60154a90..d99aecda 100644 --- a/admin/requirements-prod.txt +++ b/admin/requirements-prod.txt @@ -15,6 +15,7 @@ Flask==0.12.2 Flask-Babel==0.11.2 Flask-Bootstrap==3.3.7.1 Flask-DebugToolbar==0.10.1 +Flask-Limiter==0.9.5.1 Flask-Login==0.4.0 Flask-Migrate==2.1.1 Flask-Script==2.0.6 @@ -26,6 +27,7 @@ infinity==1.4 intervals==0.8.0 itsdangerous==0.24 Jinja2==2.9.6 +limits==1.2.1 Mako==1.0.7 MarkupSafe==1.0 passlib==1.7.1 @@ -35,6 +37,7 @@ python-dateutil==2.6.1 python-editor==1.0.3 pytz==2017.2 PyYAML==3.12 +redis==2.10.6 requests==2.18.4 six==1.11.0 SQLAlchemy==1.1.14 diff --git a/admin/requirements.txt b/admin/requirements.txt index b2c3849e..3d06b34b 100644 --- a/admin/requirements.txt +++ b/admin/requirements.txt @@ -7,6 +7,8 @@ Flask-migrate Flask-script Flask-wtf Flask-debugtoolbar +Flask-limiter +redis WTForms-Components passlib gunicorn