Skip to content

Commit

Permalink
Improvements to SAML2 authentication.
Browse files Browse the repository at this point in the history
This fixes some issues with openwebwork#2511.  This pull request is built on that
one. That pull request is a nice start, but has some issues.  Thanks to
@ionparticle for starting this work.  One of the most important things
introduced in that pull request is the development identity provider via
SimpleSAMLphp.  That gave me a way to work with this.

Although I liked the idea of using a Mojolicious plugin, that approach
does not fit into the webwork2 course environment system.  So,
unfortunately, that had to be changed.  So this rewrites the SAML2
authentication module to use the usual approach that all of the other
authentication modules use.  There is a `authen_saml2.conf.dist` file
that should be copied to `authen_saml2.conf` to use SAML2
authentication.  All settings for the authentication module are in that
file.

The primary limitation of the plugin approach was that it is not
possible for different courses to use different identity providers.  Or
for one course to use SAML2 authentication and another only use the
basic password authentication (without the need to add a URL parameter).
This is something that is expected of the authentication modules, and is
desirable for multi-institutional servers.

Another problem with the implementation is that is does not work with
`$session_management_via = "key"` set.  The reason that doesn't work is
because the implementation broke the rule of creating and accessing a
session before authentication is completed.  So this reimplementation
uses the database via the "nonce" key hack much like LTI 1.1
authentication does.

The previous implentation also used the `WEBWORK_ROOT_URL` environment
variable in the code.  That is not an environment variable that is
defined for webwork2.  It is only defined for the docker build, and can
not be used in the code.

The previous implementation also does not work with two factor
authentication.  If an identity provider does not provide two factor
authentication, then webwork2's two factor authentication should be
used.  Of course, if the identity provider does provide two factor
authentication, then the two factor authentication can be disabled in
`localOverrides.conf`.

An option to enable service provider initiated logout has also been
added. It is off by default, but if enabled, when the user clicks the
"Log Out" button, a request is sent to the identity provider to also end
its session.

The `README.md` file that was with the plugin code has been moved to the
`docker-config/idp` directory, and is now purely for instructions on how
to use the simpleSAMLphp development instance.  The readme also includes
instructions on how to set up a simpleSAMLphp development instance
without docker.  The documentation on how to configure and use the SAML2
authentication module is now all in the `authen_saml2.conf.dist` file.

Note that the webwork2 service provider certificate files are no longer
directly in the configuration file. Instead file locations are to be
provided.  It didn't really make sense to have the actual certificates
in the configuration file, then write them to a file, which the
Net::SAML2 module would read.  Furthermore, it would be very easy to add
the certificates incorrectly to the configuration file.  With the YAML
file approach if indentation were not correct, then the configuration
file would fail to load.
  • Loading branch information
drgrice1 committed Nov 24, 2024
1 parent 67b89e0 commit 576b862
Show file tree
Hide file tree
Showing 29 changed files with 831 additions and 744 deletions.
127 changes: 127 additions & 0 deletions conf/authen_saml2.conf.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#!perl
################################################################################
# Configuration for using Saml2 authentication.
# To enable this Saml2 authentication, copy this file to conf/authen_saml2.conf
# and uncomment the appropriate lines in localOverrides.conf. The Saml2
# authentication module uses the Net::SAML2 library. The library claims to be
# compatible with a wide range of SAML2 implementations, including Shibboleth.
################################################################################

# Set Saml2 as the authentication module to use.
# Comment out 'WeBWorK::Authen::Basic_TheLastOption' if bypassing Saml2
# authentication is not allowed (see $saml2{bypass_query} below).
$authen{user_module} = [
'WeBWorK::Authen::Saml2',
'WeBWorK::Authen::Basic_TheLastOption'
];

# List of authentication modules that may be used to enter the admin course.
# This is used instead of $authen{user_module} when logging into the admin
# course. Since the admin course provides overall power to add/delete courses,
# access to this course should be protected by the best possible authentication
# you have available to you.
$authen{admin_module} = [
'WeBWorK::Authen::Saml2'
];

# Note that Saml2 authentication can be used in conjunction with webwork's two
# factor authentication. If the identity provider does not provide two factor
# authentication, then it is recommended that you DO use webwork's two factor
# authentication. If the identity provider does provide two factor
# authentication, then you would not want your users need to perform two factor
# authentication twice, so you should disable webwork's two factor
# authentication. The two factor authentication settings are set in
# localOverrides.conf.

# This URL query parameter can be added to the end of a course url to skip the
# saml2 authentication module and go to the next one, for example,
# http://your.school.edu/webwork2/courseID?bypassSaml2=1. Comment out the next
# line to disable this feature.
$saml2{bypass_query} = 'bypassSaml2';

# If $external_auth is 1, and the authentication sequence reaches
# Basic_TheLastOption, then the webwork login screen will show a message
# directing the user to use the external authentication system to login. This
# prevents users from attempting to login in to WeBWorK directly.
$external_auth = 0;

# The $saml2{idps} hash contains names of identity proviers and their SAML2
# metadata URLs that are used by this server. Webwork will request the identity
# provider's metadata from the URL of the $saml2{active_idp} during the
# authentication process. Additional identity providers can also be added for a
# particular course by adding, for example, $saml2{idps}{other_idp} = '...' to
# the course.conf file of the course. Note that the names of the identity
# providers in this hash are used for a directory name in which the metadata and
# certificate for the identity provider are saved. So the names should only
# contain alpha numeric characters and underscores.
$saml2{idps} = {
default => 'http://idp/simplesaml/module.php/saml/idp/metadata',
# Add additional identity providers used by this server below.
#other_idp => 'http://other.idp.server/metadata',
};

# The $saml2{active_idp} is the identity provider in the $saml2{idps} hash that
# will be used. If different identity providers are used for different courses,
# then set $saml2{active_idp} = 'other_idp' in the course.conf file of each
# course.
$saml2{active_idp} = 'default';

# This the id for the webwork2 service provider. This is usually the application
# root URL plus the base path to the service provider.
$saml2{sp}{entity_id} = 'http://localhost:8080/webwork2/saml2';

# This is the organization metadata information for the webwork2 service
# provider. The Saml2 authentication module will generate xml metadata that can
# be obtained by the identity provider for configuration from the URL
# https://webwork.yourschool.edu/webwork2/saml2/metadata if Saml2 authentication
# is enabled site wide. The URL needs to have the courseID URL parameter added
# if Saml2 authentication is not enabled site wide, but is enabled for some
# courses in those course's course.conf files. So for example if one course is
# myTestCourse, then the metadata URL would be
# https://webwork.yourschool.edu/webwork2/saml2/metadata?courseID=myTestCourse
# Further note that if multiple courses use that same identity provider then
# just pick any one of the courses to use in the metadata URL. All of the other
# courses share the same metedata.
$saml2{sp}{org} = {
contact => '[email protected]',
name => 'webwork',
url => 'https://localhost:8080/',
display_name => 'WeBWorK'
};

# The following list of attributes will be checked in the given order for a
# matching user in the webwork2 course. If no attributes are given, then
# webwork2 will default to the NameID. It is recommended that you use the
# attribute's OID.
$saml2{sp}{attributes} = [
'urn:oid:0.9.2342.19200300.100.1.1'
];

# The following settings are the locations of the files that contain the
# certificate and private key for the webwork2 service provider. A certificate
# and private key can be generated using openssl. For example,
# openssl req -newkey rsa:3072 -new -x509 -days 3652 -nodes -out saml.crt -keyout saml.pem
# The files saml.crt and saml.pem that are generated contain the public
# "certificate" and the "private_key", respectively.
# Note that if the files are placed within the root webwork2 app directory, then
# the paths may be given relative to the the root webwork2 app directory.
# Otherwise the absolute path must be given. Make sure that the webwork2 app has
# read permissions for those files.
$saml2{sp}{certificate_file} = 'docker-config/idp/certs/saml.crt';
$saml2{sp}{private_key_file} = 'docker-config/idp/certs/saml.pem';

##############################################################################
# SECURITY WARNING
# For production, you MUST provide your own unique 'certificate' and
# 'private_key' files. The files referred to in the default settings above are
# only intended to be used in development, and are publicly exposed. Hence, they
# provide NO SECURITY.
##############################################################################

# If this is set to 1, then service provider initiated logout from the identity
# provider is enabled. This means that when the user clicks the webwork2 "Log
# Out" button, a request is sent to the identity provider that also ends the
# session for the user with the identity provider.
$saml2{sp}{enable_sp_initiated_logout} = 0;

1;
120 changes: 0 additions & 120 deletions conf/authen_saml2.dist.yml

This file was deleted.

