Skip to content

Installation guide with CentOS 7

Dylan Klomparens edited this page Jan 26, 2018 · 30 revisions

This guide is intended for setting up a production instance of NEMO. See the desktop development guide if you just want to try out NEMO and explore its features.

This guide 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 sqlite-devel openssl-devel git
useradd --comment "NEMO" nemo
su nemo

Execute these commands as the nemo user (unprivileged):

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:$PATH
PYTHONPATH=/home/nemo
DJANGO_SETTINGS_MODULE="settings"
export PATH PYTHONPATH 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 (see deployment instructions)

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.hide_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': {
		'OPTIONS': {
			'threaded': True,
		},
		'ENGINE': 'django.db.backends.postgresql',
		'NAME': 'NEMO',
		'USER': 'NEMO',
		'PASSWORD': None,  # Replace this with an actual password. You can generate one using 'nemo generate_secret_key' on the command line.
		'HOST': 'database.example.org',
		'PORT': '5432',
		'CONN_MAX_AGE': 60,  # seconds
	}
}

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'],
}