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 SAML2 auth #2511

Closed
wants to merge 4 commits into from
Closed

Add SAML2 auth #2511

wants to merge 4 commits into from

Conversation

ionparticle
Copy link
Contributor

@ionparticle ionparticle commented Aug 14, 2024

Hello, this is a SAML2 authen module. It has two components, a regular Webwork Authen module and a
Mojolicious plugin for SAML2. The plugin uses the Net::SAML2 library which claims to be tested against many SAML2 flavours (see list in description here).

As Shibboleth is also based on SAML2, this is an alternate way to address #2286 that doesn't require Apache mod_shib.

Usage info is documented in lib/Mojolicious/Plugin/Saml2/README.md

We've been running this code in prod for the summer term with no issues so far.

Copy link
Sponsor Member

@drgrice1 drgrice1 left a comment

Choose a reason for hiding this comment

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

I see some initial code changes that are needed, and I commented on them.

Note that the lib/Mojolicious/Plugin/SAML2/README.md file needs a little clean up. Try to adhere to a 120 character limit on lines as much as possible, and fix linting errors shown by markdownlint. I see errors for missing blank lines around lists, hard tabs, duplicate headings, trailing spaces, and headings that increment to far. If you don't know how to run markdownlint, I can help you with this.

The php files use a four space tab instead of hard tabs. The project contains an .editorconfig file and this conflicts with the settings in that file. Note that only YAML files use a soft tab.

This will take some time to review thoroughly since there is a lot of code. Also, most of us will not be able to test this, so ideally someone else that can test SAML would provide a review.

In any case, thanks for the work. This will make a nice addition to the current authentication modules.

Dockerfile Outdated Show resolved Hide resolved
DockerfileStage1 Outdated Show resolved Hide resolved
lib/WeBWorK/Authen/Saml2.pm Outdated Show resolved Hide resolved
lib/Mojolicious/Plugin/Saml2/Router.pm Outdated Show resolved Hide resolved
lib/Mojolicious/Plugin/Saml2/Saml2Plugin.pm Outdated Show resolved Hide resolved
lib/Mojolicious/Plugin/Saml2/Saml2Plugin.pm Outdated Show resolved Hide resolved
lib/Mojolicious/Plugin/Saml2/Saml2Plugin.pm Outdated Show resolved Hide resolved
@drgrice1
Copy link
Sponsor Member

Also, I would recommend that you rebase this onto the develop branch. I see that this is missing some of the recent changes. It would be best for you and others to test this with everything in develop in case there are regressions caused by other changes.

@ionparticle
Copy link
Contributor Author

Thanks for the speedy review, I've rebased latest develop and hopefully fixed all the issues.

One exception though is regarding the php files. The stuff in docker-config/idp is a separate service, it's meant to run an instance of SimpleSAMLphp configured as an Identity Provider to allow easier testing by other developers (instructions in README.md). It's an external development tool, not part of the SAML2 plugin itself and should not be present in any kind of prod use. I would request they be considered external to Webwork and exempt. They're following SimpleSAMLphp standards because they're from that project.

Another note, when I ran markdownlint, it was enforcing 80 characters per line, so my fixes followed that standard.

@drgrice1
Copy link
Sponsor Member

If a file is included in the webwork2 repository it is part of the webwork2 project, and can not be exempted from any coding standards of the project. It will not be allowed to follow any external projects coding standards, and can not be considered external. External files are files not in the webwork2 repository. If the files are directly from another project, then they should be used from that project instead of being included in the repository. For php, composer should be used. Although, this is another thing that I wanted to bring up. Is there any way that could be done with Perl instead of using php to begin with?

For the README.md file a 80 character limit is acceptable. Although if you use markdownlint correctly, it will honor the settings in the .editorconfig file and use a 120 character limit.

@ionparticle
Copy link
Contributor Author

No problem, reformatted docker-config/idp to follow editorconfig rules.

Though, I want to clarify that git and composer are already used to grab source files from SimpleSAMLphp during the docker build of the idp. The files checked in here are just the configuration files which necessarily needs to be customized.

@drgrice1
Copy link
Sponsor Member

I see. Thanks for the clarification. If those are files that need to be customized, then they should be considered part of the webwork2 project. Thanks for reformatting them.

I will try to take another deeper look at this when I have more time.

This has two components, a regular Webwork Authen module and a
Mojolicious plugin for SAML2.

The plugin implements a SAML2 SP using the Net::SAML2 library.
Net::SAML2 claims to be compatible with many SAML2 implementations,
including Shibboleth.  The intent for this plugin is to replace the old
Shibboleth auth that depended on the shibd service which requires
Apache's mod_shib.

