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 two factor authentication. #2335

Merged
merged 14 commits into from
Mar 20, 2024

Conversation

drgrice1
Copy link
Member

This uses the TOTP (time-based one-time password) protocol. Any authenticator app on a mobile device that supports this protocol can be used (for example, Google Authenticator, Microsoft Authenticator, Twilio Authy, etc.).

Whether this is enabled or not is controlled by new course environment variable $twoFA{enabled}. If that is set to 0, then two factor authentication is disabled for all courses. If that is 1 (the default), then two factor authentication is enabled for all courses that use password authentication (of course two factor authentication does not apply to courses that use external authentication methods like LTI, CAS, Shibboleth, etc.). If that is a string course name like 'admin', then two factor authentication is enabled only for that course. If that is an array of string course names, then two factor authentication is enabled only for those courses listed. This can also be set in a course's course.conf file. Note that only the values of 0 and 1 make sense there.

There are two methods that can be used to setup two factor authentication when a user signs in for the first time. The setup information can be emailed to the user, or can be directly displayed in the browser on the next page that is shown after password verification succeeds.

This is controlled by the new course environment variable $twoFA{email_sender}. If that is set, then the email approach will be used. In this case, after a user signs in and the password is verified, the user will be sent an email containing a QR code and instructions on how to set up a OTP generator app. This is probably a more secure way to set up two factor authentication, as it ensures the user setting it up is the correct user. Note that if a user does not have an email address, then the browser method below will be used as a fallback.

If $twoFA{email_sender} is not set, then after a user signs in and the password is verified, the QR code, OTP link, and instructions will be displayed directly on the page in the browser. This is potentially less secure because a hacker could guess a username and password before a user has setup two factor authentication (particularly if the username and password are initially the same), and then the hacker would gain access to that user's account, and the actual user would be locked out. Note that you will need to use this option if your server can not send emails. Also note that no-reply addresses may be blocked by the email server or marked as spam. So it may be better to find a valid email address to use for this.

This requires a new database column otp_secret that was added to the password table (for lack of a better place to put it).

There is a new wwsh script (bin/reset2fa) that can be used to reset two factor authentication for a user if a user somehow loses their setup in an authentactor app on their mobile device. That just removes the OTP secret from the database. This means that the user will need to go through the two factor authentication setup process again. To use it execute: wwsh courseID /opt/webwork/webwork2/bin/reset2fa userID. Multiple user ids can be listed if you want to reset more than one user at a time. Note that this script is the only way that an admin user can reset their own two factor authentication (and there should never be another way for admin users to do this for themselves). Perhaps a page in the admin course could be added for resetting this for instructors.

A form has been added to the Accounts Manager for resetting two factor authentication for students. This form does not allow the user to reset their own two factor authentication secret, but that of other users at equal or lesser permission level to their own. Note that in the admin course if there are multiple admin users, then one admin user can reset two factor authentication for another. Also there is some clean up and issue fixes in the htdocs/js/UserList/userlist.js file with form validation. The "change" event handler was being added multiple times to the users list table. More clean up is needed though (with this and the other pages with action forms). There is a lot of redundancy with this form validation implementation.

There is an option to skip two factor authentication on trusted devices. A checkbox is on the two factor verification page. If that is checked, then a signed cookie (separate from the session cookie) is set. If that cookie is set, then two factor verification is skipped for sign in attempts.

This builds on #2333 and #2334, and is the final part of the authentication system revamp.

@drgrice1
Copy link
Member Author

I should point out that the option to skip two factor authentication on trusted devices uses a cookie (not the session cookie, another one) to work. This is the case regardless of what session_management_via is set to. There really is no other way that this can be done.

@drgrice1 drgrice1 force-pushed the two-factor-authentication branch 4 times, most recently from b02bee6 to c7f2d42 Compare February 24, 2024 21:14
@drgrice1
Copy link
Member Author

I realized that allowing the render_rpc to disable cookies via a URL parameter is a security vulnerability. So I added a new configuration option in webwork2.mojolicious.dist.yml named allow_unsecured_rpc. If that is set then the render_rpc and html2xml endpoints can disable cookies (html2xml always does if that is set, and render_rpc can by passing the URL parameter disableCookies). This option should always be 0 (disabled) for a typical webwork server. It should only be enabled if you are @Alex-Jordan (or a PreTeXt serving cohort)!

