From 9c7767ba13a5d52f4aa58578f51043c2e974e007 Mon Sep 17 00:00:00 2001 From: Matt Toups Date: Fri, 12 Aug 2022 11:12:44 -0500 Subject: [PATCH 1/7] installation documentation updates for install onto a bare Debian 11 system: * don't assume we have github ssh key installed - this would make sense on a dev system but not for a production system * make is not necessarily already installed * in recent versions of docker, "docker compose" (via docker-compose-plugin) is used instead of "docker-compose" * sudo is not necessarily installed, and should not be required --- docs/installation/docker-compose.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/installation/docker-compose.md b/docs/installation/docker-compose.md index acb18130a..830389840 100644 --- a/docs/installation/docker-compose.md +++ b/docs/installation/docker-compose.md @@ -10,7 +10,7 @@ First ensure that you have Docker and Docker Compose installed on your machine. 1. Clone this repository and its Autolab and Tango submodules: :::bash - git clone --recurse-submodules -j8 git@github.com:autolab/docker.git autolab-docker + git clone --recurse-submodules -j8 https://github.com/autolab/docker.git autolab-docker 2. Enter the project directory: @@ -23,6 +23,11 @@ First ensure that you have Docker and Docker Compose installed on your machine. :::bash make update +3a. If the previous step fails with `make: command not found`, you may need to install `make` with the appropriate command for your Linux system, such as: + + :::bash + apt install make + 4. Create initial default configs: :::bash @@ -31,12 +36,12 @@ First ensure that you have Docker and Docker Compose installed on your machine. 5. Build the Dockerfiles for both Autolab and Tango: :::bash - docker-compose build + docker compose build 6. Run the Docker containers: :::bash - docker-compose up -d + docker compose up -d Note at this point Nginx will still be crash-looping in the Autolab container because TLS/SSL has not been configured/disabled yet. @@ -98,7 +103,7 @@ There are three options for TLS: using Let's Encrypt (for free TLS certificates) 2. Ensure that port 443 is exposed on your server (i.e checking your firewall, AWS security group settings, etc) 3. In `ssl/init-letsencrypt.sh`, change `domains=(example.com)` to the list of domains that your host is associated with, and change `email` to be your email address so that Let's Encrypt will be able to email you when your certificate is about to expire 4. If necessary, change `staging=0` to `staging=1` to avoid being rate-limited by Let's Encrypt since there is a limit of 20 certificates/week. Setting this is helpful if you have an experimental setup. -5. Run your modified script: `sudo sh ./ssl/init-letsencrypt.sh` +5. Run your modified script: `su -c 'sh ./ssl/init-letsencrypt.sh'` ### Option 2: Using your own TLS certificate 1. Copy your private key to `ssl/privkey.pem` @@ -304,4 +309,4 @@ You can resolve this by changing the owner of the files to be your current user, This happens when you are accessing Autolab via localhost, as Tango will attempt to send the autograder logs to its own localhost instead. -To remedy this, add `127.0.0.1 autolab` to `/etc/hosts` and access Autolab via `http://autolab` instead of `http://localhost`. \ No newline at end of file +To remedy this, add `127.0.0.1 autolab` to `/etc/hosts` and access Autolab via `http://autolab` instead of `http://localhost`. From 093005f330215db84439de4d6c6a6f92f82e0305 Mon Sep 17 00:00:00 2001 From: Matt Toups Date: Fri, 12 Aug 2022 12:03:02 -0500 Subject: [PATCH 2/7] add these Gems to get SAML authentication support (including MS Active Directory) --- Gemfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Gemfile b/Gemfile index 37629e435..547f5c545 100644 --- a/Gemfile +++ b/Gemfile @@ -60,6 +60,8 @@ gem 'omniauth', '>=1.2.2' gem 'omniauth-facebook', '>=2.0.0' gem 'omniauth-google-oauth2', '>=0.2.5' gem 'omniauth-shibboleth', '>=1.1.2' +gem 'omniauth-saml', '>=1.7.0' +gem 'omniauth-rails_csrf_protection' # OAuth2 authentication gem 'oauth2' From f5291b21230831376ed06e3146a5cb624194b381 Mon Sep 17 00:00:00 2001 From: Matt Toups Date: Fri, 12 Aug 2022 12:03:55 -0500 Subject: [PATCH 3/7] modify sign-in page for SAML auth --- app/views/devise/shared/_links.erb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/views/devise/shared/_links.erb b/app/views/devise/shared/_links.erb index 04778da05..5f49d11d9 100755 --- a/app/views/devise/shared/_links.erb +++ b/app/views/devise/shared/_links.erb @@ -18,15 +18,12 @@ <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
<% end -%> -<% if false %> <%- if devise_mapping.omniauthable? %> <%- resource_class.omniauth_providers.each do |provider| %> <% if provider.to_s.titleize == "Shibboleth" %> - <%= link_to "Sign in with your CMU account", user_omniauth_authorize_path(provider) %>
+ <%= button_to "Sign in with your CMU account", user_saml_omniauth_authorize_path %>
<% else %> - <%= link_to "Sign in with #{provider.to_s.titleize}", user_omniauth_authorize_path(provider) %>
+ <%= button_to "Sign in with #{provider.to_s.titleize}", user_saml_omniauth_authorize_path %>
<% end %> <% end -%> <% end %> - -<% end -%> From 6be043c581395053f994b78e739b3d223a9c7f6b Mon Sep 17 00:00:00 2001 From: Matt Toups Date: Fri, 12 Aug 2022 12:04:55 -0500 Subject: [PATCH 4/7] here we replace shibboleth with saml for authentication --- app/models/user.rb | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index 48af212e2..b3b177909 100755 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -9,7 +9,7 @@ class User < ApplicationRecord :recoverable, :rememberable, :trackable, :validatable, :confirmable - devise :omniauthable, omniauth_providers: [:shibboleth] + devise :omniauthable, omniauth_providers: [:saml] has_many :course_user_data, dependent: :destroy has_many :courses, through: :course_user_data @@ -97,6 +97,12 @@ def self.find_for_shibboleth_oauth(auth, _signed_in_resource = nil) uid: auth.uid) return authentication.user if authentication&.user end + def self.find_for_saml(auth_uid, _signed_in_resource = nil) + authentication = Authentication.find_by(provider: "saml", + uid: auth_uid) + return authentication.user if authentication && authentication.user + end + def self.new_with_session(params, session) super.tap do |user| @@ -116,6 +122,11 @@ def self.new_with_session(params, session) user.email = data["uid"] # email is uid in our case user.authentications.new(provider: "CMU-Shibboleth", uid: data["uid"]) + elsif (data = session["devise.saml_data"]) + user.email = data["uid"] + user.authentications.new(provider: "saml", + uid: data["uid"]) + end end end From d38b902eefbe7b9b70a9f3c13a67138e410299c5 Mon Sep 17 00:00:00 2001 From: Matt Toups Date: Fri, 12 Aug 2022 12:05:28 -0500 Subject: [PATCH 5/7] add function for saml auth --- .../users/omniauth_callbacks_controller.rb | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb index 61b37f6f5..d28521d81 100755 --- a/app/controllers/users/omniauth_callbacks_controller.rb +++ b/app/controllers/users/omniauth_callbacks_controller.rb @@ -106,4 +106,66 @@ def shibboleth end end end + + def saml + if user_signed_in? + if data = request.env["omniauth.auth"] + # add this authentication object to current user + if current_user.authentications.where(provider: data["provider"], + uid: data["uid"]).empty? + current_user.authentications.create(provider: data["provider"], + uid: data["uid"]) + end + end + redirect_to(root_path) && return + else + @user = User.find_for_saml(request.env["omniauth.auth"]["extra"]["raw_info"].attributes["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"][0], current_user) + if @user # this user has signed into autolab before (or is on a class roster so autolab knows who they are) + sign_in_and_redirect @user, event: :authentication # this will throw if @user is not activated + set_flash_message(:notice, :success, kind: "SAML") if is_navigational_format? + else + # Skip sign up for a known user + data = request.env["omniauth.auth"] + + @user = User.where(email: data["extra"]["raw_info"].attributes["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"][0]).first # use the email as uid + + # If user doesn't exist, create one first (school specific stuff follows) + if @user.nil? + @user = User.new + @user.email = data["extra"]["raw_info"].attributes["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"][0] # it turns out this is where identity is + @user.first_name = data["extra"]["raw_info"].attributes["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"][0] + @user.last_name = data["extra"]["raw_info"].attributes["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"][0] + + # Set user info based on LDAP lookup + if @user.email.include? "@andrew.cmu.edu" + ldapResult = User.ldap_lookup(@user.email.split("@")[0]) + if ldapResult + @user.first_name = ldapResult[:first_name] + @user.last_name = ldapResult[:last_name] + @user.school = ldapResult[:school] + @user.major = ldapResult[:major] + @user.year = ldapResult[:year] + end + end + + # If LDAP lookup failed, use (blank) as place holder + @user.first_name = "(blank)" if @user.first_name.nil? + @user.last_name = "(blank)" if @user.last_name.nil? + + temp_pass = Devise.friendly_token[0, 20] # generate a random token + @user.password = temp_pass + @user.password_confirmation = temp_pass + @user.skip_confirmation! + end +# Rails.logger.error(data["uid"]) +# Rails.logger.error( data["extra"]["raw_info"].attributes["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"][0] ) + + @user.authentications.new(provider: "saml", + uid: data["extra"]["raw_info"].attributes["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"][0]) + @user.save! + sign_in_and_redirect @user, event: :authentication # this will throw if @user is not activated + set_flash_message(:notice, :success, kind: data["extra"]["raw_info"].attributes["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"][0]) if is_navigational_format? #"SAML") if is_navigational_format? + end + end + end end From d573fd76956e1450ac0c42ef0305da6e81b94a93 Mon Sep 17 00:00:00 2001 From: Matt Toups Date: Fri, 12 Aug 2022 12:05:42 -0500 Subject: [PATCH 6/7] this commit I did not feel great about, but commenting out this line was the only way I could find to get SAML auth to work. I would very much prefer another solution or workaround. --- app/controllers/application_controller.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 432a3734f..b0c3c4a3d 100755 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -132,7 +132,8 @@ def verify_authenticity_token " please contact the Autolab Development team at the " \ "contact link below" - authentication_failed(msg) unless verified_request? +# NOTE: commenting out the line below was the only way to get SAML auth to work +# authentication_failed(msg) unless verified_request? end def maintenance_mode? From 7ecc04c4b43bad49209280c33901774aaee4a88a Mon Sep 17 00:00:00 2001 From: Matt Toups Date: Fri, 12 Aug 2022 12:06:35 -0500 Subject: [PATCH 7/7] This is an example of what a school using Microsoft Active Directory will need to enable SAML authentication for autolab. The "Enterprise Applications" admin will need to provide the fingerprint and target URL/entity ID specific to your site. (They may call this "metadata") --- config/initializers/devise.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 49349a2cb..725362dcb 100755 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -229,6 +229,14 @@ # up on your models and hooks. # config.omniauth :github, 'APP_ID', 'APP_SECRET', :scope => 'user,public_repo' + config.omniauth :saml, idp_cert_fingerprint: 'nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn', # insert your fingerprint here + idp_sso_target_url: 'https://login.microsoftonline.com/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/saml2', # replace with your url + issuer: 'https://autolab.myschool.edu', + assertion_consumer_service_url: 'https://autolab.myschool.edu/auth/users/auth/saml/callback', + idp_entity_id: 'https://sts.windows.net/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/', # similar to the target URL + name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent', + attribute_statements: { email: ['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress']} + # ==> Warden configuration # If you want to use other strategies, that are not supported by Devise, or # change the failure app, you can configure them inside the config.warden block.