Skip to content

Installation guide with CentOS 7

Mathieu Rampant edited this page Apr 9, 2020 · 30 revisions

This guide is intended for setting up a production instance of NEMO, and assumes you have the CentOS 7 operating system installed, and nothing else.

You will need root privileges on the server to proceed.

Recommended environment

The following components are recommended for running NEMO, and the installation process for each one will be explained.

  • CentOS 7 (operating system)
  • Python 3.6
  • Gunicorn (Python WSGI server)
  • Nginx (static content server and reverse proxy)
  • SQLite (database)

There is nothing to prevent you from using NEMO in a different environment, however, the recommended components are tested and known to work well.

Install the Python 3.6 interpreter

Download and install the following packages in order to compile Python. Also create a nemo user which will run the NEMO application and web server.

Execute these commands with root privileges:

yum --assumeyes install gcc wget openssl-devel git unzip make
useradd --comment "NEMO" nemo
su nemo

Execute these commands as the nemo user (unprivileged):

To compile and install SQLite... (the version in centos isn't compatible with django 2.2)

cd /home/nemo
wget https://www.sqlite.org/2019/sqlite-autoconf-3290000.tar.gz
tar -xzf sqlite-autoconf-3290000.tar.gz
cd /home/nemo/sqlite-autoconf-3290000
./configure --prefix=/home/nemo/sqlite
make
make install
rm -rf /home/nemo/sqlite-autoconf-3290000 /home/nemo/sqlite-autoconf-3290000.tar.gz

To compile and install Python...

cd /home/nemo
wget https://www.python.org/ftp/python/3.6.4/Python-3.6.4.tgz
tar xf Python-3.6.4.tgz
cd Python-3.6.4/
./configure --prefix=/home/nemo/python
make
make install
cd /home/nemo
rm -rf Python-3.6.4 Python-3.6.4.tgz

Python is now installed in the /home/nemo/python/ directory.

Configure environment variables

You'll need to set some environment variables to specify the location of the Python interpreter, and where NEMO should read its settings from.

All Django projects (including NEMO) read in their settings from one important file: settings.py. The Django documentation states you should specify the settings location using the environment variable DJANGO_SETTINGS_MODULE. The value of DJANGO_SETTINGS_MODULE should be in Python path syntax.

Additionally, the settings file should be on the Python import search path. You will probably also need to set PYTHONPATH to the directory that contains the settings file. For example, if the file settings.py resides in the directory /home/nemo/, you would set PYTHONPATH=/home/nemo and DJANGO_SETTINGS_MODULE=settings.

Edit /home/nemo/.bashrc to set environment variables to point to the correct Python interpreter and settings by adding:

PATH=/home/nemo/python/bin:/home/nemo/nginx:/home/nemo/sqlite/bin:$PATH
PYTHONPATH=/home/nemo
LD_LIBRARY_PATH=/home/nemo/sqlite/lib
LD_RUN_PATH=/home/nemo/sqlite/lib
DJANGO_SETTINGS_MODULE=settings
export PATH PYTHONPATH LD_LIBRARY_PATH LD_RUN_PATH DJANGO_SETTINGS_MODULE 

Refresh the environment variables by closing the bash terminal and reopening it as the nemo user. Ensure the proper Python interpreter is available in your PATH environment variable using which python3... the first line of output should be /home/nemo/python/bin.

Install NEMO and Gunicorn

pip3 install git+https://github.com/usnistgov/NEMO.git gunicorn

Runtime information and settings customization

Below is a template for NEMO settings that would be suitable for production. The settings must be customized appropriately for your organization. This is the single most important file for NEMO to run properly, and you should take your time to ensure it is correct. Furthermore, it's probably the most likely place where things can go wrong when configured improperly. So grab a coffee, take your time, and be thorough when crafting this file for your organization. In order to make things easier, several methods are described on the Initialization wiki page to test your configuration and ensure it's working properly.

The settings reference particular locations on disk that must exist, and external services that must be available for NEMO to work properly. A single, consolidated directory that contains all NEMO runtime information is recommended. Here is the suggested directory strcuture and contents:

/home/nemo/
|
|--- logs/                        # Optional: store all log files. (Recommended approach: don't store logs locally... instead, send them to a central logging server via syslog so your disk never overflows)
|--- media/                       # Images and files uploaded to NEMO are stored here
|--- nginx/                       # Reverse proxy and static content server
|    |--- configuration           # Configuration file for Nginx
|    |--- nginx                   # Nginx binary executable
|--- python/                      # Python 3.6+ interpreter with NEMO package installed
|--- secrets/                     # Contains all passwords, certificates, and keys that NEMO uses
|    |--- nemo.example.org.key    # Private TLS key used for encryption
|    |--- nemo.example.org.crt    # Public TLS certificate, signed by a certificate authority
|    |--- Other certificates      # Other certificates, such as public TLS certs for email or LDAPS authentication
|--- static/                      # JavaScript, images, and CSS
|--- settings.py                  # NEMO settings file
|--- sqlite.db                    # SQLite database - this is automatically created by NEMO

Create a logs directory if you intend to store logs on the web server. Alternatively, you can configure NEMO to send logs to syslog, and redirect the output to a centralized log server... however, this is outside the scope installing NEMO and is not discussed here.

Create directories media, static, and secrets. Each of these directories will be referenced in the settings.py file.

settings.py template for production deployment of NEMO

Copy and paste this template into your settings.py file, the customize it to suit your organization.

# -------------------- Django settings for NEMO --------------------
# Customize these to suit your needs. Documentation can be found at:
# https://docs.djangoproject.com/en/1.11/ref/settings/

# Core settings
# DANGER: SETTING "DEBUG = True" ON A PRODUCTION SYSTEM IS EXTREMELY DANGEROUS.
# ONLY SET "DEBUG = True" FOR DEVELOPMENT AND TESTING!!!
DEBUG = False
AUTH_USER_MODEL = 'NEMO.User'
WSGI_APPLICATION = 'NEMO.wsgi.application'
ROOT_URLCONF = 'NEMO.urls'

# Information security
SESSION_COOKIE_AGE = 2419200  # 2419200 seconds == 4 weeks
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_AGE = None
CSRF_USE_SESSIONS = False
X_FRAME_OPTIONS = 'DENY'
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_SECONDS = 15768000
SECURE_SSL_REDIRECT = True

# Authentication
LOGIN_URL = 'login'
LOGIN_REDIRECT_URL = 'login'

# Date and time formats
DATETIME_FORMAT = "l, F jS, Y @ g:i A"
DATE_FORMAT = "m/d/Y"
TIME_FORMAT = "g:i A"
DATETIME_INPUT_FORMATS = ['%m/%d/%Y %I:%M %p']
DATE_INPUT_FORMATS = ['%m/%d/%Y']
TIME_INPUT_FORMATS = ['%I:%M %p']

USE_I18N = False
USE_L10N = False
USE_TZ = True

INSTALLED_APPS = [
	'django.contrib.auth',
	'django.contrib.contenttypes',
	'django.contrib.sessions',
	'django.contrib.messages',
	'django.contrib.staticfiles',
	'django.contrib.admin',
	'django.contrib.humanize',
	'NEMO',
	'rest_framework',
	'django_filters',
]

MIDDLEWARE = [
	'django.middleware.security.SecurityMiddleware',
	'django.middleware.common.CommonMiddleware',
	'django.contrib.sessions.middleware.SessionMiddleware',
	'django.middleware.csrf.CsrfViewMiddleware',
	'django.contrib.auth.middleware.AuthenticationMiddleware',
	'django.contrib.auth.middleware.RemoteUserMiddleware',
	'django.contrib.messages.middleware.MessageMiddleware',
	'django.middleware.clickjacking.XFrameOptionsMiddleware',
	'django.middleware.common.BrokenLinkEmailsMiddleware',
	'NEMO.middleware.DeviceDetectionMiddleware',
]

TEMPLATES = [
	{
		'BACKEND': 'django.template.backends.django.DjangoTemplates',
		'APP_DIRS': True,
		'OPTIONS': {
			'context_processors': [
				'NEMO.context_processors.show_logout_button',  # Add a 'request context processor' in order to figure out whether to display the logout button. If the site is configured to use the LDAP authentication backend then we want to provide a logoff button (in the menu bar). Otherwise the Kerberos authentication backend is used and no logoff button is necessary.
				'NEMO.context_processors.device',  # Informs the templating engine whether the template is being rendered for a desktop or mobile device.
				'django.contrib.auth.context_processors.auth',
				'django.template.context_processors.debug',
				'django.template.context_processors.media',
				'django.template.context_processors.static',
				'django.template.context_processors.tz',
				'django.contrib.messages.context_processors.messages',
			],
		},
	},
]


# -------------------- Third party Django addons for NEMO --------------------
# These are third party capabilities that NEMO employs. They are documented on
# the respective project sites. Only customize these if you know what you're doing.

# Django REST framework:
# http://www.django-rest-framework.org/
REST_FRAMEWORK = {
	'DEFAULT_PERMISSION_CLASSES': ('NEMO.permissions.BillingAPI',),
	'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',),
	'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
	'PAGE_SIZE': 1000,
}


# ------------ Organization specific settings (officially supported by Django) ------------
# Customize these to suit your needs. Documentation can be found at:
# https://docs.djangoproject.com/en/1.11/ref/settings/

ALLOWED_HOSTS = [
	'nemo.example.org',
]

SERVER_EMAIL = 'NEMO Server Administrator <nemo.admin@example.org>'

ADMINS = [
	('System administrator', 'sysadmin@example.org'),
]
MANAGERS = ADMINS

EMAIL_HOST = 'mail.example.org'
EMAIL_PORT = 25

TIME_ZONE = 'America/New_York'

DATABASES = {
	'default': {
		'ENGINE': 'django.db.backends.sqlite3',
		'NAME': '/home/nemo/sqlite.db',
	}
}

STATIC_ROOT = '/home/nemo/static/'
STATIC_URL = '/static/'
MEDIA_ROOT = '/home/nemo/media/'
MEDIA_URL = '/media/'

# Make this unique, and don't share it with anybody.
SECRET_KEY = None  # Replace this with an actual secret key. You can generate one using 'nemo generate_secret_key' on the command line.

LOGGING = {
	'version': 1,
	'disable_existing_loggers': False,
	'handlers': {
		'mail_admins': {
			'level': 'INFO',
			'class': 'django.utils.log.AdminEmailHandler'
		},
		'error_file': {
			'level': 'WARNING',
			'class': 'logging.FileHandler',
			'filename': '/home/nemo/logs/django_error.log'
		},
		'security_file': {
			'level': 'INFO',
			'class': 'logging.FileHandler',
			'filename': '/home/nemo/logs/django_security.log'
		},
	},
	'loggers': {
		'django.request': {
			'handlers': ['mail_admins', 'error_file'],
			'level': 'WARNING',
			'propagate': True,
		},
		'django.security': {
			'handlers': ['mail_admins', 'security_file'],
			'level': 'WARNING',
			'propagate': True,
		},
	}
}


# ------------ Organization specific settings (NEMO specific; NOT supported by Django) ------------
# Customize these to suit your needs

# When true, all available URLs and NEMO functionality is enabled.
# When false, conditional URLs are removed to reduce the attack surface of NEMO.
# Reduced functionality for NEMO is desirable for the public facing version
# of the site in order to mitigate security risks.
ALLOW_CONDITIONAL_URLS = True

# There are two options to authenticate users:
#   1) A decoupled "REMOTE_USER" method (such as Kerberos authentication from a reverse proxy)
#   2) LDAP authentication from NEMO itself
AUTHENTICATION_BACKENDS = ['NEMO.views.authentication.LDAPAuthenticationBackend']

# Specify your list of LDAP authentication servers only if you choose to use LDAP authentication
LDAP_SERVERS = [
	{
		'url': 'ldap.another.org',
		'domain': 'ANOTHER',
		'certificate': '/home/nemo/secrets/root.crt',
	},
	{
		'url': 'ldap.example.org',
		'domain': 'EXAMPLE',
		'certificate': '/home/nemo/secrets/root.crt',
	},
]

# NEMO can integrate with a custom Identity Service to manage user accounts on
# related computer systems, which streamlines user onboarding and offboarding.
IDENTITY_SERVICE = {
	'available': False,
	'url': 'https://identity.example.org/',
	'domains': ['EXAMPLE', 'ANOTHER'],
}

Collect static files

NEMO comes with static files, such as JavaScript and CSS, to improve the look and feel of the website. There are also static files included with the Django web framework, used in the /admin/ section of the site. These static files are dispersed throughout the source tree, and can be collected into a single place by specifying STATIC_ROOT in settings.py. These files are normally collected into a central location and served by a reverse proxy and static content server. The recommended default location is STATIC_ROOT = '/nemo/static/'. Then collect the files using this command:

django-admin collectstatic

Create the database

Now that NEMO has been installed and configured, you'll need to create the database. An SQLite database will be suitable for most organizations. If your organization has hundreds of concurrent NEMO users then consider using PostgreSQL. The procedure to construct the database is the same for SQLite and Postgres:

django-admin makemigrations NEMO
django-admin migrate

If you are using SQLite, the file /home/nemo/sqlite.db should now exist.

Create a super user

You will need to log in to NEMO in order to access and manage it. Create a "super user" with this command:

django-admin createsuperuser

You will be prompted for a username, first name, last name, email address, and password. Enter the appropriate information. Note, that even though you enter a password, NEMO is designed to not store passwords in the database, therefore the password you enter is discarded. It will not work when you try to log in. NEMO relies exclusively on external authentication sources (such as LDAP or Kerberos) for authentication. Usernames are stored in NEMO, and these are authenticated against the external authentication source(s). So, your NEMO username must match the username of the external authentication source.

Install Nginx

The Gunicorn documentation recommends using Nginx as a reverse proxy and static content server, and this guide follows that reference architecture. Installing Nginx will ensure you have a robust and secure production deployment of NEMO.

Navigate to the NEMO home directory:

cd /home/nemo

Use the script below to install Nginx at /home/nemo/nginx. You can copy and paste it into a file called compile_nginx.sh, make it executable with chmod 700 compile_nginx.sh, then run it with ./compile_nginx.sh. (Note: eventually, the ability to download, compile, and deploy Nginx will be built into the command line nemo executable).

NGINX_VERSION=1.12.2
OPENSSL_VERSION=1.1.0f

set -e # Exit the script if an error is encountered

rm -rf build
mkdir build
cd build

wget https://nginx.org/download/nginx-$NGINX_VERSION.tar.gz
tar xf nginx-$NGINX_VERSION.tar.gz
cd nginx-$NGINX_VERSION

wget https://www.openssl.org/source/openssl-$OPENSSL_VERSION.tar.gz
tar xf openssl-$OPENSSL_VERSION.tar.gz
mv openssl-$OPENSSL_VERSION $OPENSSL_VERSION

wget https://github.com/stnoonan/spnego-http-auth-nginx-module/archive/master.zip
unzip master.zip
mv spnego-http-auth-nginx-module-master spnego-http-auth-nginx-module

./configure                                 \
--prefix=.                                  \
--sbin-path=nginx                           \
--conf-path=configuration                   \
--pid-path=process_identifier               \
--error-log-path=error_log                  \
--http-log-path=http_log                    \
--user=nobody                               \
--group=nobody                              \
--without-http_gzip_module                  \
--without-http_rewrite_module               \
--with-cc-opt="-O2"                         \
--with-http_ssl_module                      \
--with-openssl=$OPENSSL_VERSION             \
--add-module=spnego-http-auth-nginx-module

make

cd ../..
rm -rf nginx
mkdir nginx
cp build/nginx-$NGINX_VERSION/objs/nginx nginx/
touch nginx/configuration
rm -rf build

Grant Nginx the ability to bind to port 443 with this command:

sudo setcap cap_net_bind_service=+eip /home/nemo/nginx/nginx

Now that Nginx is installed, edit the configuration file (that was automatically created) at /home/nemo/nginx/configuration. You can use this configuration:

user nemo nemo;

worker_processes 2; # The number of processes should be less than or equal to the number of actual CPUs
worker_priority 15; # renice workers to reduce priority compared to system processes for machine health. Worst case nginx will get ~25% system resources at nice=15

events {
        worker_connections 1024;
}

error_log /home/nemo/logs/nginx/main_error.log;

http {
        error_log /home/nemo/logs/nginx/http_error.log;
        server_tokens off; # Don't send the nginx version number in error pages and server header

        # Timeouts, do not keep connections open longer then necessary to reduce resource usage and deny Slowloris type attacks.
        client_body_timeout 4s; # Maximum time between packets the client can pause when sending nginx any data
        client_header_timeout 4s; # Maximum time the client has to send the entire header to nginx
        keepalive_timeout 75s; # Timeout which a single keep-alive client connection will stay open
        send_timeout 24s; # Maximum time between packets nginx is allowed to pause when sending the client data

        log_format meaningful '$time_iso8601 $remote_addr $request_method $request_uri "$http_user_agent" $http_referer $request_length $bytes_sent $request_time';

        ssl_protocols TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
        ssl_session_timeout 1d;
        ssl_session_cache shared:SSL:50m;
        ssl_session_tickets off;

        types {
                application/javascript                js;
                text/css                              css;
                image/x-icon                          ico;
                text/plain                            txt;
                application/json                      map;
                application/font-woff                 woff;
                application/font-woff2                woff2;
                application/vnd.ms-fontobject         eot;
                application/x-font-ttf                ttf;
                image/svg+xml                         svg;
                image/png                             png;
        }

        server {
                server_name nemo.example.org;
                listen 0.0.0.0:443 ssl;

                location / {
                        proxy_set_header Host $host;
                        proxy_set_header X-Forwarded-Host $server_name;
                        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                        proxy_set_header X-Forwarded-Proto https;
                        proxy_set_header X-Real-IP $remote_addr;
                        proxy_pass http://127.0.0.1:8000;
                }

                location /static/ { alias /home/nemo/static/; }
                location /favicon.ico { alias /home/nemo/static/favicon.ico; }

                ssl_certificate /home/nemo/secrets/nemo.example.org.crt;
                ssl_certificate_key /home/nemo/secrets/nemo.example.org.key;

                access_log /home/nemo/logs/nginx/proxy_server_access.log meaningful;
                error_log /home/nemo/logs/nginx/proxy_server_error.log info;
        }
}

TODO: this guide is not yet complete. More to come on running and testing NEMO...