Jupyterhub stack (#21)
Signed-off-by: omrishiv <327609+omrishiv@users.noreply.github.com>
This commit is contained in:
parent
7e0474b3bb
commit
8a38e3c94b
5 changed files with 250 additions and 0 deletions
17
jupyterhub/README.md
Normal file
17
jupyterhub/README.md
Normal file
|
@ -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
jupyterhub/jupyterhub.yaml
Normal file
54
jupyterhub/jupyterhub.yaml
Normal file
|
@ -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
jupyterhub/jupyterhub/manifests/jupyterhub-config.yaml
Normal file
127
jupyterhub/jupyterhub/manifests/jupyterhub-config.yaml
Normal file
|
@ -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)'"
|
||||
}]'
|
|
@ -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
jupyterhub/jupyterhub/manifests/jupyterhub-ingress.yaml
Normal file
32
jupyterhub/jupyterhub/manifests/jupyterhub-ingress.yaml
Normal file
|
@ -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
|
Loading…
Reference in a new issue