@drgrice1 drgrice1 force-pushed the two-factor-authentication branch 5 times, most recently from 02b7b56 to b4fea40 Compare March 2, 2024 00:31
@drgrice1 drgrice1 force-pushed the two-factor-authentication branch 3 times, most recently from 37e173f to 31dcf44 Compare March 7, 2024 21:11
@pstaabp
Copy link
Member

pstaabp commented Mar 11, 2024

Testing this with the new default settings ($twoFA{enabled}=1) and logging on to the admin course, getting

Use of uninitialized value in subroutine entry at template ContentGenerator/TwoFactorAuthentication.html.ep line 10.

and a broken image link for the QR code. It seems that the $img_data variable in TwoFactorAuthentication.pm is not getting defined.

@drgrice1
Copy link
Member Author

Have you installed the new dependencies (Imager::Color, Imager::QRCode, and Mime::Base32)?

@drgrice1
Copy link
Member Author

I am unable to reproduce what you are seeing. It seems that if the dependencies were not installed, then the application would not compile, and you wouldn't even be able to load any page. So that is probably not the issue. You might check versions of the dependencies. Maybe there is some issue with certain versions? On my system I have version 0.033 of Imager::QRCode and 1.013 of Imager::Color (the versions in the Ubuntu packages), and I have version 1.303 of MIME::Base32 (installed from cpan).

@pstaabp
Copy link
Member

pstaabp commented Mar 11, 2024

I'm testing this on a ubuntu development machine. All of the modules have been installed, restarted hypnotoad and the only error I'm seeing in webwork2.log is the message above.

@drgrice1
Copy link
Member Author

I tested this on a production machine (well, really a test server but with a production setup), and I still can't reproduce what you are seeing. That machine also has Ubuntu 22.04 installed, and I used the Ubuntu pacakges libimager-perl, libimager-qrcode-perl, and libmime-base32-perl.

@pstaabp
Copy link
Member

pstaabp commented Mar 11, 2024

I had originally added them via cpanm (my standard on my development machine), but removed and then added the ubuntu packages you mentioned. I see the QR code now.

And successfully logged on.

@drgrice1
Copy link
Member Author

I tried uninstalling the Ubuntu packages for Imager and Imager::QRCode and installing from cpan, and have the same problem. In fact, attempting to use those modules directly from a script also fails. No image is generated. I also tried downgrading Imager::QRCode to the earlier versions. Version 0.035 (the latest) installs but no images are generated. Version 0.034 is the same. Version 0.033 (the same version that is in the Ubuntu package libimager-qrcode-perl) does not install. The build fails. So Ubuntu has something changed in the package to make it work. I haven't tested other versions of Imager as of yet.

This may be something that only affects Ubuntu. We will need testing on other distributions probably.

Have you by chance tested this on OSX with morbo?

@dlglin
Copy link
Member

dlglin commented Mar 11, 2024

There doesn't appear to be a package for Imager::QRCode in RHEL8.

Do you have a MWE script that I can use to test the package on other distros without a WW installation?

@drgrice1
Copy link
Member Author

Here is a minimal script:

#!/usr/bin/env perl

use strict;
use warnings;

use Imager::QRCode;
use Imager::Color;
use Mojo::File qw(path);

my $img_data;
Imager::QRCode->new(
	size          => 4,
	margin        => 3,
	level         => 'L',
	casesensitive => 1,
	lightcolor    => Imager::Color->new(255, 255, 255, 0),
	darkcolor     => Imager::Color->new(0,   0,   0),
)->plot('blah blah')->write(data => \$img_data, type => 'png');

path('img.png')->spew($img_data);

If it works the file img.png should be written in the directory the script is executed from and contain a valid png image.

@drgrice1
Copy link
Member Author

drgrice1 commented Mar 11, 2024

Note it does use Mojo::File so it also depends on Mojolicious. That was just a quick way to be able to write the file. You could also use:

#!/usr/bin/env perl

use strict;
use warnings;

use Imager::QRCode;
use Imager::Color;

my $img_data;
Imager::QRCode->new(
	size          => 4,
	margin        => 3,
	level         => 'L',
	casesensitive => 1,
	lightcolor    => Imager::Color->new(255, 255, 255, 0),
	darkcolor     => Imager::Color->new(0,   0,   0),
)->plot('blah blah')->write(file => 'img.png', type => 'png');

drgrice1 added 13 commits March 18, 2024 11:21
This uses the TOTP (time-based one-time password) protocol. Any
authenticator app on a mobile device that supports this protocol can be
used (for example, Google Authenticator, Microsoft Authenticator, Twilio
Authy, etc.).

Whether this is enabled or not is controlled by new course environment
variable `$twoFA{enabled}`.  If that is set to 0, then two factor
authentication is disabled for all courses. If that is 1 (the default),
then two factor authentication is enabled for all courses that use
password authentication (of course two factor authentication does not
apply to courses that use external authentication methods like LTI, CAS,
Shibboleth, etc.). If that is a string course name like 'admin', then
two factor authentication is enabled only for that course. If that is an
array of string course names, then two factor authentication is enabled
only for those courses listed. This can also be set in a course's
course.conf file. Note that only the values of 0 and 1 make sense there.

There are two methods that can be used to setup two factor
authentication when a user signs in for the first time. The setup
information can be emailed to the user, or can be directly displayed in
the browser on the next page that is shown after password verification
succeeds.

This is controlled by the new course environment variable
`$twoFA{email_sender}`.  If that is set, then the email approach will be
used.  In this case, after a user signs in and the password is verified,
the user will be sent an email containing a QR code and instructions on
how to set up a OTP generator app. This is probably a more secure way to
set up two factor authentication, as it ensures the user setting it up
is the correct user. Note that if a user does not have an email address,
then the browser method below will be used as a fallback.

If `$twoFA{email_sender}` is not set, then after a user signs in and the
password is verified, the QR code, OTP link, and instructions will be
displayed directly on the page in the browser. This is potentially less secure
because a hacker could guess a username and password before a user has setup
two factor authentication (particularly if the username and password are
initially the same), and then the hacker would gain access to that user's
account, and the actual user would be locked out. Note that you will need to
use this option if your server can not send emails. Also note that no-reply
addresses may be blocked by the email server or marked as spam. So it may be
better to find a valid email address to use for this.

This requires a new database column `otp_secret` that was added to the
password table (for lack of a better place to put it).

There is a new wwsh script (`bin/reset2fa`) that can be used to reset
two factor authentication for a user if a user somehow loses their setup
in an authentactor app on their mobile device. That just removes the OTP
secret from the database.  This means that the user will need to go
through the two factor authentication setup process again.  To use it
execute: `wwsh courseID /opt/webwork/webwork2/bin/reset2fa userID`.
Multiple user ids can be listed if you want to reset more than one user
at a time. This is the only way that an admin user can reset their own
two factor authentication (and there should never be another way for
admin users to do this for themselves).  Perhaps a page in the admin
course could be added for resetting this for instructors. I plan to add
a way that instructors can reset two factor authentication for students.
…ation for students.

This form does not allow the user to reset their own two factor
authentication secret, but that of other users at equal or lesser
permission level to their own.

Note that in the admin course if there are multiple admin users, then
one admin user can reset two factor authentication for another.

Also some clean up and issue fixes in the
`htdocs/js/UserList/userlist.js` file with form validation.  The
"change" event handler was being added multiple times to the users list
table. More clean up is needed though (with this and the other pages
with action forms).  There is a lot of redundancy with this
form validation implementation.
A checkbox is added to the two factor verification page.  If that is
checked, then a signed cookie (separate from the session cookie) is set.
If that cookie is set, then two factor verification is skipped for sign
in attempts.
…ing unsecured rpc usage.

The option allow_unsecured_rpc (which defaults to off) allows the
html2xml and render_rpc endpoints to be used without cookies, and thus
skipping two factor authentication.  This should never be enabled for a
typical webwork server.  This should only be enabled if you want to
allow serving content via these endpoints to links in external websites
with usernames and passwords embedded in them such as for PreTeXt
textbooks.
The `Imager::QRCode` seems to have some issues on various linux
distributions.  The Ubuntu packages apparently have some changes applied
that fix these issues.  However, if the package is installed from cpan
it fails.  On Oracle it seems the same issues occur.  In testing the
`GD::Barcode::QRcode` package works on Ubuntu, Oracle, and Redhat.
Roles with this permission level are required to use two factor
authentication to sign in.  Users below this permission level
can sign in directly.  The default user role with this permission is
"student".  But if there is strong opposition to this default, then I
suppose it could be switched to "login_proctor".

