Skip to content

RBAC breaks when Databricks Apps user token passthrough is disabled — all users locked to 'use' role #22

@carrossoni

Description

@carrossoni

Problem

PR #21 (feature/rbac-unity-catalog) assumes X-Forwarded-Access-Token is always available inside a Databricks App. In
practice, user token passthrough is disabled by default in some organizations (it requires an explicit workspace-level
setting). When disabled, X-Forwarded-Access-Token is never injected, and the entire RBAC layer breaks silently.

Impact (when token passthrough is disabled)

Symptom Root Cause
/users/me returns 401 extract_bearer_token raises 401 when header is absent
Frontend sees no role → defaults to use for everyone Frontend never gets a valid role back
"Create Gateway" button never shown isOwner = false because role resolution failed
Genie Spaces / Warehouses dropdowns always empty /workspace/genie-spaces and /workspace/warehouses call
extract_bearer_token directly → 401
All management endpoints (POST/PUT/DELETE gateways, settings) return 401 _require_role in gateway_routes.py calls
extract_bearer_token before auth check

The app is completely non-functional for any management operation on these workspaces, with no visible error — just a
locked-down UI.

Root Cause

extract_bearer_token (auth_helpers.py:11) raises HTTP 401 when:

  • X-Forwarded-Access-Token is absent (passthrough disabled)
  • Authorization: Bearer is also absent (browser-initiated requests to Databricks Apps never send this)

This is called unconditionally in:

  • gateway_routes._require_role (line 32)
  • gateway_routes.list_genie_spaces, list_warehouses, list_serving_endpoints (direct call)
  • rbac_routes._resolve_caller (line 34)

Why SP token fallback is NOT the right fix

Commit 0052fb9b correctly removed the service principal token fallback. Using a SP token to call SCIM /Me on behalf of
users is architecturally wrong — the SP token always resolves to the SP's own identity/groups, not the user's, making the admin
check meaningless.

Proposed Fix

When X-Forwarded-Access-Token is absent but X-Forwarded-Email is present (user authenticated via Databricks Apps SSO), the
correct behavior is graceful degradation:

  1. Skip the SCIM admin check — can't verify workspace admin without the user's token. Already safe: is_workspace_admin
    returns False when token is empty (rbac.py:54).
  2. Still do the DB role lookupuser_roles table lookup by email works without a token.
  3. Default to use if no explicit DB assignment (current behavior).
  4. Workspace discovery endpoints — fall back to ambient app credentials so Genie Spaces/Warehouses dropdowns populate
    correctly.

rbac_routes._resolve_caller:

async def _resolve_caller(req: Request):                  
    identity = req.headers.get("X-Forwarded-Email", "")
    try:
        token = extract_bearer_token(req)
    except HTTPException as e:                                                                                                 
        if e.status_code == 401 and identity:
            # Token passthrough disabled — user is SSO-authenticated but no token injected.                                    
            # SCIM admin check is skipped; DB role lookup still works by email.
            role = await resolve_role(identity, "", _get_host())                                                               
            return identity, "", role
        return "", "", "use"                                                                                                   
    role = await resolve_role(identity, token, _get_host())
    return identity, token, role
                                                                                                                               
gateway_routes._require_role:
                                                                                                                               
async def _require_role(req: Request, min_role: str):     
    identity = req.headers.get("X-Forwarded-Email", "")
    try:
        token = extract_bearer_token(req)                                                                                      
    except HTTPException as e:
        if e.status_code == 401 and identity:                                                                                  
            token = ""  # No passthrough token; SCIM admin check will be skipped
        else:
            raise
    role = await resolve_role(identity, token, _get_host())
    if not role_gte(role, min_role):                                                                                           
        raise HTTPException(status_code=403, detail=f"Role '{min_role}' required. You have '{role}'.")
                                                                                                                               
Bootstrap: first-time admin assignment                    
                                                                                                                               
With this fix, the app deployer (who may not have token passthrough) can bootstrap owner access by:                            
- Calling POST /api/admin/users/{email}/role directly with Authorization: Bearer <personal-access-token> (e.g., via the
built-in API docs at /docs).                                                                                                   
- Or: adding BOOTSTRAP_ADMIN_EMAIL env var supportauto-assign owner on first startup if no users exist in DB.
                                                                                                                               
How to Reproduce                                                                                                               
 
1. Deploy this app to a workspace where user token passthrough is disabled (SettingsAppsUser token passthrough = OFF).   
2. Open the app URL in a browser.                         
3. Observe: UI shows "No gateways yet" with no Create button. DevToolsNetworkGET /api/admin/users/me returns 401.        
                                                          

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions