Skip to content

Commit

Permalink
Jupyterhub stack (#21)
Browse files Browse the repository at this point in the history
Signed-off-by: omrishiv <[email protected]>
  • Loading branch information
omrishiv authored Aug 26, 2024
1 parent 7e0474b commit 8a38e3c
Show file tree
Hide file tree
Showing 5 changed files with 250 additions and 0 deletions.
17 changes: 17 additions & 0 deletions jupyterhub/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Jupyterhub Stack

This directory contains a Jupyterhub deployment that's integrated with Keycloak

## Caveats
1) Reliance on `ref-implementation` for SSO
- This is possible to work around by setting `authenticator_class` in the `jupyterhub.yaml` to `dummy`.

## Components
- Jupyterhub

## Installation
Note: The stack is configured to use Keycloak for SSO; therefore, the ref-implementation is required for this to work.

`idpbuilder create --use-path-routing -p https://github.com/cnoe-io/stacks//ref-implementation -p https://github.com/cnoe-io/stacks//jupyterhub`

A `jupyterhub-config` job will be deployed into the keycloak namespace to create/patch some of the keycloak components. If deployed at the same time as the `ref-implementation`, this job will fail until the `config` job succeeds. This is normal
54 changes: 54 additions & 0 deletions jupyterhub/jupyterhub.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: jupyterhub
namespace: argocd
labels:
env: dev
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
sources:
- repoURL: 'https://jupyterhub.github.io/helm-chart/'
targetRevision: 3.3.7
helm:
releaseName: jupyterhub
values: |
hub:
baseUrl: /jupyterhub
extraEnv:
- name: OAUTH_TLS_VERIFY # for getting around self signed certificate issue
value: "0"
- name: OAUTH_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: jupyterhub-oidc
key: JUPYTERHUB_OAUTH_CLIENT_SECRET
config:
GenericOAuthenticator:
oauth_callback_url: https://cnoe.localtest.me:8443/jupyterhub/hub/oauth_callback
client_id: jupyterhub
authorize_url: https://cnoe.localtest.me:8443/keycloak/realms/cnoe/protocol/openid-connect/auth
token_url: https://cnoe.localtest.me:8443/keycloak/realms/cnoe/protocol/openid-connect/token
userdata_url: https://cnoe.localtest.me:8443/keycloak/realms/cnoe/protocol/openid-connect/userinfo
scope:
- openid
- profile
username_key: "preferred_username"
login_service: "keycloak"
allow_all: true # Allows all oauth authenticated users to use Jupyterhub. For finer grained control, you can use `allowed_users`: https://jupyterhub.readthedocs.io/en/stable/tutorial/getting-started/authenticators-users-basics.html#deciding-who-is-allowed
JupyterHub:
authenticator_class: generic-oauth
chart: jupyterhub
- repoURL: cnoe://jupyterhub
targetRevision: HEAD
path: "manifests"
destination:
server: "https://kubernetes.default.svc"
namespace: jupyterhub
syncPolicy:
syncOptions:
- CreateNamespace=true
automated:
selfHeal: true
127 changes: 127 additions & 0 deletions jupyterhub/jupyterhub/manifests/jupyterhub-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
---
apiVersion: v1
kind: ConfigMap
metadata:
name: jupyterhub-config-job
namespace: keycloak
data:
jupyterhub-client-payload.json: |
{
"protocol": "openid-connect",
"clientId": "jupyterhub",
"name": "Jupyterhub Client",
"description": "Used for Jupyterhub SSO",
"publicClient": false,
"authorizationServicesEnabled": false,
"serviceAccountsEnabled": false,
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": true,
"standardFlowEnabled": true,
"frontchannelLogout": true,
"attributes": {
"saml_idp_initiated_sso_url_name": "",
"oauth2.device.authorization.grant.enabled": false,
"oidc.ciba.grant.enabled": false
},
"alwaysDisplayInConsole": false,
"rootUrl": "",
"baseUrl": "",
"redirectUris": [
"https://cnoe.localtest.me:8443/jupyterhub/hub/oauth_callback"
],
"webOrigins": [
"/*"
]
}
---
apiVersion: batch/v1
kind: Job
metadata:
name: jupyterhub-config
namespace: keycloak
spec:
template:
metadata:
generateName: jupyterhub-config
spec:
serviceAccountName: keycloak-config
restartPolicy: Never
volumes:
- name: keycloak-config
secret:
secretName: keycloak-config
- name: config-payloads
configMap:
name: jupyterhub-config-job
containers:
- name: kubectl
image: docker.io/library/ubuntu:22.04
volumeMounts:
- name: keycloak-config
readOnly: true
mountPath: "/var/secrets/"
- name: config-payloads
readOnly: true
mountPath: "/var/config/"
command: ["/bin/bash", "-c"]
args:
- |
#! /bin/bash
set -ex -o pipefail
apt -qq update && apt -qq install curl jq gettext-base -y
curl -sS -LO "https://dl.k8s.io/release/v1.28.3//bin/linux/amd64/kubectl"
chmod +x kubectl
echo "checking if we're ready to start"
set +e
./kubectl get secret -n keycloak keycloak-clients &> /dev/null
if [ $? -ne 0 ]; then
exit 1
fi
set -e
ADMIN_PASSWORD=$(cat /var/secrets/KEYCLOAK_ADMIN_PASSWORD)
KEYCLOAK_URL=http://keycloak.keycloak.svc.cluster.local:8080/keycloak
KEYCLOAK_TOKEN=$(curl -sS --fail-with-body -X POST -H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "username=cnoe-admin" \
--data-urlencode "password=${ADMIN_PASSWORD}" \
--data-urlencode "grant_type=password" \
--data-urlencode "client_id=admin-cli" \
${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token | jq -e -r '.access_token')
set +e
curl --fail-with-body -H "Authorization: bearer ${KEYCLOAK_TOKEN}" "${KEYCLOAK_URL}/admin/realms/cnoe" &> /dev/null
if [ $? -ne 0 ]; then
exit 0
fi
set -e
echo "creating Jupyterhub client"
curl -sS -H "Content-Type: application/json" \
-H "Authorization: bearer ${KEYCLOAK_TOKEN}" \
-X POST --data @/var/config/jupyterhub-client-payload.json \
${KEYCLOAK_URL}/admin/realms/cnoe/clients
CLIENT_ID=$(curl -sS -H "Content-Type: application/json" \
-H "Authorization: bearer ${KEYCLOAK_TOKEN}" \
-X GET ${KEYCLOAK_URL}/admin/realms/cnoe/clients | jq -e -r '.[] | select(.clientId == "jupyterhub") | .id')
CLIENT_SCOPE_GROUPS_ID=$(curl -sS -H "Content-Type: application/json" -H "Authorization: bearer ${KEYCLOAK_TOKEN}" -X GET ${KEYCLOAK_URL}/admin/realms/cnoe/client-scopes | jq -e -r '.[] | select(.name == "groups") | .id')
curl -sS -H "Content-Type: application/json" -H "Authorization: bearer ${KEYCLOAK_TOKEN}" -X PUT ${KEYCLOAK_URL}/admin/realms/cnoe/clients/${CLIENT_ID}/default-client-scopes/${CLIENT_SCOPE_GROUPS_ID}
JUPYTERHUB_CLIENT_SECRET=$(curl -sS -H "Content-Type: application/json" \
-H "Authorization: bearer ${KEYCLOAK_TOKEN}" \
-X GET ${KEYCLOAK_URL}/admin/realms/cnoe/clients/${CLIENT_ID} | jq -e -r '.secret')
./kubectl patch secret -n keycloak keycloak-clients --type=json \
-p='[{
"op" : "add" ,
"path" : "/data/JUPYTERHUB_CLIENT_SECRET" ,
"value" : "'$(echo -n "$JUPYTERHUB_CLIENT_SECRET" | base64 -w 0)'"
},{
"op" : "add" ,
"path" : "/data/JUPYTERHUB_CLIENT_ID" ,
"value" : "'$(echo -n "jupyterhub" | base64 -w 0)'"
}]'
20 changes: 20 additions & 0 deletions jupyterhub/jupyterhub/manifests/jupyterhub-external-secrets.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: keycloak-oidc
namespace: jupyterhub
spec:
secretStoreRef:
name: keycloak
kind: ClusterSecretStore
target:
name: jupyterhub-oidc
data:
- secretKey: JUPYTERHUB_OAUTH_CLIENT_ID
remoteRef:
key: keycloak-clients
property: JUPYTERHUB_CLIENT_ID
- secretKey: JUPYTERHUB_OAUTH_CLIENT_SECRET
remoteRef:
key: keycloak-clients
property: JUPYTERHUB_CLIENT_SECRET
32 changes: 32 additions & 0 deletions jupyterhub/jupyterhub/manifests/jupyterhub-ingress.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: jupyterhub-ingress
namespace: jupyterhub
annotations:
nginx.ingress.kubernetes.io/backend-protocol: HTTP
nginx.ingress.kubernetes.io/rewrite-target: /jupyterhub/$2
nginx.ingress.kubernetes.io/use-regex: 'true'
spec:
ingressClassName: nginx
rules:
- host: cnoe.localtest.me
http:
paths:
- path: /jupyterhub(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: proxy-public
port:
number: 80
- host: localhost
http:
paths:
- path: /jupyterhub(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: proxy-public
port:
number: 80

0 comments on commit 8a38e3c

Please sign in to comment.