Merge branch '15881-ldap'
authorTom Clegg <tom@tomclegg.ca>
Thu, 14 May 2020 17:38:04 +0000 (13:38 -0400)
committerTom Clegg <tom@tomclegg.ca>
Thu, 14 May 2020 17:38:04 +0000 (13:38 -0400)
refs #15881

Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom@tomclegg.ca>

36 files changed:
build/rails-package-scripts/arvados-sso-server.sh
doc/_config.yml
doc/admin/metrics.html.textile.liquid
doc/admin/migrating-providers.html.textile.liquid
doc/admin/upgrading.html.textile.liquid
doc/admin/user-management.html.textile.liquid
doc/install/google-auth.html.textile.liquid [deleted file]
doc/install/install-components.html.textile.liquid [deleted file]
doc/install/install-manual-prerequisites.html.textile.liquid
doc/install/install-sso.html.textile.liquid [deleted file]
doc/install/install-ws.html.textile.liquid
doc/install/setup-login.html.textile.liquid
go.mod
go.sum
lib/config/config.default.yml
lib/config/deprecated.go
lib/config/deprecated_test.go
lib/config/export.go
lib/config/generated_config.go
lib/controller/federation/login_test.go
lib/controller/handler_test.go
lib/controller/localdb/login.go
lib/controller/localdb/login_google.go
lib/controller/localdb/login_google_test.go
lib/controller/localdb/login_ldap.go [new file with mode: 0644]
lib/controller/localdb/login_ldap_docker_test.go [new file with mode: 0644]
lib/controller/localdb/login_ldap_docker_test.sh [moved from lib/controller/localdb/login_pam_docker_test.sh with 63% similarity]
lib/controller/localdb/login_ldap_test.go [new file with mode: 0644]
lib/controller/localdb/login_pam.go
lib/controller/localdb/login_pam_docker_test.go [deleted file]
lib/controller/localdb/login_pam_test.go
sdk/go/arvados/config.go
sdk/python/tests/run_test_server.py
services/api/config/arvados_config.rb
services/api/config/initializers/omniauth_init.rb
tools/arvbox/lib/arvbox/docker/cluster-config.sh

index fff582bb18dbb591446713356ab12f8335d8f66a..e88da0d3a6733f71e3edb33922ed6a9b9a837936 100644 (file)
@@ -8,6 +8,6 @@
 PACKAGE_NAME=arvados-sso-server
 INSTALL_PATH=/var/www/arvados-sso
 CONFIG_PATH=/etc/arvados/sso
-DOC_URL="http://doc.arvados.org/install/install-sso.html#configure"
+DOC_URL="https://doc.arvados.org/v2.0/install/install-sso.html#configure"
 RAILSPKG_DATABASE_LOAD_TASK=db:schema:load
 RAILSPKG_SUPPORTS_CONFIG_CHECK=0
index 9917b0fdf127c49e76c474dddb64fea7ca39e936..48fe1b53d49149139cf36e0b602e0f0fe4d2ab3e 100644 (file)
@@ -209,7 +209,6 @@ navbar:
       - install/install-keep-balance.html.textile.liquid
     - User interface:
       - install/setup-login.html.textile.liquid
-      - install/install-sso.html.textile.liquid
       - install/install-workbench-app.html.textile.liquid
       - install/install-workbench2-app.html.textile.liquid
       - install/install-composer.html.textile.liquid
@@ -227,5 +226,4 @@ navbar:
       - install/install-postgresql.html.textile.liquid
       - install/ruby.html.textile.liquid
       - install/nginx.html.textile.liquid
-      - install/google-auth.html.textile.liquid
       - install/install-docker.html.textile.liquid
index a6a0862c4f1d1383a44a80832b42cebaafd7f569..1d6b87da62116027a96788c8fe7b73c44a269133 100644 (file)
@@ -42,7 +42,6 @@ table(table table-bordered table-condensed table-hover).
 |keepstore|✓|
 |keep-balance|✓|
 |keep-web|✓|
-|sso-provider||
 |workbench1||
 |workbench2||
 
index 6dd0d866e74c80e1f8c39fa3fbfa9c0456d08787..b684111f95300d52f23e28708a9eee1528f9e661 100644 (file)
@@ -9,37 +9,8 @@ Copyright (C) The Arvados Authors. All rights reserved.
 SPDX-License-Identifier: CC-BY-SA-3.0
 {% endcomment %}
 
-This page describes how to enable users to use more than one upstream identity provider to log into the same Arvados account.  This can be used to migrate account providers, for example, from LDAP to Google.  In order to do this, users must be able to log into both the "old" and "new" providers.
+When a user logs in to Arvados, their email address (as returned by the authentication provider) is used as the primary key for their Arvados account.
 
-h2. Configure multiple or alternate provider in SSO
+If you reconfigure Arvados to use a different authentication provider after some users have created accounts, you should either ensure the new provider returns the same email addresses as the old one, or update your Arvados users' @email@ attributes to match the email addresses returned by the new provider.
 
