Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Release History

# Unreleased
- Kernel backend (`use_kernel=True`): `session_configuration` is forwarded to the kernel verbatim (values stringified). Unlike the Thrift backend — which pins `spark.thriftserver.arrowBasedRowSet.timestampAsString` to `false` and rejects any non-false override — the kernel/SEA path neither injects nor validates that flag, since it is a Thrift-only `ExecuteStatement` conf with no meaning on the SEA session path (the kernel handles types natively via Arrow / complex-types).
- Kernel backend (`use_kernel=True`): OAuth **M2M with a JWT private-key client assertion** (RFC 7523) is now supported. Pass `oauth_client_id` + `oauth_jwt_key_file` + `oauth_jwt_kid` (with optional `oauth_jwt_passphrase` for an encrypted PKCS#8 key, `oauth_jwt_algorithm` defaulting to `RS256`, `oauth_scopes`, and `token_url` for the IdP token endpoint) and the connector routes them to the kernel's `auth_type="oauth-m2m-jwt"`, which signs a short-lived assertion with the private key instead of sending a client secret. The kernel owns the token lifecycle. A private-key file is treated as unambiguous JWT M2M intent and is mutually exclusive with `oauth_client_secret` / `credentials_provider` (both raise `NotSupportedError`). Verified end-to-end against an Azure Databricks workspace with the service principal's public certificate registered on its Entra ID app registration. Requires `databricks-sql-kernel >= 0.2.0` with JWT support.
- 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)
- 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)
Expand Down
21 changes: 15 additions & 6 deletions src/databricks/sql/backend/kernel/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,16 +310,25 @@ def open_session(
session_conf: Optional[Dict[str, str]] = None
if session_configuration:
session_conf = {k: str(v) for k, v in session_configuration.items()}
# The kwarg builds run INSIDE the try so the ``finally`` scrub
# below always fires — including when ``kernel_auth_kwargs``
# The kwarg builds — and the session-conf validation — run INSIDE
# the try so the ``finally`` scrub below always fires, including
# when the validation rejects or when ``kernel_auth_kwargs``
# itself raises mid-build (e.g. an OAuth token-exchange failure
# while the M2M secret is in hand). Pre-declared empty so the
# ``finally`` can reference them unconditionally even on an early
# raise. Building here (not in ``__init__``) keeps the bearer
# token's in-process lifetime as short as possible.
# while the M2M secret is in hand). ``self._auth_options`` already
# holds the secret at this point (set in ``__init__``), so an
# early raise before the try would leave it un-scrubbed. Pre-declared
# empty so the ``finally`` can reference them unconditionally even
# on an early raise. Building here (not in ``__init__``) keeps the
# bearer token's in-process lifetime as short as possible.
auth_kwargs: Dict[str, Any] = {}
tls_kwargs: Dict[str, Any] = {}
try:
# ``session_conf`` is forwarded to the kernel verbatim. Unlike the
# Thrift backend (``thrift_backend.py``), the kernel/SEA path does
# NOT inject or validate ``timestampAsString``: the kernel owns type
# handling natively (Arrow / complex-types), and that flag is a
# Thrift-only ``ExecuteStatement`` conf that has no meaning on the
# SEA session path, so there is nothing to pin or guard here.
auth_kwargs = kernel_auth_kwargs(
self._auth_provider,
self._auth_options,
Expand Down
31 changes: 31 additions & 0 deletions tests/unit/test_kernel_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,37 @@ def test_open_session_rejects_double_open(monkeypatch):
c.open_session(session_configuration=None, catalog=None, schema=None)


def test_open_session_forwards_timestamp_as_string_verbatim(monkeypatch):
"""The kernel/SEA path does NOT guard the Thrift-only
``timestampAsString`` conf: it is forwarded to the kernel verbatim
(stringified) rather than rejected, since that flag has no meaning
on the SEA session path and nobody passes it on purpose."""
captured = {}

def fake_session(**kw):
captured.update(kw)
sess = MagicMock()
sess.session_id = "sess-id"
return sess

monkeypatch.setattr(kernel_client._kernel, "Session", fake_session)

c = _make_client()

c.open_session(
session_configuration={
"spark.thriftserver.arrowBasedRowSet.timestampAsString": True
},
catalog=None,
schema=None,
)

assert (
captured["session_conf"]["spark.thriftserver.arrowBasedRowSet.timestampAsString"]
== "True"
)


@pytest.mark.parametrize(
"kwargs, expected_flag",
[
Expand Down
Loading