Skip to content
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

Add support for RHEL8 #89

Merged
merged 2 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 9 additions & 51 deletions fabfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use Node 20 or is that crazy talk?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not crazy at all, I will try it.


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"
Expand Down Expand Up @@ -65,55 +59,13 @@ 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")

# Set up deploy root and grant permissions to deploy user.
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):
Expand All @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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!")
49 changes: 49 additions & 0 deletions settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Loading