Skip to content

Commit 9b92edd

Browse files
committed
Filter profiles by JupyterHub groups
1 parent dbf5684 commit 9b92edd

File tree

1 file changed

+88
-4
lines changed

1 file changed

+88
-4
lines changed

config/clusters/earthscope/common.values.yaml

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,12 @@ basehub:
7676
7777
return False
7878
79+
async def authenticate(self, *args, **kwargs):
80+
resp = await super().authenticate(*args, **kwargs)
81+
# Set scope to groups
82+
resp["groups"] = resp["auth_state"]["scope"]
83+
return resp
84+
7985
def populate_token(spawner, auth_state):
8086
# For our deployment-service-check health check user, there is no auth_state.
8187
# So these env variables need not be set.
@@ -89,7 +95,75 @@ basehub:
8995
9096
c.Spawner.auth_state_hook = populate_token
9197
98+
c.GenericOAuthenticator.manage_groups = True
99+
92100
c.JupyterHub.authenticator_class = CustomGenericOAuthenticator
101+
102+
05-gh-teams: |
103+
# Filters profileList based on group membership of JupyterHubs
104+
import copy
105+
106+
from textwrap import dedent
107+
from tornado import web
108+
from oauthenticator.github import GitHubOAuthenticator
109+
110+
original_profile_list = c.KubeSpawner.profile_list
111+
112+
async def profile_list_allowed_groups_filter(spawner):
113+
"""
114+
Returns the initially configured profile_list filtered based on the
115+
user's membership in each profile's `allowed_groups`. If
116+
`allowed_groups` isn't set for a profile, its not filtered out.
117+
118+
`allowed_groups` is a list of JupyterHub groups.
119+
120+
If the returned profile_list is filtered to not include a profile,
121+
an error is raised and the user isn't allowed to start a server.
122+
"""
123+
if spawner.user.name == "deployment-service-check":
124+
print("Ignoring allowed_teams check for deployment-service-check")
125+
return original_profile_list
126+
127+
groups = {g.name.casefold() for g in spawner.user.groups}
128+
print(f"User {spawner.user.name} is part of groups {groups}")
129+
130+
# Filter out profiles with allowed_groups set if the user isn't part of any.
131+
allowed_profiles = []
132+
for profile in copy.deepcopy(original_profile_list):
133+
allowed_groups = set(profile.get("allowed_groups"))
134+
if allowed_groups is None:
135+
# If no allowed_groups are set, allow access to everything
136+
allowed_profiles.append(profile)
137+
continue
138+
139+
if allowed_groups & groups:
140+
print(f"Allowing profile {profile['display_name']} for user {spawner.user.name} based on group membership")
141+
allowed_profiles.append(profile)
142+
continue
143+
144+
if len(allowed_profiles) == 0:
145+
# If no profiles are allowed, user should not be able to spawn anything!
146+
# If we don't explicitly stop this, user will be logged into the 'default' settings
147+
# set in singleuser, without any profile overrides. Not desired behavior
148+
# FIXME: User doesn't actually see this error message, just the generic 403.
149+
error_msg = dedent(f"""
150+
Your Group team membership is insufficient to launch any server profiles.
151+
152+
GitHub teams you are a member of that this JupyterHub knows about are {', '.join(groups)}.
153+
154+
If you are part of additional teams, log out of this JupyterHub and log back in to refresh that information.
155+
""")
156+
raise web.HTTPError(403, error_msg)
157+
158+
return allowed_profiles
159+
160+
# Only set this customized profile_list *if* we already have a profile_list set
161+
# otherwise, we'll show users a blank server options form and they won't be able to
162+
# start their server
163+
if c.KubeSpawner.profile_list:
164+
# Customize list of profiles dynamically, rather than override options form.
165+
# This is more secure, as users can't override the options available to them via the hub API
166+
c.KubeSpawner.profile_list = profile_list_allowed_groups_filter
93167
config:
94168
# JupyterHub:
95169
# authenticator_class: auth0
@@ -110,7 +184,7 @@ basehub:
110184
username_claim: sub
111185
# Convert 'scope' from the OAuth2 response into JupyterHub groups
112186
manage_groups: true
113-
claim_groups_key: 'scope'
187+
# claim_groups_key: 'scope'
114188
CILogonOAuthenticator:
115189
allowed_idps:
116190
http://github.com/login/oauth/authorize:
@@ -129,6 +203,10 @@ basehub:
129203
profileList:
130204
- display_name: "Shared Small: 1-4 CPU, 8-32 GB"
131205
description: "A shared machine, the recommended option until you experience a limitation."
206+
allowed_groups:
207+
- geolab
208+
- geolab:dev
209+
- geolab:power
132210
profile_options: &profile_options
133211
image:
134212
display_name: Image
@@ -163,30 +241,36 @@ basehub:
163241
mem_limit: null
164242
node_selector:
165243
node.kubernetes.io/instance-type: r5.xlarge
166-
167244
- display_name: "Small: 4 CPU, 32 GB"
168245
description: "A dedicated machine for you."
246+
allowed_groups:
247+
- geolab
248+
- geolab:dev
249+
- geolab:power
169250
profile_options: *profile_options
170251
kubespawner_override:
171252
mem_guarantee: 28.937G
172253
cpu_guarantee: 0.4
173254
mem_limit: null
174255
node_selector:
175256
node.kubernetes.io/instance-type: r5.xlarge
176-
177257
- display_name: "Medium: 16 CPU, 128 GB"
178258
description: "A dedicated machine for you."
179259
profile_options: *profile_options
260+
allowed_groups:
261+
- geolab:dev
262+
- geolab:power
180263
kubespawner_override:
181264
mem_guarantee: 120.513G
182265
cpu_guarantee: 1.6
183266
mem_limit: null
184267
node_selector:
185268
node.kubernetes.io/instance-type: r5.4xlarge
186-
187269
- display_name: "Large: 64 CPU, 512 GB"
188270
description: "A dedicated machine for you"
189271
profile_options: *profile_options
272+
allowed_groups:
273+
- geolab:power
190274
kubespawner_override:
191275
mem_guarantee: 489.13G
192276
cpu_guarantee: 6.4

0 commit comments

Comments
 (0)