Understanding token scope difference

I’m testing the new RBAC features and noticed interesting discrepancy in how different tokens are scoped.
For some reason, there are different scopes assigned to user token in JUPYTERHUB_API_TOKEN singleuser server env variable vs. the token manually requested from JupyterHub UI at /hub/token.
I use ‘simple’ spawner and ‘dummy’ authenticator.

To check the difference, I run GET request on JUPYTERHUB_API_URL/user
In case of the token assigned to env variable I only get the minimal scope required to access my own servers, read my user and my own activity:

'scopes': [
  'access:servers!user=ktaletsk',
  'read:users:activity!user=ktaletsk',
  'users:activity!user=ktaletsk'
]

In case of token requested in UI I get the full scope as configured in my config file:

'scopes': [
  'access:servers!user=ktaletsk',
  'groups!group=server_sharing_ktaletsk',
  'read:groups!group=server_sharing_ktaletsk',
  'read:groups:name!group=server_sharing_ktaletsk',
  'read:servers!user=ktaletsk',
  'read:tokens!user=ktaletsk',
  'read:users!user=ktaletsk',
  'read:users:activity!user=ktaletsk',
  'read:users:groups!user=ktaletsk',
  'read:users:name!user=ktaletsk',
  'servers!user=ktaletsk',
  'tokens!user=ktaletsk',
  'users!user=ktaletsk',
  'users:activity!user=ktaletsk'
]

I am trying to understand why is there a difference, and how would I automatically set the JUPYTERHUB_API_TOKEN with the “full” scoped token?

1 Like

I narrowed it down to different parameters in call to …

JUPYTERHUB_API_TOKEN is generated inside User.spawn:

with this call:

api_token = self.new_api_token(note=note, roles=['server'])

Requesting the token through command line calls:

token = user.new_api_token(note="command-line generated")

As I can see, User.spawn calls has additional parameter roles=['server'], whereas another call does not. Indeed, removing roles=['server'] parameter and reinstalling JupyterHub yield two identical scopes.

I am still wondering, why this difference exists and how would I get this behavior without forking JupyterHub…

Thanks for testing this and giving feedback!

I am trying to understand why is there a difference

There are two “whys”:

  1. the direct reason is that they have a different ‘role’ (token for the default token role, and server for the server token) which in turn may have different scopes (they do by default)
  2. the reason those roles differ is that servers don’t usually need much permissions to function, so they’ve been restricted to just what hey need by default.

The relevant part of the rbac docs is the default roles.

and how would I automatically set the JUPYTERHUB_API_TOKEN with the “full” scoped token?

These roles have default scopes, but you can also override them if you want a different collection of scopes assigned to the roles. It sounds like you want the server token to have full permissions of the owning user, which is encapsulated in the inherit scope:

c.JupyterHub.load_roles = [
 {
   'name': 'server',
   'scopes': ['inherit'],
 }
]

This gets the same permissions you would have with 1.x. Depending on what you are using it for, I’d recommend looking at the available scopes and grant explicit permissions for the additional functionality you want instead of automatically assigning full permissions, but that’s up to you for what makes sense in your deployment.

I opened this pr to add some of these details and this example to the docs.

Extra note:

  • Tokens issued via the UI are assigned the token role, but the REST API the page uses supports assigning arbitrary roles. This is not exposed in the UI yet, so only the default is used when you click the ‘token’ button

EDIT: changed ‘all’ to ‘inherit’, which was the name we ended up releasing with

3 Likes