Skip to content

Commit dbf2b5a

Browse files
refactor(kernel): drop azure-sp-m2m management token on the kernel path
Mirror the kernel-side simplification: the kernel's `azure-sp-m2m` no longer supports the Azure management-token flow (RBAC-only SPs), so stop forwarding `azure_workspace_resource_id` to the kernel. The SP must be a workspace member on `use_kernel=True`. `azure_workspace_resource_id` is still a valid connection parameter for the Thrift path (unchanged). On the kernel path, setting it now logs a warning (rather than silently dropping a security-relevant auth parameter and failing later with an opaque 403) and the SP authenticates with the Databricks-audience data token alone. Drops the threading through `kernel_auth_options` in session.py. RBAC-only-SP support is a documented follow-up. Co-authored-by: Isaac Signed-off-by: eric-wang-1990 <e.wang@databricks.com>
1 parent 311e3f0 commit dbf2b5a

6 files changed

Lines changed: 38 additions & 35 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) OAuth is now supported.** The kernel is the Azure-aware auth core (it owns the endpoints, scopes, app ids, and tenant discovery); the connector forwards the selector and Azure credentials unchanged, so `connect()` is byte-identical between the Thrift and `use_kernel=True` paths. `auth_type="azure-oauth"` (Azure AD U2M) runs the kernel's browser flow against the workspace v2.0 authorize/token endpoints with the Azure app client id (`96eecda7-…`), redirect port `8030`, and the `{app_id}/user_impersonation offline_access` scope. `auth_type="azure-sp-m2m"` (Azure service principal) forwards `azure_client_id` / `azure_client_secret`; the kernel 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). `azure_workspace_resource_id` is an optional add-on: set it and the kernel additionally sends the Azure management-token header pair (`X-Databricks-Azure-SP-Management-Token` + `X-Databricks-Azure-Workspace-Resource-Id`) so an SP that holds only an Azure RBAC role (not a workspace member) can authenticate; omit it and the SP authenticates with the data token alone (PECOBLR-4141; PECOBLR-4120)
5+
- Kernel backend (`use_kernel=True`): **Azure Entra (Azure AD) OAuth is now supported.** The kernel is the Azure-aware auth core (it owns the endpoints, scopes, app ids, and tenant discovery); the connector forwards the selector and Azure credentials unchanged, so `connect()` is byte-identical between the Thrift and `use_kernel=True` paths. `auth_type="azure-oauth"` (Azure AD U2M) runs the kernel's browser flow against the workspace v2.0 authorize/token endpoints with the Azure app client id (`96eecda7-…`), redirect port `8030`, and the `{app_id}/user_impersonation offline_access` scope. `auth_type="azure-sp-m2m"` (Azure service principal) forwards `azure_client_id` / `azure_client_secret`; the kernel 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 service principal authenticates with the Databricks-audience data token, so it must be a workspace member; the kernel path does not yet support the Azure management-token flow (for an RBAC-only SP), so `azure_workspace_resource_id` is ignored with a warning on `use_kernel=True` (it still applies on the Thrift path) (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
@@ -78,7 +78,7 @@ to change without notice.
7878
| `identity_federation_client_id` | `str` ||| `None` | Workload identity / token-federation client id (kernel support added in #910). |
7979
| `experimental_oauth_persistence` | `OAuthPersistence` ||| `None` | **Thrift-only.** The kernel owns its own token lifecycle and does not accept a persistence store. |
8080
| `azure_client_id` / `azure_client_secret` / `azure_tenant_id` | `str` ||| `None` | Azure service-principal (Entra ID M2M), selected by `auth_type="azure-sp-m2m"`. On the kernel path the connector forwards these to the kernel, which owns Azure resolution (Entra v2.0 token endpoint + the Databricks-resource `.default` scope) (#919). **`azure_tenant_id` is optional on the kernel path too** — like Thrift, the kernel auto-discovers it from the workspace's `/aad/auth` redirect when omitted. |
81-
| `azure_workspace_resource_id` | `str` || | `None` | Optional add-on for `azure-sp-m2m`. When set, the SP **management token** (`X-Databricks-Azure-SP-Management-Token` + `X-Databricks-Azure-Workspace-Resource-Id`) is sent to authorize an SP that has an Azure RBAC role but is not a workspace member. On the kernel path the connector forwards it and the kernel fetches the management token and emits the header pair; omit it and the SP authenticates with the Databricks-audience data token alone. |
81+
| `azure_workspace_resource_id` | `str` || ⚠️ | `None` | For `azure-sp-m2m`. When set, the SP **management token** (`X-Databricks-Azure-SP-Management-Token` + `X-Databricks-Azure-Workspace-Resource-Id`) is sent to authorize an SP that has an Azure RBAC role but is not a workspace member. **Not yet supported on the kernel path**the SP must be a workspace member there; setting it on `use_kernel=True` logs a warning and is otherwise ignored (RBAC-only-SP support is a planned follow-up). Applies normally on the Thrift path. |
8282
| `_use_cert_as_auth` (+ `_tls_client_cert_file`) | `bool` ||| `False` | Authenticate with a TLS client certificate instead of a token. Thrift-only. |
8383
| `username` / `password` | `str` ||| `None` | **Removed.** Basic auth is no longer supported; passing either raises `ValueError`. |
8484

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

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@
3636
The kernel builds the Entra v2.0 token endpoint and the
3737
``{effective_app_id}/.default`` scope, and auto-discovers the tenant from
3838
the workspace's ``/aad/auth`` redirect when ``azure_tenant_id`` is omitted
39-
(Thrift parity). ``azure_workspace_resource_id`` is an optional add-on:
40-
forward it and the kernel additionally sends the Azure management-token
41-
header pair, so an RBAC-only SP (not a workspace member) can authenticate
39+
(Thrift parity). The SP must be a workspace member (the kernel path does not
40+
yet support the Azure management-token flow, so ``azure_workspace_resource_id``
41+
is ignored with a warning; RBAC-only SPs are a planned follow-up)
4242
(PECOBLR-4141).
4343
4444
``identity_federation_client_id`` is forwarded with whichever auth shape
@@ -239,12 +239,10 @@ def kernel_auth_kwargs(
239239
# (so connect() is byte-identical between Thrift and use_kernel=True).
240240
# PECOBLR-4141.
241241
#
242-
# azure_workspace_resource_id is an optional add-on: forward it and the
243-
# kernel additionally fetches an Azure-management token and sends the
244-
# X-Databricks-Azure-SP-Management-Token + X-Databricks-Azure-Workspace-
245-
# Resource-Id pair, so an SP that holds only an Azure RBAC role (not a
246-
# workspace member) can authenticate. Omit it (the common case) and the SP
247-
# authenticates with the Databricks-audience data token alone.
242+
# The SP authenticates with the Databricks-audience data token, so it must be
243+
# a workspace member. The kernel path does not yet support the Azure
244+
# management-token flow (for an RBAC-only SP), so azure_workspace_resource_id
245+
# is ignored with a warning here — see below.
248246
if auth_type == "azure-sp-m2m":
249247
azure_client_id = opts.get("azure_client_id")
250248
azure_client_secret = opts.get("azure_client_secret")
@@ -258,14 +256,22 @@ def kernel_auth_kwargs(
258256
"azure_client_id": azure_client_id,
259257
"azure_client_secret": azure_client_secret,
260258
}
261-
# Optional passthroughs: the kernel auto-discovers the tenant when
262-
# absent, and sends the data token alone when no resource id is set.
259+
# Optional passthrough: the kernel auto-discovers the tenant when absent.
263260
azure_tenant_id = opts.get("azure_tenant_id")
264261
if azure_tenant_id:
265262
kwargs["azure_tenant_id"] = azure_tenant_id
266-
azure_workspace_resource_id = opts.get("azure_workspace_resource_id")
267-
if azure_workspace_resource_id:
268-
kwargs["azure_workspace_resource_id"] = azure_workspace_resource_id
263+
# azure_workspace_resource_id drives the Azure management-token flow on
264+
# the Thrift path (for an SP with only an Azure RBAC role, not a
265+
# workspace member). The kernel path does NOT support it yet — the SP
266+
# must be a workspace member — so warn rather than silently dropping a
267+
# security-relevant auth parameter and failing later with an opaque 403.
268+
if opts.get("azure_workspace_resource_id"):
269+
logger.warning(
270+
"azure_workspace_resource_id is ignored on use_kernel=True: the "
271+
"Azure SP management-token flow is not yet supported on the "
272+
"kernel path. The service principal must be a workspace member "
273+
"(the Databricks-audience token alone authenticates it)."
274+
)
269275
if federation_client_id:
270276
kwargs["identity_federation_client_id"] = federation_client_id
271277
return kwargs

src/databricks/sql/session.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -181,15 +181,13 @@ def _create_backend(
181181
# kernel owns Azure resolution (endpoint/scope/tenant discovery),
182182
# so these raw kwargs are the only source; without threading them
183183
# the bridge would fail with "requires azure_client_id". The
184-
# tenant and workspace-resource-id are optional (kernel
185-
# auto-discovers the tenant; the resource id gates the optional
186-
# management token). Kernel-only; Thrift / SEA are unaffected.
184+
# tenant is optional (the kernel auto-discovers it). Kernel-only;
185+
# Thrift / SEA are unaffected. (azure_workspace_resource_id is not
186+
# forwarded — the kernel path does not yet support the Azure
187+
# management-token flow; the bridge warns if it is set.)
187188
"azure_client_id": kwargs.get("azure_client_id"),
188189
"azure_client_secret": kwargs.get("azure_client_secret"),
189190
"azure_tenant_id": kwargs.get("azure_tenant_id"),
190-
"azure_workspace_resource_id": kwargs.get(
191-
"azure_workspace_resource_id"
192-
),
193191
}
194192
# Forward the connector's retry-tuning kwargs so the kernel's
195193
# own retry policy honours them (the kernel owns the retry

tests/unit/test_kernel_auth_bridge.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -537,21 +537,22 @@ def test_azure_sp_m2m_tenant_optional_kernel_autodiscovers(self):
537537
}
538538
assert "azure_tenant_id" not in kwargs
539539

540-
def test_azure_sp_m2m_forwards_workspace_resource_id(self):
541-
# An optional add-on: when set, the kernel fetches an Azure-management
542-
# token and emits the X-Databricks-Azure-* header pair (for an SP with
543-
# only an Azure RBAC role, not a workspace member). The binding forwards
544-
# it rather than dropping it.
540+
def test_azure_sp_m2m_ignores_workspace_resource_id_with_warning(self, caplog):
541+
# The kernel path does not yet support the Azure management-token flow
542+
# (for an RBAC-only SP), so azure_workspace_resource_id is NOT forwarded
543+
# — but we warn rather than silently dropping a security-relevant param.
545544
opts = dict(
546545
self._CREDS,
547546
azure_workspace_resource_id="/subscriptions/s/resourceGroups/rg/workspace/w",
548547
)
549-
kwargs = kernel_auth_kwargs(
550-
_FakeOAuthProvider(), opts, hostname="adb-1.azuredatabricks.net"
551-
)
552-
assert (
553-
kwargs["azure_workspace_resource_id"]
554-
== "/subscriptions/s/resourceGroups/rg/workspace/w"
548+
with caplog.at_level("WARNING"):
549+
kwargs = kernel_auth_kwargs(
550+
_FakeOAuthProvider(), opts, hostname="adb-1.azuredatabricks.net"
551+
)
552+
assert "azure_workspace_resource_id" not in kwargs
553+
assert any(
554+
"azure_workspace_resource_id is ignored" in r.message
555+
for r in caplog.records
555556
)
556557

557558
def test_azure_sp_m2m_omits_workspace_resource_id_when_absent(self):

tests/unit/test_session.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,6 @@ def test_azure_sp_m2m_kwargs_threaded_into_kernel_auth_options(self):
512512
azure_client_id="azure-sp",
513513
azure_client_secret="azure-secret",
514514
azure_tenant_id="tenant-123",
515-
azure_workspace_resource_id="/subscriptions/s/rg/w",
516515
enable_telemetry=False,
517516
)
518517
try:
@@ -522,7 +521,6 @@ def test_azure_sp_m2m_kwargs_threaded_into_kernel_auth_options(self):
522521
assert opts["azure_client_id"] == "azure-sp"
523522
assert opts["azure_client_secret"] == "azure-secret"
524523
assert opts["azure_tenant_id"] == "tenant-123"
525-
assert opts["azure_workspace_resource_id"] == "/subscriptions/s/rg/w"
526524
finally:
527525
conn.close()
528526

0 commit comments

Comments
 (0)