9 changes: 9 additions & 0 deletions conf/localOverrides.conf.dist
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,15 @@ $mail{feedbackRecipients} = [

#include("conf/authen_shibboleth.conf");

# Saml2 Authentication
################################################################################
# Uncomment the following line to enable authentication via a Saml2 identity
# provider. You will also need to copy the file authen_saml2.conf.dist to
# authen_saml2.conf, and then edit that file to fill in the settings for your
# installation.

#include("conf/authen_saml2.conf");

################################################################################
# Session Management
################################################################################
Expand Down
36 changes: 8 additions & 28 deletions docker-config/docker-compose.dist.yml
Original file line number Diff line number Diff line change
Expand Up @@ -251,47 +251,27 @@ services:
#ports:
# - "6311:6311"

# saml2 dev use only, separate profile from the other services so it doesn't
# start in normal usage. Use "docker compose --profile saml2dev up" to start.
# SimpleSAMLphp Saml2 identity provider for development use only. This is a separate profile from the other services
# so it doesn't start in normal usage. Use "docker compose --profile saml2dev up" to start, and "docker compose
# --profile saml2dev down" to stop.
idp:
build:
context: ./docker-config/idp/ # SimpleSAMLphp based IDP
context: ./docker-config/idp/
profiles:
- saml2dev
ports:
- '8180:80'
environment:
SP_METADATA_URL: 'http://app.docker:8080/saml2/metadata'
# the healthcheck url is simplesamlphp's url for triggering cron jobs, the
# cron job it'll trigger is to automatically grab webwork sp's metadata
SP_METADATA_URL: 'http://app:8080/webwork2/saml2/metadata'
# The healthcheck url is SimpleSAMLphp's url for triggering cron jobs. The cron job it triggers will
# automatically fetch the webwork2 service provider's metadata.
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost/simplesaml/module.php/cron/run/docker/healthcheck']
test: ['CMD', 'curl', '-f', 'http://localhost/simplesaml/module.php/cron/run/metarefresh/webwork2']
start_period: 1m
start_interval: 15s
interval: 1h
retries: 1
timeout: 10s
# Send internal docker traffic for the idp external port to the idp internal
# port. Needed so the webwork saml2 plugin can request the idp metadata.
socat-idp:
image: alpine/socat:1.8.0.0
profiles:
- saml2dev
command: 'TCP-LISTEN:8180,fork,reuseaddr TCP:idp:80'
networks:
default:
aliases:
- idp.docker
# same port redirect so the idp can get the webwork saml2 plugin metadata
socat-app:
image: alpine/socat:1.8.0.0
profiles:
- saml2dev
command: 'TCP-LISTEN:8080,fork,reuseaddr TCP:app:8080'
networks:
default:
aliases:
- app.docker

volumes:
oplVolume:
Expand Down
26 changes: 14 additions & 12 deletions docker-config/idp/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,36 +1,38 @@
# actual image we'll run in the end
FROM php:8.3-apache
WORKDIR /var/www

# Install composer & php extension installer
# Install composer and the php extension installer.
COPY --from=composer/composer:2-bin /composer /usr/bin/composer
COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/

RUN apt-get update && \
apt-get -y install git curl vim && \
install-php-extensions ldap zip

# dirs used by simplesamlphp needs to be accessible by apache user
# Directories used by simplesamlphp. These need to be accessible by the apache2 user.
RUN mkdir simplesamlphp/ /var/cache/simplesamlphp
RUN chown www-data. simplesamlphp/ /var/cache/simplesamlphp
# Composer doesn't like to be root, so we'll run the rest as the apache user
RUN chown www-data simplesamlphp/ /var/cache/simplesamlphp

COPY ./idp.apache2.conf /etc/apache2/conf-available
RUN a2enconf idp.apache2

# Composer doesn't like to be root, so run the rest as the apache user.
USER www-data

# Install simplesamlphp
ARG SIMPLESAMLPHP_TAG=v2.2.1
RUN git clone --branch $SIMPLESAMLPHP_TAG https://github.com/simplesamlphp/simplesamlphp.git
RUN git clone --branch v2.2.1 https://github.com/simplesamlphp/simplesamlphp.git
WORKDIR /var/www/simplesamlphp

# Generate certs
# Generate the server certificates.
RUN cd cert/ && \
openssl req -newkey rsa:3072 -new -x509 -days 3652 -nodes -out server.crt -keyout server.pem -subj "/C=CA/SP=BC/L=Vancouver/O=UBC/CN=idp.docker"
openssl req -newkey rsa:3072 -new -x509 -days 3652 -nodes -out server.crt -keyout server.pem \
-subj "/C=US/S=New York/L=Rochester/O=WeBWorK/CN=idp.webwork2"

# Use composer to install dependencies
# Use composer to install dependencies.
RUN composer install && \
composer require simplesamlphp/simplesamlphp-module-metarefresh

# Copy config files
# Copy configuration files.
COPY ./config/ config/
COPY ./metadata/ metadata/

COPY ./apache.conf /etc/apache2/sites-available/000-default.conf
Loading

0 comments on commit 576b862

Please sign in to comment.