From adb6c83c0015a3368f5aeca4729ab2b7440a33af Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 18 May 2021 02:56:59 +0530 Subject: [PATCH 1/2] Support username / password auth provider Uses a 'database' connector[1] with auth0 to provide username / password authentication. demo.cloudbank.2i2c.cloud is now moved over to this, making it easier for people to give demos. This also removes any current restrictions on who can log in, and opens it up to everyone. We add some resource restrictions to match this. Fixes https://github.com/2i2c-org/pilot-hubs/issues/403 [1]: https://auth0.com/docs/connections/database --- config/hubs/cloudbank.cluster.yaml | 20 ++++++++++---------- config/hubs/schema.yaml | 3 ++- deployer/auth.py | 25 +++++++++++++++++++++++-- deployer/hub.py | 3 ++- 4 files changed, 37 insertions(+), 14 deletions(-) diff --git a/config/hubs/cloudbank.cluster.yaml b/config/hubs/cloudbank.cluster.yaml index 8751273d8e..f527944e7d 100644 --- a/config/hubs/cloudbank.cluster.yaml +++ b/config/hubs/cloudbank.cluster.yaml @@ -186,7 +186,7 @@ hubs: domain: demo.cloudbank.2i2c.cloud template: basehub auth0: - connection: google-oauth2 + connection: password config: jupyterhub: homepage: @@ -206,15 +206,15 @@ hubs: url: http://cloudbank.org/ hub: config: - Authenticator: - allowed_users: &demo_users - - yuvipanda@gmail.com - - choldgraf@gmail.com - - georgiana.dolocan@gmail.com - - ericvd@gmail.com - - sean.smorris@berkeley.edu - - colliand@gmail.com - admin_users: *demo_users + JupyterHub: + # No more than 100 users at a time + active_server_limit: 100 + cull: + # Cull after 30min of inactivity + every: 300 + timeout: 1800 + # No pods over 12h long + maxAge: 43200 - name: lassen domain: lassen.cloudbank.2i2c.cloud template: basehub diff --git a/config/hubs/schema.yaml b/config/hubs/schema.yaml index ea2e2fd420..df718fa9a5 100644 --- a/config/hubs/schema.yaml +++ b/config/hubs/schema.yaml @@ -117,10 +117,11 @@ properties: properties: connection: type: string - enum: + enum: - google-oauth2 - github - ORCID + - password description: | Authentication method users of the hub can use to log in to the hub. We support a subset of the [connectors](https://auth0.com/docs/identityproviders) diff --git a/deployer/auth.py b/deployer/auth.py index ede9df2eee..8c8db1a800 100644 --- a/deployer/auth.py +++ b/deployer/auth.py @@ -6,7 +6,8 @@ USERNAME_KEYS = { 'github': 'nickname', 'google-oauth2': 'email', - 'ORCID': 'sub' + 'ORCID': 'sub', + 'password': 'email' } @@ -79,6 +80,7 @@ def _ensure_client_callback(self, client, domains): } ) + def ensure_client(self, name, domains, connection_name): current_clients = self.get_clients() if name not in current_clients: @@ -89,12 +91,31 @@ def ensure_client(self, name, domains, connection_name): self._ensure_client_callback(client, domains) current_connections = self.get_connections() + + if connection_name == 'password': + # All hubs attached to a single database 'connection' + # will share username / password. So we create one + # connection per hub. + db_connection_name = f'database-{name}' + + if db_connection_name not in current_connections: + # connection doesn't exist yet, create it + connection = self.auth0.connections.create({ + 'name': db_connection_name, + 'display_name': name, + 'strategy': 'auth0' + }) + current_connections[db_connection_name] = connection + selected_connection_name = db_connection_name + else: + selected_connection_name = connection_name + for connection in current_connections.values(): # The chosen connection! enabled_clients = connection['enabled_clients'].copy() needs_update = False client_id = client['client_id'] - if connection['name'] == connection_name: + if connection['name'] == selected_connection_name: if client_id not in enabled_clients: enabled_clients.append(client_id) needs_update = True diff --git a/deployer/hub.py b/deployer/hub.py index a74742ca6e..451e618c93 100644 --- a/deployer/hub.py +++ b/deployer/hub.py @@ -1,3 +1,4 @@ +from auth import KeyProvider import hashlib import hmac import json @@ -100,7 +101,7 @@ def __init__(self, cluster, spec): self.cluster = cluster self.spec = spec - def get_generated_config(self, auth_provider, secret_key): + def get_generated_config(self, auth_provider: KeyProvider, secret_key): """ Generate config automatically for each hub From dbe77b85902bbe53659a9fa3b4804efda89f5fc5 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 18 May 2021 14:13:06 +0530 Subject: [PATCH 2/2] Clarify why we create a db per hub --- deployer/auth.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/deployer/auth.py b/deployer/auth.py index 8c8db1a800..4dff4e36cb 100644 --- a/deployer/auth.py +++ b/deployer/auth.py @@ -93,9 +93,10 @@ def ensure_client(self, name, domains, connection_name): current_connections = self.get_connections() if connection_name == 'password': - # All hubs attached to a single database 'connection' - # will share username / password. So we create one - # connection per hub. + # Users should not be shared between hubs - each hub + # should have its own username / password database. + # So we create a new 'database connection' per hub, + # instead of sharing one across hubs. db_connection_name = f'database-{name}' if db_connection_name not in current_connections: