diff --git a/fabfile.py b/fabfile.py index 2a40444..457b34f 100644 --- a/fabfile.py +++ b/fabfile.py @@ -14,18 +14,12 @@ DEPLOY_ROOT = "/opt" -# Node 18 doesn't seem to work on RHEL 7. -# https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V18.md#toolchain-and-compiler-upgrades -NODE_VERSION = "16" +NODE_VERSION = "18" SQLITE_VERSION = "3390200" SQLITE_BASENAME = f"sqlite-autoconf-{SQLITE_VERSION}" SQLITE_INSTALL_ROOT = f"{DEPLOY_ROOT}/{SQLITE_BASENAME}" -PYTHON_VERSION = "3.6.15" -PYTHON_BASENAME = f"Python-{PYTHON_VERSION}" -PYTHON_INSTALL_ROOT = f"{DEPLOY_ROOT}/{PYTHON_BASENAME}" - SOURCE_PARENT = f"{DEPLOY_ROOT}/cfpb" SOURCE_REPO = "https://github.com/cfpb/website-indexer.git" SOURCE_DIRNAME = "website-indexer" @@ -65,13 +59,6 @@ def configure(conn): ) conn.sudo("yum install -y nodejs") - # Install the Yarn package repository. - # https://classic.yarnpkg.com/lang/en/docs/install/#centos-stable - conn.run( - "curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo | sudo tee /etc/yum.repos.d/yarn.repo" - ) - conn.sudo("yum install -y yarn") - # Install git to be able to clone source code repository. conn.sudo("yum install -y git") @@ -79,41 +66,6 @@ def configure(conn): conn.sudo(f"mkdir -p {DEPLOY_ROOT}") conn.sudo(f"chown -R {conn.user}:{conn.user} {DEPLOY_ROOT}") - # Build and install SQLite (needs to happen before installing Python). - conn.sudo("yum install -y gcc sqlite-devel") - with conn.cd(DEPLOY_ROOT): - conn.run(f"curl -O https://www.sqlite.org/2022/{SQLITE_BASENAME}.tar.gz") - conn.run(f"tar xzf {SQLITE_BASENAME}.tar.gz") - conn.run(f"rm {SQLITE_BASENAME}.tar.gz") - - with conn.cd(SQLITE_INSTALL_ROOT): - conn.run("./configure && make") - - # https://github.com/pyinvoke/invoke/issues/459 - conn.sudo(f'bash -c "cd {SQLITE_INSTALL_ROOT} && make install"') - - # Build and install Python 3. - # This sets /usr/local/bin python and python3 commands to point to Python 3. - # This doesn't update /usr/bin/python (used by sudo) - conn.sudo("yum install -y openssl-devel bzip2-devel libffi-devel") - - with conn.cd(DEPLOY_ROOT): - conn.run( - f"curl -O https://www.python.org/ftp/python/{PYTHON_VERSION}/{PYTHON_BASENAME}.tgz" - ) - conn.run(f"tar xzf {PYTHON_BASENAME}.tgz") - conn.run(f"rm {PYTHON_BASENAME}.tgz") - - with conn.cd(PYTHON_INSTALL_ROOT): - conn.run("LD_RUN_PATH=/usr/local/lib ./configure --enable-optimizations") - - # https://github.com/pyinvoke/invoke/issues/459 - conn.sudo( - f"bash -c " - f'"cd {PYTHON_INSTALL_ROOT} && LD_RUN_PATH=/usr/local/lib make install"' - ) - conn.sudo("ln -sf /usr/local/bin/python3 /usr/local/bin/python") - @task def deploy(conn): @@ -131,8 +83,9 @@ def deploy(conn): # Build the viewer app and update any dependencies. with conn.cd(SOURCE_ROOT): + conn.sudo("corepack enable") conn.run("yarn && yarn build") - conn.run("python -m venv venv") + conn.run("python3 -m venv venv") with conn.prefix("source venv/bin/activate"): conn.run("pip install -r requirements/base.txt") @@ -183,7 +136,7 @@ def deploy(conn): # SELinux: Allow logrotate to write to log files. # This gets persisted to /etc/selinux/targeted/contexts/files/file_contexts.local - conn.sudo(f"semanage fcontext -a -t var_log_t '{LOG_DIR}(/.*)?'") + conn.sudo(f"semanage fcontext -m -t var_log_t '{LOG_DIR}(/.*)?'") # Configure gunicorn to run via systemd. print("Configuring gunicorn service") @@ -218,4 +171,9 @@ def deploy(conn): conn.sudo(f"systemctl restart {SYSTEMD_SERVICE}") conn.sudo(f"systemctl status {SYSTEMD_SERVICE}") + + # Open firewall so gunicorn can run on port 8000. + conn.sudo("firewall-cmd --zone=public --permanent --add-port 8000/tcp") + conn.sudo("firewall-cmd --reload") + print("Done!") diff --git a/settings.py b/settings.py index b706559..51bf03e 100644 --- a/settings.py +++ b/settings.py @@ -7,6 +7,7 @@ For the full list of settings and their values, see https://docs.djangoproject.com/en/3.2/ref/settings/ """ + import os import sys from pathlib import Path @@ -160,3 +161,51 @@ }, }, } + +# Monkey patch hashlib.md5 for FIPS mode compliance on RHEL8. +# http://blog.serindu.com/2019/11/12/django-in-fips-mode/ +import hashlib +import importlib + + +def _non_security_md5(*args, **kwargs): + kwargs["usedforsecurity"] = False + return hashlib.md5(*args, **kwargs) + + +def monkey_patch_md5(modules_to_patch): + """Monkey-patch calls to MD5 that aren't used for security purposes. + + Sets RHEL's custom flag `usedforsecurity` to False allowing MD5 in FIPS mode. + `modules_to_patch` must be an iterable of module names (strings). + Modules must use `import hashlib` and not `from hashlib import md5`. + """ + # Manually load a module as a unique instance + # https://stackoverflow.com/questions/11170949/how-to-make-a-copy-of-a-python-module-at-runtime + HASHLIB_SPEC = importlib.util.find_spec("hashlib") + patched_hashlib = importlib.util.module_from_spec(HASHLIB_SPEC) + HASHLIB_SPEC.loader.exec_module(patched_hashlib) + + patched_hashlib.md5 = _non_security_md5 # Monkey patch MD5 + + # Inject our patched_hashlib for all requested modules + for module_name in modules_to_patch: + module = importlib.import_module(module_name) + module.hashlib = patched_hashlib + + +modules_to_patch = [ + "django.contrib.staticfiles.storage", + "django.core.cache.backends.filebased", + "django.core.cache.utils", + "django.db.backends.utils", + "django.db.backends.sqlite3.base", + "django.utils.cache", +] + +try: + import hashlib + + hashlib.md5() +except ValueError: + monkey_patch_md5(modules_to_patch)