-
Notifications
You must be signed in to change notification settings - Fork 51
feat(kernel): JWT private-key M2M auth on useKernel #504
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
651159e
19e0d0e
7b2110a
6d21d06
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -230,6 +230,19 @@ export type KernelNativeConnectionOptions = KernelSessionDefaults & | |
| oauthClientId: string; | ||
| oauthClientSecret: string; | ||
| oauthScopes?: Array<string>; | ||
| tokenUrl?: string; | ||
| } | ||
| | { | ||
| hostName: string; | ||
| httpPath: string; | ||
| authMode: 'OAuthM2mJwt'; | ||
| oauthClientId: string; | ||
| jwtKeyFile: string; | ||
| jwtKid: string; | ||
| jwtPassphrase?: string; | ||
| jwtAlgorithm?: string; | ||
| oauthScopes?: Array<string>; | ||
| tokenUrl?: string; | ||
| } | ||
| | { | ||
| hostName: string; | ||
|
|
@@ -602,6 +615,11 @@ export function buildKernelConnectionOptions(options: ConnectionOptions): Kernel | |
| azureTenantId?: string; | ||
| useDatabricksOAuthInAzure?: boolean; | ||
| persistence?: unknown; | ||
| oauthJwtKeyFile?: string; | ||
| oauthJwtKid?: string; | ||
| oauthJwtPassphrase?: string; | ||
| oauthJwtAlgorithm?: string; | ||
| tokenUrl?: string; | ||
| }; | ||
|
|
||
| if (authType === undefined || authType === 'access-token') { | ||
|
|
@@ -611,9 +629,13 @@ export function buildKernelConnectionOptions(options: ConnectionOptions): Kernel | |
| "kernel backend: a non-empty PAT must be supplied via `token` when using `authType: 'access-token'`.", | ||
| ); | ||
| } | ||
| if (oauth.oauthClientId !== undefined || oauth.oauthClientSecret !== undefined) { | ||
| if ( | ||
| oauth.oauthClientId !== undefined || | ||
| oauth.oauthClientSecret !== undefined || | ||
| oauth.oauthJwtKeyFile !== undefined | ||
| ) { | ||
| throw new HiveDriverError( | ||
| 'kernel backend: cannot supply both `token` and `oauthClientId`/`oauthClientSecret` ' + | ||
| 'kernel backend: cannot supply both `token` and `oauthClientId`/`oauthClientSecret`/`oauthJwtKeyFile` ' + | ||
| "on the same connection. Pick one: 'access-token' (PAT) uses `token`; " + | ||
| "'databricks-oauth' uses the OAuth fields.", | ||
| ); | ||
|
|
@@ -637,6 +659,55 @@ export function buildKernelConnectionOptions(options: ConnectionOptions): Kernel | |
| ); | ||
| } | ||
|
|
||
| // JWT private-key M2M (RFC 7523 client assertion). A private-key file is | ||
| // unambiguous JWT M2M intent, so this is checked before the U2M/M2M | ||
| // secret split. The kernel signs a short-lived assertion with the key | ||
| // (`authMode: 'OAuthM2mJwt'`) instead of sending a client secret. Requires | ||
| // `oauthClientId` (assertion issuer/subject) and `oauthJwtKid` (key id). | ||
| // Mutually exclusive with `oauthClientSecret`. | ||
| if (oauth.oauthJwtKeyFile !== undefined) { | ||
| if (oauth.oauthClientSecret !== undefined) { | ||
| throw new HiveDriverError( | ||
| 'kernel backend: cannot supply both `oauthJwtKeyFile` (JWT private-key M2M) ' + | ||
| 'and `oauthClientSecret` (shared-secret M2M). Pick one.', | ||
| ); | ||
| } | ||
| if (oauth.persistence !== undefined) { | ||
| throw new HiveDriverError( | ||
| 'kernel backend: `persistence` is not supported on JWT private-key M2M ' + | ||
| '(M2M tokens have no refresh token; the kernel re-issues on expiry).', | ||
| ); | ||
| } | ||
| if (oauth.oauthClientId === undefined) { | ||
| throw new AuthenticationError( | ||
| 'kernel backend: JWT private-key M2M (`oauthJwtKeyFile`) requires `oauthClientId` ' + | ||
| '(the service principal / OAuth client id used as the assertion issuer and subject).', | ||
| ); | ||
| } | ||
| if (oauth.oauthJwtKid === undefined) { | ||
| throw new AuthenticationError( | ||
| 'kernel backend: JWT private-key M2M (`oauthJwtKeyFile`) requires `oauthJwtKid` ' + | ||
| '(the key id written into the JWT header so the IdP can select the registered public key).', | ||
| ); | ||
| } | ||
| const jwt = { | ||
| ...base, | ||
| authMode: 'OAuthM2mJwt' as const, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 Low — The JWT M2M branch validates |
||
| oauthClientId: oauth.oauthClientId, | ||
| jwtKeyFile: oauth.oauthJwtKeyFile, | ||
| jwtKid: oauth.oauthJwtKid, | ||
| // Configurable (parity with pyo3); defaults to `['all-apis']` in the kernel. | ||
| oauthScopes: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 Low — The JWT branch defaults |
||
| Array.isArray(oauth.oauthScopes) && oauth.oauthScopes.length > 0 ? oauth.oauthScopes : M2M_DEFAULT_SCOPES, | ||
| }; | ||
| return { | ||
| ...jwt, | ||
| ...(oauth.oauthJwtPassphrase !== undefined ? { jwtPassphrase: oauth.oauthJwtPassphrase } : {}), | ||
| ...(oauth.oauthJwtAlgorithm !== undefined ? { jwtAlgorithm: oauth.oauthJwtAlgorithm } : {}), | ||
| ...(oauth.tokenUrl !== undefined ? { tokenUrl: oauth.tokenUrl } : {}), | ||
| }; | ||
| } | ||
|
|
||
| // Flow selector + client-id resolution mirror the Thrift driver EXACTLY | ||
| // (`DBSQLClient.createAuthProvider`, DBSQLClient.ts:220): | ||
| // flow = oauthClientSecret === undefined ? U2M : M2M (strict undefined) | ||
|
|
@@ -680,16 +751,17 @@ export function buildKernelConnectionOptions(options: ConnectionOptions): Kernel | |
| '(M2M tokens have no refresh token; the kernel re-issues on expiry).', | ||
| ); | ||
| } | ||
| return { | ||
| const m2m = { | ||
| ...base, | ||
| authMode: 'OAuthM2m', | ||
| authMode: 'OAuthM2m' as const, | ||
| // Thrift: `getClientId()` = `oauthClientId ?? defaultClientId`. | ||
| oauthClientId: oauth.oauthClientId ?? DEFAULT_OAUTH_CLIENT_ID, | ||
| oauthClientSecret: oauth.oauthClientSecret, | ||
| // Configurable (parity with pyo3); defaults to `['all-apis']`. | ||
| oauthScopes: | ||
| Array.isArray(oauth.oauthScopes) && oauth.oauthScopes.length > 0 ? oauth.oauthScopes : M2M_DEFAULT_SCOPES, | ||
| }; | ||
| return oauth.tokenUrl !== undefined ? { ...m2m, tokenUrl: oauth.tokenUrl } : m2m; | ||
|
rahuls-db marked this conversation as resolved.
|
||
| } | ||
|
|
||
| throw new HiveDriverError( | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.