Skip to content

Latest commit

 

History

History
552 lines (401 loc) · 20.4 KB

DEPLOYMENT.md

File metadata and controls

552 lines (401 loc) · 20.4 KB

A+ Deployment Instructions

These instructions are checked against Ubuntu, but they should work for Debian and probably for many derivatives of it.

$ lsb_release -a
Distributor ID: Ubuntu
Description:    Ubuntu 18.04.1 LTS
Release:        18.04
Codename:       bionic

A+ can be deployed with Apache 2 or NGINX. Bot web servers work well enough and can support Shibboleth authentication too. Sadly, combination of NGINX, uWSGI protocol, uWSGI and Shibboleth is not yet tested, but the HTTP proxy version works.

Aalto University is currently running following combinations on Ubuntu 20.04 or Ubuntu 18.04:

  • Apache, uWSGI protocol, uWSGI, Shibboleth, uWSGI logs written to a file
  • NGINX, HTTP proxy, gunicorn, Shibboleth, Gunicorn logs written to journald
  • NGINX, uWSGI protocol, uWSGI, uWSGI logs written to journald

Table of contents

Common system configuration

  1. Install required packages

    sudo apt-get install \
      git gettext \
      postgresql libpq-dev memcached \
      python3-virtualenv \
      python3-certifi python3-lz4 python3-reportlab python3-reportlab-accel \
      build-essential libxml2-dev libxslt-dev python3-dev python3-venv \
      libpcre3 libpcre3-dev
    
  2. Create a new user for a-plus

    sudo adduser --system --group \
      --shell /bin/bash --home /srv/aplus \
      --gecos "A-plus LMS webapp server" \
      aplus
    
  3. Create a database and add permissions

    sudo -Hu postgres createuser aplus
    sudo -Hu postgres createdb -O aplus aplus
    
  4. Create a run directory

    echo "d /run/aplus 0750 aplus www-data - -" \
      | sudo tee /etc/tmpfiles.d/aplus.conf > /dev/null
    sudo systemd-tmpfiles --create
    
  5. Create RSA keys for JWT authentication

    # generate private key
    openssl genrsa -out private.pem 2048
    # extract public key
    openssl rsa -in private.pem -out public.pem -pubout
    

The Application

  1. Change to our service user

    sudo -u aplus -Hi
    
  2. Clone the Django application

    # as user aplus in /srv/aplus
    git clone --branch production https://github.com/apluslms/a-plus.git
    mkdir a-plus/static \
          a-plus/media
    
  3. Install virtualenv

    # as user aplus in /srv/aplus
    python3 -m venv venv
    source ~/venv/bin/activate
    pip install -r a-plus/requirements_prod.txt
    pip install -r a-plus/requirements.txt
    
  4. Configure Django

    # as user aplus in /srv/aplus
    cat > a-plus/aplus/local_settings.py <<EOF
    BASE_URL = "https://$(hostname)/"
    SERVER_EMAIL = "aplus@$(hostname)"
    EOF
    
    awk '/(BASE_URL|DEBUG|SECRET_KEY|SERVER_EMAIL)/ {next};
         /^## (Database|Cache|Logging)/ {while (/^#/ && getline>0); next} 1' \
      a-plus/aplus/local_settings.example.py >> a-plus/aplus/local_settings.py
    
    cat >> a-plus/aplus/local_settings.py <<EOF
    ## Database
    DATABASES = {
      'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'aplus',
      }
    }
    ## Cache
    CACHES = {
      'default': {
        'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
        'LOCATION': '127.0.0.1:11211',
      }
    }
    ## Session
    SESSION_ENGINE = "django.contrib.sessions.backends.cache"
    SESSION_COOKIE_SECURE = True
    EOF
    

    Define the branding settings in aplus/local_settings.py: BRAND_NAME, BRAND_INSTITUTION_NAME, BRAND_INSTITUTION_NAME_FI, etc. Check settings.py and local_settings.example.py.

    Ensure that you use DEBUG = False in production (local_setting.py).

    Fill the APLUS_AUTH settings. Check settings.py and local_settings.example.py.

  5. Run Django deployment tasks

    # as user aplus in /srv/aplus
    . venv/bin/activate
    pushd a-plus
    
    # migrate db
    ./manage.py migrate
    
    # compile localisation files
    ./manage.py compilemessages
    
    # collect static files to be served via dedicated web server
    ./manage.py collectstatic --no-input
    
    popd
    
  6. Create uWSGI configuration

    # as user aplus in /srv/aplus
    cp ~/a-plus/doc/uwsgi-aplus-*.ini ~
    

    NOTE: Select number of processes and threads based on number of of CPUs. Probably a good number is around two times cpus for the web (as there is a lot of io wait) and number of cpus for the API.

    You can gracefully chain reload uWSGI services by touching (editing) above files. In addition, sudo systemctl restart aplus-*-uwsgi.service will also do a chain reload. To fully reload python engine, do stop and then start for these services.

  7. End

    Exit the aplus user shell and return to the admin

    exit
    