The WeBWorK::Authen::Saml2 authen module's main purpose is to call the
Saml2 plugin helper sendLoginRequest() that properly sends the user to
the IdP to authenticate. There is a bypass option that allows skipping
the Saml2 authen in favour of the authen module after it. So you can,
for example, put Basic_TheLastOption after it allow access to the
internal username/password auth.

It seems to be standard for Mojolicous plugins to be located in
lib/Mojolicious/Plugin, so I've put the Saml2 plugin there. Additional
detail and configuration instructions can be found in
lib/Mojolicious/Plugin/Saml2/README.md

The Saml2 plugin will only be loaded if the corresponding conf file
exists. Copy conf/authen_saml2.dist.yml to conf/authen_saml2.yml to
enable it. 3 settings in the conf are crucial, the idp.metadata_url must
be set to the IdP's xml metadata endpoint and a unique sp.cert &
sp.signing_key pair should be generated. The example cert and
signing_key must not be used for prod under any circumstances.

I initially put the config in conf/mojolicious.webwork.yml under a saml2
section but seeing as how other authen modules have their own conf
files, I figure it was better to follow that convention. And as there
seems to be more work around the yaml config, I've made the saml2 authen
conf also a yaml file.  The NotYAMLConfig plugin for reading yaml conf
files gave me some trouble though, as I didn't want to step on the main
conf and had to read the code to figure out how to use just the load
conf file functionality.

The Saml2 plugin will generate its own xml metadata that can be used by
the IdP for configuration. This is available at the /saml2/metadata URL
under the default config. Note that endpoints are configurable as I
wanted to be able to change them to match shibd endpoints.

The Saml2 plugin has its own set of controllers and a router for them.
The AcsPostController is the most important one as it handles the
SAMLResponse that sends the user back to Webwork from the IdP. The
errors aren't the most friendly unfortunately, should probably add a
proper 401 error template so users don't see the more scary stacktrace
one.

Note that unlike shibd, attribute maps are not supported. So you
probably have to replace user friendly attribute names like
"studentNumber" with URN style names like
"urn:mace:dir:attribute-def:studentNumber". You can check your IdP's
attribute map xml for the official URN names.

Some discussion about alternatives that I tried before settling on the
Mojolicious plugin approach:

The Saml2 as a plugin idea started after trying out
Mojolicious::Plugin::SAML.  Mojolicious::Plugin::SAML didn't work out in
the end due to two downsides. The major one being a lack of RelayState
support. RelayState is the defacto standard way for SPs to store where
the user wants to go after returning from a successful auth from the
IdP. This is a necessary feature for Webwork as auth to each course is
handled separately and we need to know exactly what course the user
wants to get into. The minor issue is that it doesn't parse the
SAMLResponse attributes for you and I really didn't want to have to muck
with xml parsing.

Apache mod_proxy using mod_shib and shibd was another possibility. This
requires passing shib params through HTTP headers via the use of the
ShibUseHeaders setting. Shibboleth documentation says the ShibUseHeaders
option should be avoided as they're not confident in the protections
against spoofed HTTP header attacks.

Running Webwork under mod_perl again so we don't need to use mod_proxy.
This resulted in hard to debug issues with a few sections of async code.
While I was able to 'unasync' those sections to get the thing running,
it feels unmaintainable in the long run, especially if Webwork increases
usage of async features.
WEBWORK_ROOT_URL definition in Dockerfiles used two colons before the
port when it's supposed to be only 1.

Also deleted version line from docker-compose.dist.yml, as I get a
warning message that `version` is obsolete.
Mojo::Base's -strict option removed where -signature is present as it
makes strict redundant.

Use Mojo::JSON's functions instead of JSON.

When using WeBWorK::Debug, specify import of debug() function.

Remove import of WeBWorK::Debug and Data::Dumper where they're not
actually being used.

Fix README.md to pass markdownlint inspection.
Reformatted to follow .editorconfig rules.
drgrice1 added a commit to drgrice1/webwork2 that referenced this pull request Aug 30, 2024
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 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.
drgrice1 added a commit to drgrice1/webwork2 that referenced this pull request Aug 30, 2024
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.
@ionparticle
Copy link
Contributor Author

Rolled into pull request #2547

drgrice1 added a commit to drgrice1/webwork2 that referenced this pull request Sep 3, 2024
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.
drgrice1 added a commit to drgrice1/webwork2 that referenced this pull request Sep 3, 2024
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.
drgrice1 added a commit to drgrice1/webwork2 that referenced this pull request Sep 10, 2024
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants