From 2f99b1324b20b2d42b38e1e72e56d98037fd2dd6 Mon Sep 17 00:00:00 2001 From: Allie Crevier Date: Fri, 12 Feb 2021 18:27:50 -0800 Subject: [PATCH] add server os eol warning Signed-off-by: Allie Crevier --- .../files/usr.sbin.apache2 | 1 + securedrop/journalist_app/__init__.py | 12 +++- securedrop/journalist_app/os_eol.py | 25 +++++++ securedrop/journalist_templates/base.html | 14 ++-- securedrop/tests/test_journalist.py | 67 +++++++++++++++++++ 5 files changed, 110 insertions(+), 9 deletions(-) create mode 100644 securedrop/journalist_app/os_eol.py diff --git a/install_files/ansible-base/roles/build-securedrop-app-code-deb-pkg/files/usr.sbin.apache2 b/install_files/ansible-base/roles/build-securedrop-app-code-deb-pkg/files/usr.sbin.apache2 index e6a7541c425..a2417247a04 100644 --- a/install_files/ansible-base/roles/build-securedrop-app-code-deb-pkg/files/usr.sbin.apache2 +++ b/install_files/ansible-base/roles/build-securedrop-app-code-deb-pkg/files/usr.sbin.apache2 @@ -176,6 +176,7 @@ /var/www/securedrop/journalist_app/decorators.py r, /var/www/securedrop/journalist_app/forms.py r, /var/www/securedrop/journalist_app/main.py r, + /var/www/securedrop/journalist_app/os_eol.py r, /var/www/securedrop/journalist_app/utils.py r, /var/www/securedrop/journalist_templates/_confirmation_modal.html r, /var/www/securedrop/journalist_templates/_source_row.html r, diff --git a/securedrop/journalist_app/__init__.py b/securedrop/journalist_app/__init__.py index 88cdd759e41..16283ae592d 100644 --- a/securedrop/journalist_app/__init__.py +++ b/securedrop/journalist_app/__init__.py @@ -16,7 +16,7 @@ from crypto_util import CryptoUtil from db import db -from journalist_app import account, admin, api, main, col +from journalist_app import account, admin, api, main, col, os_eol from journalist_app.utils import (get_source, logged_in, JournalistInterfaceSessionInterface, cleanup_expired_revoked_tokens) @@ -56,6 +56,11 @@ def create_app(config: 'SDConfig') -> Flask: def _url_exists(u: str) -> bool: return path.exists(path.join(config.SECUREDROP_DATA_ROOT, u)) + app.config.update( + OS_PAST_EOL=os_eol.is_server_os_past_eol(), + OS_UPCOMING_EOL=os_eol.is_server_os_close_to_eol() + ) + # TODO: Attaching a Storage dynamically like this disables all type checking (and # breaks code analysis tools) for code that uses current_app.storage; it should be refactored app.storage = Storage(config.STORE_DIR, @@ -157,6 +162,11 @@ def setup_g() -> 'Optional[Response]': else: g.organization_name = gettext('SecureDrop') + if app.config['OS_PAST_EOL']: + g.show_os_past_eol_warning = True + elif app.config['OS_UPCOMING_EOL']: + g.show_os_upcoming_eol_warning = True + if request.path.split('/')[1] == 'api': pass # We use the @token_required decorator for the API endpoints else: # We are not using the API diff --git a/securedrop/journalist_app/os_eol.py b/securedrop/journalist_app/os_eol.py new file mode 100644 index 00000000000..b92907ee9b9 --- /dev/null +++ b/securedrop/journalist_app/os_eol.py @@ -0,0 +1,25 @@ +from datetime import date + +FOCAL_VER = "20.04" +XENIAL_EOL_DATE = date(2021, 4, 30) + +with open("/etc/lsb-release", "r") as f: + server_os = f.readlines()[1].split("=")[1].strip("\n") + + +def is_server_os_past_eol() -> bool: + """ + Assumption: Any OS that is not Focal is an earlier version of the OS. + """ + if (server_os != FOCAL_VER and date.today() > XENIAL_EOL_DATE): + return True + return False + + +def is_server_os_close_to_eol() -> bool: + """ + Assumption: Any OS that is not Focal is an earlier version of the OS. + """ + if (server_os != FOCAL_VER and date.today() <= XENIAL_EOL_DATE): + return True + return False diff --git a/securedrop/journalist_templates/base.html b/securedrop/journalist_templates/base.html index a036b10f9d0..071c7b91800 100644 --- a/securedrop/journalist_templates/base.html +++ b/securedrop/journalist_templates/base.html @@ -19,15 +19,13 @@ {% if g.user %} - {% if g.show_v2_onion_eol_warning %} -
- {{ gettext('Update Required  Set up v3 Onion Services before April 30 to keep your SecureDrop servers online. Please contact your administrator. Learn More') }} + {% if g.show_os_past_eol_warning %} +
+ {{ gettext ('Critical Security:  The operating system used by your SecureDrop servers has reached its end-of-life. A manual update is required to re-enable the Source Interface and remain safe. Learn More') }}
- {% endif %} - - {% if g.show_v2_onion_migration_warning %} -
- {{ gettext('Update Required  Complete the v3 Onion Services setup before April 30. Please contact your administrator. Learn More') }} + {% elif g.show_os_upcoming_eol_warning %} +
+ {{ gettext ('Critical Security:  The operating system used by your SecureDrop servers is approaching its end-of-life on April 30, 2021. A manual update is urgently required to remain safe. Learn More') }}
{% endif %} diff --git a/securedrop/tests/test_journalist.py b/securedrop/tests/test_journalist.py index 9529061fd01..c4f241ae721 100644 --- a/securedrop/tests/test_journalist.py +++ b/securedrop/tests/test_journalist.py @@ -64,6 +64,73 @@ def _login_user(app, username, password, otp_secret): assert hasattr(g, 'user') # ensure logged in +def test_user_sees_os_warning_if_server_past_eol(config, journalist_app, test_journo): + journalist_app.config.update(OS_PAST_EOL=True, OS_UPCOMING_EOL=False) + with journalist_app.test_client() as app: + _login_user( + app, + test_journo['username'], + test_journo['password'], + test_journo['otp_secret']) + + resp = app.get(url_for('main.index')) + + text = resp.data.decode('utf-8') + assert 'id="os-eol-reached"' in text, text + assert 'id="os-eol-upcoming"' not in text, text + + +def test_user_sees_os_warning_if_server_past_eol_sanity_check(config, journalist_app, test_journo): + """ + Sanity check (both conditions cannot be True but test guard against developer error) + """ + journalist_app.config.update(OS_PAST_EOL=True, OS_UPCOMING_EOL=True) + with journalist_app.test_client() as app: + _login_user( + app, + test_journo['username'], + test_journo['password'], + test_journo['otp_secret']) + + resp = app.get(url_for('main.index')) + + text = resp.data.decode('utf-8') + assert 'id="os-eol-reached"' in text, text + assert 'id="os-eol-upcoming"' not in text, text + + +def test_user_sees_os_warning_if_server_close_to_eol(config, journalist_app, test_journo): + journalist_app.config.update(OS_PAST_EOL=False, OS_UPCOMING_EOL=True) + with journalist_app.test_client() as app: + _login_user( + app, + test_journo['username'], + test_journo['password'], + test_journo['otp_secret']) + + resp = app.get(url_for('main.index')) + + text = resp.data.decode('utf-8') + assert 'id="os-eol-reached"' not in text, text + assert 'id="os-eol-upcoming"' in text, text + + +def test_user_does_not_see_os_warning_if_server_is_current(config, journalist_app, test_journo): + journalist_app.config.update(OS_PAST_EOL=False, OS_UPCOMING_EOL=False) + with journalist_app.test_client() as app: + _login_user( + app, + test_journo['username'], + test_journo['password'], + test_journo['otp_secret']) + + resp = app.get(url_for('main.index')) + + text = resp.data.decode('utf-8') + assert 'id="os-eol-reached"' not in text, text + assert 'id="os-eol-upcoming"' not in text, text + + def test_user_with_whitespace_in_username_can_login(journalist_app): # Create a user with whitespace at the end of the username with journalist_app.app_context():