Skip to content

feat(kernel): JWT private-key M2M auth on useKernel - #504

Open
rahuls-db wants to merge 1 commit into
mainfrom
feat/kernel-jwt-private-key-m2m
Open

feat(kernel): JWT private-key M2M auth on useKernel#504
rahuls-db wants to merge 1 commit into
mainfrom
feat/kernel-jwt-private-key-m2m

Conversation

@rahuls-db

Copy link
Copy Markdown
Collaborator

What

Adds OAuth machine-to-machine auth with a JWT private-key client assertion (RFC 7523) on the kernel backend (useKernel: true). The kernel signs a short-lived JWT with the service principal's private key instead of sending a client secret, and owns the token lifecycle; the workspace's OAuth IdP verifies it against the SP's registered public key.

Companion to the kernel-side feature (databricks-sql-kernel #249; napi token_url in #275) and the parallel databricks-sql-python / databricks-sql-go changes.

How

  • lib/kernel/KernelAuth.ts — new JWT branch in buildKernelConnectionOptions (checked before the U2M/M2M-secret split; a private-key file is unambiguous JWT M2M intent) plus the OAuthM2mJwt native-option shape. Requires oauthClientId + oauthJwtKid; optional oauthJwtPassphrase / oauthJwtAlgorithm / oauthScopes / tokenUrl. Mutually exclusive with oauthClientSecret. Also threads tokenUrl through the existing M2M branch.
  • lib/contracts/IDBSQLClient.ts — new oauthJwt* + tokenUrl fields on the databricks-oauth ConnectionOptions member.
  • lib/DBSQLClient.ts — on the useKernel path, do not build the connector's own OAuth provider. It eagerly starts the U2M browser flow / M2M token exchange at connect time (a telemetry / feature-flag client calls authProvider.authenticate()) before the kernel is consulted — which, for the no-secret JWT case, launched a spurious browser listener. Hand over a minimal PAT provider only when a token is present. Mirrors the Python connector's use_kernel handling.

Usage

await client.connect({
  host: 'adb-….azuredatabricks.net',
  path: '/sql/1.0/warehouses/…',
  authType: 'databricks-oauth',
  oauthClientId: '<sp-client-id>',
  oauthJwtKeyFile: '/path/private_key.pem',
  oauthJwtKid: '<kid>',
  tokenUrl: 'https://login.microsoftonline.com/<tenant>/oauth2/v2.0/token',
  oauthScopes: ['<databricks-resource-id>/.default'],
  useKernel: true,
});

Testing

  • 9 new unit tests in tests/unit/kernel/auth-m2m-jwt.test.ts (routing, precedence, required-field validation, ambiguity guards); full kernel unit suite 318 passing. prettier + eslint clean.
  • Verified end-to-end against an Azure Databricks warehouse: SELECT 1[{"n":1}], with the client's backend asserted to be KernelBackend (kernel path, not Thrift).

Requires a @databricks/databricks-sql-kernel build with JWT + tokenUrl support.

This pull request and its description were written by Isaac.

Add JWT private-key client-assertion auth (RFC 7523) to the kernel
backend. On `authType: 'databricks-oauth'`, supplying `oauthJwtKeyFile`
selects the JWT flow: the kernel signs a short-lived assertion with the
private key instead of sending a client secret and owns the token
lifecycle (`authMode: 'OAuthM2mJwt'`).

- KernelAuth: new JWT branch in buildKernelConnectionOptions (checked
  before the U2M/M2M-secret split; a private-key file is unambiguous JWT
  M2M intent), plus the OAuthM2mJwt native option shape. Requires
  oauthClientId + oauthJwtKid; optional oauthJwtPassphrase /
  oauthJwtAlgorithm / oauthScopes / tokenUrl. Mutually exclusive with
  oauthClientSecret. Also threads tokenUrl through the existing M2m branch.
- IDBSQLClient: new oauthJwt* + tokenUrl fields on the databricks-oauth
  ConnectionOptions member.
- DBSQLClient: on the useKernel path, do not build the connector's own
  OAuth provider (it eagerly starts the U2M browser flow / M2M exchange
  before the kernel is consulted); hand over a minimal PAT provider only
  when a token is present. Mirrors the Python connector.
- tests: 9 unit tests for JWT routing / precedence / validation.

Verified end-to-end: SELECT 1 via useKernel against an Azure Databricks
warehouse, authenticated by Entra ID with a JWT private-key assertion
(tokenUrl pointed at the Entra token endpoint).

Requires a @databricks/databricks-sql-kernel build with JWT + tokenUrl
support (kernel PRs #249 merged, #275 for tokenUrl).

Signed-off-by: Rahul Singhal <rahul.singhal@databricks.com>

@peco-review-bot peco-review-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: 2 Low

Solid, well-documented addition; the JWT branch validation and precedence logic look correct and mirror the existing M2M/U2M handling. Two low-severity notes: telemetry authType misclassifies JWT M2M as external-browser (since it keys off oauthClientSecret), and the connect() auth-provider behavior change lacks direct unit coverage. Minor consistency gap worth noting: the PAT branch's ambiguity guard rejects token + oauthClientId/oauthClientSecret but not token + oauthJwtKeyFile, so a JWT key silently drops when authType: 'access-token' is used.

Comment thread lib/DBSQLClient.ts
// racing — and conflicting with — the kernel's auth. So for `useKernel` we
// hand over only a minimal PAT provider when a `token` is present, and
// `undefined` otherwise. Mirrors Python's use_kernel auth-provider handling.
if (internalOptions.useKernel) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Low — The new JWT private-key M2M flow is not reflected in telemetry authType. mapAuthType (same file, called unconditionally at the top of connect()) keys off oauthClientSecret === undefined ? 'external-browser' : 'oauth-m2m'. For the JWT path oauthClientSecret is (and must be) undefined, so every JWT M2M kernel connection is reported to telemetry as external-browser (i.e. U2M browser flow) — the opposite of its actual machine-to-machine nature. Consider distinguishing the JWT case (e.g. presence of oauthJwtKeyFile) so telemetry attribution is accurate.

Comment thread lib/DBSQLClient.ts
// hand over only a minimal PAT provider when a `token` is present, and
// `undefined` otherwise. Mirrors Python's use_kernel auth-provider handling.
if (internalOptions.useKernel) {
const { token } = options as { token?: string };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Low — This is a behavior change to connect() — on the useKernel path the connector now skips createAuthProvider entirely and installs a PAT-only provider (or undefined). The new unit test file only exercises buildKernelConnectionOptions; there is no coverage asserting that (a) a useKernel OAuth/JWT connection ends up with authProvider === undefined (no eager browser flow), and (b) a useKernel connection with a token still gets a PlainHttpAuthentication provider. Since the stated motivation is preventing a spurious browser listener, a regression test guarding that behavior would be valuable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant