-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
7b19d8e
commit 1744e20
Showing
14 changed files
with
567 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
In this tutorial, we will deploy Jupyverse through JupyterHub on a public [OVHcloud](https://www.ovhcloud.com) instance, and allow authentication using a [GitHub](https://github.com) account. | ||
|
||
## OVH setup | ||
|
||
### Create and connect to a public instance | ||
|
||
Let's follow the guide on [Creating and connecting to your first Public Cloud instance](https://help.ovhcloud.com/csm/en-public-cloud-compute-getting-started?id=kb_article_view&sysparm_article=KB0051009). We first need to create SSH keys, so that we can connect to our instance using SSH. Enter in a terminal: | ||
|
||
```bash | ||
ssh-keygen -b 4096 | ||
# Generating public/private rsa key pair. | ||
# Enter file in which to save the key (/home/user/.ssh/id_rsa): | ||
``` | ||
|
||
You can hit _Enter_. You are then asked to enter a passphrase, we will need it later. | ||
|
||
The public key can be accessed with: | ||
|
||
```bash | ||
cat ~/.ssh/id_rsa.pub | ||
``` | ||
|
||
Copy this public key into your clipboard. | ||
|
||
In the OVHcloud Control Panel, click on "Instances" and then "Create an instance". Choose the "B2-7" model, which is a light and general use instance, and click "Next". | ||
|
||
Select a region of you choice and click "Next". | ||
|
||
Select the "Ubuntu 23.04" image and click "Add a key" under "SSH key". Give it a name an paste your public key, then click "Next". Your instance should already be configured, you can click "Next" again. In the network configuration, make sure "Public mode" is checked, and click "Next". Then select your preferred billing period and click "Create an instance". | ||
|
||
Your instance should activate shortly. You can see it has a public IP, something like `1.2.3.4`. Let's connect to the instance using this IP address: | ||
|
||
```bash | ||
ssh [email protected] | ||
# The authenticity of host '1.2.3.4 (1.2.3.4)' can't be established. | ||
# ED25519 key fingerprint is SHA256:Q1&tbgX3fp9+7J90zyK0ctuKe1aqPoEY76Qi58uoSnA. | ||
# This key is not known by any other names | ||
# Are you sure you want to continue connecting (yes/no/[fingerprint])? | ||
``` | ||
Enter "yes", then enter your passphrase. You should now be connected to your instance. | ||
|
||
### Set up the environment | ||
|
||
Let's install [micromamba](https://mamba.readthedocs.io/en/latest/installation.html#micromamba) and configure it: | ||
|
||
```bash | ||
sudo apt install bzip2 | ||
curl -Ls https://micro.mamba.pm/api/micromamba/linux-64/latest | tar -xvj bin/micromamba | ||
bin/micromamba shell init --shell bash --root-prefix=~/micromamba | ||
exec bash | ||
``` | ||
|
||
Now create a conda environment and install Python and Node.js: | ||
|
||
```bash | ||
micromamba create -n jupyterhub | ||
micromamba activate jupyterhub | ||
micromamba install -c conda-forge python nodejs | ||
``` | ||
|
||
And install JupyterHub and Jupyverse: | ||
|
||
```bash | ||
pip install jupyverse[jupyterlab,auth-jupyterhub] | ||
pip install jupyter-collaboration | ||
pip install oauthenticator | ||
pip install https://github.com/davidbrochart/jupyterhub/archive/jupyverse.zip | ||
npm install -g configurable-http-proxy | ||
``` | ||
|
||
### Set up HTTPS | ||
|
||
For this you will need a domain name, like [https://my.jupyverse.com](https://my.jupyverse.com), that must point to your instance through its IP address. | ||
|
||
We'll use the [Certbot](https://certbot.eff.org) ACME client to manage SSL/TLS certificates. Enter in a terminal: | ||
|
||
```bash | ||
sudo snap install --classic certbot | ||
sudo ln -s /snap/bin/certbot /usr/bin/certbot | ||
sudo certbot certonly --standalone | ||
``` | ||
|
||
## Create a GitHub App | ||
|
||
We'll [register a new GitHub App](https://github.com/settings/apps/new). In the "GitHub App name", enter "JupyterHub-Jupyverse". In "Homepage URL", enter the URL of your public instance, [https://my.jupyverse.com](https://my.jupyverse.com). In "Callback URL", enter [https://my.jupyverse.com/hub/oauth_callback](https://my.jupyverse.com/hub/oauth_callback). Make sure "Expire user authorization tokens" and "Request user authorization (OAuth) during installation" are checked. Uncheck "Active" for "Webhook". In "Account permissions", give "Read-only" access to "Email addresses". Finally, hit the "Create GitHub App" button at the bottom. | ||
|
||
If this was successful, you can now generate a private key. Click on "Generate a new client secret", and copy it somewhere safe. Let's also copy the client ID shown on the same page. | ||
|
||
## Run the server | ||
|
||
### Configure JupyterHub | ||
|
||
Let's create a JupyterHub configuration file. Fill in the `allowed_users` and `admin_users` as you like. | ||
|
||
```bash | ||
sudo mkdir /etc/jupyterhub | ||
sudo vim /etc/jupyterhub/jupyterhub_config.py | ||
``` | ||
|
||
With the following content: | ||
|
||
```py | ||
# jupyterhub_config.py file | ||
c = get_config() | ||
|
||
import os | ||
pjoin = os.path.join | ||
|
||
runtime_dir = os.path.join('/srv/jupyterhub') | ||
|
||
# Allows multiple single-server per user | ||
c.JupyterHub.allow_named_servers = True | ||
|
||
# https on :443 | ||
c.JupyterHub.port = 443 | ||
c.JupyterHub.ssl_key = '/etc/letsencrypt/live/jupyterhub.quantstack.net/privkey.pem' | ||
c.JupyterHub.ssl_cert = '/etc/letsencrypt/live/jupyterhub.quantstack.net/cert.pem' | ||
|
||
# put the JupyterHub cookie secret and state db | ||
# in /var/run/jupyterhub | ||
c.JupyterHub.cookie_secret_file = pjoin(runtime_dir, 'cookie_secret') | ||
c.JupyterHub.db_url = pjoin(runtime_dir, 'jupyterhub.sqlite') | ||
# or `--db=/path/to/jupyterhub.sqlite` on the command-line | ||
|
||
# use GitHub OAuthenticator for local users | ||
c.JupyterHub.authenticator_class = 'oauthenticator.LocalGitHubOAuthenticator' | ||
c.GitHubOAuthenticator.oauth_callback_url = os.environ['OAUTH_CALLBACK_URL'] | ||
|
||
# create system users that don't exist yet | ||
c.LocalAuthenticator.create_system_users = True | ||
|
||
# specify users and admin | ||
c.Authenticator.allowed_users = {'rgbkrk', 'minrk', 'jhamrick'} | ||
c.Authenticator.admin_users = {'jhamrick', 'rgbkrk'} | ||
``` | ||
|
||
### Launch JupyterHub | ||
|
||
Let's launch JupyterHub with some environment variables. Use the GitHub client ID and secret of your GitHub App. | ||
|
||
```bash | ||
sudo mkdir /srv/jupyterhub | ||
chmod -R o+rx /home/ubuntu | ||
mkdir jupyterhub | ||
cd jupyterhub | ||
sudo env "PATH=$PATH" \ | ||
"OAUTH_CALLBACK_URL=https://my.jupyverse.com/hub/oauth_callback" \ | ||
"GITHUB_CLIENT_ID=github_id" \ | ||
"GITHUB_CLIENT_SECRET=github_secret" \ | ||
jupyterhub -f /etc/jupyterhub/jupyterhub_config.py | ||
``` | ||
|
||
Now open a browser window at [https://my.jupyverse.com](https://my.jupyverse.com), and click "Sign in with GitHub". Enter your credentials and click "Sign in". If you have two-factor authentication enabled on your GitHub account, you may have to approve the request by entering a code e.g. in your mobile phone GitHub application. | ||
|
||
After a while, JupyterLab should start. You should see an icon for your user in the top-right corner, with your initials. Any other connected user should be visible in the "Collaboration" tab on the left, and if you work on the same notebook, you should see them collaborate live! |
2 changes: 1 addition & 1 deletion
2
docs/tutorials/deployment.md → ...orials/standalone_jupyverse_deployment.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
# Licensing terms | ||
|
||
This project is licensed under the terms of the Modified BSD License | ||
(also known as New or Revised or 3-Clause BSD), as follows: | ||
|
||
- Copyright (c) 2021-, Jupyter Development Team | ||
|
||
All rights reserved. | ||
|
||
Redistribution and use in source and binary forms, with or without | ||
modification, are permitted provided that the following conditions are met: | ||
|
||
Redistributions of source code must retain the above copyright notice, this | ||
list of conditions and the following disclaimer. | ||
|
||
Redistributions in binary form must reproduce the above copyright notice, this | ||
list of conditions and the following disclaimer in the documentation and/or | ||
other materials provided with the distribution. | ||
|
||
Neither the name of the Jupyter Development Team nor the names of its | ||
contributors may be used to endorse or promote products derived from this | ||
software without specific prior written permission. | ||
|
||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE | ||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
|
||
## About the Jupyter Development Team | ||
|
||
The Jupyter Development Team is the set of all contributors to the Jupyter project. | ||
This includes all of the Jupyter subprojects. | ||
|
||
The core team that coordinates development on GitHub can be found here: | ||
https://github.com/jupyter/. | ||
|
||
## Our Copyright Policy | ||
|
||
Jupyter uses a shared copyright model. Each contributor maintains copyright | ||
over their contributions to Jupyter. But, it is important to note that these | ||
contributions are typically only changes to the repositories. Thus, the Jupyter | ||
source code, in its entirety is not the copyright of any single person or | ||
institution. Instead, it is the collective copyright of the entire Jupyter | ||
Development Team. If individual contributors want to maintain a record of what | ||
changes/contributions they have specific copyright on, they should indicate | ||
their copyright in the commit message of the change, when they commit the | ||
change to one of the Jupyter repositories. | ||
|
||
With this in mind, the following banner should be used in any source code file | ||
to indicate the copyright and license terms: | ||
|
||
# Copyright (c) Jupyter Development Team. | ||
# Distributed under the terms of the Modified BSD License. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# fps-auth-jupyterhub | ||
|
||
An FPS plugin for the authentication API, using JupyterHub. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
__version__ = "0.2.0" | ||
|
||
from .launch import launch # noqa |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
from jupyverse_api.auth import AuthConfig | ||
from pydantic import Field | ||
|
||
|
||
class AuthJupyterHubConfig(AuthConfig): | ||
db_url: str = Field( | ||
description="The connection URL passed to create_engine()", | ||
default="sqlite+aiosqlite:///:memory:", | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
from sqlalchemy import JSON, Boolean, Column, String, Text | ||
from sqlalchemy.ext.asyncio import AsyncAttrs | ||
from sqlalchemy.orm import DeclarativeBase | ||
|
||
|
||
class Base(AsyncAttrs, DeclarativeBase): | ||
pass | ||
|
||
|
||
class UserDB(Base): | ||
__tablename__ = "user_account" | ||
|
||
token = Column(String(32), primary_key=True) | ||
anonymous = Column(Boolean, default=True, nullable=False) | ||
username = Column(String(length=32), nullable=False, unique=True) | ||
name = Column(String(length=32), default="") | ||
display_name = Column(String(length=32), default="") | ||
initials = Column(String(length=8), nullable=True) | ||
color = Column(String(length=32), nullable=True) | ||
avatar_url = Column(String(length=32), nullable=True) | ||
workspace = Column(Text(), default="{}", nullable=False) | ||
settings = Column(Text(), default="{}", nullable=False) | ||
permissions = Column(JSON, default={}, nullable=False) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import os | ||
from urllib.parse import urlparse | ||
|
||
from jupyverse_api.cli import main | ||
|
||
|
||
def launch(): | ||
service_url = os.environ.get("JUPYTERHUB_SERVICE_URL") | ||
url = urlparse(service_url) | ||
try: | ||
return main.callback( | ||
open_browser=True, | ||
host=url.hostname, | ||
port=url.port, | ||
set_=[ | ||
f"frontend.base_url={url.path}", | ||
f"app.mount_path={url.path}", | ||
], | ||
disable=[], | ||
) | ||
except Exception: | ||
return |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import httpx | ||
from asphalt.core import Component, ContainerComponent, Context, context_teardown | ||
from jupyverse_api.auth import Auth, AuthConfig | ||
from jupyverse_api.app import App | ||
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession | ||
|
||
from .config import AuthJupyterHubConfig | ||
from .db import Base | ||
from .routes import auth_factory | ||
|
||
|
||
class _AuthJupyterHubComponent(Component): | ||
@context_teardown | ||
async def start( | ||
self, | ||
ctx: Context, | ||
) -> None: | ||
app = await ctx.request_resource(App) | ||
db_session = await ctx.request_resource(AsyncSession) | ||
db_engine = await ctx.request_resource(AsyncEngine) | ||
|
||
http_client = httpx.AsyncClient() | ||
auth_jupyterhub = auth_factory(app, db_session, http_client) | ||
ctx.add_resource(auth_jupyterhub, types=Auth) | ||
|
||
async with db_engine.begin() as conn: | ||
await conn.run_sync(Base.metadata.create_all) | ||
|
||
yield | ||
|
||
await http_client.aclose() | ||
|
||
|
||
class AuthJupyterHubComponent(ContainerComponent): | ||
def __init__(self, **kwargs): | ||
self.auth_jupyterhub_config = AuthJupyterHubConfig(**kwargs) | ||
super().__init__() | ||
|
||
async def start( | ||
self, | ||
ctx: Context, | ||
) -> None: | ||
ctx.add_resource(self.auth_jupyterhub_config, types=AuthConfig) | ||
self.add_component( | ||
"sqlalchemy", | ||
url=self.auth_jupyterhub_config.db_url, | ||
) | ||
self.add_component("auth_jupyterhub", type=_AuthJupyterHubComponent) | ||
await super().start(ctx) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
from typing import Dict, List | ||
|
||
from jupyverse_api.auth import User | ||
from pydantic import ConfigDict | ||
|
||
|
||
class JupyterHubUser(User): | ||
model_config = ConfigDict(from_attributes=True) | ||
|
||
token: str | ||
anonymous: bool = True | ||
permissions: Dict[str, List[str]] |
Oops, something went wrong.