Common Shibboleth configuration

To configure Shibboleth, follow a guide for your federation. Because A+ is used a lot in Finnish Universities, we have some examples using HAKA, the identity federation for Finnish universities.

For HAKA, you need at least schemas from here and metadata certificate from here. In addition, you can start from apache2/shibboleth2.xml or nginx/shibboleth2.xml. More details below for both web servers.

Key generation

Here is a command to create sp-key.pem and sp-cert.pem files. As of writing, shib-keygen does create too small keys and uses sha1 for hashing. Due to security considerations, you should use following instead:

# set your domain here and then copy-paste command below
host=$(hostname)
entityid=https://$host

cd /etc/shibboleth
printf '[req]\ndistinguished_name=req\n[san]\nsubjectAltName=DNS:%s, URI:%s\n' "$host" "$entityid" | \
openssl req -x509 -sha256 -nodes \
  -newkey rsa:4096 -keyout sp-key.pem \
  -days 3650 -out sp-cert.pem \
  -subj "/CN=$host" -extensions san -config /dev/stdin
chown _shibd:_shibd sp-cert.pem sp-key.pem
chmod 0400 sp-key.pem

You can print the certificate information with this command:

openssl x509 -in /etc/shibboleth/sp-cert.pem -noout -text

Apache 2 configuration

Resources for Apache 2 configuration can be found under apache2 directory. Following instructions expect that the applocation is installed under /srv/aplus/a-plus/.

  1. Install packages

    sudo apt-get install apache2 libapache2-mod-uwsgi
    sudo a2enmod uwsgi
    sudo a2enmod ssl
    
  2. Configure Apache 2

    sudo ln -s ../sites-available/$(hostname).conf /etc/apache2/sites-enabled/000-$(hostname).conf
    sed -e "s/__HOSTNAME__/$(hostname)/g" /srv/aplus/a-plus/doc/apache2/aplus-apache2.conf \
     | sudo tee /etc/apache2/sites-available/$(hostname).conf > /dev/null
    sudo a2enmod rewrite
    sudo a2dissite 000-default
    
  3. Create systemd service files for uWSGI processes

    sudo cp /srv/aplus/a-plus/doc/apache2/aplus-*-uwsgi.service \
            /etc/systemd/system
    sudo systemctl daemon-reload
    sudo systemctl enable aplus-web-uwsgi.service aplus-api-uwsgi.service
    sudo systemctl start aplus-web-uwsgi.service aplus-api-uwsgi.service
    
  4. Reload Apache 2

    sudo systemctl restart apache2.service
    

Shibboleth with Apache 2

If you are using shibboleth

  1. Install Shibboleth 2

    sudo apt-get install libapache2-mod-shib2
    sudo a2enmod shib2
    
  2. Configure Shibboleth for your federation or organization

    Configuration is under directory /etc/shibboleth. For HAKA, there is apache2/shibboleth2.xml.

  3. Shibboleth configuration in The local_settings.py

    Map your federations variables to ones used in A+. Most of the values are common, so only defining PREFIX should be enough. Currently, STUDENT_DOMAIN is a required variable, as A+ presumes student numbers to be from a single domain. Rest of the options are documented in settings.py.

    sudo tee -a /srv/aplus/a-plus/aplus/local_settings.py << EOF
    # Shibboleth
    SHIBBOLETH_ENVIRONMENT_VARS = {
        'PREFIX': 'SHIB_',
        'STUDENT_DOMAIN': 'example.com', # XXX: change this!
    }
    EOF
    
  4. Reload shibboleth

    sudo systemctl restart shibd.service
    

NGINX configuration

Resources for NGINX configuration can be found under nginx directory. Following instructions expect that the applocation is installed under /srv/aplus/a-plus/.

  1. Install packges

    sudo apt-get install nginx
    
  2. Configure NGINX

    if [ -d /etc/nginx/sites-available ] && grep -qs sites-enabled /etc/nginx/nginx.conf; then
        dest=/etc/nginx/sites-available/$(hostname).conf
        sudo ln -s ../sites-available/$(hostname).conf /etc/nginx/sites-enabled/$(hostname).conf
    else
        dest=/etc/nginx/conf.d/$(hostname).conf
    fi
    sed -e "s/__HOSTNAME__/$(hostname)/g" /srv/aplus/a-plus/doc/nginx/aplus-nginx.conf \
     | sudo tee "$dest" > /dev/null
    openssl dhparam -out /etc/nginx/dhparams.pem 4096
    

    NOTE: If you are going to use shibboleth, then use file nginx/aplus-nginx-shib.conf.

  3. Create systemd service files for uWSGI processes

    sudo cp /srv/aplus/a-plus/doc/nginx/aplus-*-uwsgi.service \
            /etc/systemd/system
    sudo systemctl daemon-reload
    sudo systemctl enable aplus-web-uwsgi.service aplus-api-uwsgi.service
    sudo systemctl start aplus-web-uwsgi.service aplus-api-uwsgi.service
    

    If you prefer to use Gunicorn, then you can use nginx/aplus-gunicorn.service.

  4. Reload NGINX

    sudo systemctl restart nginx.service
    

Shibboleth with NGINX

This guide bases on NGINX module nginx-http-shibboleth. This module uses fastcgi and shibboleth scripts to provide similar integration as Apache 2 plugin. As of March 2022, the latest version of libnginx-mod-http-headers-more-filter is only compatible up to NGINX version 1.18.0 and needs to be built manually for more recent versions of NGINX.

  1. Starting from Ubuntu Bionic or NGINX 1.11 we can use dynamic modules

    # as root
    cd /usr/src
    
    # edit /etc/apt/sources.list if necessary, make sure to enable the
    # -updates repository to get the latest version
    apt-get install build-essential devscripts
    apt-get source nginx
    apt-get build-dep nginx
    
    git clone https://github.com/nginx-shib/nginx-http-shibboleth.git
    git clone https://github.com/openresty/headers-more-nginx-module
    
    pushd nginx-1.*/
    # The module has to be configured using the same arguments as nginx
    NGINX_CONFIGURE_FLAGS=$(nginx -V 2>&1 | grep configure\ arguments | \
      sed 's/configure arguments: //')
    eval $(echo ./configure \
      --add-dynamic-module=../nginx-http-shibboleth \
      --add-dynamic-module=../headers-more-nginx-module \
      $NGINX_CONFIGURE_FLAGS)
    make modules -j$(nproc)
    
    mkdir -p /usr/local/lib/nginx/modules
    chmod 644 \
      objs/ngx_http_shibboleth_module.so \
      objs/ngx_http_headers_more_filter_module.so
    cp objs/ngx_http_shibboleth_module.so \
      objs/ngx_http_headers_more_filter_module.so \
      /usr/local/lib/nginx/modules
    
    mkdir -p /etc/nginx/modules-available
    echo "load_module /usr/local/lib/nginx/modules/ngx_http_shibboleth_module.so;" > \
      /etc/nginx/modules-available/50-mod-http-shibboleth.conf
    echo "load_module /usr/local/lib/nginx/modules/ngx_http_headers_more_filter_module.so;" > \
      /etc/nginx/modules-available/50-mod-http-headers-more.conf
    ln -s ../modules-available/50-mod-http-shibboleth.conf \
      /etc/nginx/modules-enabled/50-mod-http-shibboleth.conf
    ln -s ../modules-available/50-mod-http-headers-more.conf \
      /etc/nginx/modules-enabled/50-mod-http-headers-more.conf
    systemctl restart nginx
    
    Obsolete: With Ubuntu xenial or before NGINX 1.11

    With Ubuntu xenial (16.04) and before NGINX 1.11, dynamic modules are not supported, so you need to rebuild the whole NGINX package.

    sudo -i
    cd /usr/src
    
    apt-get install build-essential devscripts
    apt-get source nginx
    apt-get build-dep nginx
    
    git clone https://github.com/nginx-shib/nginx-http-shibboleth.git
    
    pushd nginx-1.*/
    
    # add shib module to be build: debian/rules
    patch -l -p1 <<PATCH
    --- a/debian/rules
    +++ b/debian/rules
    @@ -98,6 +98,8 @@
       --with-mail \\
       --with-mail_ssl_module \\
       --with-threads \\
    +  --add-module=\$(MODULESDIR)/headers-more-nginx-module \\
    +  --add-module=/usr/src/nginx-http-shibboleth/ \\
       --add-module=\$(MODULESDIR)/nginx-auth-pam \\
       --add-module=\$(MODULESDIR)/nginx-dav-ext-module \\
       --add-module=\$(MODULESDIR)/nginx-echo \\
    @@ -126,6 +128,7 @@
       --with-stream_ssl_module \\
       --with-threads \\
       --add-module=\$(MODULESDIR)/headers-more-nginx-module \\
    +  --add-module=/usr/src/nginx-http-shibboleth/ \\
       --add-module=\$(MODULESDIR)/nginx-auth-pam \\
       --add-module=\$(MODULESDIR)/nginx-cache-purge \\
       --add-module=\$(MODULESDIR)/nginx-dav-ext-module \\
    PATCH
    
    # Create debian release
    dch -lshib "Add Shibboleth"
    dch -r ""
    
    # build debian packages
    dpkg-buildpackage -uc -us
    popd
    
    # install the packages
    dpkg -i $(ls nginx-common_*shib*_all.deb | sort | tail -n1) \
            $(ls nginx-full_*shib*_amd64.deb | sort | tail -n1)
    
    exit # exit sudo session
    
  2. Get NGINX supporting files

    cd /etc/nginx
    wget https://raw.githubusercontent.com/nginx-shib/nginx-http-shibboleth/master/includes/shib_clear_headers
    
  3. Install required packages

    sudo apt-get install \
      shibboleth-sp-common \
      shibboleth-sp-utils \
      xmltooling-schemas
    
  4. Create systemd services and sockets for shibboleth scripts

    sudo cp /srv/aplus/a-plus/doc/nginx/shib*.socket \
            /srv/aplus/a-plus/doc/nginx/shib*.service \
            /etc/systemd/system
    sudo systemctl daemon-reload
    sudo systemctl enable shibauthorizer.socket shibresponder.socket
    sudo systemctl start shibauthorizer.socket shibresponder.socket
    
  5. Configure Shibboleth for your federation or organization

    Configuration is under directory /etc/shibboleth. For HAKA, there is nginx/shibboleth2.xml.

  6. Shibboleth configuration in The local_settings.py

    Map your federations variables to ones used in A+. Most of the values are common, so only defining PREFIX should be enough. Currently, STUDENT_DOMAIN is a required variable, as A+ presumes student numbers to be from a single domain. Rest of the options are documented in settings.py.

    sudo tee -a /srv/aplus/a-plus/aplus/local_settings.py << EOF
    # Shibboleth
    SHIBBOLETH_ENVIRONMENT_VARS = {
        'PREFIX': 'HTTP_SHIB_',
        'STUDENT_DOMAIN': 'example.com', # XXX: change this!
    }
    EOF
    
  7. Reload shibboleth

    sudo systemctl restart shibd.service
    

Final steps

  1. Create or set a superuser.

    It is not typically needed to create local superuser, thus you can skip the first part.

    sudo -Hu aplus sh -c "cd /srv/aplus/a-plus;
      ../venv/bin/python3 ./manage.py createsuperuser"
    

    Instead, it is recommended to setup Shibboleth or Social Auth and login your first admin. After that, you can make an existing user a superuser:

    sudo -Hu aplus sh -c "cd /srv/aplus/a-plus;
      ../venv/bin/python3 ./manage.py set_superuser"
    

    NOTE: You might need to use --first-name, --last-name or --email to limit the number of users.

  2. Create privacy notice, accessibility statement and support page.

    Copy privacy notice, accessibility statement and support page templates.

    sudo -Hu aplus sh -c "
      mkdir -p /srv/aplus/a-plus/local_templates/
      cp /srv/aplus/a-plus/templates/privacy_notice_* \
         /srv/aplus/a-plus/local_templates/
      cp /srv/aplus/a-plus/templates/institution_accessibility_text* \
         /srv/aplus/a-plus/local_templates/
      cp /srv/aplus/a-plus/templates/support_channels* \
         /srv/aplus/a-plus/local_templates/
    "
    

    Edit your local templates in /srv/aplus/a-plus/local_templates/ to match your local information. If you want to translate the content to different languages, you can use language suffixes in the filenames. For example, you can create the following files for the local support page in English and Finnish.

    • local_templates/support_channels_en.html
    • local_templates/support_channels_fi.html

Integration with Student Information System

A+ can be integrated with the organisation's Student Information System (SIS, a system that manages information on courses and its participants). For example, Aalto University, along with a few other Finnish universities use "SISU" for this purpose. The integration with the organisation-specific SIS system is done using a SIS plugin. For example, Aalto's SISU plugin can be found in GitHub. The plugin must implement the interface StudentInfoSystem.

If a SIS plugin is used, and after it is installed, two variables need to be set in local settings to enable it in A+, as in the following example:

SIS_PLUGIN_MODULE = 'course.sis_test'
SIS_PLUGIN_CLASS = 'SisTest'

Variable SIS_PLUGIN_MODULE specifies the Python module path name of the plugin, and SIS_PLUGIN_CLASS is the name of the class implementing the generic SIS interface. The proper values for these, along with the plugin installation instructions, should be described in the plugin's documentation. See the Aalto SISU plugin and its README file for an example. In addition to these parameters, a plugin may have its own plugin-specific additional parameters, as should be described in the plugin installation instructions.

There is an option to periodically update enrollment information from SIS using Celery and Redis. Redis can be installed from dockerhub as a dedicated container. For local testing, for example, the following can be added to docker-compose.yml:

redis:
  image: redis:latest
  command: redis-server --save 60 1 --loglevel warning
  expose:
    - 6379

Periodic updates can be enabled by setting variable SIS_ENROLL_SCHEDULE in the Django settings. For example, the following activates periodic enrollment every night at 1:00. More information about configuring periodic tasks can be found in the Celery documentation.

from celery.schedules import crontab
SIS_ENROLL_SCHEDULE = crontab(hour=1, minute=0)

If this variable is not set, enrollments are not done automatically, but can still be triggered manually by the teacher if SIS is in use.