You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Merge origin/main into eric-wang/kernel-azure-oauth
Resolve conflicts from #921 (JWT private-key M2M on use_kernel=True):
- CHANGELOG: keep main's JWT M2M entry + the U2M/Azure entries; the Azure
entry supersedes main's stale 'azure-oauth not supported' note.
- auth_bridge.py docstring/comments: adopt main's resolution-order numbering
(JWT M2M = step 1) while keeping azure-oauth as a U2M type in step 4.
- Extend the JWT-vs-U2M ambiguity guard to cover azure-oauth (main only
guarded databricks-oauth); azure-oauth is now a U2M type, so
oauth_jwt_key_file + azure-oauth must be rejected as ambiguous rather than
falling through to JWT M2M. Parametrized the guard test over both U2M types.
- Final catch-all error message: list JWT M2M alongside the Azure flows.
Copy file name to clipboardExpand all lines: CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,7 @@
1
1
# Release History
2
2
3
3
# Unreleased
4
+
- 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.
4
5
- 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
6
- 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)
Copy file name to clipboardExpand all lines: CONNECTION_PARAMETERS.md
+5Lines changed: 5 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -73,6 +73,11 @@ to change without notice.
73
73
|`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. |
74
74
|`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`.) |
75
75
|`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. |
76
+
|`oauth_jwt_key_file` (OAuth M2M, JWT private key) |`str`| ❌ | ✅ |`None`|**Kernel-only.** Path to the PEM private key for JWT private-key M2M (RFC 7523 client assertion). Supplying it selects the JWT flow: the kernel signs a short-lived assertion with the key instead of sending a client secret. Requires `oauth_client_id` + `oauth_jwt_kid`; mutually exclusive with `oauth_client_secret` / `credentials_provider`. |
77
+
|`oauth_jwt_kid` (OAuth M2M, JWT private key) |`str`| ❌ | ✅ |`None`|**Kernel-only.** Key id written into the JWT header so the IdP can select the registered public key. Required with `oauth_jwt_key_file`. (For Entra ID this is the certificate's `x5t` thumbprint.) |
78
+
|`oauth_jwt_passphrase` (OAuth M2M, JWT private key) |`str`| ❌ | ✅ |`None`|**Kernel-only.** Passphrase for an encrypted PKCS#8 private key; omit for an unencrypted key. |
|`token_url` (OAuth M2M) |`str`| ❌ | ✅ |`None` ⇒ OIDC discovery |**Kernel-only.** OAuth IdP token endpoint override. Required for JWT M2M against an external-IdP-backed workspace (e.g. Entra ID for Azure Databricks), since Databricks-native OIDC does not advertise the `private_key_jwt` method. Applies to shared-secret M2M too. |
76
81
|`oauth_scopes`|`List[str]`| ❌ | ✅ |`["sql","offline_access"]`|**Thrift ignores custom scopes** — it always uses the built-in scope set. Only the kernel honors a custom `oauth_scopes`. |
77
82
|`credentials_provider`|`CredentialsProvider`| ✅ | ❌ |`None`| Custom external credentials provider. **Rejected on the kernel path** (`NotSupportedError`) — it is an opaque token source, so the kernel cannot own the token lifecycle; use `oauth_client_id` + `oauth_client_secret` for M2M, or the Thrift backend. |
78
83
|`identity_federation_client_id`|`str`| ✅ | ✅ |`None`| Workload identity / token-federation client id (kernel support added in #910). |
Copy file name to clipboardExpand all lines: CONTRIBUTING.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -153,7 +153,7 @@ reproduce on a SEA or kernel connection, and vice versa:
153
153
| Backend | Select via (connect kwarg / `extra_params`) | Where its tests live |
154
154
| --- | --- | --- |
155
155
|**Thrift** (default) |*(nothing — the default path)*| the general `tests/e2e` suite (the `{}` parametrize case) and mocked `tests/unit`|
156
-
|**SEA** (Statement Execution API) |`use_sea=True`| the general `tests/e2e` suite (the `{"use_sea": True}` parametrize case, e.g. `tests/e2e/test_driver.py`) and mocked `tests/unit`|
156
+
|**SEA** (Statement Execution API) *(deprecated — use Kernel for SEA-native connections)*|`use_sea=True`| the general `tests/e2e` suite (the `{"use_sea": True}` parametrize case, e.g. `tests/e2e/test_driver.py`) and mocked `tests/unit`|
157
157
|**Kernel** (Rust, optional) |`use_kernel=True`| the dedicated `tests/e2e/test_kernel_backend.py` / `test_kernel_tls.py`, plus the offline routing test `tests/unit/test_session.py -m realkernel`|
0 commit comments