Skip to content

Commit b9290eb

Browse files
feat(kernel): route azure-oauth (Azure AD U2M) to the kernel OAuth U2M flow
On use_kernel=True, auth_type='azure-oauth' was rejected with a pointer to databricks-oauth. Instead route it to the kernel's oauth-u2m, identically to databricks-oauth: the kernel runs the in-house workspace-federated browser flow, which Azure Databricks workspaces support (the workspace federates the login to Microsoft Entra). It forwards the connector's databricks-sql-python app bundle, NOT the Thrift Azure app (96eecda7 / redirect port 8030): that app is registered for Thrift's direct-Entra flow (login.microsoftonline.com config + {tenant}/user_impersonation scopes), which the kernel does not perform. So on the kernel path azure-oauth and databricks-oauth are the same flow. The U2M-vs-M2M ambiguity guard now also covers azure-oauth, so azure-oauth + oauth_client_secret is rejected as ambiguous rather than silently falling through to the M2M branch. Connector-only; the kernel already exposes oauth-u2m. (PECOBLR-4120.) Co-authored-by: Isaac Signed-off-by: eric-wang-1990 <e.wang@databricks.com>
1 parent 60adcaf commit b9290eb

4 files changed

Lines changed: 100 additions & 63 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# Unreleased
44
- Kernel backend (`use_kernel=True`): OAuth U2M with `auth_type="databricks-oauth"` now forwards the connector's `databricks-sql-python` OAuth-app bundle (`client_id` + `sql offline_access` scopes + redirect port) into the kernel, so a bare U2M connection authenticates as `databricks-sql-python` — parity with the Thrift path — instead of inheriting the kernel's own `databricks-sql-connector` default. A caller-supplied `oauth_client_id` (with its coupled `oauth_redirect_port`) is honored, as is a caller-supplied `oauth_scopes`; absent one, the connector default (`sql offline_access`) is forwarded. Note: the kernel binds a single U2M redirect port, so unlike the Thrift path (which tries the full `8020..8024` range) the kernel path uses only one port and does not fall back to the next port if it is already bound — pass `oauth_redirect_port` (with `oauth_client_id`) to pick a free one on a port collision (PECOBLR-4040)
5-
- Kernel backend (`use_kernel=True`): **Azure Entra (Azure AD) service-principal M2M is now supported.** `auth_type="azure-sp-m2m"` forwards `azure_client_id` / `azure_client_secret`; the kernel is the Azure-aware auth core — it builds the Entra v2.0 token endpoint and the `{app_id}/.default` scope, and **auto-discovers the tenant** from the workspace's `/aad/auth` redirect when `azure_tenant_id` is omitted (matching Thrift). The `Authorization` bearer is the Databricks-audience data token, which alone authenticates a workspace-member SP. Set `azure_workspace_resource_id` and the kernel also sends the Azure SP management token (`X-Databricks-Azure-SP-Management-Token`) + `X-Databricks-Azure-Workspace-Resource-Id` header (matching the JDBC driver), so a service principal with an Azure RBAC role but no workspace membership can authenticate; omit it and no ARM management-scope token is fetched. Azure AD **U2M** (`auth_type="azure-oauth"`) is **not** supported on the kernel path — use `auth_type="databricks-oauth"`, whose in-house OAuth browser flow works against Azure workspaces (the workspace federates login to Entra); `azure-oauth` on `use_kernel=True` is rejected with that pointer (PECOBLR-4141; PECOBLR-4120)
5+
- Kernel backend (`use_kernel=True`): **Azure Entra (Azure AD) service-principal M2M is now supported.** `auth_type="azure-sp-m2m"` forwards `azure_client_id` / `azure_client_secret`; the kernel is the Azure-aware auth core — it builds the Entra v2.0 token endpoint and the `{app_id}/.default` scope, and **auto-discovers the tenant** from the workspace's `/aad/auth` redirect when `azure_tenant_id` is omitted (matching Thrift). The `Authorization` bearer is the Databricks-audience data token, which alone authenticates a workspace-member SP. Set `azure_workspace_resource_id` and the kernel also sends the Azure SP management token (`X-Databricks-Azure-SP-Management-Token`) + `X-Databricks-Azure-Workspace-Resource-Id` header (matching the JDBC driver), so a service principal with an Azure RBAC role but no workspace membership can authenticate; omit it and no ARM management-scope token is fetched. Azure AD **U2M** (`auth_type="azure-oauth"`) now routes to the kernel's OAuth U2M flow, identically to `auth_type="databricks-oauth"`: the kernel runs the in-house workspace-federated browser flow, which Azure workspaces support (the workspace federates login to Entra). It forwards the connector's `databricks-sql-python` OAuth app, not the Thrift Azure app (`96eecda7` / port 8030), which is registered for Thrift's direct-Entra flow the kernel does not perform (PECOBLR-4141; PECOBLR-4120)
66

77
# 4.4.0 (2026-07-22)
88
- Raised the minimum supported Python version to 3.10, dropping the end-of-life 3.8/3.9, to update the lockfile and clear CVE-flagged dependencies in the repo (databricks/databricks-sql-python#798)

CONNECTION_PARAMETERS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ to change without notice.
6969
| Option | Type | Thrift | Kernel | Default Value | Note |
7070
| --------------------------------------------------- | -------------------- | :----: | :----: | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
7171
| `access_token` (PAT) | `str` ||| `None` | Personal Access Token / bearer token. The default auth mode when set; otherwise auth falls back to OAuth. |
72-
| `auth_type` | `str` ||| `None` ⇒ Databricks OAuth | `databricks-oauth` (U2M), `azure-oauth` (Azure AD U2M), or `azure-sp-m2m` (Azure service-principal M2M). On the kernel path: `databricks-oauth` and `azure-sp-m2m` are supported (#919); `azure-oauth` is **not** — use `databricks-oauth` instead (its in-house browser flow works against Azure workspaces), and `azure-oauth` on `use_kernel=True` is rejected with that pointer. All three work on the Thrift path. |
72+
| `auth_type` | `str` ||| `None` ⇒ Databricks OAuth | `databricks-oauth` (U2M), `azure-oauth` (Azure AD U2M), or `azure-sp-m2m` (Azure service-principal M2M). All three are supported on the kernel path (#919): `azure-oauth` routes to the kernel's OAuth U2M flow identically to `databricks-oauth` (the in-house workspace-federated browser flow, which Azure workspaces support), so it uses the `databricks-sql-python` app rather than Thrift's direct-Entra Azure app. All three work on the Thrift path. |
7373
| `oauth_client_id` (U2M) | `str` ||| built-in client id | Custom U2M client id. Forwarded on both; when absent, each path applies its own built-in default. |
7474
| `oauth_redirect_port` (U2M) | `int` ||| `None` | Localhost redirect port for the browser flow. On **both** backends it is only honored when a custom `oauth_client_id` is also supplied — then that single port becomes the redirect URI. With the built-in client id (or when omitted) the connector uses the full registered range 8020–8024 and binds the first free port, so a bare `oauth_redirect_port` has no effect. (Thrift: `auth.py` `oauth_redirect_port_range`; Kernel: same logic, forwarded as `redirect_ports`.) |
7575
| `oauth_client_secret` (OAuth M2M) | `str` ||| `None` | **Kernel-only in practice.** The Thrift auth path never reads `oauth_client_secret`; use `credentials_provider` or an Azure service principal for M2M on Thrift. |

src/databricks/sql/backend/kernel/auth_bridge.py

Lines changed: 45 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
connector's own OAuth provider because the kernel re-mints tokens
1616
itself and the client secret is not recoverable from a built
1717
provider.
18-
- **OAuth U2M** — for ``auth_type`` ``databricks-oauth`` (the browser
19-
authorization-code flow), the connector's ``databricks-sql-python``
18+
- **OAuth U2M** — for ``auth_type`` ``databricks-oauth`` *or* ``azure-oauth``
19+
(the browser authorization-code flow), the connector's ``databricks-sql-python``
2020
app bundle (``client_id`` + ``redirect_ports`` list, with the optional
2121
``oauth_client_id`` / ``oauth_redirect_port`` overriding it) is
2222
forwarded to the kernel's ``auth_type='oauth-u2m'`` and the kernel
@@ -25,10 +25,16 @@
2525
owns the endpoints, scopes, app ids, and tenant discovery); the binding
2626
forwards the selector + credentials and does not construct endpoints:
2727
28-
- ``azure-oauth`` (U2M) → **not supported on the kernel path**; rejected with
29-
a pointer to ``databricks-oauth``, whose in-house U2M browser flow works
30-
against Azure workspaces (the workspace federates login to Entra). A
31-
dedicated Azure U2M flow may return later (PECOBLR-4120).
28+
- ``azure-oauth`` (U2M) → routed to the kernel's ``oauth-u2m`` via the shared
29+
U2M path above, exactly like ``databricks-oauth``. The kernel runs the
30+
in-house workspace-federated browser flow (the workspace federates login to
31+
Entra), which Azure Databricks workspaces support; it forwards the
32+
``databricks-sql-python`` bundle, **not** the Thrift Azure app
33+
(``96eecda7`` / redirect port ``8030``) — that app is registered for
34+
Thrift's *direct-Entra* flow (``login.microsoftonline.com`` config +
35+
``{tenant}/user_impersonation`` scopes), which the kernel does not perform.
36+
So on the kernel path ``azure-oauth`` and ``databricks-oauth`` are the same
37+
flow (PECOBLR-4120).
3238
- ``azure-sp-m2m`` (M2M) → forward ``auth_type='azure-sp-m2m'`` with the
3339
Azure service-principal ``azure_client_id`` / ``azure_client_secret`` (plus
3440
optional ``azure_tenant_id`` / ``azure_workspace_resource_id``). The kernel
@@ -167,25 +173,27 @@ def kernel_auth_kwargs(
167173
rather than silently picking one flow (and failing later as a
168174
confusing 401 against the wrong principal):
169175
- a custom ``credentials_provider`` *and* M2M kwargs together;
170-
- a U2M ``auth_type`` (``databricks-oauth``) *and*
176+
- a U2M ``auth_type`` (``databricks-oauth`` / ``azure-oauth``) *and*
171177
``oauth_client_secret`` together.
172178
173-
(The Azure Entra auth types are handled up front, before these guards:
174-
``azure-sp-m2m`` forwards to the kernel's Azure SP flow; ``azure-oauth``
175-
is rejected with a pointer to ``databricks-oauth``. See the module
176-
docstring.)
179+
(The ``azure-sp-m2m`` Azure Entra auth type is handled up front, before
180+
these guards, forwarding to the kernel's Azure SP flow. ``azure-oauth`` is
181+
a U2M type and is handled by step 3 alongside ``databricks-oauth``. See the
182+
module docstring.)
177183
1. **OAuth M2M** — ``oauth_client_id`` + ``oauth_client_secret``
178184
both present → forward raw creds to the kernel's ``oauth-m2m``.
179185
2. **PAT** — the built provider is (or wraps) an
180186
``AccessTokenAuthProvider`` → extract the bearer token.
181-
3. **OAuth U2M** — ``auth_type`` is ``databricks-oauth`` → forward the
182-
connector's coupled ``databricks-sql-python`` bundle (``client_id``
183-
+ ``redirect_ports`` list, defaulting scopes to ``PYSQL_OAUTH_SCOPES``
184-
when the caller supplies none) to the kernel's ``oauth-u2m``, so a
185-
bare U2M connection authenticates as ``databricks-sql-python`` —
186-
forwarding the connector's own OAuth app rather than the kernel's
187-
``databricks-sql-connector`` default (PECOBLR-4039/4040). Unlike the
188-
Thrift path, a caller-supplied ``oauth_scopes`` is honored here.
187+
3. **OAuth U2M** — ``auth_type`` is ``databricks-oauth`` or ``azure-oauth``
188+
→ forward the connector's coupled ``databricks-sql-python`` bundle
189+
(``client_id`` + ``redirect_ports`` list, defaulting scopes to
190+
``PYSQL_OAUTH_SCOPES`` when the caller supplies none) to the kernel's
191+
``oauth-u2m``, so a bare U2M connection authenticates as
192+
``databricks-sql-python`` — forwarding the connector's own OAuth app
193+
rather than the kernel's ``databricks-sql-connector`` default
194+
(PECOBLR-4039/4040). ``azure-oauth`` resolves identically: the kernel's
195+
workspace-federated flow serves Azure workspaces too (PECOBLR-4120).
196+
Unlike the Thrift path, a caller-supplied ``oauth_scopes`` is honored here.
189197
4. **Custom credentials_provider** → ``NotSupportedError`` (opaque
190198
token source; no raw creds for the kernel to own).
191199
5. Anything else → ``NotSupportedError``.
@@ -212,20 +220,15 @@ def kernel_auth_kwargs(
212220
# creds in azure_* kwargs, not oauth_client_id/secret, so it would
213221
# otherwise fall through to the final "unsupported" error).
214222

215-
# azure-oauth (Azure AD U2M) is NOT supported on the kernel path. The
216-
# in-house `databricks-oauth` browser flow works against Azure workspaces
217-
# (the workspace federates the login to Entra), so it is the U2M path on the
218-
# kernel — reject `azure-oauth` with a clear pointer rather than silently
219-
# changing the user's selected flow. (A dedicated Azure U2M flow may return
220-
# later; for now Azure U2M = `databricks-oauth`.)
221-
if auth_type == "azure-oauth":
222-
raise NotSupportedError(
223-
"auth_type='azure-oauth' is not supported on use_kernel=True. Use "
224-
"auth_type='databricks-oauth' instead — the in-house OAuth U2M "
225-
"browser flow works against Azure Databricks workspaces (the "
226-
"workspace federates the login to Microsoft Entra). Or use the "
227-
"Thrift backend (default) for the dedicated Azure AD U2M flow."
228-
)
223+
# azure-oauth (Azure AD U2M) routes to the kernel's oauth-u2m, handled by the
224+
# shared U2M branch below (step 3) alongside databricks-oauth. The kernel runs
225+
# the in-house workspace-federated browser flow — the workspace federates the
226+
# login to Microsoft Entra — which Azure Databricks workspaces support. It uses
227+
# the connector's databricks-sql-python app bundle, NOT the Thrift Azure app
228+
# (96eecda7 / redirect port 8030): that app is registered for Thrift's
229+
# direct-Entra flow (login.microsoftonline.com config + {tenant}/user_impersonation
230+
# scopes), which the kernel does not perform. So on the kernel path azure-oauth
231+
# and databricks-oauth are the same flow. PECOBLR-4120.
229232

230233
# azure-sp-m2m (Azure service principal, client-credentials): forward the
231234
# selector + Azure SP credentials; the KERNEL owns Azure resolution (it is
@@ -278,7 +281,7 @@ def kernel_auth_kwargs(
278281
"kernel-managed M2M, or use the Thrift backend (default) for "
279282
"credentials_provider."
280283
)
281-
if client_secret and auth_type == "databricks-oauth":
284+
if client_secret and auth_type in ("databricks-oauth", "azure-oauth"):
282285
raise NotSupportedError(
283286
f"Ambiguous auth on use_kernel=True: auth_type={auth_type!r} selects "
284287
"the U2M browser flow, but oauth_client_secret was also provided "
@@ -314,7 +317,10 @@ def kernel_auth_kwargs(
314317
return kwargs
315318

316319
# 3. OAuth U2M — browser authorization-code flow; the kernel runs it.
317-
# Only databricks-oauth reaches here (azure-oauth rejected up front).
320+
# Both databricks-oauth and azure-oauth reach here: on the kernel path
321+
# they are the same in-house workspace-federated flow (Azure workspaces
322+
# federate the browser login to Entra), so azure-oauth is an alias for
323+
# databricks-oauth here — see the note near the top of this function.
318324
# Forward the connector's own databricks-sql-python bundle instead of
319325
# the kernel's databricks-sql-connector default, for parity with the
320326
# Thrift path. client_id + redirect ports are coupled per app (each
@@ -326,7 +332,7 @@ def kernel_auth_kwargs(
326332
# fallback), mirroring the Thrift DatabricksOAuthProvider which retries
327333
# the next port when one is bound. A caller overriding client_id
328334
# supplies its own single registered port.
329-
if auth_type == "databricks-oauth":
335+
if auth_type in ("databricks-oauth", "azure-oauth"):
330336
redirect_port = opts.get("oauth_redirect_port")
331337
# Honor a caller-supplied oauth_scopes (normalized to a list of
332338
# strings); fall back to the connector default when none is given.
@@ -367,8 +373,9 @@ def kernel_auth_kwargs(
367373
)
368374
raise NotSupportedError(
369375
f"use_kernel=True requires PAT (access_token), OAuth M2M "
370-
f"(oauth_client_id + oauth_client_secret), or OAuth U2M "
371-
f"(auth_type='databricks-oauth'), but got "
376+
f"(oauth_client_id + oauth_client_secret), OAuth U2M "
377+
f"(auth_type='databricks-oauth' or 'azure-oauth'), or Azure SP M2M "
378+
f"(auth_type='azure-sp-m2m'), but got "
372379
f"{provider_desc} with auth_type={auth_type!r}. Use the Thrift "
373380
"backend (default) for other auth flows."
374381
)

0 commit comments

Comments
 (0)