diff --git a/base/Dockerfile b/base/Dockerfile index 9d5360b..18d40a7 100644 --- a/base/Dockerfile +++ b/base/Dockerfile @@ -56,7 +56,7 @@ RUN \ npm install -g configurable-http-proxy && \ git clone https://github.com/jupyterhub/jupyterhub.git && \ cd jupyterhub && \ - git checkout tags/0.9.2 && \ + git checkout tags/0.9.4 && \ /opt/anaconda3/bin/python setup.py js && \ /opt/anaconda3/bin/pip --no-cache-dir install . && \ cp examples/cull-idle/cull_idle_servers.py /opt/anaconda3/bin/. && \ diff --git a/base/build.sh b/base/build.sh index 79c223b..919beda 100644 --- a/base/build.sh +++ b/base/build.sh @@ -1,3 +1,4 @@ #!/bin/bash -docker build --no-cache -t registry.spin.nersc.gov/das/jupyterhub-base.gaffer:latest . +branch=deploy-18-10 +docker build --no-cache -t registry.spin.nersc.gov/das/jupyterhub-base.$branch:latest . diff --git a/jupyter-dev/Dockerfile b/jupyter-dev/Dockerfile index 9699434..3edaeaf 100644 --- a/jupyter-dev/Dockerfile +++ b/jupyter-dev/Dockerfile @@ -1,38 +1,36 @@ -FROM registry.spin.nersc.gov/das/jupyterhub-base.gaffer:latest +ARG branch=unknown + +FROM registry.spin.nersc.gov/das/jupyterhub-base.${branch}:latest LABEL maintainer="Rollin Thomas " +WORKDIR /srv -# Install gsissh +# Authenticator and spawner RUN \ - apt-get update && \ - apt-get install --yes \ - apt-transport-https && \ - echo "deb http://downloads.globus.org/toolkit/gt6/stable/deb/ jessie contrib" >> /etc/apt/sources.list && \ - apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 44AE7EC2FAF24365 && \ - apt-get update && \ - apt-get install --yes \ - gsi-openssh-clients + pip install git+https://github.com/nersc/sshapiauthenticator.git && \ + pip install git+https://github.com/nersc/sshspawner.git + +# MFA-enabled login template -# Add certs +ADD templates templates -ADD certs /etc/grid-security/certificates +# Install announcement service components -# JupyterHub GSI Authenticator and SSH Spawner from NERSC - RUN \ - pip install --no-cache-dir git+https://github.com/NERSC/gsiauthenticator.git && \ - pip install --no-cache-dir git+https://github.com/NERSC/sshspawner.git + cp /tmp/jupyterhub/examples/service-announcement/announcement.py . && \ + cp /tmp/jupyterhub/examples/service-announcement/templates/page.html templates/. + +# Hub scripts + +ADD hub-scripts/*.sh /srv/ # Volume for user cert/key files VOLUME /certs -# Config and entrypoint script +# Entrypoint and command -ADD jupyterhub_config.py docker-entrypoint.sh /srv/ -ADD hub-scripts/*.sh /srv/ - -WORKDIR /srv +ADD docker-entrypoint.sh jupyterhub_config.py /srv/ RUN chmod +x docker-entrypoint.sh -CMD ["jupyterhub", "--debug"] ENTRYPOINT ["./docker-entrypoint.sh"] +CMD ["jupyterhub", "--debug"] diff --git a/jupyter-dev/build.sh b/jupyter-dev/build.sh index 15f71d7..4ca3fd9 100644 --- a/jupyter-dev/build.sh +++ b/jupyter-dev/build.sh @@ -1,3 +1,8 @@ #!/bin/bash -docker build --no-cache -t registry.spin.nersc.gov/das/jupyterhub-jupyter-dev.gaffer:latest . +branch=deploy-18-10 + +docker build \ + --build-arg branch=$branch \ + --no-cache \ + --tag registry.spin.nersc.gov/das/jupyterhub-jupyter-dev.$branch:latest . diff --git a/jupyter-dev/certs/70d35895.0 b/jupyter-dev/certs/70d35895.0 deleted file mode 100644 index 0c1bacb..0000000 --- a/jupyter-dev/certs/70d35895.0 +++ /dev/null @@ -1,23 +0,0 @@ ------BEGIN CERTIFICATE----- -MIID0TCCArmgAwIBAgIJAKr1/WtE48FeMA0GCSqGSIb3DQEBCwUAMGgxEzARBgoJ -kiaJk/IsZAEZFgNvcmcxFzAVBgoJkiaJk/IsZAEZFgdjaWxvZ29uMQswCQYDVQQG -EwJVUzEQMA4GA1UEChMHQ0lMb2dvbjEZMBcGA1UEAxMQQ0lMb2dvbiBPU0cgQ0Eg -MTAeFw0xNDA0MzAxNDE4MDhaFw0zNDA0MzAxNDE4MDhaMGgxEzARBgoJkiaJk/Is -ZAEZFgNvcmcxFzAVBgoJkiaJk/IsZAEZFgdjaWxvZ29uMQswCQYDVQQGEwJVUzEQ -MA4GA1UEChMHQ0lMb2dvbjEZMBcGA1UEAxMQQ0lMb2dvbiBPU0cgQ0EgMTCCASIw -DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMQQzsB9Uc37VuIyt5xJxcYYkc6K -XpYihHgskTQp6YYB4XHVimouHafMYyoFsnenrcgf2NGFDvi9l9x9mnL77920JqGr -LijieMiFEyP1nhGW8C6nJjkSsXLbgZNh9u6U+0oAbspsFRwdHDZOI7gIHSJ2zuiY -CkMAvjw9TN44Q4IFCvSIf7mfzZgBH7AW1sbgznqnAJsWQhQGTpxZAxubItesyduD -vj8tz9eb5u8JO3iQ/LYhMspNnxcpTFdaLn2v82NAFTtCrZdCd7aLj1DM0DPEX7Nw -V/rt/l+tlscglYyEoUnlPYuSQN0Q6Aj5i1GcKPvnFS0Oy9lGY1lT1vZJ4F0CAwEA -AaN+MHwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE -FP7bnvI4TIqtrM+KGgCvedJiQpuHMB8GA1UdIwQYMBaAFP7bnvI4TIqtrM+KGgCv -edJiQpuHMBkGA1UdEQQSMBCBDmNhQGNpbG9nb24ub3JnMA0GCSqGSIb3DQEBCwUA -A4IBAQCq5KUHQNg51uh1pxKMXQ98ADj2bNzQbswdAFslPow8tTZIBMwhdrq02ZHC -XPyp2IHxfv+G+pMV1JFtdR0fy8ivilMNyjObEGh1Ss3kvvU7d1z3XwPxqpNcwDqs -1K6RRg4zpNWCFPcliAkPDsDbaN1B6A6zJXqOpGgzwocU3dZbPe5sYLgkWZO2/8MI -eAEk7zoU1ZPSZiu5HghPafKuE1HYshvsak090tRgC6VLvaSLoNZlwR0GuFVGdewH -4jR1HpENH7QiLCB1NGCoJgDi3qiFosw3M2+0ExevE1afj2Usm4oZir+Uty0rvR8D -03RHH8yYbZ9rw0kuwTkJEo3bYDxH ------END CERTIFICATE----- diff --git a/jupyter-dev/certs/70d35895.signing_policy b/jupyter-dev/certs/70d35895.signing_policy deleted file mode 100644 index 811874f..0000000 --- a/jupyter-dev/certs/70d35895.signing_policy +++ /dev/null @@ -1,3 +0,0 @@ -access_id_CA X509 '/DC=org/DC=cilogon/C=US/O=CILogon/CN=CILogon OSG CA 1' -pos_rights globus CA:sign -cond_subjects globus '"/DC=org/DC=cilogon/C=US/O=CILogon/CN=CILogon OSG CA 1" "/DC=org/DC=opensciencegrid/*"' diff --git a/jupyter-dev/certs/b6b8acc0.0 b/jupyter-dev/certs/b6b8acc0.0 deleted file mode 100644 index 7098408..0000000 --- a/jupyter-dev/certs/b6b8acc0.0 +++ /dev/null @@ -1,17 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICnTCCAgagAwIBAgIJAJxQdm//nkK5MA0GCSqGSIb3DQEBBQUAMG0xDjAMBgNV -BAoTBU5FUlNDMRowGAYDVQQLExFORVJTQyBJbnRlcm5hbCBDQTEkMCIGA1UECxMb -c2ltcGxlQ0EtbmVyc2NjYTIubmVyc2MuZ292MRkwFwYDVQQDExBHbG9idXMgU2lt -cGxlIENBMB4XDTEyMDEyNDE5MjkyM1oXDTE3MDEyMjE5MjkyM1owbTEOMAwGA1UE -ChMFTkVSU0MxGjAYBgNVBAsTEU5FUlNDIEludGVybmFsIENBMSQwIgYDVQQLExtz -aW1wbGVDQS1uZXJzY2NhMi5uZXJzYy5nb3YxGTAXBgNVBAMTEEdsb2J1cyBTaW1w -bGUgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAJ1gr7jQpgLfOjurq7Rv -qmnpT/fQXwekv2iO1Fa3SI2UcmIdm6Ya8kKaU0LWNf4to56WavwuU4Cuac12NG1q -b3uULwQkYF3/ON01uEuLc+lk7XuYy0atDxEnq8JWDDk+ebAlkiO3oqYaHcVk2BXG -iyAijye4KfWnnC1F2MupVaixAgMBAAGjRTBDMA8GA1UdEwEB/wQFMAMBAf8wHQYD -VR0OBBYEFFs2zn1cO4uwHGcwEOt53NWcv097MBEGCWCGSAGG+EIBAQQEAwIABzAN -BgkqhkiG9w0BAQUFAAOBgQCGuWnlQrFYf09oWJQdYwMa/snd1xsvHuwterrXox3h -4V2dTYuUuBEgtPfVuFaqqrpKz0ZAsLuV/GmrtAFZk11YIG8Uu9NjHcRFJx0cQFjW -aCs6fUZ/9SsMFhTM5NkyBbOia/aGRYmCvfIjms4GCsRDK6/Mw9yCS6+WC0vPvIic -YQ== ------END CERTIFICATE----- diff --git a/jupyter-dev/certs/b6b8acc0.signing_policy b/jupyter-dev/certs/b6b8acc0.signing_policy deleted file mode 100644 index 7089c52..0000000 --- a/jupyter-dev/certs/b6b8acc0.signing_policy +++ /dev/null @@ -1,35 +0,0 @@ -# ca-signing-policy.conf, see ca-signing-policy.doc for more information -# -# This is the configuration file describing the policy for what CAs are -# allowed to sign whoses certificates. -# -# This file is parsed from start to finish with a given CA and subject -# name. -# subject names may include the following wildcard characters: -# * Matches any number of characters. -# ? Matches any single character. -# -# CA names must be specified (no wildcards). Names containing whitespaces -# must be included in single quotes, e.g. 'Certification Authority'. -# Names must not contain new line symbols. -# The value of condition attribute is represented as a set of regular -# expressions. Each regular expression must be included in double quotes. -# -# This policy file dictates the following policy: -# -The Globus CA can sign Globus certificates -# -# Format: -#------------------------------------------------------------------------ -# token type | def.authority | value -#--------------|---------------|----------------------------------------- -# EACL entry #1| - - access_id_CA X509 '/O=NERSC/OU=NERSC Internal CA/OU=simpleCA-nerscca2.nersc.gov/CN=Globus Simple CA' - - pos_rights globus CA:sign - - cond_subjects globus '"/DC=gov/DC=nersc/*"' - -# cond_subjects globus '"/O=NERSC/OU=NERSC Internal CA/OU=simpleCA-nerscca2.nersc.gov/*"' - -# end of EACL diff --git a/jupyter-dev/docker-entrypoint.sh b/jupyter-dev/docker-entrypoint.sh index 41c23e7..057050a 100644 --- a/jupyter-dev/docker-entrypoint.sh +++ b/jupyter-dev/docker-entrypoint.sh @@ -24,5 +24,6 @@ file_env() { } file_env 'POSTGRES_PASSWORD' +file_env 'SKEY' exec "$@" diff --git a/jupyter-dev/hub-scripts/flush-certs.sh b/jupyter-dev/hub-scripts/flush-certs.sh index ca1c1a8..5c1e2cf 100644 --- a/jupyter-dev/hub-scripts/flush-certs.sh +++ b/jupyter-dev/hub-scripts/flush-certs.sh @@ -1,5 +1,9 @@ #!/bin/bash -# Get rid of any certs older than 2 weeks +# Get rid of any cert files older than 2 weeks -find /certs -type f -name 'x509_*' -mtime +14 -exec rm {} \; +limit=14 + +find /certs -type f -name '*.key' -mtime +$limit -exec rm {} \; +find /certs -type f -name '*.key-cert.pub' -mtime +$limit -exec rm {} \; +find /certs -type f -name '*.key.pub' -mtime +$limit -exec rm {} \; diff --git a/jupyter-dev/hub-scripts/kill-cori.sh b/jupyter-dev/hub-scripts/kill-cori.sh index 77c6a62..fa255db 100644 --- a/jupyter-dev/hub-scripts/kill-cori.sh +++ b/jupyter-dev/hub-scripts/kill-cori.sh @@ -5,27 +5,28 @@ usernames=("$@") if [ ${#usernames[@]} -eq 0 ]; then - for cert in /certs/x509_* + for cert in /certs/*.key do - username=$(echo $cert | cut -b 13-) + username=$(echo $cert | cut -b8- | sed 's/\.key$//') usernames=(${usernames[@]} $username) done fi for username in ${usernames[@]} do - cert=/certs/x509_$username + cert=/certs/$username.key echo $username $cert if [ ! -f $cert ]; then echo " ... SKIPPED no cert for $username" continue fi - export X509_USER_CERT=$cert - export X509_USER_KEY=$cert - gsissh \ - -o StrictHostKeyChecking=no \ - -l $username \ - -p 2222 cori19-224.nersc.gov \ + /usr/bin/ssh \ + -i $cert \ + -l $username \ + -o PreferredAuthentications=publickey \ + -o StrictHostKeyChecking=no \ + -p 22 \ + cori19-224.nersc.gov \ /global/common/shared/das/jupyterhub/kill-my-old-jupyters.sh sleep 1 done diff --git a/jupyter-dev/hub-scripts/scram-user.sh b/jupyter-dev/hub-scripts/scram-user.sh index 43cbeab..0e8f750 100644 --- a/jupyter-dev/hub-scripts/scram-user.sh +++ b/jupyter-dev/hub-scripts/scram-user.sh @@ -6,20 +6,21 @@ for username in "$@" do - cert=/certs/x509_$username + cert=/certs/$username.key echo $username $cert if [ ! -f $cert ]; then echo " ... SKIPPED no cert for $username" continue fi - export X509_USER_CERT=$cert - export X509_USER_KEY=$cert for i in 1 2 3 do - gsissh \ - -o StrictHostKeyChecking=no \ - -l $username \ - -p 2222 cori19-224.nersc.gov \ + /usr/bin/ssh \ + -i $cert \ + -l $username \ + -o PreferredAuthentications=publickey \ + -o StrictHostKeyChecking=no \ + -p 22 \ + cori19-224.nersc.gov \ killall -u $username sleep 1 done diff --git a/jupyter-dev/hub-scripts/test-user.sh b/jupyter-dev/hub-scripts/test-user.sh index e10959a..1cf64ca 100644 --- a/jupyter-dev/hub-scripts/test-user.sh +++ b/jupyter-dev/hub-scripts/test-user.sh @@ -1,17 +1,18 @@ #!/bin/bash -# Test a user's ability to gsissh in. +# Test user's ability to ssh username=$1 -cert=/certs/x509_$username +cert=/certs/$username.key echo $username $cert if [ ! -f $cert ]; then echo " ... no cert for $username" exit 1 fi -export X509_USER_CERT=$cert -export X509_USER_KEY=$cert -gsissh \ - -o StrictHostKeyChecking=no \ - -l $username \ - -p 2222 cori19-224.nersc.gov +/usr/bin/ssh \ + -i $cert \ + -l $username \ + -o PreferredAuthentications=publickey \ + -o StrictHostKeyChecking=no \ + -p 22 \ + cori19-224.nersc.gov diff --git a/jupyter-dev/jupyterhub_config.py b/jupyter-dev/jupyterhub_config.py index 4fa7c73..909017e 100644 --- a/jupyter-dev/jupyterhub_config.py +++ b/jupyter-dev/jupyterhub_config.py @@ -1,6 +1,7 @@ # Configuration file for jupyterhub. import os +import sys import requests @@ -15,6 +16,7 @@ def comma_split(string): else: return list() +ip = requests.get('https://v4.ifconfig.co/json').json()['ip'] bindir = '/global/common/cori/software/python/3.6-anaconda-5.2/bin/' if 'BASE_PATH' in os.environ: @@ -98,7 +100,7 @@ def comma_split(string): # where `handler` is the calling web.RequestHandler, # and `data` is the POST form data from the login page. #c.JupyterHub.authenticator_class = 'jupyterhub.auth.PAMAuthenticator' -c.JupyterHub.authenticator_class = 'gsiauthenticator.auth.GSIAuthenticator' +c.JupyterHub.authenticator_class = 'sshapiauthenticator.auth.SSHAPIAuthenticator' ## The base URL of the entire application. # @@ -388,6 +390,11 @@ def comma_split(string): 'name': 'cull-idle', 'admin': True, 'command': 'cull_idle_servers.py --timeout=43200'.split(), + }, + { + 'name': 'announcement', + 'url': 'http://127.0.0.1:8888', + 'command': ["python", "-m", "announcement"], } ] @@ -434,6 +441,7 @@ def comma_split(string): ## Paths to search for jinja templates, before using the default templates. #c.JupyterHub.template_paths = [] +c.JupyterHub.template_paths = ["templates"] ## Extra variables to be passed into jinja templates #c.JupyterHub.template_vars = {} @@ -688,7 +696,7 @@ def comma_split(string): # JupyterHub modifies its own state accordingly and removes appropriate routes # from the configurable proxy. #c.Spawner.poll_interval = 30 -c.Spawner.poll_interval = 1800 +c.Spawner.poll_interval = 900 ## The port for single-user servers to listen on. # @@ -959,31 +967,20 @@ def comma_split(string): #c.CryptKeeper.n_threads = 2 #------------------------------------------------------------------------------ -# GSIAuthenticator(Authenticator) configuration +# SSHAPIAuthenticator(Authenticator) configuration #------------------------------------------------------------------------------ -c.GSIAuthenticator.proxy_lifetime = 999999 -c.GSIAuthenticator.server = 'nerscca1.nersc.gov' -c.GSIAuthenticator.cert_path_prefix = '/certs/x509_' +c.SSHAPIAuthenticator.server = 'https://sshproxy.nersc.gov/create_pair/jupyter/' +c.SSHAPIAuthenticator.skey = os.environ.get('SKEY') +c.SSHAPIAuthenticator.cert_path = '/certs' #------------------------------------------------------------------------------ # SSHSpawner(Spawner) configuration #------------------------------------------------------------------------------ c.SSHSpawner.remote_hosts = ['cori19-224.nersc.gov'] -c.SSHSpawner.remote_port = '2222' -c.SSHSpawner.ssh_command = 'gsissh' -if 'REMOTE_HOST' in os.environ: - host, port = os.environ['REMOTE_HOST'].split(':') - c.SSHSpawner.remote_host = host - c.SSHSpawner.remote_port = port - -c.SSHSpawner.hub_api_url = 'http://{}:8081/hub/api'.format(requests.get('https://ifconfig.co/json').json()['ip']) -if 'HUB_API_URL' in os.environ: - c.SSHSpawner.hub_api_url = os.environ['HUB_API_URL'] - -c.SSHSpawner.use_gsi = True +c.SSHSpawner.remote_port = '22' +c.SSHSpawner.hub_api_url = "http://{}:8081/hub/api".format(ip) c.SSHSpawner.path = bindir + ':/global/common/cori/das/jupyterhub/:/usr/common/usg/bin:/usr/bin:/bin' c.SSHSpawner.remote_port_command = '/usr/bin/python /global/common/cori/das/jupyterhub/get_port.py' -c.SSHSpawner.gsi_cert_path = '/certs/x509_{username}' -c.SSHSpawner.gsi_key_path = '/certs/x509_{username}' +c.SSHSpawner.ssh_keyfile = '/certs/{username}.key' diff --git a/jupyter-dev/templates/login.html b/jupyter-dev/templates/login.html new file mode 100644 index 0000000..92d57dc --- /dev/null +++ b/jupyter-dev/templates/login.html @@ -0,0 +1,99 @@ +{% extends "page.html" %} {# This extends page.html, which has announcements in it. #} +{% if announcement_login %} + {% set announcement = announcement_login %} +{% endif %} + +{% block login_widget %} +{% endblock %} + +{% block main %} + +{% block login %} +
+{% if custom_html %} +{{ custom_html | safe }} +{% elif login_service %} + +{% else %} +
+
+ Sign in +
+
+ + + + {% if login_error %} + + {% endif %} + + + + + + + + + +
+
+{% endif %} +
+{% endblock login %} + +{% endblock %} + +{% block script %} +{{ super() }} + + + +{% endblock %}