-In @application.yml@ for the SSO server, you can enable both @google_oauth2@ and @ldap@ providers:
-
-<pre>
-production:
-  google_oauth2_client_id: abcd
-  google_oauth2_client_secret: abcd
-
-  use_ldap:
-    title: Example LDAP
-    host: ldap.example.com
-    port: 636
-    method: ssl
-    base: "ou=Users, dc=example, dc=com"
-    uid: uid
-    username: uid
-</pre>
-
-Restart the SSO server after changing the configuration.
-
-h2. Matching on email address
-
-If the new account provider supplies an email address (primary or alternate) that matches an existing user account, the user will be logged into that account.  No further migration is necessary, and the old provider can be removed from the SSO configuration.
-
-h2. Link accounts
-
-If the new provider cannot provide matching email addresses, users will have to migrate manually by "linking accounts":{{site.baseurl}}/user/topics/link-accounts.html
-
-After linking accounts, users can use the new provider to access their existing Arvados account.
-
-Once all users have migrated, the old account provider can be removed from the SSO configuration.
+Otherwise, next time users log in, they will be given new accounts instead of logging in to their existing accounts.
index 23d71204385af2e8d7a7a9ae17514b7223bea9c8..070e58983a50fc01c8943d6d29aa46b3d0453361 100644 (file)
@@ -38,7 +38,11 @@ h2(#master). development master (as of 2020-02-07)
 
 "Upgrading from 2.0.0":#v2_0_0
 
-None in current development master.
+h3. Removing sso-provider
+
+The SSO (single sign-on) component is deprecated and will not be supported in future releases. Existing configurations will continue to work in this release, but you should switch to one of the built-in authentication mechanisms as soon as possible. See "setting up web based login":{{site.baseurl}}/install/setup-login.html for details.
+
+After migrating your configuration, uninstall the @arvados-sso-provider@ package.
 
 h2(#v2_0_0). v2.0.0 (2020-02-07)
 
index 177abd8dbe8a25c37396e88354c9b5b535a470b4..9e53775ed4abc212ead38e249e42125a3eb260b1 100644 (file)
@@ -24,7 +24,7 @@ After completing the log in and authentication process, the API server receives
 
 If a provider identifier is given, the API server searches for a matching user record.
 
-If a provider identifier is not given, no match is found, it next searches by primary email and then alternate email address.  This enables "provider migration":migrating-providers.html and "pre-activated accounts.":#pre-activated
+If a provider identifier is not given, no match is found, it next searches by primary email and then alternate email address.  This enables "provider migration":migrating-providers.html and "pre-activated accounts.":#pre-activated
 
 If no user account is found, a new user account is created with the information from the identity provider.
 
diff --git a/doc/install/google-auth.html.textile.liquid b/doc/install/google-auth.html.textile.liquid
deleted file mode 100644 (file)
index fad10ff..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
----
-layout: default
-navsection: installguide
-title: Setting up Google auth
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-In order to use Google for authentication, you must use the <a href="https://console.developers.google.com" target="_blank">Google Developers Console</a> to create a set of client credentials.
-
-# Go to the <a href="https://console.developers.google.com" target="_blank">Google Developers Console</a> and select or create a project; this will take you to the project page.
-# Click on *+ Enable APIs and Services*
-## Search for *People API* and click on *Enable API*.
-# Navigate back to the main "APIs & Services" page
-# On the sidebar, click on *OAuth consent screen*
-## On consent screen settings, enter your identifying details
-## Under *Authorized domains* add @example.com@
-## Click on *Save*.
-# On the sidebar, click on *Credentials*; then click on *Create credentials*&rarr;*OAuth Client ID*
-# Under *Application type* select *Web application*.
-# You must set the authorization origins.  Edit @auth.example.com@ to the appropriate hostname that you will use to access the SSO service:
-## JavaScript origin should be @https://ClusterID.example.com/@ (using Arvados-controller based login) or @https://auth.example.com/@ (for the SSO server)
-## Redirect URI should be @https://ClusterID.example.com/login@ (using Arvados-controller based login) or @https://auth.example.com/users/auth/google_oauth2/callback@ (for the SSO server)
-# Copy the values of *Client ID* and *Client secret* from the Google Developers Console and add them to the appropriate configuration.
diff --git a/doc/install/install-components.html.textile.liquid b/doc/install/install-components.html.textile.liquid
deleted file mode 100644 (file)
index 15fbe11..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
----
-layout: default
-navsection: installguide
-title: Choosing which components to install
-...
-
-Arvados consists of many components, some of which may be omitted (at the cost of reduced functionality.)  It may also be helpful to review the "Arvados Architecture":{{site.baseurl}}/architecture to understand how these components interact.
-
-table(table table-bordered table-condensed).
-|\3=. *Core*|
-|"Postgres database":install-postgresql.html |Stores data for the API server.|Required.|
-|"API server":install-api-server.html |Core Arvados logic for managing users, groups, collections, containers, and enforcing permissions.|Required.|
-|\3=. *Keep (storage)*|
-|"Keepstore":install-keepstore.html |Stores content-addressed blocks in a variety of backends (local filesystem, cloud object storage).|Required.|
-|"Keepproxy":install-keepproxy.html |Gateway service to access keep servers from external networks.|Required to be able to use arv-put, arv-get, or arv-mount outside the private Arvados network.|
-|"Keep-web":install-keep-web.html |Gateway service providing read/write HTTP and WebDAV support on top of Keep.|Required to be able to download files from Keep over plain HTTP in Workbench.|
-|"Keep-balance":install-keep-balance.html |Storage cluster maintenance daemon responsible for moving blocks to their optimal server location, adjusting block replication levels, and trashing unreferenced blocks.|Required to free deleted data from underlying storage, and to ensure proper replication and block distribution (including support for storage classes).|
-|\3=. *User interface*|
-|"Single Sign On server":install-sso.html |Login server.|Required for web based login to Workbench.|
-|"Workbench":install-workbench-app.html, "Workbench2":install-workbench2-app.html |Primary graphical user interface for working with file collections and running containers.|Optional.  Depends on API server, SSO server, keep-web, websockets server.|
-|"Workflow Composer":install-composer.html |Graphical user interface for editing Common Workflow Language workflows.|Optional.  Depends on git server (arv-git-httpd).|
-|\3=. *Additional services*|
-|"Websockets server":install-ws.html |Event distribution server.|Required to view streaming container logs in Workbench.|
-|"Shell server":install-shell-server.html |Synchronize (create/delete/configure) Unix shell accounts with Arvados users.|Optional.|
-|"Git server":install-arv-git-httpd.html |Arvados-hosted git repositories, with Arvados-token based authentication.|Optional, but required by Workflow Composer.|
-|\3=. *Crunch (running containers)*|
-|"crunch-dispatch-slurm":crunch2-slurm/install-prerequisites.html |Run analysis workflows using Docker containers distributed across a SLURM cluster.|Optional if you wish to use Arvados for data management only.|
-|"Node Manager":install-nodemanager.html |Allocate and free cloud VM instances on demand based on workload.|Optional, not needed for a static SLURM cluster (such as on-premise HPC).|
index ea6ad47794d1ecf4afff02e7c1644f5c028f13d6..2ce6e36a612b701ec7b8de0494a6f71b19f4b175 100644 (file)
@@ -53,8 +53,7 @@ table(table table-bordered table-condensed).
 |"Keep-web":install-keep-web.html |Gateway service providing read/write HTTP and WebDAV support on top of Keep.|Required to access files from Workbench.|
 |"Keep-balance":install-keep-balance.html |Storage cluster maintenance daemon responsible for moving blocks to their optimal server location, adjusting block replication levels, and trashing unreferenced blocks.|Required to free deleted data from underlying storage, and to ensure proper replication and block distribution (including support for storage classes).|
 |\3=. *User interface*|
-|"Single Sign On server":install-sso.html |Web based login to Workbench.|Depends on identity provider.  Not required for Google.  Required for LDAP or standalone database.|
-|"Workbench":install-workbench-app.html, "Workbench2":install-workbench2-app.html |Primary graphical user interface for working with file collections and running containers.|Optional.  Depends on API server, SSO server, keep-web, websockets server.|
+|"Workbench":install-workbench-app.html, "Workbench2":install-workbench2-app.html |Primary graphical user interface for working with file collections and running containers.|Optional.  Depends on API server, keep-web, websockets server.|
 |"Workflow Composer":install-composer.html |Graphical user interface for editing Common Workflow Language workflows.|Optional.  Depends on git server (arv-git-httpd).|
 |\3=. *Additional services*|
 |"Websockets server":install-ws.html |Event distribution server.|Required to view streaming container logs in Workbench.|
@@ -68,9 +67,9 @@ h2(#identity). Identity provider
 
 Choose which backend you will use to authenticate users.
 
-* Google login to authenticate users with a Google account.  Note: if you only use this identity provider, login can be handled by @arvados-controller@ (recommended), and you do not need to install the Arvados Single Sign-On server (SSO).
-* LDAP login to authenticate users using the LDAP protocol, supported by many services such as OpenLDAP and Active Directory.  Supports username/password authentication.
-* Standalone SSO server user database.  Supports username/password authentication.  Supports new user sign-up.
+* Google login to authenticate users with a Google account.
+* LDAP login to authenticate users by username/password using the LDAP protocol, supported by many services such as OpenLDAP and Active Directory.
+* PAM login to authenticate users by username/password according to the PAM configuration on the controller node.
 
 h2(#storage). Storage backend
 
@@ -102,16 +101,14 @@ For a production installation, this is a reasonable starting point:
 table(table table-bordered table-condensed).
 |_. Function|_. Number of nodes|_. Recommended specs|
 |Postgres database, Arvados API server, Arvados controller, Git, Websockets, Container dispatcher|1|16+ GiB RAM, 4+ cores, fast disk for database|
-|Single Sign-On (SSO) server ^1^|1|2 GiB RAM|
 |Workbench, Keepproxy, Keep-web, Keep-balance|1|8 GiB RAM, 2+ cores|
-|Keepstore servers ^2^|2+|4 GiB RAM|
-|Compute worker nodes ^2^|0+ |Depends on workload; scaled dynamically in the cloud|
-|User shell nodes ^3^|0+|Depends on workload|
+|Keepstore servers ^1^|2+|4 GiB RAM|
+|Compute worker nodes ^1^|0+ |Depends on workload; scaled dynamically in the cloud|
+|User shell nodes ^2^|0+|Depends on workload|
 </div>
 
-^1^ May be omitted when using Google login support in @arvados-controller@
-^2^ Should be scaled up as needed
-^3^ Refers to shell nodes managed by Arvados, that provide ssh access for users to interact with Arvados at the command line.  Optional.
+^1^ Should be scaled up as needed
+^2^ Refers to shell nodes managed by Arvados, that provide ssh access for users to interact with Arvados at the command line.  Optional.
 
 {% include 'notebox_begin' %}
 For a small demo installation, it is possible to run all the Arvados services on a single node.  Special considerations for single-node installs will be noted in boxes like this.
@@ -140,7 +137,6 @@ table(table table-bordered table-condensed).
 |Arvados API|@ClusterID.example.com@|
 |Arvados Git server|git.@ClusterID.example.com@|
 |Arvados Websockets endpoint|ws.@ClusterID.example.com@|
-|Arvados SSO Server|@auth.example.com@|
 |Arvados Workbench|workbench.@ClusterID.example.com@|
 |Arvados Workbench 2|workbench2.@ClusterID.example.com@|
 |Arvados Keepproxy server|keep.@ClusterID.example.com@|
diff --git a/doc/install/install-sso.html.textile.liquid b/doc/install/install-sso.html.textile.liquid
deleted file mode 100644 (file)
index 72d4ed1..0000000
+++ /dev/null
@@ -1,237 +0,0 @@
----
-layout: default
-navsection: installguide
-title: Install the Single Sign On (SSO) server
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-{% include 'notebox_begin_warning' %}
-Skip this section if you are using Google login via @arvados-controller@.
-{% include 'notebox_end' %}
-
-# "Install dependencies":#dependencies
-# "Set up database":#database-setup
-# "Update config.yml":#update-config
-# "Configure the SSO server":#create-application-yml
-# "Update Nginx configuration":#update-nginx
-# "Install arvados-sso-server":#install-packages
-# "Create arvados-server client record":#client
-# "Restart the API server and controller":#restart-api
-
-h2(#dependencies). Install dependencies
-
-# "Install PostgreSQL":install-postgresql.html
-# "Install Ruby and Bundler":ruby.html  Important!  The Single Sign On server only supports Ruby 2.3, to avoid version conflicts we recommend installing it on a different server from the API server.  When installing Ruby, ensure that you get the right version by installing the "ruby2.3" package, or by using RVM with @--ruby=2.3@
-# "Install nginx":nginx.html
-# "Install Phusion Passenger":https://www.phusionpassenger.com/library/walkthroughs/deploy/ruby/ownserver/nginx/oss/install_passenger_main.html
-
-h2(#database-setup). Set up the database
-
-{% assign service_role = "arvados_sso" %}
-{% assign service_database = "arvados_sso_production" %}
-{% assign use_contrib = false %}
-{% include 'install_postgres_database' %}
-
-Now create @/etc/arvados/sso/database.yml@
-
-<pre>
-production:
-  adapter: postgresql
-  encoding: utf8
-  database: arvados_sso_production
-  username: arvados_sso
-  password: $password
-  host: localhost
-  template: template0
-</pre>
-
-h2(#update-config). Update config.yml
-
-<pre>
-    Services:
-      SSO:
-        ExternalURL: auth.ClusterID.example.com
-    Login:
-      ProviderAppID: "arvados-server"
-      ProviderAppSecret: $app_secret
-</pre>
-
-Generate @ProviderAppSecret@:
-
-<notextile>
-<pre><code>~$ <span class="userinput">ruby -e 'puts rand(2**400).to_s(36)'</span>
-zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
-</code></pre></notextile>
-
-h2(#create-application-yml). Configure the SSO server
-
-The SSO server runs from the @/var/www/arvados-sso/current/@ directory. The files @/var/www/arvados-sso/current/config/application.yml@ and @/var/www/arvados-sso/current/config/database.yml@ will be symlinked to the configuration files in @/etc/arvados/sso/@.
-
-The SSO server reads the @config/application.yml@ file, as well as the @config/application.defaults.yml@ file. Values in @config/application.yml@ take precedence over the defaults that are defined in @config/application.defaults.yml@. The @config/application.yml.example@ file is not read by the SSO server and is provided for installation convenience only.
-
-Consult @config/application.default.yml@ for a full list of configuration options.  Local configuration goes in @/etc/arvados/sso/application.yml@, do not edit @config/application.default.yml@.
-
-Create @/etc/arvados/sso/application.yml@ and add these keys:
-
-<pre>
-production:
-  uuid_prefix: xxxxx
-  secret_token: zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
-</pre>
-
-h3(#uuid_prefix). uuid_prefix
-
-Most of the time, you want this to be the same as your @ClusterID@.  If not, generate a new one from the command line listed previously.
-
-h3(#secret_token). secret_token
-
-Generate a new secret token for signing cookies:
-
-<notextile>
-<pre><code>~$ <span class="userinput">ruby -e 'puts rand(2**400).to_s(36)'</span>
-zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
-</code></pre></notextile>
-
-h3(#authentication_methods). Authentication methods
-
-Authentication methods are configured in @application.yml@.  Currently three authentication methods are supported: local accounts, LDAP, and Google.  If neither Google nor LDAP are enabled, the SSO server defaults to local user accounts.   Only one authentication mechanism should be in use at a time.  Choose your authentication method and add the listed configuration items to the @production@ section.
-
-h4(#local_accounts). Local account authentication
-
-There are two configuration options for local accounts:
-
-<pre>
-  # If true, allow new creation of new accounts in the SSO server's internal
-  # user database.
-  allow_account_registration: false
-
-  # If true, send an email confirmation before activating new accounts in the
-  # SSO server's internal user database (otherwise users are activated immediately.)
-  require_email_confirmation: false
-</pre>
-
-For more information about configuring backend support for sending email (required to send email confirmations) see "Configuring Action Mailer":http://guides.rubyonrails.org/configuring.html#configuring-action-mailer
-
-If @allow_account_registration@ is false, you may manually create local accounts on the SSO server from the Rails console.  {% include 'install_rails_command' %}
-
-Enter the following commands at the console.
-
-<notextile>
-<pre><code>:001 &gt; <span class="userinput">user = User.new(:email =&gt; "test@example.com")</span>
-:002 &gt; <span class="userinput">user.password = "passw0rd"</span>
-:003 &gt; <span class="userinput">user.save!</span>
-:004 &gt; <span class="userinput">quit</span>
-</code></pre>
-</notextile>
-
-h4(#ldap). LDAP authentication
-
-The following options are available to configure LDAP authentication.  Note that you must preserve the indentation of the fields listed under @use_ldap@.
-
-<pre>
-  use_ldap:
-    title: Example LDAP
-    host: ldap.example.com
-    port: 636
-    method: ssl
-    base: "ou=Users, dc=example, dc=com"
-    uid: uid
-    email_domain: example.com
-    #bind_dn: "some_user"
-    #password: "some_password"
-</pre>
-
-table(table).
-|_. Option|_. Description|
-|title |Title displayed to the user on the login page|
-|host  |LDAP server hostname|
-|port  |LDAP server port|
-|method|One of "plain", "ssl", "tls"|
-|base  |Directory lookup base|
-|uid   |User id field used for directory lookup|
-|email_domain|Strip off specified email domain from login and perform lookup on bare username|
-|bind_dn|If required by server, username to log with in before performing directory lookup|
-|password|If required by server, password to log with before performing directory lookup|
-
-h4(#google). Google authentication
-
-First, visit "Setting up Google auth.":google-auth.html
-
-Next, copy the values of *Client ID* and *Client secret* from the Google Developers Console into the Google section of @config/application.yml@, like this:
-
-<notextile>
-<pre><code>  # Google API tokens required for OAuth2 login.
-  google_oauth2_client_id: <span class="userinput">"---YOUR---CLIENT---ID---HERE--"-</span>
-  google_oauth2_client_secret: <span class="userinput">"---YOUR---CLIENT---SECRET---HERE--"-</span></code></pre></notextile>
-
-h2(#update-nginx). Update nginx configuration
-
-Use a text editor to create a new file @/etc/nginx/conf.d/arvados-sso.conf@ with the following configuration.  Options that need attention are marked in <span class="userinput">red</span>.
-
-<notextile>
-<pre><code>server {
-  listen       <span class="userinput">auth.ClusterID.example.com</span>:443 ssl;
-  server_name  <span class="userinput">auth.ClusterID.example.com</span>;
-
-  ssl on;
-  ssl_certificate     <span class="userinput">/YOUR/PATH/TO/cert.pem</span>;
-  ssl_certificate_key <span class="userinput">/YOUR/PATH/TO/cert.key</span>;
-
-  root   /var/www/arvados-sso/current/public;
-  index  index.html;
-
-  passenger_enabled on;
-
-  # <span class="userinput">If you are using RVM, uncomment the line below.</span>
-  # <span class="userinput">If you're using system ruby, leave it commented out.</span>
-  #passenger_ruby /usr/local/rvm/wrappers/default/ruby;
-}
-</code></pre>
-</notextile>
-
-h2(#install-packages). Install arvados-sso-server package
-
-h3. Centos 7
-
-<notextile>
-<pre><code># <span class="userinput">yum install arvados-sso-server</span>
-</code></pre>
-</notextile>
-
-h3. Debian and Ubuntu
-
-<notextile>
-<pre><code># <span class="userinput">apt-get install --no-install-recommends arvados-sso-server</span>
-</code></pre>
-</notextile>
-
-h2(#client). Create arvados-server client record
-
-{% assign railshost = "" %}
-{% assign railsdir = "/var/www/arvados-sso/current" %}
-Use @rails console@ to create a @Client@ record that will be used by the Arvados API server.  {% include 'install_rails_command' %}
-
-Enter the following commands at the console.  The values that appear after you assign @app_id@ and @app_secret@ will be copied to  @Login.ProviderAppID@ and @Login.ProviderAppSecret@ in @config.yml@.
-
-<notextile>
-<pre><code>:001 &gt; <span class="userinput">c = Client.new</span>
-:002 &gt; <span class="userinput">c.name = "joshid"</span>
-:003 &gt; <span class="userinput">c.app_id = "arvados-server"</span>
-:004 &gt; <span class="userinput">c.app_secret = "the value of Login.ProviderAppSecret"</span>
-:005 &gt; <span class="userinput">c.save!</span>
-:006 &gt; <span class="userinput">quit</span>
-</code></pre>
-</notextile>
-
-h2(#restart-api). Restart the API server and controller
-
-After adding the SSO server to the Services section, make sure the cluster config file is up to date on the API server host, and restart the API server and controller processes to ensure the changes are applied.
-
-<notextile>
-<pre><code># <span class="userinput">systemctl restart nginx arvados-controller</span>
-</code></pre>
-</notextile>
index e7b20f45a0833f5f34c41c2e913cc6abcf6ff7ac..11862a6ae6767c73ab935e8e93c4134991026789 100644 (file)
@@ -71,21 +71,12 @@ server {
 
 {% include 'restart_api' %}
 
-h2(#restart-api). Restart the API server and controller
-
-After adding the SSO server to the Services section, make sure the cluster config file is up to date on the API server host, and restart the API server and controller processes to ensure the changes are applied.
-
-<notextile>
-<pre><code># <span class="userinput">systemctl restart nginx arvados-controller</span>
-</code></pre>
-</notextile>
-
 h2(#confirm). Confirm working installation
 
 Confirm the service is listening on its assigned port and responding to requests.
 
 <notextile>
-<pre><code>~$ <span class="userinput">curl https://<span class="userinput">ws.ClusterID.example.com</span>/status.json</span>
-{"Clients":1}
+<pre><code>~$ <span class="userinput">curl https://<span class="userinput">ws.ClusterID.example.com</span>/websocket</span>
+not websocket protocol
 </code></pre>
 </notextile>
index c9e65ca7e1c5e3dc37aa5e46f40a83552f6350f8..3fe442c75b2e48e67cc9de126abeabb011fb65c4 100644 (file)
@@ -12,23 +12,62 @@ SPDX-License-Identifier: CC-BY-SA-3.0
 Select one of the following login mechanisms for your cluster.
 
 # If all users will authenticate with Google, "configure Google login":#google.
+# If all users will authenticate with an existing LDAP service, "configure LDAP":#ldap.
 # If all users will authenticate using PAM as configured on your controller node, "configure PAM":#pam.
-# If you need to enable multiple authentication methods, or your backend can't be configured as a PAM service on your controller node, "configure a separate single sign-on (SSO) server":#sso.
 
 h2(#google). Google login
 
 With this configuration, users will sign in with their Google accounts.
 
-First, visit "Setting up Google auth.":google-auth.html
+Use the <a href="https://console.developers.google.com" target="_blank">Google Developers Console</a> to create a set of client credentials.
+# Select or create a project.
+# Click *+ Enable APIs and Services*.
+#* Search for *People API* and click *Enable API*.
+#* Navigate back to the main "APIs & Services" page.
+# On the sidebar, click *OAuth consent screen*.
+#* On consent screen settings, enter your identifying details.
+#* Under *Authorized domains* add your domain (@example.com@).
+#* Click *Save*.
+# On the sidebar, click *Credentials*, then click *Create credentials*&rarr;*OAuth client ID*
+# Under *Application type* select *Web application*.
+# Add the JavaScript origin: @https://ClusterID.example.com/@
+# Add the Redirect URI: @https://ClusterID.example.com/login@
+# Copy the values of *Client ID* and *Client secret* to the @Login.Google@ section of @config.yml@.
 
-Next, copy the values of *Client ID* and *Client secret* from the Google Developers Console into @Login.GoogleClientID@ and @Login.GoogleClientSecret@ of @config.yml@:
+<pre>
+    Login:
+      Google:
+        Enable: true
+        ClientID: "0000000000000-zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz.apps.googleusercontent.com"
+        ClientSecret: "zzzzzzzzzzzzzzzzzzzzzzzz"
+</pre>
+
+h2(#ldap). LDAP
+
+With this configuration, authentication uses an external LDAP service like OpenLDAP or Active Directory.
+
+Enable LDAP authentication and provide your LDAP server's host, port, and credentials (if needed to search the directory) in @config.yml@:
 
 <pre>
     Login:
-      GoogleClientID: "0000000000000-zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz.apps.googleusercontent.com"
-      GoogleClientSecret: "zzzzzzzzzzzzzzzzzzzzzzzz"
+      LDAP:
+        Enable: true
+        URL: ldap://ldap.example.com:389
+        SearchBindUser: cn=lookupuser,dc=example,dc=com
+        SearchBindPassword: xxxxxxxx
+        SearchBase: ou=Users,dc=example,dc=com
 </pre>
 
+The email address reported by LDAP will be used as primary key for Arvados accounts. This means *users must not be able to edit their own email addresses* in the directory.
+
+Additional configuration settings are available:
+* @StartTLS@ is enabled by default.
+* @StripDomain@ and @AppendDomain@ modify the username entered by the user before searching for it in the directory.
+* @SearchAttribute@ (default @uid@) is the LDAP attribute used when searching for usernames.
+* @SearchFilters@ accepts LDAP filter expressions to control which users can log in.
+
+Check the LDAP section in the "default config file":{{site.baseurl}}/admin/config.html for more details and configuration options.
+
 h2(#pam). PAM (experimental)
 
 With this configuration, authentication is done according to the Linux PAM ("Pluggable Authentication Modules") configuration on your controller host.
@@ -37,7 +76,8 @@ Enable PAM authentication in @config.yml@:
 
 <pre>
     Login:
-      PAM: true
+      PAM:
+        Enable: true
 </pre>
 
 Check the "default config file":{{site.baseurl}}/admin/config.html for more PAM configuration options.
@@ -47,9 +87,3 @@ The default PAM configuration on most Linux systems uses the local password data
 PAM can also be configured to use different backends like LDAP. In a production environment, PAM configuration should use the service name ("arvados" by default) to set a separate policy for Arvados logins: generally, Arvados users should not have shell accounts on the controller node.
 
 For information about configuring PAM, refer to the "PAM System Administrator's Guide":http://www.linux-pam.org/Linux-PAM-html/Linux-PAM_SAG.html.
-
-h2(#sso). Separate single-sign-on (SSO) server
-
-With this configuration, Arvados passes off authentication to a separate SSO server that supports Google, LDAP, and a local password database.
-
-See "Install the Single Sign On (SSO) server":install-sso.html
diff --git a/go.mod b/go.mod
index 34b7e0779072fcf060d325ddffe77f54adffdfbf..cc5457975f54da4d6e00702a955451f104fe39d1 100644 (file)
--- a/go.mod
+++ b/go.mod
@@ -12,6 +12,7 @@ require (
        github.com/arvados/cgofuse v1.2.0-arvados1
        github.com/aws/aws-sdk-go v1.25.30
        github.com/bgentry/speakeasy v0.1.0 // indirect
+       github.com/bradleypeabody/godap v0.0.0-20170216002349-c249933bc092
        github.com/coreos/go-oidc v2.1.0+incompatible
        github.com/coreos/go-systemd v0.0.0-20180108085132-cc4f39464dc7
        github.com/dgrijalva/jwt-go v3.1.0+incompatible // indirect
@@ -25,6 +26,8 @@ require (
        github.com/fsnotify/fsnotify v1.4.9
        github.com/ghodss/yaml v1.0.0
        github.com/gliderlabs/ssh v0.2.2 // indirect
+       github.com/go-asn1-ber/asn1-ber v1.4.1 // indirect
+       github.com/go-ldap/ldap v3.0.3+incompatible
        github.com/gogo/protobuf v1.1.1
        github.com/gorilla/context v1.1.1 // indirect
        github.com/gorilla/mux v1.6.1-0.20180107155708-5bbbb5b2b572
@@ -57,6 +60,7 @@ require (
        golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
        golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd
        google.golang.org/api v0.13.0
+       gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
        gopkg.in/check.v1 v1.0.0-20161208181325-20d25e280405
        gopkg.in/square/go-jose.v2 v2.3.1
        gopkg.in/src-d/go-billy.v4 v4.0.1
diff --git a/go.sum b/go.sum
index 03b2f77b6d9ce62ecc5c2cf6abae2f4995bb3152..38153ce3eaa08844dd2abfb944b9318145fbeed0 100644 (file)
--- a/go.sum
+++ b/go.sum
@@ -29,6 +29,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/bradleypeabody/godap v0.0.0-20170216002349-c249933bc092 h1:0Di2onNnlN5PAyWPbqlPyN45eOQ+QW/J9eqLynt4IV4=
+github.com/bradleypeabody/godap v0.0.0-20170216002349-c249933bc092/go.mod h1:8IzBjZCRSnsvM6MJMG8HNNtnzMl48H22rbJL2kRUJ0Y=
 github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA=
 github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
@@ -62,8 +64,12 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
 github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
+github.com/go-asn1-ber/asn1-ber v1.4.1 h1:qP/QDxOtmMoJVgXHCXNzDpA0+wkgYB2x5QoLMVOciyw=
+github.com/go-asn1-ber/asn1-ber v1.4.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-ldap/ldap v3.0.3+incompatible h1:HTeSZO8hWMS1Rgb2Ziku6b8a7qRIZZMHjsvuZyatzwk=
+github.com/go-ldap/ldap v3.0.3+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
@@ -249,6 +255,8 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi
 google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM=
+gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20161208181325-20d25e280405 h1:829vOVxxusYHC+IqBtkX5mbKtsY9fheQiQn0MZRVLfQ=
 gopkg.in/check.v1 v1.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
index 12f4bd9ded026471fe8fb8ed3d8641d1479d8b6e..0efe49c1cb9331621ebb6d141ef55697c52464fe 100644 (file)
@@ -524,54 +524,123 @@ Clusters:
         MaxUUIDEntries:       1000
 
     Login:
-      # These settings are provided by your OAuth2 provider (eg
-      # Google) used to perform upstream authentication.
-      ProviderAppID: ""
-      ProviderAppSecret: ""
-
-      # (Experimental) Authenticate with Google, bypassing the
-      # SSO-provider gateway service. Use the Google Cloud console to
-      # enable the People API (APIs and Services > Enable APIs and
-      # services > Google People API > Enable), generate a Client ID
-      # and secret (APIs and Services > Credentials > Create
-      # credentials > OAuth client ID > Web application) and add your
-      # controller's /login URL (e.g.,
-      # "https://zzzzz.example.com/login") as an authorized redirect
-      # URL.
-      #
-      # Incompatible with ForceLegacyAPI14. ProviderAppID must be
-      # blank.
-      GoogleClientID: ""
-      GoogleClientSecret: ""
-
-      # Allow users to log in to existing accounts using any verified
-      # email address listed by their Google account. If true, the
-      # Google People API must be enabled in order for Google login to
-      # work. If false, only the primary email address will be used.
-      GoogleAlternateEmailAddresses: true
-
-      # (Experimental) Use PAM to authenticate logins, using the
-      # specified PAM service name.
-      #
-      # Cannot be used in combination with OAuth2 (ProviderAppID) or
-      # Google (GoogleClientID). Cannot be used on a cluster acting as
-      # a LoginCluster.
-      PAM: false
-      PAMService: arvados
-
-      # Domain name (e.g., "example.com") to use to construct the
-      # user's email address if PAM authentication returns a username
-      # with no "@". If empty, use the PAM username as the user's
-      # email address, whether or not it contains "@".
-      #
-      # Note that the email address is used as the primary key for
-      # user records when logging in. Therefore, if you change
-      # PAMDefaultEmailDomain after the initial installation, you
-      # should also update existing user records to reflect the new
-      # domain. Otherwise, next time those users log in, they will be
-      # given new accounts instead of accessing their existing
-      # accounts.
-      PAMDefaultEmailDomain: ""
+      # One of the following mechanisms (SSO, Google, PAM, LDAP, or
+      # LoginCluster) should be enabled; see
+      # https://doc.arvados.org/install/setup-login.html
+
+      Google:
+        # Authenticate with Google.
+        Enable: false
+
+        # Use the Google Cloud console to enable the People API (APIs
+        # and Services > Enable APIs and services > Google People API
+        # > Enable), generate a Client ID and secret (APIs and
+        # Services > Credentials > Create credentials > OAuth client
+        # ID > Web application) and add your controller's /login URL
+        # (e.g., "https://zzzzz.example.com/login") as an authorized
+        # redirect URL.
+        #
+        # Incompatible with ForceLegacyAPI14. ProviderAppID must be
+        # blank.
+        ClientID: ""
+        ClientSecret: ""
+
+        # Allow users to log in to existing accounts using any verified
+        # email address listed by their Google account. If true, the
+        # Google People API must be enabled in order for Google login to
+        # work. If false, only the primary email address will be used.
+        AlternateEmailAddresses: true
+
+      PAM:
+        # (Experimental) Use PAM to authenticate users.
+        Enable: false
+
+        # PAM service name. PAM will apply the policy in the
+        # corresponding config file (e.g., /etc/pam.d/arvados) or, if
+        # there is none, the default "other" config.
+        Service: arvados
+
+        # Domain name (e.g., "example.com") to use to construct the
+        # user's email address if PAM authentication returns a
+        # username with no "@". If empty, use the PAM username as the
+        # user's email address, whether or not it contains "@".
+        #
+        # Note that the email address is used as the primary key for
+        # user records when logging in. Therefore, if you change
+        # PAMDefaultEmailDomain after the initial installation, you
+        # should also update existing user records to reflect the new
+        # domain. Otherwise, next time those users log in, they will
+        # be given new accounts instead of accessing their existing
+        # accounts.
+        DefaultEmailDomain: ""
+
+      LDAP:
+        # Use an LDAP service to authenticate users.
+        Enable: false
+
+        # Server URL, like "ldap://ldapserver.example.com:389" or
+        # "ldaps://ldapserver.example.com:636".
+        URL: "ldap://ldap:389"
+
+        # Use StartTLS upon connecting to the server.
+        StartTLS: true
+
+        # Skip TLS certificate name verification.
+        InsecureTLS: false
+
+        # Strip the @domain part if a user supplies an email-style
+        # username with this domain. If "*", strip any user-provided
+        # domain. If "", never strip the domain part. Example:
+        # "example.com"
+        StripDomain: ""
+
+        # If, after applying StripDomain, the username contains no "@"
+        # character, append this domain to form an email-style
+        # username. Example: "example.com"
+        AppendDomain: ""
+
+        # The LDAP attribute to filter on when looking up a username
+        # (after applying StripDomain and AppendDomain).
+        SearchAttribute: uid
+
+        # Bind with this username (DN or UPN) and password when
+        # looking up the user record.
+        #
+        # Example user: "cn=admin,dc=example,dc=com"
+        SearchBindUser: ""
+        SearchBindPassword: ""
+
+        # Directory base for username lookup. Example:
+        # "ou=Users,dc=example,dc=com"
+        SearchBase: ""
+
+        # Additional filters for username lookup. Special characters
+        # in assertion values must be escaped (see RFC4515). Example:
+        # "(objectClass=person)"
+        SearchFilters: ""
+
+        # LDAP attribute to use as the user's email address.
+        #
+        # Important: This must not be an attribute whose value can be
+        # edited in the directory by the users themselves. Otherwise,
+        # users can take over other users' Arvados accounts trivially
+        # (email address is the primary key for Arvados accounts.)
+        EmailAttribute: mail
+
+        # LDAP attribute to use as the preferred Arvados username. If
+        # no value is found (or this config is empty) the username
+        # originally supplied by the user will be used.
+        UsernameAttribute: uid
+
+      SSO:
+        # Authenticate with a separate SSO server. (Deprecated)
+        Enable: false
+
+        # ProviderAppID and ProviderAppSecret are generated during SSO
+        # setup; see
+        # https://doc.arvados.org/v2.0/install/install-sso.html#update-config
+        ProviderAppID: ""
+        ProviderAppSecret: ""
 
       # The cluster ID to delegate the user database.  When set,
       # logins on this cluster will be redirected to the login cluster
index bbbc9acf7534cd562c1e5dad0615fc688b07fd9b..1be7208ee38facce00e71f2cfdf07885ccffde08 100644 (file)
@@ -23,6 +23,13 @@ type deprRequestLimits struct {
 type deprCluster struct {
        RequestLimits deprRequestLimits
        NodeProfiles  map[string]nodeProfile
+       Login         struct {
+               GoogleClientID                *string
+               GoogleClientSecret            *string
+               GoogleAlternateEmailAddresses *bool
+               ProviderAppID                 *string
+               ProviderAppSecret             *string
+       }
 }
 
 type deprecatedConfig struct {
@@ -80,6 +87,34 @@ func (ldr *Loader) applyDeprecatedConfig(cfg *arvados.Config) error {
                if dst, n := &cluster.API.MaxRequestAmplification, dcluster.RequestLimits.MultiClusterRequestConcurrency; n != nil && *n != *dst {
                        *dst = *n
                }
+
+               // Google* moved to Google.*
+               if dst, n := &cluster.Login.Google.ClientID, dcluster.Login.GoogleClientID; n != nil && *n != *dst {
+                       *dst = *n
+                       if *n != "" {
+                               // In old config, non-empty ClientID meant enable
+                               cluster.Login.Google.Enable = true
+                       }
+               }
+               if dst, n := &cluster.Login.Google.ClientSecret, dcluster.Login.GoogleClientSecret; n != nil && *n != *dst {
+                       *dst = *n
+               }
+               if dst, n := &cluster.Login.Google.AlternateEmailAddresses, dcluster.Login.GoogleAlternateEmailAddresses; n != nil && *n != *dst {
+                       *dst = *n
+               }
+
+               // Provider* moved to SSO.Provider*
+               if dst, n := &cluster.Login.SSO.ProviderAppID, dcluster.Login.ProviderAppID; n != nil && *n != *dst {
+                       *dst = *n
+                       if *n != "" {
+                               // In old config, non-empty ID meant enable
+                               cluster.Login.SSO.Enable = true
+                       }
+               }
+               if dst, n := &cluster.Login.SSO.ProviderAppSecret, dcluster.Login.ProviderAppSecret; n != nil && *n != *dst {
+                       *dst = *n
+               }
+
                cfg.Clusters[id] = cluster
        }
        return nil
index 8a49c2cf8d2c7b744712f809f525a2d82afa36b5..87e26fd09672805aa5bd840b757df71778477057 100644 (file)
@@ -89,6 +89,41 @@ Clusters:
 `)
 }
 
+func (s *LoadSuite) TestDeprecatedLoginBackend(c *check.C) {
+       checkEquivalent(c, `
+Clusters:
+ z1111:
+  Login:
+   GoogleClientID: aaaa
+   GoogleClientSecret: bbbb
+   GoogleAlternateEmailAddresses: true
+`, `
+Clusters:
+ z1111:
+  Login:
+   Google:
+    Enable: true
+    ClientID: aaaa
+    ClientSecret: bbbb
+    AlternateEmailAddresses: true
+`)
+       checkEquivalent(c, `
+Clusters:
+ z1111:
+  Login:
+   ProviderAppID: aaaa
+   ProviderAppSecret: bbbb
+`, `
+Clusters:
+ z1111:
+  Login:
+   SSO:
+    Enable: true
+    ProviderAppID: aaaa
+    ProviderAppSecret: bbbb
+`)
+}
+
 func (s *LoadSuite) TestLegacyKeepWebConfig(c *check.C) {
        content := []byte(`
 {
index ded03fc3030c8811a6d12210a6c1f9b57253dfaf..26782c8ba61dca3e4b657b911b6361439522ebb6 100644 (file)
@@ -131,15 +131,34 @@ var whitelist = map[string]bool{
        "InstanceTypes.*":                              true,
        "InstanceTypes.*.*":                            true,
        "Login":                                        true,
-       "Login.GoogleClientID":                         false,
-       "Login.GoogleClientSecret":                     false,
-       "Login.GoogleAlternateEmailAddresses":          false,
-       "Login.PAM":                                    true,
-       "Login.PAMService":                             false,
-       "Login.PAMDefaultEmailDomain":                  false,
-       "Login.ProviderAppID":                          false,
-       "Login.ProviderAppSecret":                      false,
+       "Login.Google":                                 true,
+       "Login.Google.AlternateEmailAddresses":         false,
+       "Login.Google.ClientID":                        false,
+       "Login.Google.ClientSecret":                    false,
+       "Login.Google.Enable":                          true,
+       "Login.LDAP":                                   true,
+       "Login.LDAP.AppendDomain":                      false,
+       "Login.LDAP.EmailAttribute":                    false,
+       "Login.LDAP.Enable":                            true,
+       "Login.LDAP.InsecureTLS":                       false,
+       "Login.LDAP.SearchAttribute":                   false,
+       "Login.LDAP.SearchBase":                        false,
+       "Login.LDAP.SearchBindPassword":                false,
+       "Login.LDAP.SearchBindUser":                    false,
+       "Login.LDAP.SearchFilters":                     false,
+       "Login.LDAP.StartTLS":                          false,
+       "Login.LDAP.StripDomain":                       false,
+       "Login.LDAP.URL":                               false,
+       "Login.LDAP.UsernameAttribute":                 false,
        "Login.LoginCluster":                           true,
+       "Login.PAM":                                    true,
+       "Login.PAM.DefaultEmailDomain":                 false,
+       "Login.PAM.Enable":                             true,
+       "Login.PAM.Service":                            false,
+       "Login.SSO":                                    true,
+       "Login.SSO.Enable":                             true,
+       "Login.SSO.ProviderAppID":                      false,
+       "Login.SSO.ProviderAppSecret":                  false,
        "Login.RemoteTokenRefresh":                     true,
        "Mail":                                         true,
        "Mail.MailchimpAPIKey":                         false,
index 88cb9139a23e0361444969fd307bf34d399c05d5..c9d29f814ddddc6d3d443006b823199d2f3e9e93 100644 (file)
@@ -530,54 +530,123 @@ Clusters:
         MaxUUIDEntries:       1000
 
     Login:
-      # These settings are provided by your OAuth2 provider (eg
-      # Google) used to perform upstream authentication.
-      ProviderAppID: ""
-      ProviderAppSecret: ""
-
-      # (Experimental) Authenticate with Google, bypassing the
-      # SSO-provider gateway service. Use the Google Cloud console to
-      # enable the People API (APIs and Services > Enable APIs and
-      # services > Google People API > Enable), generate a Client ID
-      # and secret (APIs and Services > Credentials > Create
-      # credentials > OAuth client ID > Web application) and add your
-      # controller's /login URL (e.g.,
-      # "https://zzzzz.example.com/login") as an authorized redirect
-      # URL.
-      #
-      # Incompatible with ForceLegacyAPI14. ProviderAppID must be
-      # blank.
-      GoogleClientID: ""
-      GoogleClientSecret: ""
-
-      # Allow users to log in to existing accounts using any verified
-      # email address listed by their Google account. If true, the
-      # Google People API must be enabled in order for Google login to
-      # work. If false, only the primary email address will be used.
-      GoogleAlternateEmailAddresses: true
-
-      # (Experimental) Use PAM to authenticate logins, using the
-      # specified PAM service name.
-      #
-      # Cannot be used in combination with OAuth2 (ProviderAppID) or
-      # Google (GoogleClientID). Cannot be used on a cluster acting as
-      # a LoginCluster.
-      PAM: false
-      PAMService: arvados
-
-      # Domain name (e.g., "example.com") to use to construct the
-      # user's email address if PAM authentication returns a username
-      # with no "@". If empty, use the PAM username as the user's
-      # email address, whether or not it contains "@".
-      #
-      # Note that the email address is used as the primary key for
-      # user records when logging in. Therefore, if you change
-      # PAMDefaultEmailDomain after the initial installation, you
-      # should also update existing user records to reflect the new
-      # domain. Otherwise, next time those users log in, they will be
-      # given new accounts instead of accessing their existing
-      # accounts.
-      PAMDefaultEmailDomain: ""
+      # One of the following mechanisms (SSO, Google, PAM, LDAP, or
+      # LoginCluster) should be enabled; see
+      # https://doc.arvados.org/install/setup-login.html
+
+      Google:
+        # Authenticate with Google.
+        Enable: false
+
+        # Use the Google Cloud console to enable the People API (APIs
+        # and Services > Enable APIs and services > Google People API
+        # > Enable), generate a Client ID and secret (APIs and
+        # Services > Credentials > Create credentials > OAuth client
+        # ID > Web application) and add your controller's /login URL
+        # (e.g., "https://zzzzz.example.com/login") as an authorized
+        # redirect URL.
+        #
+        # Incompatible with ForceLegacyAPI14. ProviderAppID must be
+        # blank.
+        ClientID: ""
+        ClientSecret: ""
+
+        # Allow users to log in to existing accounts using any verified
+        # email address listed by their Google account. If true, the
+        # Google People API must be enabled in order for Google login to
+        # work. If false, only the primary email address will be used.
+        AlternateEmailAddresses: true
+
+      PAM:
+        # (Experimental) Use PAM to authenticate users.
+        Enable: false
+
+        # PAM service name. PAM will apply the policy in the
+        # corresponding config file (e.g., /etc/pam.d/arvados) or, if
+        # there is none, the default "other" config.
+        Service: arvados
+
+        # Domain name (e.g., "example.com") to use to construct the
+        # user's email address if PAM authentication returns a
+        # username with no "@". If empty, use the PAM username as the
+        # user's email address, whether or not it contains "@".
+        #
+        # Note that the email address is used as the primary key for
+        # user records when logging in. Therefore, if you change
+        # PAMDefaultEmailDomain after the initial installation, you
+        # should also update existing user records to reflect the new
+        # domain. Otherwise, next time those users log in, they will
+        # be given new accounts instead of accessing their existing
+        # accounts.
+        DefaultEmailDomain: ""
+
+      LDAP:
+        # Use an LDAP service to authenticate users.
+        Enable: false
+
+        # Server URL, like "ldap://ldapserver.example.com:389" or
+        # "ldaps://ldapserver.example.com:636".
+        URL: "ldap://ldap:389"
+
+        # Use StartTLS upon connecting to the server.
+        StartTLS: true
+
+        # Skip TLS certificate name verification.
+        InsecureTLS: false
+
+        # Strip the @domain part if a user supplies an email-style
+        # username with this domain. If "*", strip any user-provided
+        # domain. If "", never strip the domain part. Example:
+        # "example.com"
+        StripDomain: ""
+
+        # If, after applying StripDomain, the username contains no "@"
+        # character, append this domain to form an email-style
+        # username. Example: "example.com"
+        AppendDomain: ""
+
+        # The LDAP attribute to filter on when looking up a username
+        # (after applying StripDomain and AppendDomain).
+        SearchAttribute: uid
+
+        # Bind with this username (DN or UPN) and password when
+        # looking up the user record.
+        #
+        # Example user: "cn=admin,dc=example,dc=com"
+        SearchBindUser: ""
+        SearchBindPassword: ""
+
+        # Directory base for username lookup. Example:
+        # "ou=Users,dc=example,dc=com"
+        SearchBase: ""
+
+        # Additional filters for username lookup. Special characters
+        # in assertion values must be escaped (see RFC4515). Example:
+        # "(objectClass=person)"
+        SearchFilters: ""
+
+        # LDAP attribute to use as the user's email address.
+        #
+        # Important: This must not be an attribute whose value can be
+        # edited in the directory by the users themselves. Otherwise,
+        # users can take over other users' Arvados accounts trivially
+        # (email address is the primary key for Arvados accounts.)
+        EmailAttribute: mail
+
+        # LDAP attribute to use as the preferred Arvados username. If
+        # no value is found (or this config is empty) the username
+        # originally supplied by the user will be used.
+        UsernameAttribute: uid
+
+      SSO:
+        # Authenticate with a separate SSO server. (Deprecated)
+        Enable: false
+
+        # ProviderAppID and ProviderAppSecret are generated during SSO
+        # setup; see
+        # https://doc.arvados.org/v2.0/install/install-sso.html#update-config
+        ProviderAppID: ""
+        ProviderAppSecret: ""
 
       # The cluster ID to delegate the user database.  When set,
       # logins on this cluster will be redirected to the login cluster
index 2de260fdc2493a30857894a85ebef22e7d898670..ad91bcf8028d60960044a4c578a79320587a90ed 100644 (file)
@@ -43,7 +43,8 @@ func (s *LoginSuite) TestDeferToLoginCluster(c *check.C) {
 func (s *LoginSuite) TestLogout(c *check.C) {
        s.cluster.Services.Workbench1.ExternalURL = arvados.URL{Scheme: "https", Host: "workbench1.example.com"}
        s.cluster.Services.Workbench2.ExternalURL = arvados.URL{Scheme: "https", Host: "workbench2.example.com"}
-       s.cluster.Login.GoogleClientID = "zzzzzzzzzzzzzz"
+       s.cluster.Login.Google.Enable = true
+       s.cluster.Login.Google.ClientID = "zzzzzzzzzzzzzz"
        s.addHTTPRemote(c, "zhome", &arvadostest.APIStub{})
        s.cluster.Login.LoginCluster = "zhome"
        // s.fed is already set by SetUpTest, but we need to
index f09203f72486739d467b88f28b1d6875bc2f1959..3c7ae3a2d9e5abf2773af742e0bcbc1b2213aea5 100644 (file)
@@ -167,8 +167,9 @@ func (s *HandlerSuite) TestProxyNotFound(c *check.C) {
 }
 
 func (s *HandlerSuite) TestProxyRedirect(c *check.C) {
-       s.cluster.Login.ProviderAppID = "test"
-       s.cluster.Login.ProviderAppSecret = "test"
+       s.cluster.Login.SSO.Enable = true
+       s.cluster.Login.SSO.ProviderAppID = "test"
+       s.cluster.Login.SSO.ProviderAppSecret = "test"
        req := httptest.NewRequest("GET", "https://0.0.0.0:1/login?return_to=foo", nil)
        resp := httptest.NewRecorder()
        s.handler.ServeHTTP(resp, req)
@@ -182,7 +183,8 @@ func (s *HandlerSuite) TestProxyRedirect(c *check.C) {
 }
 
 func (s *HandlerSuite) TestLogoutSSO(c *check.C) {
-       s.cluster.Login.ProviderAppID = "test"
+       s.cluster.Login.SSO.Enable = true
+       s.cluster.Login.SSO.ProviderAppID = "test"
        req := httptest.NewRequest("GET", "https://0.0.0.0:1/logout?return_to=https://example.com/foo", nil)
        resp := httptest.NewRecorder()
        s.handler.ServeHTTP(resp, req)
@@ -197,7 +199,8 @@ func (s *HandlerSuite) TestLogoutGoogle(c *check.C) {
                // Google login N/A
                return
        }
-       s.cluster.Login.GoogleClientID = "test"
+       s.cluster.Login.Google.Enable = true
+       s.cluster.Login.Google.ClientID = "test"
        req := httptest.NewRequest("GET", "https://0.0.0.0:1/logout?return_to=https://example.com/foo", nil)
        resp := httptest.NewRecorder()
        s.handler.ServeHTTP(resp, req)
index ae59849993346afaf6eddf37dd53cfa08c5cce5e..0fd0a9ad2348045c1abd85b3f47a54a5d25dc202 100644 (file)
@@ -8,8 +8,11 @@ import (
        "context"
        "errors"
        "net/http"
+       "net/url"
 
+       "git.arvados.org/arvados.git/lib/controller/rpc"
        "git.arvados.org/arvados.git/sdk/go/arvados"
+       "git.arvados.org/arvados.git/sdk/go/auth"
        "git.arvados.org/arvados.git/sdk/go/httpserver"
 )
 
@@ -20,19 +23,22 @@ type loginController interface {
 }
 
 func chooseLoginController(cluster *arvados.Cluster, railsProxy *railsProxy) loginController {
-       wantGoogle := cluster.Login.GoogleClientID != ""
-       wantSSO := cluster.Login.ProviderAppID != ""
-       wantPAM := cluster.Login.PAM
+       wantGoogle := cluster.Login.Google.Enable
+       wantSSO := cluster.Login.SSO.Enable
+       wantPAM := cluster.Login.PAM.Enable
+       wantLDAP := cluster.Login.LDAP.Enable
        switch {
-       case wantGoogle && !wantSSO && !wantPAM:
+       case wantGoogle && !wantSSO && !wantPAM && !wantLDAP:
                return &googleLoginController{Cluster: cluster, RailsProxy: railsProxy}
-       case !wantGoogle && wantSSO && !wantPAM:
+       case !wantGoogle && wantSSO && !wantPAM && !wantLDAP:
                return &ssoLoginController{railsProxy}
-       case !wantGoogle && !wantSSO && wantPAM:
+       case !wantGoogle && !wantSSO && wantPAM && !wantLDAP:
                return &pamLoginController{Cluster: cluster, RailsProxy: railsProxy}
+       case !wantGoogle && !wantSSO && !wantPAM && wantLDAP:
+               return &ldapLoginController{Cluster: cluster, RailsProxy: railsProxy}
        default:
                return errorLoginController{
-                       error: errors.New("configuration problem: exactly one of Login.GoogleClientID, Login.ProviderAppID, or Login.PAM must be configured"),
+                       error: errors.New("configuration problem: exactly one of Login.Google, Login.SSO, Login.PAM, and Login.LDAP must be enabled"),
                }
        }
 }
@@ -68,3 +74,23 @@ func noopLogout(cluster *arvados.Cluster, opts arvados.LogoutOptions) (arvados.L
        }
        return arvados.LogoutResponse{RedirectLocation: target}, nil
 }
+
+func createAPIClientAuthorization(ctx context.Context, conn *rpc.Conn, rootToken string, authinfo rpc.UserSessionAuthInfo) (arvados.APIClientAuthorization, error) {
+       ctxRoot := auth.NewContext(ctx, &auth.Credentials{Tokens: []string{rootToken}})
+       resp, err := conn.UserSessionCreate(ctxRoot, rpc.UserSessionCreateOptions{
+               // Send a fake ReturnTo value instead of the caller's
+               // opts.ReturnTo. We won't follow the resulting
+               // redirect target anyway.
+               ReturnTo: ",https://none.invalid",
+               AuthInfo: authinfo,
+       })
+       if err != nil {
+               return arvados.APIClientAuthorization{}, err
+       }
+       target, err := url.Parse(resp.RedirectLocation)
+       if err != nil {
+               return arvados.APIClientAuthorization{}, err
+       }
+       token := target.Query().Get("api_token")
+       return conn.APIClientAuthorizationCurrent(auth.NewContext(ctx, auth.NewCredentials(token)), arvados.GetOptions{})
+}
index bf1754c158968e40c6cb7d32a31d55ab4c83fcde..144b04c46d7ee6ab865eff24a7acc06db08f5dd9 100644 (file)
@@ -71,8 +71,8 @@ func (ctrl *googleLoginController) Login(ctx context.Context, opts arvados.Login
                return loginError(fmt.Errorf("error making redirect URL: %s", err))
        }
        conf := &oauth2.Config{
-               ClientID:     ctrl.Cluster.Login.GoogleClientID,
-               ClientSecret: ctrl.Cluster.Login.GoogleClientSecret,
+               ClientID:     ctrl.Cluster.Login.Google.ClientID,
+               ClientSecret: ctrl.Cluster.Login.Google.ClientSecret,
                Endpoint:     provider.Endpoint(),
                Scopes:       []string{oidc.ScopeOpenID, "profile", "email"},
                RedirectURL:  redirURL.String(),
@@ -162,7 +162,7 @@ func (ctrl *googleLoginController) getAuthInfo(ctx context.Context, cluster *arv
                ret.Email = claims.Email
        }
 
-       if !ctrl.Cluster.Login.GoogleAlternateEmailAddresses {
+       if !ctrl.Cluster.Login.Google.AlternateEmailAddresses {
                if ret.Email == "" {
                        return nil, fmt.Errorf("cannot log in with unverified email address %q", claims.Email)
                }
index 9e16e2e90439a8ab7767930b6c701fe6d6ab604a..495fbb69b31e5c659b4476c772303658e46592c1 100644 (file)
@@ -146,10 +146,10 @@ func (s *LoginSuite) SetUpTest(c *check.C) {
 
        cfg, err := config.NewLoader(nil, ctxlog.TestLogger(c)).Load()
        s.cluster, err = cfg.GetCluster("")
-       s.cluster.Login.ProviderAppID = ""
-       s.cluster.Login.ProviderAppSecret = ""
-       s.cluster.Login.GoogleClientID = "test%client$id"
-       s.cluster.Login.GoogleClientSecret = "test#client/secret"
+       s.cluster.Login.SSO.Enable = false
+       s.cluster.Login.Google.Enable = true
+       s.cluster.Login.Google.ClientID = "test%client$id"
+       s.cluster.Login.Google.ClientSecret = "test#client/secret"
        s.cluster.Users.PreferDomainForUsername = "PreferDomainForUsername.example.com"
        c.Assert(err, check.IsNil)
 
@@ -227,7 +227,7 @@ func (s *LoginSuite) setupPeopleAPIError(c *check.C) {
 }
 
 func (s *LoginSuite) TestGoogleLogin_PeopleAPIDisabled(c *check.C) {
-       s.cluster.Login.GoogleAlternateEmailAddresses = false
+       s.cluster.Login.Google.AlternateEmailAddresses = false
        s.authEmail = "joe.smith@primary.example.com"
        s.setupPeopleAPIError(c)
        state := s.startLogin(c)
diff --git a/lib/controller/localdb/login_ldap.go b/lib/controller/localdb/login_ldap.go
new file mode 100644 (file)
index 0000000..6c430d6
--- /dev/null
@@ -0,0 +1,152 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+package localdb
+
+import (
+       "context"
+       "crypto/tls"
+       "errors"
+       "fmt"
+       "net"
+       "net/http"
+       "strings"
+
+       "git.arvados.org/arvados.git/lib/controller/rpc"
+       "git.arvados.org/arvados.git/sdk/go/arvados"
+       "git.arvados.org/arvados.git/sdk/go/ctxlog"
+       "git.arvados.org/arvados.git/sdk/go/httpserver"
+       "github.com/go-ldap/ldap"
+)
+
+type ldapLoginController struct {
+       Cluster    *arvados.Cluster
+       RailsProxy *railsProxy
+}
+
+func (ctrl *ldapLoginController) Logout(ctx context.Context, opts arvados.LogoutOptions) (arvados.LogoutResponse, error) {
+       return noopLogout(ctrl.Cluster, opts)
+}
+
+func (ctrl *ldapLoginController) Login(ctx context.Context, opts arvados.LoginOptions) (arvados.LoginResponse, error) {
+       return arvados.LoginResponse{}, errors.New("interactive login is not available")
+}
+
+func (ctrl *ldapLoginController) UserAuthenticate(ctx context.Context, opts arvados.UserAuthenticateOptions) (arvados.APIClientAuthorization, error) {
+       log := ctxlog.FromContext(ctx)
+       conf := ctrl.Cluster.Login.LDAP
+       errFailed := httpserver.ErrorWithStatus(fmt.Errorf("LDAP: Authentication failure (with username %q and password)", opts.Username), http.StatusUnauthorized)
+
+       if conf.SearchAttribute == "" {
+               return arvados.APIClientAuthorization{}, errors.New("config error: SearchAttribute is blank")
+       }
+       if opts.Password == "" {
+               log.WithField("username", opts.Username).Error("refusing to authenticate with empty password")
+               return arvados.APIClientAuthorization{}, errFailed
+       }
+
+       log = log.WithField("URL", conf.URL.String())
+       l, err := ldap.DialURL(conf.URL.String())
+       if err != nil {
+               log.WithError(err).Error("ldap connection failed")
+               return arvados.APIClientAuthorization{}, err
+       }
+       defer l.Close()
+
+       if conf.StartTLS {
+               var tlsconfig tls.Config
+               if conf.InsecureTLS {
+                       tlsconfig.InsecureSkipVerify = true
+               } else {
+                       if host, _, err := net.SplitHostPort(conf.URL.Host); err != nil {
+                               // Assume SplitHostPort error means
+                               // port was not specified
+                               tlsconfig.ServerName = conf.URL.Host
+                       } else {
+                               tlsconfig.ServerName = host
+                       }
+               }
+               err = l.StartTLS(&tlsconfig)
+               if err != nil {
+                       log.WithError(err).Error("ldap starttls failed")
+                       return arvados.APIClientAuthorization{}, err
+               }
+       }
+
+       username := opts.Username
+       if at := strings.Index(username, "@"); at >= 0 {
+               if conf.StripDomain == "*" || strings.ToLower(conf.StripDomain) == strings.ToLower(username[at+1:]) {
+                       username = username[:at]
+               }
+       }
+       if conf.AppendDomain != "" && !strings.Contains(username, "@") {
+               username = username + "@" + conf.AppendDomain
+       }
+
+       if conf.SearchBindUser != "" {
+               err = l.Bind(conf.SearchBindUser, conf.SearchBindPassword)
+               if err != nil {
+                       log.WithError(err).WithField("user", conf.SearchBindUser).Error("ldap authentication failed")
+                       return arvados.APIClientAuthorization{}, err
+               }
+       }
+
+       search := fmt.Sprintf("(%s=%s)", ldap.EscapeFilter(conf.SearchAttribute), ldap.EscapeFilter(username))
+       if conf.SearchFilters != "" {
+               search = fmt.Sprintf("(&%s%s)", conf.SearchFilters, search)
+       }
+       log = log.WithField("search", search)
+       req := ldap.NewSearchRequest(
+               conf.SearchBase,
+               ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 1, 0, false,
+               search,
+               []string{"DN", "givenName", "SN", conf.EmailAttribute, conf.UsernameAttribute},
+               nil)
+       resp, err := l.Search(req)
+       if ldap.IsErrorWithCode(err, ldap.LDAPResultNoResultsReturned) ||
+               ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) ||
+               (err == nil && len(resp.Entries) == 0) {
+               log.WithError(err).Info("ldap lookup returned no results")
+               return arvados.APIClientAuthorization{}, errFailed
+       } else if err != nil {
+               log.WithError(err).Error("ldap lookup failed")
+               return arvados.APIClientAuthorization{}, err
+       }
+       userdn := resp.Entries[0].DN
+       if userdn == "" {
+               log.Warn("refusing to authenticate with empty dn")
+               return arvados.APIClientAuthorization{}, errFailed
+       }
+       log = log.WithField("DN", userdn)
+
+       attrs := map[string]string{}
+       for _, attr := range resp.Entries[0].Attributes {
+               if attr == nil || len(attr.Values) == 0 {
+                       continue
+               }
+               attrs[strings.ToLower(attr.Name)] = attr.Values[0]
+       }
+       log.WithField("attrs", attrs).Debug("ldap search succeeded")
+
+       // Now that we have the DN, try authenticating.
+       err = l.Bind(userdn, opts.Password)
+       if err != nil {
+               log.WithError(err).Info("ldap user authentication failed")
+               return arvados.APIClientAuthorization{}, errFailed
+       }
+       log.Debug("ldap authentication succeeded")
+
+       email := attrs[strings.ToLower(conf.EmailAttribute)]
+       if email == "" {
+               log.Errorf("ldap returned no email address in %q attribute", conf.EmailAttribute)
+               return arvados.APIClientAuthorization{}, errors.New("authentication succeeded but ldap returned no email address")
+       }
+
+       return createAPIClientAuthorization(ctx, ctrl.RailsProxy, ctrl.Cluster.SystemRootToken, rpc.UserSessionAuthInfo{
+               Email:     email,
+               FirstName: attrs["givenname"],
+               LastName:  attrs["sn"],
+               Username:  attrs[strings.ToLower(conf.UsernameAttribute)],
+       })
+}
diff --git a/lib/controller/localdb/login_ldap_docker_test.go b/lib/controller/localdb/login_ldap_docker_test.go
new file mode 100644 (file)
index 0000000..79b5f16
--- /dev/null
@@ -0,0 +1,48 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+package localdb
+
+import (
+       "os"
+       "os/exec"
+       "testing"
+
+       check "gopkg.in/check.v1"
+)
+
+func haveDocker() bool {
+       _, err := exec.Command("docker", "info").CombinedOutput()
+       return err == nil
+}
+
+func (s *LDAPSuite) TestLoginLDAPViaPAM(c *check.C) {
+       if testing.Short() {
+               c.Skip("skipping docker test in short mode")
+       }
+       if !haveDocker() {
+               c.Skip("skipping docker test because docker is not available")
+       }
+       cmd := exec.Command("bash", "login_ldap_docker_test.sh")
+       cmd.Stdout = os.Stderr
+       cmd.Stderr = os.Stderr
+       cmd.Env = append(os.Environ(), "config_method=pam")
+       err := cmd.Run()
+       c.Check(err, check.IsNil)
+}
+
+func (s *LDAPSuite) TestLoginLDAPBuiltin(c *check.C) {
+       if testing.Short() {
+               c.Skip("skipping docker test in short mode")
+       }
+       if !haveDocker() {
+               c.Skip("skipping docker test because docker is not available")
+       }
+       cmd := exec.Command("bash", "login_ldap_docker_test.sh")
+       cmd.Stdout = os.Stderr
+       cmd.Stderr = os.Stderr
+       cmd.Env = append(os.Environ(), "config_method=ldap")
+       err := cmd.Run()
+       c.Check(err, check.IsNil)
+}
similarity index 63%
rename from lib/controller/localdb/login_pam_docker_test.sh
rename to lib/controller/localdb/login_ldap_docker_test.sh
index b8f281bc2e69dfd80f3c3451b161330bbc0a2d47..4e0679f620bf6b4ec67d8fc118b66db5ef3332ac 100755 (executable)
@@ -2,9 +2,9 @@
 
 # This script demonstrates using LDAP for Arvados user authentication.
 #
-# It configures pam_ldap(5) and arvados controller in a docker
-# container, with pam_ldap configured to authenticate against an
-# OpenLDAP server in a second docker container.
+# It configures arvados controller in a docker container, optionally
+# with pam_ldap(5) configured to authenticate against an OpenLDAP
+# server in a second docker container.
 #
 # After adding a "foo" user entry, it uses curl to check that the
 # Arvados controller's login endpoint accepts the "foo" account
@@ -24,6 +24,15 @@ if [[ -n ${ARVADOS_DEBUG} ]]; then
     set -x
 fi
 
+case "${config_method}" in
+    pam | ldap)
+        ;;
+    *)
+        echo >&2 "\$config_method env var must be 'pam' or 'ldap'"
+        exit 1
+        ;;
+esac
+
 hostname="$(hostname)"
 tmpdir="$(mktemp -d)"
 cleanup() {
@@ -86,15 +95,38 @@ Clusters:
         ExternalURL: http://0.0.0.0:9999/
         InternalURLs:
           "http://0.0.0.0:9999/": {}
-    Login:
-      PAM: true
-      # Without this magic PAMDefaultEmailDomain, inserted users would
-      # prevent subsequent database/reset from working (see
-      # database_controller.rb).
-      PAMDefaultEmailDomain: example.com
     SystemLogs:
       LogLevel: debug
 EOF
+case "${config_method}" in
+    pam)
+        setup_pam_ldap="apt update && DEBIAN_FRONTEND=noninteractive apt install -y ldap-utils libpam-ldap && pam-auth-update --package /usr/share/pam-configs/ldap"
+        cat >>"${tmpdir}/zzzzz.yml" <<EOF
+    Login:
+      PAM:
+        Enable: true
+        # Without this specific DefaultEmailDomain, inserted users
+        # would prevent subsequent database/reset from working (see
+        # database_controller.rb).
+        DefaultEmailDomain: example.com
+EOF
+        ;;
+    ldap)
+        setup_pam_ldap=""
+        cat >>"${tmpdir}/zzzzz.yml" <<EOF
+    Login:
+      LDAP:
+        Enable: true
+        URL: ${ldapurl}
+        StartTLS: false
+        SearchBase: dc=example,dc=org
+        SearchBindUser: cn=admin,dc=example,dc=org
+        SearchBindPassword: admin
+EOF
+            ;;
+esac
+
+cat >&2 "${tmpdir}/zzzzz.yml"
 
 cat >"${tmpdir}/pam_ldap.conf" <<EOF
 base dc=example,dc=org
@@ -113,12 +145,12 @@ cn: bar
 gidNumber: 11111
 description: "Example group 'bar'"
 
-dn: uid=foo,dc=example,dc=org
-uid: foo
-cn: foo
+dn: uid=foo-bar,dc=example,dc=org
+uid: foo-bar
+cn: "Foo Bar"
 givenName: Foo
 sn: Bar
-mail: foobar@example.org
+mail: foo-bar-baz@example.com
 objectClass: inetOrgPerson
 objectClass: posixAccount
 objectClass: top
@@ -130,11 +162,11 @@ shadowLastChange: 10701
 loginShell: /bin/bash
 uidNumber: 11111
 gidNumber: 11111
-homeDirectory: /home/foo
+homeDirectory: /home/foo-bar
 userPassword: ${passwordhash}
 EOF
 
-echo >&2 "Adding example user entry user=foo pass=secret (retrying until server comes up)"
+echo >&2 "Adding example user entry user=foo-bar pass=secret (retrying until server comes up)"
 docker run --rm --entrypoint= \
        -v "${tmpdir}/add_example_user.ldif":/add_example_user.ldif:ro \
        osixia/openldap:1.3.0 \
@@ -152,7 +184,7 @@ docker run --detach --rm --name=${ctrlctr} \
        -v "${tmpdir}/zzzzz.yml":/etc/arvados/config.yml:ro \
        -v $(realpath "${PWD}/../../.."):/arvados:ro \
        debian:10 \
-       bash -c "apt update && DEBIAN_FRONTEND=noninteractive apt install -y ldap-utils libpam-ldap && pam-auth-update --package /usr/share/pam-configs/ldap && arvados-server controller"
+       bash -c "${setup_pam_ldap:-true} && arvados-server controller"
 docker logs --follow ${ctrlctr} 2>$debug >$debug &
 ctrlhostport=$(docker port ${ctrlctr} 9999/tcp)
 
@@ -178,16 +210,42 @@ check_contains() {
     fi
 }
 
+set +x
+
 echo >&2 "Testing authentication failure"
-resp="$(curl -s --include -d username=foo -d password=nosecret "http://${ctrlhostport}/arvados/v1/users/authenticate" | tee $debug)"
+resp="$(set -x; curl -s --include -d username=foo-bar -d password=nosecret "http://${ctrlhostport}/arvados/v1/users/authenticate" | tee $debug)"
 check_contains "${resp}" "HTTP/1.1 401"
-check_contains "${resp}" '{"errors":["PAM: Authentication failure (with username \"foo\" and password)"]}'
+if [[ "${config_method}" = ldap ]]; then
+    check_contains "${resp}" '{"errors":["LDAP: Authentication failure (with username \"foo-bar\" and password)"]}'
+else
+    check_contains "${resp}" '{"errors":["PAM: Authentication failure (with username \"foo-bar\" and password)"]}'
+fi
 
 echo >&2 "Testing authentication success"
-resp="$(curl -s --include -d username=foo -d password=secret "http://${ctrlhostport}/arvados/v1/users/authenticate" | tee $debug)"
+resp="$(set -x; curl -s --include -d username=foo-bar -d password=secret "http://${ctrlhostport}/arvados/v1/users/authenticate" | tee $debug)"
 check_contains "${resp}" "HTTP/1.1 200"
 check_contains "${resp}" '"api_token":"'
 check_contains "${resp}" '"scopes":["all"]'
 check_contains "${resp}" '"uuid":"zzzzz-gj3su-'
 
+secret="${resp##*api_token\":\"}"
+secret="${secret%%\"*}"
+uuid="${resp##*uuid\":\"}"
+uuid="${uuid%%\"*}"
+token="v2/$uuid/$secret"
+echo >&2 "New token is ${token}"
+
+resp="$(set -x; curl -s --include -H "Authorization: Bearer ${token}" "http://${ctrlhostport}/arvados/v1/users/current" | tee $debug)"
+check_contains "${resp}" "HTTP/1.1 200"
+if [[ "${config_method}" = ldap ]]; then
+    # user fields come from LDAP attributes
+    check_contains "${resp}" '"first_name":"Foo"'
+    check_contains "${resp}" '"last_name":"Bar"'
+    check_contains "${resp}" '"username":"foobar"' # "-" removed by rails api
+    check_contains "${resp}" '"email":"foo-bar-baz@example.com"'
+else
+    # PAMDefaultEmailDomain
+    check_contains "${resp}" '"email":"foo-bar@example.com"'
+fi
+
 cleanup
diff --git a/lib/controller/localdb/login_ldap_test.go b/lib/controller/localdb/login_ldap_test.go
new file mode 100644 (file)
index 0000000..9a8f83f
--- /dev/null
@@ -0,0 +1,133 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+package localdb
+
+import (
+       "context"
+       "encoding/json"
+       "net"
+       "net/http"
+
+       "git.arvados.org/arvados.git/lib/config"
+       "git.arvados.org/arvados.git/lib/controller/railsproxy"
+       "git.arvados.org/arvados.git/sdk/go/arvados"
+       "git.arvados.org/arvados.git/sdk/go/arvadostest"
+       "git.arvados.org/arvados.git/sdk/go/auth"
+       "git.arvados.org/arvados.git/sdk/go/ctxlog"
+       "github.com/bradleypeabody/godap"
+       check "gopkg.in/check.v1"
+)
+
+var _ = check.Suite(&LDAPSuite{})
+
+type LDAPSuite struct {
+       cluster *arvados.Cluster
+       ctrl    *ldapLoginController
+       ldap    *godap.LDAPServer // fake ldap server that accepts auth goodusername/goodpassword
+}
+
+func (s *LDAPSuite) TearDownSuite(c *check.C) {
+       // Undo any changes/additions to the user database so they
+       // don't affect subsequent tests.
+       arvadostest.ResetEnv()
+       c.Check(arvados.NewClientFromEnv().RequestAndDecode(nil, "POST", "database/reset", nil, nil), check.IsNil)
+}
+
+func (s *LDAPSuite) SetUpSuite(c *check.C) {
+       cfg, err := config.NewLoader(nil, ctxlog.TestLogger(c)).Load()
+       c.Assert(err, check.IsNil)
+       s.cluster, err = cfg.GetCluster("")
+       c.Assert(err, check.IsNil)
+
+       ln, err := net.Listen("tcp", "127.0.0.1:0")
+       s.ldap = &godap.LDAPServer{
+               Listener: ln,
+               Handlers: []godap.LDAPRequestHandler{
+                       &godap.LDAPBindFuncHandler{
+                               LDAPBindFunc: func(binddn string, bindpw []byte) bool {
+                                       return binddn == "cn=goodusername,dc=example,dc=com" && string(bindpw) == "goodpassword"
+                               },
+                       },
+                       &godap.LDAPSimpleSearchFuncHandler{
+                               LDAPSimpleSearchFunc: func(req *godap.LDAPSimpleSearchRequest) []*godap.LDAPSimpleSearchResultEntry {
+                                       if req.FilterAttr != "uid" || req.BaseDN != "dc=example,dc=com" {
+                                               return []*godap.LDAPSimpleSearchResultEntry{}
+                                       }
+                                       return []*godap.LDAPSimpleSearchResultEntry{
+                                               &godap.LDAPSimpleSearchResultEntry{
+                                                       DN: "cn=" + req.FilterValue + "," + req.BaseDN,
+                                                       Attrs: map[string]interface{}{
+                                                               "SN":   req.FilterValue,
+                                                               "CN":   req.FilterValue,
+                                                               "uid":  req.FilterValue,
+                                                               "mail": req.FilterValue + "@example.com",
+                                                       },
+                                               },
+                                       }
+                               },
+                       },
+               },
+       }
+       go func() {
+               ctxlog.TestLogger(c).Print(s.ldap.Serve())
+       }()
+
+       s.cluster.Login.LDAP.Enable = true
+       err = json.Unmarshal([]byte(`"ldap://`+ln.Addr().String()+`"`), &s.cluster.Login.LDAP.URL)
+       s.cluster.Login.LDAP.StartTLS = false
+       s.cluster.Login.LDAP.SearchBindUser = "cn=goodusername,dc=example,dc=com"
+       s.cluster.Login.LDAP.SearchBindPassword = "goodpassword"
+       s.cluster.Login.LDAP.SearchBase = "dc=example,dc=com"
+       c.Assert(err, check.IsNil)
+       s.ctrl = &ldapLoginController{
+               Cluster:    s.cluster,
+               RailsProxy: railsproxy.NewConn(s.cluster),
+       }
+}
+
+func (s *LDAPSuite) TestLoginSuccess(c *check.C) {
+       resp, err := s.ctrl.UserAuthenticate(context.Background(), arvados.UserAuthenticateOptions{
+               Username: "goodusername",
+               Password: "goodpassword",
+       })
+       c.Check(err, check.IsNil)
+       c.Check(resp.APIToken, check.Not(check.Equals), "")
+       c.Check(resp.UUID, check.Matches, `zzzzz-gj3su-.*`)
+       c.Check(resp.Scopes, check.DeepEquals, []string{"all"})
+
+       ctx := auth.NewContext(context.Background(), &auth.Credentials{Tokens: []string{"v2/" + resp.UUID + "/" + resp.APIToken}})
+       user, err := railsproxy.NewConn(s.cluster).UserGetCurrent(ctx, arvados.GetOptions{})
+       c.Check(err, check.IsNil)
+       c.Check(user.Email, check.Equals, "goodusername@example.com")
+       c.Check(user.Username, check.Equals, "goodusername")
+}
+
+func (s *LDAPSuite) TestLoginFailure(c *check.C) {
+       // search returns no results
+       s.cluster.Login.LDAP.SearchBase = "dc=example,dc=invalid"
+       resp, err := s.ctrl.UserAuthenticate(context.Background(), arvados.UserAuthenticateOptions{
+               Username: "goodusername",
+               Password: "goodpassword",
+       })
+       c.Check(err, check.ErrorMatches, `LDAP: Authentication failure \(with username "goodusername" and password\)`)
+       hs, ok := err.(interface{ HTTPStatus() int })
+       if c.Check(ok, check.Equals, true) {
+               c.Check(hs.HTTPStatus(), check.Equals, http.StatusUnauthorized)
+       }
+       c.Check(resp.APIToken, check.Equals, "")
+
+       // search returns result, but auth fails
+       s.cluster.Login.LDAP.SearchBase = "dc=example,dc=com"
+       resp, err = s.ctrl.UserAuthenticate(context.Background(), arvados.UserAuthenticateOptions{
+               Username: "badusername",
+               Password: "badpassword",
+       })
+       c.Check(err, check.ErrorMatches, `LDAP: Authentication failure \(with username "badusername" and password\)`)
+       hs, ok = err.(interface{ HTTPStatus() int })
+       if c.Check(ok, check.Equals, true) {
+               c.Check(hs.HTTPStatus(), check.Equals, http.StatusUnauthorized)
+       }
+       c.Check(resp.APIToken, check.Equals, "")
+}
index 01dfc1379d3064b06ad7a3e7760d60250cc00a52..2447713a2cf453ea05cfc29e2c643fa0713848a9 100644 (file)
@@ -9,12 +9,10 @@ import (
        "errors"
        "fmt"
        "net/http"
-       "net/url"
        "strings"
 
        "git.arvados.org/arvados.git/lib/controller/rpc"
        "git.arvados.org/arvados.git/sdk/go/arvados"
-       "git.arvados.org/arvados.git/sdk/go/auth"
        "git.arvados.org/arvados.git/sdk/go/ctxlog"
        "git.arvados.org/arvados.git/sdk/go/httpserver"
        "github.com/msteinert/pam"
@@ -37,7 +35,7 @@ func (ctrl *pamLoginController) Login(ctx context.Context, opts arvados.LoginOpt
 func (ctrl *pamLoginController) UserAuthenticate(ctx context.Context, opts arvados.UserAuthenticateOptions) (arvados.APIClientAuthorization, error) {
        errorMessage := ""
        sentPassword := false
-       tx, err := pam.StartFunc(ctrl.Cluster.Login.PAMService, opts.Username, func(style pam.Style, message string) (string, error) {
+       tx, err := pam.StartFunc(ctrl.Cluster.Login.PAM.Service, opts.Username, func(style pam.Style, message string) (string, error) {
                ctxlog.FromContext(ctx).Debugf("pam conversation: style=%v message=%q", style, message)
                switch style {
                case pam.ErrorMsg:
@@ -82,28 +80,15 @@ func (ctrl *pamLoginController) UserAuthenticate(ctx context.Context, opts arvad
                return arvados.APIClientAuthorization{}, err
        }
        email := user
-       if domain := ctrl.Cluster.Login.PAMDefaultEmailDomain; domain != "" && !strings.Contains(email, "@") {
+       if domain := ctrl.Cluster.Login.PAM.DefaultEmailDomain; domain != "" && !strings.Contains(email, "@") {
                email = email + "@" + domain
        }
-       ctxlog.FromContext(ctx).WithFields(logrus.Fields{"user": user, "email": email}).Debug("pam authentication succeeded")
-       ctxRoot := auth.NewContext(ctx, &auth.Credentials{Tokens: []string{ctrl.Cluster.SystemRootToken}})
-       resp, err := ctrl.RailsProxy.UserSessionCreate(ctxRoot, rpc.UserSessionCreateOptions{
-               // Send a fake ReturnTo value instead of the caller's
-               // opts.ReturnTo. We won't follow the resulting
-               // redirect target anyway.
-               ReturnTo: ",https://none.invalid",
-               AuthInfo: rpc.UserSessionAuthInfo{
-                       Username: user,
-                       Email:    email,
-               },
+       ctxlog.FromContext(ctx).WithFields(logrus.Fields{
+               "user":  user,
+               "email": email,
+       }).Debug("pam authentication succeeded")
+       return createAPIClientAuthorization(ctx, ctrl.RailsProxy, ctrl.Cluster.SystemRootToken, rpc.UserSessionAuthInfo{
+               Username: user,
+               Email:    email,
        })
-       if err != nil {
-               return arvados.APIClientAuthorization{}, err
-       }
-       target, err := url.Parse(resp.RedirectLocation)
-       if err != nil {
-               return arvados.APIClientAuthorization{}, err
-       }
-       token := target.Query().Get("api_token")
-       return ctrl.RailsProxy.APIClientAuthorizationCurrent(auth.NewContext(ctx, auth.NewCredentials(token)), arvados.GetOptions{})
 }
diff --git a/lib/controller/localdb/login_pam_docker_test.go b/lib/controller/localdb/login_pam_docker_test.go
deleted file mode 100644 (file)
index 8a02b2c..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Skip this slow test unless invoked as "go test -tags docker".
-// +build docker
-
-package localdb
-
-import (
-       "os"
-       "os/exec"
-
-       check "gopkg.in/check.v1"
-)
-
-func (s *PamSuite) TestLoginLDAPViaPAM(c *check.C) {
-       cmd := exec.Command("bash", "login_pam_docker_test.sh")
-       cmd.Stdout = os.Stderr
-       cmd.Stderr = os.Stderr
-       err := cmd.Run()
-       c.Check(err, check.IsNil)
-}
index 5b0e4533e1844c543539ebab7567e8ca2973d82c..e6b967c9440b887cc6b2e68bd3ccb5e7a8fa78eb 100644 (file)
@@ -32,8 +32,8 @@ func (s *PamSuite) SetUpSuite(c *check.C) {
        c.Assert(err, check.IsNil)
        s.cluster, err = cfg.GetCluster("")
        c.Assert(err, check.IsNil)
-       s.cluster.Login.PAM = true
-       s.cluster.Login.PAMDefaultEmailDomain = "example.com"
+       s.cluster.Login.PAM.Enable = true
+       s.cluster.Login.PAM.DefaultEmailDomain = "example.com"
        s.railsSpy = arvadostest.NewProxy(c, s.cluster.Services.RailsAPI)
        s.ctrl = &pamLoginController{
                Cluster:    s.cluster,
@@ -79,6 +79,6 @@ func (s *PamSuite) TestLoginSuccess(c *check.C) {
        c.Check(resp.Scopes, check.DeepEquals, []string{"all"})
 
        authinfo := getCallbackAuthInfo(c, s.railsSpy)
-       c.Check(authinfo.Email, check.Equals, u+"@"+s.cluster.Login.PAMDefaultEmailDomain)
+       c.Check(authinfo.Email, check.Equals, u+"@"+s.cluster.Login.PAM.DefaultEmailDomain)
        c.Check(authinfo.AlternateEmails, check.DeepEquals, []string(nil))
 }
index 69de3f05e231d29321ddbaa0b0f5f6dc1d5659e0..9f9f00e6445ec676b7ca19877cef1e7b304912e2 100644 (file)
@@ -135,16 +135,39 @@ type Cluster struct {
                Repositories string
        }
        Login struct {
-               GoogleClientID                string
-               GoogleClientSecret            string
-               GoogleAlternateEmailAddresses bool
-               PAM                           bool
-               PAMService                    string
-               PAMDefaultEmailDomain         string
-               ProviderAppID                 string
-               ProviderAppSecret             string
-               LoginCluster                  string
-               RemoteTokenRefresh            Duration
+               LDAP struct {
+                       Enable             bool
+                       URL                URL
+                       StartTLS           bool
+                       InsecureTLS        bool
+                       StripDomain        string
+                       AppendDomain       string
+                       SearchAttribute    string
+                       SearchBindUser     string
+                       SearchBindPassword string
+                       SearchBase         string
+                       SearchFilters      string
+                       EmailAttribute     string
+                       UsernameAttribute  string
+               }
+               Google struct {
+                       Enable                  bool
+                       ClientID                string
+                       ClientSecret            string
+                       AlternateEmailAddresses bool
+               }
+               PAM struct {
+                       Enable             bool
+                       Service            string
+                       DefaultEmailDomain string
+               }
+               SSO struct {
+                       Enable            bool
+                       ProviderAppID     string
+                       ProviderAppSecret string
+               }
+               LoginCluster       string
+               RemoteTokenRefresh Duration
        }
        Mail struct {
                MailchimpAPIKey                string
index 779ac4bfc0614f853da77e77068eb8ed539f8865..fe32547fcbda14eaa712924a0eb68623310dff96 100644 (file)
@@ -757,8 +757,10 @@ def setup_config():
                     "RailsSessionSecretToken": "e24205c490ac07e028fd5f8a692dcb398bcd654eff1aef5f9fe6891994b18483",
                 },
                 "Login": {
-                    "ProviderAppID": "arvados-server",
-                    "ProviderAppSecret": "608dbf356a327e2d0d4932b60161e212c2d8d8f5e25690d7b622f850a990cd33",
+                    "SSO": {
+                        "ProviderAppID": "arvados-server",
+                        "ProviderAppSecret": "608dbf356a327e2d0d4932b60161e212c2d8d8f5e25690d7b622f850a990cd33",
+                    },
                 },
                 "SystemLogs": {
                     "LogLevel": ('info' if os.environ.get('ARVADOS_DEBUG', '') in ['','0'] else 'debug'),
index 502e3e787d1e1324217983f73cd841bce0e1ea1a..7dc6481008ae8e9d0b9b168c117c68decabf49a1 100644 (file)
@@ -37,8 +37,8 @@ EOS
   # Real values will be copied from globals by omniauth_init.rb. For
   # now, assign some strings so the generic *.yml config loader
   # doesn't overwrite them or complain that they're missing.
-  Rails.configuration.Login["ProviderAppID"] = 'xxx'
-  Rails.configuration.Login["ProviderAppSecret"] = 'xxx'
+  Rails.configuration.Login["SSO"]["ProviderAppID"] = 'xxx'
+  Rails.configuration.Login["SSO"]["ProviderAppSecret"] = 'xxx'
   Rails.configuration.Services["SSO"]["ExternalURL"] = '//xxx'
   WARNED_OMNIAUTH_CONFIG = true
 end
@@ -106,8 +106,8 @@ arvcfg.declare_config "Users.EmailSubjectPrefix", String, :email_subject_prefix
 arvcfg.declare_config "Users.UserNotifierEmailFrom", String, :user_notifier_email_from
 arvcfg.declare_config "Users.NewUserNotificationRecipients", Hash, :new_user_notification_recipients, ->(cfg, k, v) { arrayToHash cfg, "Users.NewUserNotificationRecipients", v }
 arvcfg.declare_config "Users.NewInactiveUserNotificationRecipients", Hash, :new_inactive_user_notification_recipients, method(:arrayToHash)
-arvcfg.declare_config "Login.ProviderAppSecret", String, :sso_app_secret
-arvcfg.declare_config "Login.ProviderAppID", String, :sso_app_id
+arvcfg.declare_config "Login.SSO.ProviderAppSecret", String, :sso_app_secret
+arvcfg.declare_config "Login.SSO.ProviderAppID", String, :sso_app_id
 arvcfg.declare_config "Login.LoginCluster", String
 arvcfg.declare_config "Login.RemoteTokenRefresh", ActiveSupport::Duration
 arvcfg.declare_config "TLS.Insecure", Boolean, :sso_insecure
index 35a318b94fe1f5bf4bc822f06608c8b031854a1e..a1b2356bd56242389cff2dac821ad7d68f103177 100644 (file)
@@ -9,14 +9,14 @@
 
 if defined? CUSTOM_PROVIDER_URL
   Rails.logger.warn "Copying omniauth from globals in legacy config file."
-  Rails.configuration.Login["ProviderAppID"] = APP_ID
-  Rails.configuration.Login["ProviderAppSecret"] = APP_SECRET
+  Rails.configuration.Login["SSO"]["ProviderAppID"] = APP_ID
+  Rails.configuration.Login["SSO"]["ProviderAppSecret"] = APP_SECRET
   Rails.configuration.Services["SSO"]["ExternalURL"] = CUSTOM_PROVIDER_URL.sub(/\/$/, "") + "/"
 else
   Rails.application.config.middleware.use OmniAuth::Builder do
     provider(:josh_id,
-             Rails.configuration.Login["ProviderAppID"],
-             Rails.configuration.Login["ProviderAppSecret"],
+             Rails.configuration.Login["SSO"]["ProviderAppID"],
+             Rails.configuration.Login["SSO"]["ProviderAppSecret"],
              Rails.configuration.Services["SSO"]["ExternalURL"])
   end
   OmniAuth.config.on_failure = StaticController.action(:login_failure)
index ed4795d1cc8676cfdd93c052cd44cbffae08de98..dbcbc913cecdd5946461ee838376a32aec44f83f 100755 (executable)
@@ -139,8 +139,9 @@ Clusters:
       DefaultReplication: 1
       TrustAllContent: true
     Login:
-      ProviderAppSecret: $sso_app_secret
-      ProviderAppID: arvados-server
+      SSO:
+        ProviderAppSecret: $sso_app_secret
+        ProviderAppID: arvados-server
     Users:
       NewUsersAreActive: true
       AutoAdminFirstUser: true