[services] Switch to role based auth#15200
[services] Switch to role based auth#15200cjllanwarne wants to merge 42 commits intohail-is:mainfrom
Conversation
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
There was a problem hiding this comment.
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.
auth/auth/auth.py
Outdated
| """ | ||
| 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); |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
ci/ci/ci.py
Outdated
| ) | ||
| 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' |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| {% endif %} | ||
|
|
||
| {% if userdata['is_developer'] == 1 %} | ||
| {% if userdata['system_permissions']['view_monitoring_dashboards'] %} |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
auth/auth/auth.py
Outdated
| 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} |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
auth/auth/auth.py
Outdated
| 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 = [] |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
auth/auth/driver/driver.py
Outdated
| 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': |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
auth/auth/driver/driver.py
Outdated
| # 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']: |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
web_common/web_common/web_common.py
Outdated
| ) -> 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]: |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| </a> | ||
|
|
||
| {% if userdata['is_developer'] == 1 %} | ||
| {% if userdata['system_permissions']['read_system_roles'] or userdata['system_permissions']['read_users'] %} |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| <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'] %} |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
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