Skip to content

Commit a748e80

Browse files
fix(kernel): route Azure U2M to the in-house flow instead of rejecting it
The prior routing rejected Azure U2M when useDatabricksOAuthInAzure was absent/false (the 'Entra-direct' default), on the assumption the kernel needed an Entra-direct browser U2M flow it lacks. That was wrong: the kernel runs a single, cloud-blind in-house U2M flow — it uses the workspace's OIDC-discovered authorize endpoint ({host}/oidc/v1/authorize) verbatim — and that workspace-federated flow works against Azure workspaces (the workspace federates the browser login to Entra server-side; verified E2E). So all U2M now falls through to OAuthU2m with the in-house app (databricks-sql-connector) + sql/offline_access, regardless of cloud or useDatabricksOAuthInAzure — consistent with the Go connector's uniform U2M mapping. Only Entra-direct M2M (Azure host + secret + not useDatabricksOAuthInAzure) still maps to the kernel's azure-sp-m2m. Updated the test that asserted the rejection to assert OAuthU2m, and the docstring/CHANGELOG. Co-authored-by: Isaac Signed-off-by: eric-wang-1990 <e.wang@databricks.com>
1 parent acbccbf commit a748e80

3 files changed

Lines changed: 60 additions & 50 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

5-
- Kernel backend (`useKernel: true`): **Azure Entra (Azure AD) auth is now threaded through the kernel path.** On `authType: 'databricks-oauth'`, `useDatabricksOAuthInAzure` selects the flavour on an Azure host (mirroring the Thrift `OAuthManager.getManager`): `true` → the in-house workspace-federated flow, which the kernel runs natively (browser U2M → `OAuthU2m`, client-credentials M2M → `OAuthM2m`, both via workspace-OIDC discovery, which works against Azure workspaces); absent/`false` on an Azure host → the Entra-direct flow — with a secret it maps to the kernel's Azure service-principal M2M (`AzureSpM2m`, the Entra SP creds ride `oauthClientId`/`oauthClientSecret`, `azureTenantId` optional and auto-discovered when omitted), and without a secret (Entra-direct browser U2M, which the kernel does not implement) it is rejected with a pointer to `useDatabricksOAuthInAzure: true` or the Thrift backend. On a non-Azure host these flags are inert. Requires a `databricks-sql-kernel` native module that exposes the Azure SP surface ([databricks-sql-kernel#280](https://github.com/databricks/databricks-sql-kernel/pull/280)). (PECOBLR-4141 / PECOBLR-4120)
5+
- Kernel backend (`useKernel: true`): **Azure Entra (Azure AD) auth is now threaded through the kernel path.** On `authType: 'databricks-oauth'`: **U2M** (no secret) always routes to `OAuthU2m` — the kernel runs one cloud-blind in-house workspace-federated browser flow (it uses the workspace's OIDC-discovered authorize endpoint verbatim), which works against Azure workspaces, so Azure U2M forwards the in-house app (`databricks-sql-connector`) + `sql offline_access` scopes exactly like AWS/GCP, regardless of `useDatabricksOAuthInAzure` (verified E2E against a live Azure workspace). **M2M** (secret): `useDatabricksOAuthInAzure: true` (or non-Azure) → `OAuthM2m` (workspace-OIDC client-credentials); an Azure host with `useDatabricksOAuthInAzure` absent/`false` → the Entra-direct Azure service-principal M2M (`AzureSpM2m`, the Entra SP creds ride `oauthClientId`/`oauthClientSecret`, `azureTenantId` optional and auto-discovered when omitted). On a non-Azure host `useDatabricksOAuthInAzure` is inert. The `AzureSpM2m` path requires a `databricks-sql-kernel` native module that exposes the Azure SP surface ([databricks-sql-kernel#280](https://github.com/databricks/databricks-sql-kernel/pull/280)); U2M works on any kernel build. (PECOBLR-4141 / PECOBLR-4120)
66

77
## 2.0.0
88

lib/kernel/KernelAuth.ts

Lines changed: 43 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -511,22 +511,23 @@ export function buildKernelHttpOptions(options: ConnectionOptions): KernelHttpOp
511511
* binding makes them, happen below the TypeScript layer and are not
512512
* observable from this repo.
513513
*
514-
* Azure (Entra) on the OAuth path — mirrors Thrift `OAuthManager.getManager`,
515-
* with `useDatabricksOAuthInAzure` selecting the flavour on an Azure host:
516-
* - `useDatabricksOAuthInAzure: true` → **in-house** (workspace-federated).
517-
* The kernel runs it natively via workspace-OIDC discovery — U2M browser
518-
* flow (`OAuthU2m`) and M2M client-credentials (`OAuthM2m`) — so it is NOT
519-
* rejected. `azureTenantId` is ignored here (the in-house flow does not use
520-
* it), matching Thrift.
521-
* - absent/`false` on an Azure host → **Entra-direct**:
522-
* - with a secret → Azure service-principal M2M (`AzureSpM2m`); the Entra
523-
* SP creds ride `oauthClientId`/`oauthClientSecret`, `azureTenantId`
524-
* optional (kernel auto-discovers).
525-
* - without a secret → Entra-direct browser U2M, which the kernel does
526-
* not implement → **rejected** with a pointer to
527-
* `useDatabricksOAuthInAzure: true` or the Thrift backend.
528-
* - On a non-Azure host these flags are inert (the in-house flow is the only
529-
* one), matching Thrift.
514+
* Azure (Entra) on the OAuth path. The kernel runs a single, cloud-blind
515+
* in-house U2M flow and workspace-OIDC M2M; only Entra-direct **M2M** gets a
516+
* dedicated kernel mode:
517+
* - **U2M (no secret), any cloud, any `useDatabricksOAuthInAzure`** →
518+
* `OAuthU2m`. The kernel uses the workspace's OIDC-discovered authorize
519+
* endpoint (`{host}/oidc/v1/authorize`) verbatim; that in-house
520+
* workspace-federated flow works against Azure workspaces too (they federate
521+
* the browser login to Entra server-side — verified E2E). So Azure U2M is
522+
* NOT special-cased and NOT rejected — it forwards the in-house app
523+
* (`databricks-sql-connector`) + `sql offline_access`, exactly like AWS/GCP.
524+
* - **M2M (secret) with `useDatabricksOAuthInAzure: true`** (or non-Azure) →
525+
* `OAuthM2m` (workspace-OIDC client-credentials).
526+
* - **M2M (secret) on an Azure host with `useDatabricksOAuthInAzure` absent/
527+
* `false`** (Entra-direct) → Azure service-principal M2M (`AzureSpM2m`); the
528+
* Entra SP creds ride `oauthClientId`/`oauthClientSecret`, `azureTenantId`
529+
* optional (kernel auto-discovers).
530+
* - On a non-Azure host `useDatabricksOAuthInAzure` is inert.
530531
*
531532
* Out of scope on the OAuth paths (rejected with a clear error):
532533
* - `persistence` on M2M → M2M tokens are not cached (re-issuing is
@@ -710,31 +711,32 @@ export function buildKernelConnectionOptions(options: ConnectionOptions): Kernel
710711
);
711712
}
712713

713-
// Azure routing. `useDatabricksOAuthInAzure` selects the in-house
714-
// (workspace-federated) flow vs the Entra-direct flow, mirroring the Thrift
715-
// driver's `OAuthManager.getManager`: on an Azure host, `true` → in-house,
716-
// absent/false → Entra-direct. The kernel runs the in-house flow natively
717-
// via workspace-OIDC discovery (U2M browser flow AND M2M client-credentials
718-
// — Azure workspaces serve `/oidc/.well-known/...`), but has NO Entra-direct
719-
// browser U2M; Entra-direct SP M2M maps to the kernel's dedicated
720-
// azure-sp-m2m. On a non-Azure host these flags are inert (the in-house flow
721-
// is the only one), matching Thrift.
722-
const entraDirect = isAzureHost(options.host) && oauth.useDatabricksOAuthInAzure !== true;
723-
if (entraDirect) {
724-
if (oauth.oauthClientSecret === undefined) {
725-
// Entra-direct browser U2M — the kernel has no direct-Entra U2M flow.
726-
throw new HiveDriverError(
727-
'kernel backend: Azure AD (Entra-direct) OAuth U2M is not supported. Set ' +
728-
'`useDatabricksOAuthInAzure: true` to use the in-house workspace-federated browser ' +
729-
'flow (which the kernel runs against Azure Databricks workspaces), or use the Thrift ' +
730-
'backend (default) for the Entra-direct flow.',
731-
);
732-
}
733-
// Entra-direct service-principal M2M → the kernel's azure-sp-m2m. The Entra
734-
// SP credentials ride the generic `oauthClientId` / `oauthClientSecret`
735-
// (Thrift convention); forward them as `azureClientId` / `azureClientSecret`.
736-
// `azureTenantId` is optional — the kernel auto-discovers it from the
737-
// workspace `/aad/auth` redirect when omitted.
714+
// Azure Entra-direct **M2M** → the kernel's dedicated azure-sp-m2m. Mirroring
715+
// the Thrift driver's `OAuthManager.getManager`, an Azure host with
716+
// `useDatabricksOAuthInAzure` NOT set to true (the Entra-direct default) plus a
717+
// secret is an Entra service-principal client-credentials flow: the Entra SP
718+
// credentials ride the generic `oauthClientId` / `oauthClientSecret` (Thrift
719+
// convention); forward them as `azureClientId` / `azureClientSecret`.
720+
// `azureTenantId` is optional — the kernel auto-discovers it from the workspace
721+
// `/aad/auth` redirect when omitted.
722+
//
723+
// Azure **U2M** is deliberately NOT special-cased and NOT rejected. The kernel
724+
// runs a single, cloud-blind in-house U2M flow: it uses the workspace's
725+
// OIDC-discovered authorize endpoint (`{host}/oidc/v1/authorize`) verbatim, and
726+
// that in-house workspace-federated flow works against Azure workspaces (the
727+
// workspace federates the browser login to Entra server-side; verified E2E). So
728+
// ALL U2M — including Azure, with or without `useDatabricksOAuthInAzure` — falls
729+
// through to the standard `OAuthU2m` path below, which forwards the in-house app
730+
// (`databricks-sql-connector`) + `sql offline_access` scopes, exactly like
731+
// AWS/GCP. Handing the kernel the Thrift Azure Entra-direct app / scope instead
732+
// would derail its in-house flow to a broken AAD authorize URL.
733+
// The `oauthClientSecret !== undefined` check is inline (not extracted to a
734+
// const) so TypeScript narrows it to `string` for the AzureSpM2m literal below.
735+
if (
736+
isAzureHost(options.host) &&
737+
oauth.useDatabricksOAuthInAzure !== true &&
738+
oauth.oauthClientSecret !== undefined
739+
) {
738740
const azureClientId = oauth.oauthClientId;
739741
if (azureClientId === undefined) {
740742
throw new HiveDriverError(

tests/unit/kernel/auth-u2m.test.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -100,21 +100,29 @@ describe('KernelAuth + KernelBackend — OAuth U2M auth flow', () => {
100100
expect(native.httpPath).to.equal('/sql/1.0/warehouses/abc');
101101
});
102102

103-
it('rejects Entra-direct U2M (Azure host, no secret, no useDatabricksOAuthInAzure)', () => {
104-
// On an Azure host the default is the Entra-direct flow; the kernel has no
105-
// direct-Entra browser U2M, so this is rejected with a pointer to the
106-
// in-house flag (or Thrift). azureTenantId does not change that.
103+
it('routes Azure U2M (no secret, no useDatabricksOAuthInAzure) to in-house OAuthU2m', () => {
104+
// Azure U2M is NOT rejected and NOT special-cased: the kernel runs a single
105+
// cloud-blind in-house workspace-federated U2M flow (it uses the workspace's
106+
// OIDC-discovered authorize endpoint verbatim), which works against Azure
107+
// workspaces. So it routes to OAuthU2m with the in-house app +
108+
// sql/offline_access regardless of useDatabricksOAuthInAzure. azureTenantId
109+
// is inert on the kernel U2M path.
107110
const opts: ConnectionOptions = {
108111
host: 'adb-12345.0.azuredatabricks.net',
109112
path: '/sql/1.0/warehouses/abc',
110113
authType: 'databricks-oauth',
111114
azureTenantId: 'tenant-uuid',
112115
};
113116

114-
expect(() => buildKernelConnectionOptions(opts)).to.throw(
115-
HiveDriverError,
116-
/Entra-direct\) OAuth U2M is not supported/,
117-
);
117+
const native = buildKernelConnectionOptions(opts);
118+
expectNativeConnectionOptions(native, {
119+
hostName: 'adb-12345.0.azuredatabricks.net',
120+
httpPath: '/sql/1.0/warehouses/abc',
121+
intervalsAsString: true,
122+
authMode: 'OAuthU2m',
123+
oauthRedirectPort: 8030,
124+
oauthScopes: ['sql', 'offline_access'],
125+
});
118126
});
119127

120128
it('routes Azure host + useDatabricksOAuthInAzure:true (no secret) to in-house OAuthU2m', () => {

0 commit comments

Comments
 (0)