-
Notifications
You must be signed in to change notification settings - Fork 256
[services] Switch to role based auth #15200
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
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be merged with 011 and done in one go. It's a side effect of development milestones and not worth preserving in the final commit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR refactors the authentication and authorization system from a simple is_developer boolean flag to a comprehensive role-based access control (RBAC) system using system roles and permissions. This enables more granular access control with intermediate roles between full system admin and end user.
Key Changes:
- Introduces new database tables for
system_roles,system_permissions,system_role_permissions, andusers_system_roles - Replaces
is_developerchecks throughout the codebase with permission-based checks - Updates API endpoints to use new permission decorators (e.g.,
@auth.authenticated_users_with_permission()) - Migrates UI templates to conditionally render elements based on user permissions
Reviewed changes
Copilot reviewed 36 out of 36 changed files in this pull request and generated 17 comments.
Show a summary per file
| File | Description |
|---|---|
| auth/sql/011-refactor-system-permissions.sql | Adds new CI and deployed system state permissions, creates sysadmin-readonly role, and migrates existing users to new role system |
| auth/sql/estimated-current.sql | Updates schema to include new system_roles, system_permissions, and relationship tables |
| auth/auth/auth.py | Refactors user creation and management to use system roles instead of is_developer flag; adds permission checking endpoints |
| auth/auth/driver/driver.py | Updates user creation/deletion logic to check for access_developer_environments permission |
| auth/auth/templates/users.html | Updates user management UI to support multiple system roles via checkboxes |
| auth/auth/templates/roles.html | Adds conditional rendering for role assignment buttons based on assign_system_roles permission |
| auth/auth/templates/openapi.yaml | Updates API spec to reflect system_roles array replacing is_developer boolean |
| auth/auth/exceptions.py | Adds InvalidRole exception for unknown role validation |
| gear/gear/auth.py | Replaces authenticated_developers_only decorator with authenticated_users_with_permission; updates UserData type |
| gear/gear/init.py | Exports SystemPermission (duplicate entry) |
| gear/gear/system_permissions.py | Adds new permission types for CI, deployed system state, and developer activities |
| web_common/web_common/web_common.py | Updates Jinja context retrieval to check READ_PRERENDERED_JINJA2_CONTEXT permission |
| web_common/web_common/templates/new_header.html | Updates navigation menu to show/hide sections based on user permissions |
| web_common/web_common/templates/header.html | Updates legacy header to use permission-based visibility |
| batch/batch/front_end/front_end.py | Replaces is_developer checks with permission-based authorization for billing operations |
| batch/batch/front_end/templates/billing*.html | Conditionally renders billing management UI based on user permissions |
| batch/batch/driver/main.py | Updates driver endpoints to require specific system permissions |
| batch/batch/driver/templates/*.html | Adds permission guards for system state modification UI elements |
| ci/ci/ci.py | Replaces authenticated_developers_only with permission-based decorators for CI operations |
| ci/ci/templates/*.html | Conditionally renders CI management controls based on manage_ci permission |
| ci/bootstrap_create_accounts.py | Updates bootstrap user creation to use system_roles instead of is_developer |
| monitoring/monitoring/monitoring.py | Requires VIEW_MONITORING_DASHBOARDS permission for billing endpoints |
| gateway/envoy.yaml | Updates authorization path to verify access_developer_environments permission |
| grafana/deployment.yaml | Updates authorization path to verify view_monitoring_dashboards permission |
| prometheus/prometheus.yaml | Updates authorization path to verify view_monitoring_dashboards permission |
| hail/python/hailtop/auth/auth.py | Updates async_create_user to accept system_roles list instead of is_developer boolean |
| hail/python/hailtop/hailctl/auth/*.py | Updates CLI commands to support system_roles parameter |
| hail/python/hailtop/hail_logging.py | Logs system_permissions instead of is_developer |
| devbin/dev_proxy.py | Updates dev proxy to set system_roles instead of is_developer |
| build.yaml | Adds migration step for refactor-system-permissions |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| """ | ||
| INSERT INTO users (state, username, login_id, is_developer, is_service_account, hail_identity, hail_credentials_secret_name) | ||
| INSERT INTO users (state, username, login_id, is_service_account, hail_identity, hail_credentials_secret_name) | ||
| VALUES (%s, %s, %s, %s, %s, %s, %s); |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The INSERT statement is missing a value placeholder. There are 7 placeholders (%s) but only 6 values in the tuple (missing the value for is_service_account between login_id and hail_identity). The statement should have 6 placeholders to match the 6 columns.
| VALUES (%s, %s, %s, %s, %s, %s, %s); | |
| VALUES (%s, %s, %s, %s, %s, %s); |
| ) | ||
| app[AppKeys.DEVELOPERS] = [u for u in users if u['is_developer'] == 1 and u['state'] == 'active'] | ||
| app[AppKeys.DEVELOPERS] = [ | ||
| u for u in users if SystemPermission.MANAGE_CI in u['system_permissions'] and u['state'] == 'active' |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checking for membership of SystemPermission.MANAGE_CI in u['system_permissions'] is incorrect. Based on the UserData type definition and how permissions are fetched in auth.py, system_permissions is a dictionary mapping permission names to booleans (e.g., {'manage_ci': True}), not a list of SystemPermission enums. This should be:
u for u in users if u['system_permissions'].get('manage_ci', False) and u['state'] == 'active'or
u for u in users if u['system_permissions'].get(SystemPermission.MANAGE_CI, False) and u['state'] == 'active'| u for u in users if SystemPermission.MANAGE_CI in u['system_permissions'] and u['state'] == 'active' | |
| u for u in users if u['system_permissions'].get('manage_ci', False) and u['state'] == 'active' |
| {% endif %} | ||
|
|
||
| {% if userdata['is_developer'] == 1 %} | ||
| {% if userdata['system_permissions']['view_monitoring_dashboards'] %} |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing null check for userdata. Line 59 checks userdata['system_permissions'][...] without first verifying userdata exists. This should include a null check:
{% if userdata and userdata['system_permissions']['view_monitoring_dashboards'] %}| db = request.app[AppKeys.DB] | ||
| users = [x async for x in db.select_and_fetchall('SELECT * FROM users;')] | ||
| users = await _get_users(db) | ||
| page_context = {'users': users} |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The page context is missing the system_roles variable that is used in the template at line 29. The template expects system_roles to be available to render the role checkboxes in the create user form. You should add something like:
system_roles = [x async for x in db.select_and_fetchall('SELECT name FROM system_roles ORDER BY name;')]
page_context = {'users': users, 'system_roles': [r['name'] for r in system_roles]}| page_context = {'users': users} | |
| system_roles = [x async for x in db.select_and_fetchall('SELECT name FROM system_roles ORDER BY name;')] | |
| page_context = {'users': users, 'system_roles': [r['name'] for r in system_roles]} |
| system_roles_raw = post.get('system_roles', []) | ||
| if isinstance(system_roles_raw, list): | ||
| system_roles = [str(role) for role in system_roles_raw] | ||
| elif system_roles_raw: | ||
| raise web.HTTPBadRequest(text='Invalid system_roles value. Must be a list of role name strings.') | ||
| else: | ||
| system_roles = [] |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The handling of system_roles from POST data needs adjustment. HTML forms with checkboxes send the name multiple times for multiple selections, so post.get('system_roles') won't work correctly. You should use post.getall('system_roles[]') to get all selected values. The current code will only get one value or a list depending on how the form is submitted.
| system_roles_raw = post.get('system_roles', []) | |
| if isinstance(system_roles_raw, list): | |
| system_roles = [str(role) for role in system_roles_raw] | |
| elif system_roles_raw: | |
| raise web.HTTPBadRequest(text='Invalid system_roles value. Must be a list of role name strings.') | |
| else: | |
| system_roles = [] | |
| system_roles_raw = post.getall('system_roles[]') | |
| system_roles = [str(role) for role in system_roles_raw] |
| ident_token = f'{username}-{token}' | ||
|
|
||
| if user['is_developer'] == 1 or user['is_service_account'] == 1 or username == 'test': | ||
| if 'access_developer_environments' in user['system_roles'] or user['is_service_account'] == 1 or username == 'test': |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The condition checks 'access_developer_environments' in user['system_roles'] but based on the _users_in_state_with_roles function in driver.py, user['system_roles'] is a list of role names (e.g., ['developer', 'billing_manager']), not permission names. To check for a permission, you need to check if the user has a role that grants that permission. Consider using a permission-based check or verifying that the 'developer' role is in user['system_roles'] instead.
| # auth services in test namespaces cannot/should not be creating and deleting namespaces | ||
| if namespace_name is not None and namespace_name != DEFAULT_NAMESPACE and not is_test_deployment: | ||
| assert user['is_developer'] == 1 | ||
| if 'access_developer_environments' not in user['system_roles']: |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same issue: checking for 'access_developer_environments' in user['system_roles'] when system_roles contains role names, not permission names.
| ) -> web.Response: | ||
| if request.headers.get('x-hail-return-jinja-context'): | ||
| if userdata and userdata['is_developer']: | ||
| if userdata and userdata['system_permissions'][SystemPermission.READ_PRERENDERED_JINJA2_CONTEXT]: |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing null check for userdata before accessing system_permissions. While other conditionals in this file check userdata and userdata['system_permissions'][...], this line only checks the permission without first verifying userdata exists. This should be:
if userdata and userdata['system_permissions'][SystemPermission.READ_PRERENDERED_JINJA2_CONTEXT]:| </a> | ||
|
|
||
| {% if userdata['is_developer'] == 1 %} | ||
| {% if userdata['system_permissions']['read_system_roles'] or userdata['system_permissions']['read_users'] %} |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing null check for userdata. Line 6 checks userdata['system_permissions'][...] but other similar checks in header.html check userdata['system_permissions'][...] without first verifying userdata exists. This should be:
{% if userdata and userdata['system_permissions']['read_system_roles'] or userdata and userdata['system_permissions']['read_users'] %}| <div class="header-dropdown-menu"> | ||
| <div class="batch-caret header-dropdown-menu-caret"></div> | ||
| {% if userdata['is_developer'] == 1 %} | ||
| {% if userdata['system_permissions']['read_deployed_system_state'] %} |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing null check for userdata. Line 28 checks userdata['system_permissions'][...] without first verifying userdata exists, unlike line 31 and other conditionals. This should include a null check:
{% if userdata and userdata['system_permissions']['read_deployed_system_state'] %}
Change Description
Updates UI and backend to update to use the system_roles table for system-level UI and functionality auth.
NB this leaves the previous is_developer field in the database, in case we need to revert back, but hopefully we can remove that too in an upcoming PR.
Security Assessment
Impact Rating
Impact Description
Refactors the simple
is_developerauth model into one based on roles and permissions. Allows intermediate roles between "everything" and "end user", eg for accessing billing pages or developer namespaces without being a full system admin.Appsec Review