k8s-manifests/jupyter/base/values/values.yaml

336 lines
12 KiB
YAML
Raw Normal View History

2024-01-16 14:54:02 +00:00
debug:
enabled: true
2023-05-03 09:19:31 +00:00
hub:
config:
Authenticator:
auto_login: true
enable_auth_state: true
JupyterHub:
tornado_settings:
headers: { 'Content-Security-Policy': "frame-ancestors *;" }
2023-09-28 06:36:09 +00:00
db:
pvc:
storageClassName: csi-sc-cinderplugin
2023-05-03 09:19:31 +00:00
extraConfig:
oauthCode: |
2024-01-13 14:47:22 +00:00
import time
2024-01-15 11:29:56 +00:00
import requests
2024-01-15 09:14:07 +00:00
from datetime import datetime
2024-01-12 09:26:03 +00:00
from oauthenticator.generic import GenericOAuthenticator
2024-01-15 13:28:39 +00:00
token_url = 'https://' + os.environ['NEXTCLOUD_HOST'] + '/index.php/apps/oauth2/api/v1/token'
2024-01-18 11:02:55 +00:00
debug = os.environ.get('NEXTCLOUD_DEBUG_OAUTH', 'false').lower() in ['true', '1', 'yes']
2024-01-15 11:29:56 +00:00
def get_nextcloud_access_token(refresh_token):
client_id = os.environ['NEXTCLOUD_CLIENT_ID']
client_secret = os.environ['NEXTCLOUD_CLIENT_SECRET']
code = refresh_token
data = {
2024-01-15 13:25:57 +00:00
'grant_type': 'refresh_token',
2024-01-15 11:29:56 +00:00
'code': code,
2024-01-15 11:36:00 +00:00
'refresh_token': refresh_token,
2024-01-15 11:29:56 +00:00
'client_id': client_id,
'client_secret': client_secret
}
2024-01-15 13:28:39 +00:00
response = requests.post(token_url, data=data)
2024-01-15 12:43:55 +00:00
if debug:
print(response.text)
2024-01-15 11:29:56 +00:00
return response.json()
2024-01-12 13:04:13 +00:00
def post_auth_hook(authenticator, handler, authentication):
2024-01-13 14:30:12 +00:00
user = authentication['auth_state']['oauth_user']['ocs']['data']['id']
auth_state = authentication['auth_state']
auth_state['token_expires'] = time.time() + auth_state['token_response']['expires_in']
authentication['auth_state'] = auth_state
2024-01-13 14:30:12 +00:00
return authentication
2024-01-12 13:04:13 +00:00
class NextcloudOAuthenticator(GenericOAuthenticator):
def __init__(self, *args, **kwargs):
2024-01-13 14:30:12 +00:00
super().__init__(*args, **kwargs)
self.user_dict = {}
2024-01-13 15:20:19 +00:00
async def pre_spawn_start(self, user, spawner):
2024-01-13 14:30:12 +00:00
super().pre_spawn_start(user, spawner)
2024-01-13 15:20:19 +00:00
auth_state = await user.get_auth_state()
if not auth_state:
return
access_token = auth_state['access_token']
2024-01-13 14:30:12 +00:00
spawner.environment['NEXTCLOUD_ACCESS_TOKEN'] = access_token
2024-01-13 14:30:57 +00:00
async def refresh_user(self, user, handler=None):
2024-01-13 15:20:19 +00:00
auth_state = await user.get_auth_state()
if not auth_state:
2024-01-15 09:14:07 +00:00
if debug:
print(f'auth_state missing for {user}')
2024-01-13 15:20:19 +00:00
return False
access_token = auth_state['access_token']
refresh_token = auth_state['refresh_token']
2024-01-13 15:24:28 +00:00
token_response = auth_state['token_response']
2024-01-13 15:21:55 +00:00
now = time.time()
2024-01-15 09:14:07 +00:00
now_hr = datetime.fromtimestamp(now)
2024-01-13 15:24:28 +00:00
expires = auth_state['token_expires']
2024-01-15 09:14:07 +00:00
expires_hr = datetime.fromtimestamp(expires)
2024-01-15 13:12:17 +00:00
expires = 0
2024-01-15 11:07:53 +00:00
if debug:
print(f'auth_state for {user}: {auth_state}')
2024-01-13 15:21:55 +00:00
if now >= expires:
2024-01-15 09:14:07 +00:00
if debug:
print(f'Time is: {now_hr}, token expired: {expires_hr}')
2024-01-15 11:29:56 +00:00
print(f'Refreshing token for {user}')
try:
token_response = get_nextcloud_access_token(refresh_token)
auth_state['access_token'] = token_response['access_token']
auth_state['refresh_token'] = token_response['refresh_token']
auth_state['token_expires'] = now + token_response['expires_in']
auth_state['token_response'] = token_response
if debug:
2024-01-15 13:44:33 +00:00
print(f'Successfully refreshed token for {user.name}')
print(f'auth_state for {user.name}: {auth_state}')
return {'name': user.name, 'auth_state': auth_state}
2024-01-15 11:29:56 +00:00
except Exception as e:
if debug:
print(f'Failed to refresh token for {user}')
return False
2024-01-13 14:47:22 +00:00
return False
2024-01-15 09:14:07 +00:00
if debug:
print(f'Time is: {now_hr}, token expires: {expires_hr}')
2024-01-13 15:21:55 +00:00
return True
2024-01-13 14:30:57 +00:00
2024-01-12 13:04:13 +00:00
c.JupyterHub.authenticator_class = NextcloudOAuthenticator
c.NextcloudOAuthenticator.client_id = os.environ['NEXTCLOUD_CLIENT_ID']
c.NextcloudOAuthenticator.client_secret = os.environ['NEXTCLOUD_CLIENT_SECRET']
c.NextcloudOAuthenticator.login_service = 'Sunet Drive'
c.NextcloudOAuthenticator.username_claim = lambda r: r.get('ocs', {}).get('data', {}).get('id')
c.NextcloudOAuthenticator.userdata_url = 'https://' + os.environ['NEXTCLOUD_HOST'] + '/ocs/v2.php/cloud/user?format=json'
c.NextcloudOAuthenticator.authorize_url = 'https://' + os.environ['NEXTCLOUD_HOST'] + '/index.php/apps/oauth2/authorize'
2024-01-15 13:28:39 +00:00
c.NextcloudOAuthenticator.token_url = token_url
2024-01-12 13:04:13 +00:00
c.NextcloudOAuthenticator.oauth_callback_url = 'https://' + os.environ['JUPYTER_HOST'] + '/hub/oauth_callback'
c.NextcloudOAuthenticator.allow_all = True
c.NextcloudOAuthenticator.refresh_pre_spawn = True
c.NextcloudOAuthenticator.enable_auth_state = True
2024-01-15 14:03:35 +00:00
c.NextcloudOAuthenticator.auth_refresh_age = 3600
2024-01-12 13:04:13 +00:00
c.NextcloudOAuthenticator.post_auth_hook = post_auth_hook
2024-01-15 14:03:35 +00:00
serviceCode: |
2024-01-15 15:03:11 +00:00
import sys
c.JupyterHub.load_roles = [
{
"name": "refresh-token",
2024-01-15 16:26:54 +00:00
"services": [
"refresh-token"
2024-01-15 16:28:53 +00:00
],
2024-01-15 15:03:11 +00:00
"scopes": [
2024-01-18 10:42:24 +00:00
"read:users",
2024-01-15 15:03:11 +00:00
"admin:auth_state"
]
2024-01-15 15:42:31 +00:00
},
2024-01-15 15:39:54 +00:00
{
2024-01-15 15:55:11 +00:00
"name": "user",
2024-01-15 15:42:31 +00:00
"scopes": [
2024-01-15 16:26:54 +00:00
"access:services!service=refresh-token",
"read:services!service=refresh-token",
2024-01-15 16:05:37 +00:00
"self",
2024-01-15 15:42:31 +00:00
],
2024-01-16 08:32:21 +00:00
},
{
"name": "server",
"scopes": [
"access:services!service=refresh-token",
"read:services!service=refresh-token",
"inherit",
],
2024-01-15 15:39:54 +00:00
}
2024-01-15 15:03:11 +00:00
]
c.JupyterHub.services = [
{
'name': 'refresh-token',
2024-01-17 13:42:37 +00:00
'url': 'http://' + os.environ.get('HUB_SERVICE_HOST', 'hub') + ':' + os.environ.get('HUB_SERVICE_PORT_REFRESH_TOKEN', '8082'),
2024-01-16 13:05:49 +00:00
'display': False,
'oauth_no_confirm': True,
2024-01-17 15:13:19 +00:00
'api_token': os.environ['JUPYTERHUB_API_KEY'],
2024-01-16 16:30:27 +00:00
'command': [sys.executable, '/usr/local/etc/jupyterhub/refresh-token.py']
2024-01-15 15:03:11 +00:00
}
]
2024-01-18 15:34:10 +00:00
c.JupyterHub.admin_users = {"refresh-token"}
c.JupyterHub.api_tokens = {
os.environ['JUPYTERHUB_API_KEY']: "refresh-token",
}
2024-01-16 11:33:58 +00:00
extraFiles:
refresh-token.py:
mountPath: /usr/local/etc/jupyterhub/refresh-token.py
stringData: |
"""A token refresh service authenticating with the Hub.
This service serves `/services/refresh-token/`,
authenticated with the Hub,
showing the user their own info.
"""
import json
import os
2024-01-18 10:28:56 +00:00
import requests
import socket
from jupyterhub.services.auth import HubAuthenticated
from jupyterhub.utils import url_path_join
2024-01-16 11:33:58 +00:00
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
2024-01-18 10:28:56 +00:00
from tornado.web import Application, HTTPError, RequestHandler, authenticated
from urllib.parse import urlparse
2024-01-18 11:02:55 +00:00
debug = os.environ.get('NEXTCLOUD_DEBUG_OAUTH', 'false').lower() in ['true', '1', 'yes']
2024-01-18 09:12:28 +00:00
def my_debug(s):
2024-01-18 11:02:55 +00:00
if debug:
with open("/proc/1/fd/1", "a") as stdout:
print(s, file=stdout)
2024-01-16 11:33:58 +00:00
2024-01-17 14:55:42 +00:00
class RefreshHandler(HubAuthenticated, RequestHandler):
2024-01-18 10:28:56 +00:00
def api_request(self, method, url, **kwargs):
my_debug(f'{self.hub_auth}')
url = url_path_join(self.hub_auth.api_url, url)
allow_404 = kwargs.pop('allow_404', False)
headers = kwargs.setdefault('headers', {})
headers.setdefault('Authorization', f'token {self.hub_auth.api_token}')
try:
r = requests.request(method, url, **kwargs)
except requests.ConnectionError as e:
my_debug(f'Error connecting to {url}: {e}')
msg = f'Failed to connect to Hub API at {url}.'
msg += f' Is the Hub accessible at this URL (from host: {socket.gethostname()})?'
if '127.0.0.1' in url:
msg += ' Make sure to set c.JupyterHub.hub_ip to an IP accessible to' + \
' single-user servers if the servers are not on the same host as the Hub.'
raise HTTPError(500, msg)
data = None
if r.status_code == 404 and allow_404:
pass
elif r.status_code == 403:
my_debug(
2024-01-18 10:35:45 +00:00
'Lacking permission to check authorization with JupyterHub,' +
f' my auth token may have expired: [{r.status_code}] {r.reason}'
)
2024-01-18 10:28:56 +00:00
my_debug(r.text)
raise HTTPError(
500,
'Permission failure checking authorization, I may need a new token'
)
elif r.status_code >= 500:
my_debug(f'Upstream failure verifying auth token: [{r.status_code}] {r.reason}')
my_debug(r.text)
raise HTTPError(
502, 'Failed to check authorization (upstream problem)')
elif r.status_code >= 400:
my_debug(f'Failed to check authorization: [{r.status_code}] {r.reason}')
my_debug(r.text)
raise HTTPError(500, 'Failed to check authorization')
else:
data = r.json()
return data
2024-01-17 14:11:50 +00:00
@authenticated
2024-01-16 11:33:58 +00:00
def get(self):
2024-01-18 09:18:44 +00:00
user_model = self.get_current_user()
2024-01-18 10:28:56 +00:00
# Fetch current auth state
user_data = self.api_request('GET', url_path_join('users', user_model['name']))
auth_state = user_data['auth_state']
2024-01-18 11:02:55 +00:00
access_token = auth_state['access_token']
token_expires = auth_state['token_expires']
2024-01-18 10:28:56 +00:00
2024-01-16 11:33:58 +00:00
self.set_header('content-type', 'application/json')
2024-01-18 11:02:55 +00:00
self.write(json.dumps({'access_token': access_token, 'token_expires': token_expires}, indent=1, sort_keys=True))
2024-01-16 11:33:58 +00:00
2024-01-16 15:43:22 +00:00
class PingHandler(RequestHandler):
def get(self):
2024-01-18 09:12:28 +00:00
my_debug(f"DEBUG: In ping get")
2024-01-16 15:43:22 +00:00
self.set_header('content-type', 'application/json')
self.write(json.dumps({'ping': 1}))
2024-01-16 11:33:58 +00:00
def main():
2024-01-16 15:43:22 +00:00
app = Application([
(os.environ['JUPYTERHUB_SERVICE_PREFIX'] + 'tokens', RefreshHandler),
(os.environ['JUPYTERHUB_SERVICE_PREFIX'] + '/?', PingHandler),
])
2024-01-16 11:33:58 +00:00
http_server = HTTPServer(app)
2024-01-16 14:46:17 +00:00
url = urlparse(os.environ['JUPYTERHUB_SERVICE_URL'])
2024-01-16 11:33:58 +00:00
2024-01-16 15:13:24 +00:00
http_server.listen(url.port)
2024-01-16 11:33:58 +00:00
IOLoop.current().start()
2024-01-16 15:43:22 +00:00
2024-01-16 11:33:58 +00:00
if __name__ == '__main__':
main()
2024-01-16 14:11:52 +00:00
networkPolicy:
ingress:
- ports:
- port: 8082
from:
- podSelector:
matchLabels:
hub.jupyter.org/network-access-hub: "true"
service:
extraPorts:
- port: 8082
targetPort: 8082
name: refresh-token
2023-05-03 09:19:31 +00:00
extraEnv:
2024-01-18 11:02:55 +00:00
NEXTCLOUD_DEBUG_OAUTH: "no"
2023-05-03 09:19:31 +00:00
NEXTCLOUD_HOST: sunet.drive.test.sunet.se
JUPYTER_HOST: jupyter.drive.test.sunet.se
2024-01-17 15:13:19 +00:00
JUPYTERHUB_API_KEY:
valueFrom:
secretKeyRef:
name: jupyterhub-secrets
key: api-key
2024-01-13 15:09:47 +00:00
JUPYTERHUB_CRYPT_KEY:
valueFrom:
secretKeyRef:
name: jupyterhub-secrets
key: crypt-key
2023-05-03 09:19:31 +00:00
NEXTCLOUD_CLIENT_ID:
valueFrom:
secretKeyRef:
name: nextcloud-oauth-secrets
key: client-id
NEXTCLOUD_CLIENT_SECRET:
valueFrom:
secretKeyRef:
name: nextcloud-oauth-secrets
key: client-secret
networkPolicy:
enabled: false
2024-01-16 14:15:19 +00:00
proxy:
chp:
networkPolicy:
egress:
- to:
- podSelector:
matchLabels:
app: jupyterhub
component: hub
ports:
- port: 8082
2023-05-03 09:19:31 +00:00
singleuser:
image:
name: docker.sunet.se/drive/jupyter-custom
2024-01-18 13:14:56 +00:00
tag: lab-4.0.10-sunet2
2023-05-03 09:19:31 +00:00
storage:
2024-01-12 15:12:57 +00:00
dynamic:
storageClass: csi-sc-cinderplugin
2023-05-03 09:19:31 +00:00
extraEnv:
JUPYTER_ENABLE_LAB: "yes"
extraFiles:
jupyter_notebook_config:
mountPath: /home/jovyan/.jupyter/jupyter_server_config.py
stringData: |
import os
c = get_config()
c.NotebookApp.allow_origin = '*'
c.NotebookApp.tornado_settings = {
'headers': { 'Content-Security-Policy': "frame-ancestors *;" }
}
os.system('/usr/local/bin/nc-sync')
mode: 0644