Note that even if this is set to "guest", guest users will still be able
to sign in without two factor authentication since it never really makes
sense to have guests (i.e. practice users) use two factor authentication.
Apparently Microsoft Authenticator does not support the SHA256 or SHA512
algorithms.
This makes two factor authentication work more reliably for DUO.
The setting in defaults.config which can be overidden in
localOverrides.conf is $twoFA{skip_verification_code_interval}.  The
default value is set to one year.
Doing this forces the `GD::Barcode::QRcode` package to auto detect the
version, ans works on all platforms to generate a working QR code.
However, on some platforms (well really all of them except the packages
in the Ubuntu repositories) this emits warnings.  To fix that, the
warnings are simply disabled when the image is generated.
@drgrice1 drgrice1 force-pushed the two-factor-authentication branch from 8cc90c7 to d81e4f7 Compare March 18, 2024 16:21
@pstaabp
Copy link
Member

pstaabp commented Mar 19, 2024

Just retested after the past couple of days of updates.

  1. Tested using Duo, Microsoft Authenticator and the built-in iOS authenticator. All with success.
  2. Tested the permission level.

@dlglin
Copy link
Member

dlglin commented Mar 19, 2024

In Fedora switching from the dnf packaged version of Mojolicious to cpan fixed the Base64 encoded image issue.

Both Fedora 39 and RHEL 8 have GD::Barcode version 1.15 packaged. In both cases I had to uninstall the packaged version and install from CPAN (version 2.0). With the CPAN version it works on both.

I did a quick test with both the Microsoft and Google Authenticator Apps and was able to log in with 2FA. One observation: The name of the entry in the authenticator app is the course name. How about prefixing this with one of the following to make it more descriptive:

  • The name of the server
  • A string that is set in configuration, which could default to either "WeBWorK" or the $server_root_url

I didn't test the permission level.

@Alex-Jordan
Copy link
Contributor

You'll have multiple items in the authenticator app for the multiple courses you need to authenticate into as a WW admin (or even as an instructor or student). So it helps if the items in the app name the particular course too.

@drgrice1
Copy link
Member Author

Both Fedora 39 and RHEL 8 have GD::Barcode version 1.15 packaged. In both cases I had to uninstall the packaged version and install from CPAN (version 2.0). With the CPAN version it works on both.

We will just need to tell those using Fedora or RHEL to use the CPAN version. That is not too big of a deal. It wouldn't be the first time we have instructed administrators to use a CPAN version over a distribution package.

I did a quick test with both the Microsoft and Google Authenticator Apps and was able to log in with 2FA. One observation: The name of the entry in the authenticator app is the course name. How about prefixing this with one of the following to make it more descriptive:

* The name of the server
* A string that is set in configuration, which could default to either "WeBWorK" or the `$server_root_url`

I knew this would come up. I don't care what we go with. Does anyone have a preference? In any case it should still contain the course ID, but what should it be prefixed with?

@dlglin
Copy link
Member

dlglin commented Mar 19, 2024

We will just need to tell those using Fedora or RHEL to use the CPAN version. That is not too big of a deal. It wouldn't be the first time we have instructed administrators to use a CPAN version over a distribution package.

There are already several packages that have a minimum version in check_modules.pl. Should we just say version 2.0 there?

@drgrice1
Copy link
Member Author

Well, the version on Ubuntu is 1.15. That version works fine (with the Ubuntu modifications).

@drgrice1
Copy link
Member Author

Another thing with the TOTP issuer (which is what is shown as the name of the entry in the authenticator app). How should this prefix (whatever we want for that) and the course id be put together? Should : be used to separate them perhaps?

@drgrice1
Copy link
Member Author

drgrice1 commented Mar 20, 2024

I set the issuer for the otpauth URL to be the absolute URL of the set_list route without https?://. So that will be your.server.edu/webwork2/course_id. This means for the Google Authenticator app the label will be your.server.edu/webwork/course_id: user_id.

@Alex-Jordan Alex-Jordan merged commit d497e06 into openwebwork:develop Mar 20, 2024
2 checks passed
@drgrice1 drgrice1 deleted the two-factor-authentication branch March 20, 2024 22:21
@Alex-Jordan
Copy link
Contributor

I'm wondering now if "Skip two factor verification on this device." should be something like "Skip two factor verification on this device in the future." The first time you are exposed to this, it's a little confusing. Like you could just check the box and skip this altogether.

@drgrice1
Copy link
Member Author

Yeah, that would be fine.

drgrice1 added a commit to drgrice1/webwork2 that referenced this pull request Mar 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants