mirror of
https://github.com/spiral-project/ihatemoney.git
synced 2025-04-28 17:32:38 +02:00
Implement security best practices using Flask-Talisman
This commit is contained in:
parent
7554842b1f
commit
e626a1cbea
9 changed files with 64 additions and 2 deletions
|
@ -9,6 +9,7 @@ This document describes changes between each past release.
|
||||||
Breaking changes
|
Breaking changes
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
- Enable session cookie security by default (#845)
|
||||||
- Drop support for Python 2 (#483)
|
- Drop support for Python 2 (#483)
|
||||||
- Drop support for Python 3.5 (#571)
|
- Drop support for Python 3.5 (#571)
|
||||||
- Drop support for MySQL (#743)
|
- Drop support for MySQL (#743)
|
||||||
|
@ -25,6 +26,7 @@ Security
|
||||||
|
|
||||||
- Add CSRF validation on destructive actions (#796)
|
- Add CSRF validation on destructive actions (#796)
|
||||||
- Ask for private code to delete project or project history (#796)
|
- Ask for private code to delete project or project history (#796)
|
||||||
|
- Add headers to mitigate Clickjacking, XSS, and other attacks: `X-Frame-Options`, `X-XSS-Protection`, `X-Content-Type-Options`, `Content-Security-Policy`, `Referrer-Policy` (#845)
|
||||||
|
|
||||||
Added
|
Added
|
||||||
-----
|
-----
|
||||||
|
|
|
@ -21,6 +21,7 @@ ADMIN_PASSWORD = '$ADMIN_PASSWORD'
|
||||||
ALLOW_PUBLIC_PROJECT_CREATION = $ALLOW_PUBLIC_PROJECT_CREATION
|
ALLOW_PUBLIC_PROJECT_CREATION = $ALLOW_PUBLIC_PROJECT_CREATION
|
||||||
ACTIVATE_ADMIN_DASHBOARD = $ACTIVATE_ADMIN_DASHBOARD
|
ACTIVATE_ADMIN_DASHBOARD = $ACTIVATE_ADMIN_DASHBOARD
|
||||||
BABEL_DEFAULT_TIMEZONE = "$BABEL_DEFAULT_TIMEZONE"
|
BABEL_DEFAULT_TIMEZONE = "$BABEL_DEFAULT_TIMEZONE"
|
||||||
|
SESSION_COOKIE_SECURE = $SESSION_COOKIE_SECURE
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Start gunicorn without forking
|
# Start gunicorn without forking
|
||||||
|
|
|
@ -64,6 +64,21 @@ of the secret key could easily access any project and bypass the private code ve
|
||||||
- **Production value:** `ihatemoney conf-example ihatemoney.cfg` sets it to
|
- **Production value:** `ihatemoney conf-example ihatemoney.cfg` sets it to
|
||||||
something random, which is good.
|
something random, which is good.
|
||||||
|
|
||||||
|
`SESSION_COOKIE_SECURE`
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
A boolean that controls whether the session cookie will be marked "secure".
|
||||||
|
If this is the case, browsers will refuse to send the session cookie over plain HTTP.
|
||||||
|
|
||||||
|
- **Default value:** ``True``
|
||||||
|
- **Production value:** ``True`` if you run your service over HTTPS, ``False`` if you run
|
||||||
|
your service over plain HTTP.
|
||||||
|
|
||||||
|
Note: this setting is actually interpreted by Flask, see the
|
||||||
|
`Flask documentation`_ for details.
|
||||||
|
|
||||||
|
.. _Flask documentation: https://flask.palletsprojects.com/en/2.0.x/config/#SESSION_COOKIE_SECURE
|
||||||
|
|
||||||
`MAIL_DEFAULT_SENDER`
|
`MAIL_DEFAULT_SENDER`
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
|
|
|
@ -104,12 +104,20 @@ You can create a ``settings.cfg`` file, with the following content::
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
SQLACHEMY_ECHO = DEBUG
|
SQLACHEMY_ECHO = DEBUG
|
||||||
|
|
||||||
You can also set the `TESTING` flag to `True` so no mails are sent
|
|
||||||
(and no exception is raised) while you're on development mode.
|
|
||||||
Then before running the application, declare its path with ::
|
Then before running the application, declare its path with ::
|
||||||
|
|
||||||
export IHATEMONEY_SETTINGS_FILE_PATH="$(pwd)/settings.cfg"
|
export IHATEMONEY_SETTINGS_FILE_PATH="$(pwd)/settings.cfg"
|
||||||
|
|
||||||
|
You can also set the ``TESTING`` flag to ``True`` so no mails are sent
|
||||||
|
(and no exception is raised) while you're on development mode.
|
||||||
|
|
||||||
|
In some cases, you may need to disable secure cookies by setting
|
||||||
|
``SESSION_COOKIE_SECURE`` to ``False``. This is needed if you
|
||||||
|
access your dev server over the network: with the default value
|
||||||
|
of ``SESSION_COOKIE_SECURE``, the browser will refuse to send
|
||||||
|
the session cookie over insecure HTTP, so many features of Ihatemoney
|
||||||
|
won't work (project login, language change, etc).
|
||||||
|
|
||||||
.. _contributing-developer:
|
.. _contributing-developer:
|
||||||
|
|
||||||
Contributing as a developer
|
Contributing as a developer
|
||||||
|
|
|
@ -65,6 +65,17 @@ If so, pick the ``pip`` commands to use in the relevant section(s) of
|
||||||
|
|
||||||
Then follow :ref:`general-procedure` from step 1. in order to complete the update.
|
Then follow :ref:`general-procedure` from step 1. in order to complete the update.
|
||||||
|
|
||||||
|
Disable session cookie security if running over plain HTTP
|
||||||
|
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
|
.. note:: If you are running Ihatemoney over HTTPS, no special action is required.
|
||||||
|
|
||||||
|
Session cookies are now marked "secure" by default to increase security.
|
||||||
|
|
||||||
|
If you run Ihatemoney over plain HTTP, you need to explicitly disable this security
|
||||||
|
feature by setting ``SESSION_COOKIE_SECURE`` to ``False``, see :ref:`configuration`.
|
||||||
|
|
||||||
|
|
||||||
Switch to MariaDB >= 10.3.2 instead of MySQL
|
Switch to MariaDB >= 10.3.2 instead of MySQL
|
||||||
++++++++++++++++++++++++++++++++++++++++++++
|
++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
|
|
|
@ -38,3 +38,7 @@ ACTIVATE_ADMIN_DASHBOARD = False
|
||||||
# You can change the timezone used to display time. By default it will be
|
# You can change the timezone used to display time. By default it will be
|
||||||
#derived from the server OS.
|
#derived from the server OS.
|
||||||
#BABEL_DEFAULT_TIMEZONE = "Europe/Paris"
|
#BABEL_DEFAULT_TIMEZONE = "Europe/Paris"
|
||||||
|
|
||||||
|
# Enable secure cookies. Requires HTTPS. Disable if you run your ihatemoney
|
||||||
|
# service over plain HTTP.
|
||||||
|
SESSION_COOKIE_SECURE = True
|
||||||
|
|
|
@ -8,6 +8,7 @@ ACTIVATE_DEMO_PROJECT = True
|
||||||
ADMIN_PASSWORD = ""
|
ADMIN_PASSWORD = ""
|
||||||
ALLOW_PUBLIC_PROJECT_CREATION = True
|
ALLOW_PUBLIC_PROJECT_CREATION = True
|
||||||
ACTIVATE_ADMIN_DASHBOARD = False
|
ACTIVATE_ADMIN_DASHBOARD = False
|
||||||
|
SESSION_COOKIE_SECURE = True
|
||||||
SUPPORTED_LANGUAGES = [
|
SUPPORTED_LANGUAGES = [
|
||||||
"de",
|
"de",
|
||||||
"el",
|
"el",
|
||||||
|
|
|
@ -7,6 +7,7 @@ from flask import Flask, g, render_template, request, session
|
||||||
from flask_babel import Babel, format_currency
|
from flask_babel import Babel, format_currency
|
||||||
from flask_mail import Mail
|
from flask_mail import Mail
|
||||||
from flask_migrate import Migrate, stamp, upgrade
|
from flask_migrate import Migrate, stamp, upgrade
|
||||||
|
from flask_talisman import Talisman
|
||||||
from jinja2 import pass_context
|
from jinja2 import pass_context
|
||||||
from markupsafe import Markup
|
from markupsafe import Markup
|
||||||
import pytz
|
import pytz
|
||||||
|
@ -126,6 +127,24 @@ def create_app(
|
||||||
instance_relative_config=instance_relative_config,
|
instance_relative_config=instance_relative_config,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# If we need to load external JS/CSS/image resources, it needs to be added here, see
|
||||||
|
# https://github.com/wntrblm/flask-talisman#content-security-policy
|
||||||
|
csp = {
|
||||||
|
"default-src": ["'self'"],
|
||||||
|
# We have several inline javascript scripts :(
|
||||||
|
"script-src": ["'self'", "'unsafe-inline'"],
|
||||||
|
"object-src": "'none'",
|
||||||
|
}
|
||||||
|
|
||||||
|
Talisman(
|
||||||
|
app,
|
||||||
|
# Forcing HTTPS is the job of a reverse proxy
|
||||||
|
force_https=False,
|
||||||
|
# This is handled separately through the SESSION_COOKIE_SECURE Flask setting
|
||||||
|
session_cookie_secure=False,
|
||||||
|
content_security_policy=csp,
|
||||||
|
)
|
||||||
|
|
||||||
# If a configuration object is passed, use it. Otherwise try to find one.
|
# If a configuration object is passed, use it. Otherwise try to find one.
|
||||||
load_configuration(app, configuration)
|
load_configuration(app, configuration)
|
||||||
app.wsgi_app = PrefixedWSGI(app)
|
app.wsgi_app = PrefixedWSGI(app)
|
||||||
|
|
|
@ -33,6 +33,7 @@ install_requires =
|
||||||
Flask-Migrate>=2.5.3,<4 # Not following semantic versioning (e.g. https://github.com/miguelgrinberg/flask-migrate/commit/1af28ba273de6c88544623b8dc02dd539340294b)
|
Flask-Migrate>=2.5.3,<4 # Not following semantic versioning (e.g. https://github.com/miguelgrinberg/flask-migrate/commit/1af28ba273de6c88544623b8dc02dd539340294b)
|
||||||
Flask-RESTful>=0.3.9,<1
|
Flask-RESTful>=0.3.9,<1
|
||||||
Flask-SQLAlchemy>=2.4,<3
|
Flask-SQLAlchemy>=2.4,<3
|
||||||
|
Flask-Talisman>=0.8,<1
|
||||||
Flask-WTF>=0.14.3,<1
|
Flask-WTF>=0.14.3,<1
|
||||||
WTForms>=2.3.1,<2.4
|
WTForms>=2.3.1,<2.4
|
||||||
Flask>=2,<3
|
Flask>=2,<3
|
||||||
|
|
Loading…
Reference in a new issue