Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use the Hub cookie to retrieve user information #33

Closed
6 tasks done
kinow opened this issue Feb 21, 2019 · 7 comments
Closed
6 tasks done

Use the Hub cookie to retrieve user information #33

kinow opened this issue Feb 21, 2019 · 7 comments
Assignees
Labels
question Flag this as a question for the next Cylc project meeting.
Milestone

Comments

@kinow
Copy link
Member

kinow commented Feb 21, 2019

Upon logging in to JupyterHub, it creates a cookie that can be used to retrieve user information.

The cookie can be used to access information about the user.

image

This ticket is to investigate if we are able to access the Cookie from the Vue.js application too, then query this endpoint, and use the returned information afterwards. Here's what the information returned should look like.

image

  • Find out which calls and what data we need
  • Find a way to pass the API token from the Spawner to the UI Server
  • Decide how/where to store the API token in the UI Server (securely)
  • Reading the Hub user profile in Vue.js Read the value of the jupyterhub-hub-login Cookie in Vue.js
  • Retrieve user information using Axios HTTP client in Vue.js
  • Enforce that certain routes can be accessed only when logged in
@kinow kinow self-assigned this Feb 21, 2019
@kinow kinow added the question Flag this as a question for the next Cylc project meeting. label Feb 21, 2019
@kinow
Copy link
Member Author

kinow commented Feb 21, 2019

Once you log in to JupyterHub, and after you are redirected to the application spawned by the spawner, you will have two cookies.

image

One is set for localhost/, and the other for localhost/hub.

@kinow
Copy link
Member Author

kinow commented Feb 21, 2019

Find out which calls and what data we need

Right, not so hard. The spawner has access to the API token generated by JupyterHub. It changes when you restart the application.

We must find a way to pass the API token from the spawner to the UI server, and decide where to store it.

Once you have the API token. You have to send a GET HTTP request to http://localhost:8000/hub/api/authorizations/cookie/jupyterhub-hub-login/...very...long...value.......

Where jupyterhub-hub-login is the name of the Cookie Vue.js must access. And the value is a very long value.

Here's the response for my user (using Insomnia HTTP client):

{
  "kind": "user",
  "name": "kinow",
  "admin": false,
  "groups": [],
  "server": "/user/kinow/",
  "pending": null,
  "created": "2019-02-21T00:35:02.939295Z",
  "last_activity": "2019-02-21T02:36:39.421228Z",
  "servers": null
}

@kinow
Copy link
Member Author

kinow commented Feb 21, 2019

Find a way to pass the API token from the Spawner to the UI Server

In the LocalProcessSpawner, the parent class Spawner has a api_token. That value is created when the spawner is first invoked, through the method spawn(). (api_stoken is a typo that happened when I pressed CTRL+S, please ignore)

image

Later, when we spawn the UI server, there is some code inherited from LocalProcessSpawner that puts that value in an environment variable.

image

image

I suspect the spawner in JupyterHub that uses SSH should still preserve the environment variables, but that needs to be tested later.

I added a fake handler for a POST method that prints the content of the environment variable with the API token. And could confirm that the we have the right value in there.

image

Environment Variables available for UI Server

After starting the UI server, we have the following environment variables:

PATH=/home/kinow/Development/python/workspace/cylc-jupyterhub/venv/bin:/home/kinow/Development/python/anaconda3/bin:/usr/local/go/bin:/home/kinow/.nvm/versions/node/v8.11.0/bin:/home/kinow/bin:/home/kinow/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/kinow/go/bin
PYTHONPATH=/home/kinow/Development/python/workspace/cylc-jupyterhub
VIRTUAL_ENV=/home/kinow/Development/python/workspace/cylc-jupyterhub/venv
LANG=en_NZ.UTF-8
JUPYTERHUB_API_TOKEN=98c95e46815a41cc86106aef224c3b5d
JPY_API_TOKEN=98c95e46815a41cc86106aef224c3b5d
JUPYTERHUB_CLIENT_ID=jupyterhub-user-kinow
JUPYTERHUB_HOST=
JUPYTERHUB_OAUTH_CALLBACK_URL=/user/kinow/oauth_callback
JUPYTERHUB_USER=kinow
JUPYTERHUB_API_URL=http://127.0.0.1:8081/hub/api
JUPYTERHUB_BASE_URL=/
JUPYTERHUB_SERVICE_PREFIX=/user/kinow/
USER=kinow
HOME=/home/kinow
SHELL=/bin/bash

@kinow
Copy link
Member Author

kinow commented Feb 21, 2019

Decide how/where to store the API token in the UI Server (securely)

There is a intricately relationship between JupyterHub and its spawned Jupyter Notebook. Let's look first what happens when you run jupyter-notebook.

[I 09:44:34.203 NotebookApp] Writing notebook server cookie secret to /run/user/1000/jupyter/notebook_cookie_secret
[I 09:44:34.808 NotebookApp] JupyterLab extension loaded from /home/kinow/Development/python/anaconda3/lib/python3.7/site-packages/jupyterlab
[I 09:44:34.808 NotebookApp] JupyterLab application directory is /home/kinow/Development/python/anaconda3/share/jupyter/lab
[I 09:44:34.819 NotebookApp] Serving notebooks from local directory: /tmp
[I 09:44:34.819 NotebookApp] The Jupyter Notebook is running at:
[I 09:44:34.819 NotebookApp] http://localhost:8888/?token=4c691289635e55e45c24e00ca663bfddd32a1079f92646d9
[I 09:44:34.819 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 09:44:34.859 NotebookApp] 
    
    Copy/paste this URL into your browser when you connect for the first time,
    to login with a token:
        http://localhost:8888/?token=4c691289635e55e45c24e00ca663bfddd32a1079f92646d9

In the logs, there is a URL link that contains a URL parameter token. That token is used to automatically log in to your local notebook. If you open the URL without the token parameter, then you are displayed a login page.

image

If you enter the token. Then you are authenticated and can use the notebook. Now if you use the jupyterhub command instead.

[I 2019-02-22 09:49:32.842 JupyterHub spawner:1287] Spawning jupyterhub-singleuser --port=48525
[I 2019-02-22 09:49:34.924 SingleUserNotebookApp extension:59] JupyterLab extension loaded from /home/kinow/Development/python/anaconda3/lib/python3.7/site-packages/jupyterlab
[I 2019-02-22 09:49:34.924 SingleUserNotebookApp extension:60] JupyterLab application directory is /home/kinow/Development/python/anaconda3/share/jupyter/lab
[I 2019-02-22 09:49:34.933 SingleUserNotebookApp singleuser:425] Starting jupyterhub-singleuser server version 1.0.0.dev
[I 2019-02-22 09:49:34.938 JupyterHub log:158] 200 GET /hub/api (@127.0.0.1) 0.93ms
[I 2019-02-22 09:49:34.939 SingleUserNotebookApp notebookapp:1685] Serving notebooks from local directory: /home/kinow
[I 2019-02-22 09:49:34.939 SingleUserNotebookApp notebookapp:1685] The Jupyter Notebook is running at:
[I 2019-02-22 09:49:34.939 SingleUserNotebookApp notebookapp:1685] http://127.0.0.1:48525/user/kinow/
[I 2019-02-22 09:49:34.939 SingleUserNotebookApp notebookapp:1686] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).

The JupyterHub application started jupyterhub-singleserver server. That launched a Jupyter Notebook in a local TCP port, behind the proxy (the next log messages would be about the proxy for /user/kinow). But we never see the login page of the notebook.

Jupyter Notebook supports running with or without the Hub. So it handles its own authentication, unless the Hub started it and pre-authenticated the user. For Cylc, initially we have now no need to have the UI Server running without the Hub.

Cylc vs. Notebook

IPython, or Jupyter Notebook, comes with a very detailed API for security. It supports CORS, CSP, and has further server-side security in-place to prevent malicious users. The main different between Cylc and Notebooks, is that while Cylc when accessed will let users to trigger existing suites/workflows. While Notebook gives the user access to run any command when editing the Notebook.

So the security in the UI Server of Cylc has different requirements than Jupyter Notebook.

JupyterHub contains a class called HubOAuthenticated, which is a mixin. From what I could tell, this mixin is programmed to read JupyterHub settings, and deal with Cookie, API Token, and communicate with the Hub to validate if the user is really logged in or not.

Using it in initial tests, combined with Tornado's tornado.web.authenticated annotation (see Tornado documentation documentation for more, and read the part about cookie_secret too), it is possible to confirm that accessing /user/kinow/ is permitted once logged in.

But after logging out of the hub, the next request gets blocked by the mixin.

So apparently, we don't need to worry about the Cookie, or the API Token at all (for now, we need to read more, understand, and document what/how/&why we will use these bits and pieces of JupyterHub & Tornado in Cylc).

@kinow
Copy link
Member Author

kinow commented Feb 24, 2019

Reading the Hub user profile in Vue.js Read the value of the jupyterhub-hub-login Cookie in Vue.js

We are not really reading the value of the cookie anymore. Now we can create an endpoint to return the user data, if logged in.

The Tornado method get_current_user() returns the whole information of the user. We can simply write it as a JSON object, which should give us:

{
  "kind": "user",
  "name": "kinow",
  "admin": false,
  "groups": [],
  "server": "/user/kinow/",
  "pending": null,
  "created": "2019-02-21T00:35:02.939295Z",
  "last_activity": "2019-02-24T21:19:47.923746Z",
  "servers": null
}

Vue.js now has a UserService (unit tested!) that connects to JupyterHub via HTTP, using Axios, and fetches the user profile data (#38).

It creates a User object, and returns in a promise. That is later eval'd and rendered in the form/page.

@kinow
Copy link
Member Author

kinow commented Feb 28, 2019

Enforce that certain routes can be accessed only when logged in

This one is done in the Tornado app (UI Server). The Vue.js app has no idea whether the user is logged in or not. We have no way to access the cookie as it is HttpOnly. But the Tornado handlers from JupyterHub are taking care of reading the cookie, checking validity and authorization against the hub, and rejecting requests once the cookie expired or was removed.

For now, I believe we can go with this approach, of delegating authorization and authentication-check to the UI Server, and focus on the rest of the tasks for Cylc-8 🎉

@kinow
Copy link
Member Author

kinow commented Feb 28, 2019

I'm done here @hjoliver , brain is boiling after so much JupyterHub and JS/Vue.js this week. Tomorrow will probably look at the recent changes in Cylc, timesheet, cylc-admin's gantt chart, etc. But I think we have done some good progress on Vue.js/UI Server/Hub integration. 😴

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Flag this as a question for the next Cylc project meeting.
Projects
None yet
Development

No branches or pull requests

1 participant