Skip to content

Commit bc5f01e

Browse files
committed
Fix Service Account provider: clean private key in constructor
- Move private key cleaning from JWT creation to constructor - Remove unnecessary log about warning line detection - Add key ID to debug logs for better traceability - Directly modify key.private_key instead of creating new object
1 parent dce7831 commit bc5f01e

File tree

3 files changed

+50
-9
lines changed

3 files changed

+50
-9
lines changed

examples/README.MD

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,17 @@ cd examples/topic
6565
npm install && npm start
6666
```
6767

68+
### 📁 [service-account/](./service-account/)
69+
70+
EN: Yandex Cloud Service Account authentication with authorized key JSON file.
71+
72+
RU: Авторизация через Yandex Cloud Service Account с использованием authorized key JSON файла.
73+
74+
```bash
75+
cd examples/service-account
76+
npm install && npm start
77+
```
78+
6879
## 🚀 DevContainer
6980

7081
EN: Easiest way to try; spins up YDB and sets env for you.
@@ -107,6 +118,7 @@ cd examples/query && npm run dev
107118
cd examples/sls && npm start
108119
cd examples/tls && npm start
109120
cd examples/topic && npm start
121+
cd examples/service-account && npm start
110122
```
111123

112124
## 💡 Tips / Советы

package-lock.json

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

third-parties/auth-yandex-cloud/src/service-account.ts

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as fs from 'node:fs'
22
import * as path from 'node:path'
3-
import { createPrivateKey, sign } from 'node:crypto'
3+
import { constants, createPrivateKey, sign } from 'node:crypto'
44
import { CredentialsProvider } from '@ydbjs/auth'
55
import { loggers } from '@ydbjs/debug'
66
import { type RetryConfig, retry } from '@ydbjs/retry'
@@ -80,12 +80,18 @@ export class ServiceAccountCredentialsProvider extends CredentialsProvider {
8080
)
8181
}
8282

83+
// Yandex Cloud authorized keys may contain a warning line before the PEM key
84+
// Remove it if present to get clean PEM format
85+
if (key.private_key.includes('PLEASE DO NOT REMOVE')) {
86+
key.private_key = key.private_key.replace(/^.*?-----BEGIN PRIVATE KEY-----/s, '-----BEGIN PRIVATE KEY-----')
87+
}
88+
8389
this.#key = key
8490
if (options?.iamEndpoint) {
8591
this.#iamEndpoint = options.iamEndpoint
8692
}
8793

88-
dbg.log('creating service account credentials provider for SA: %s', key.service_account_id)
94+
dbg.log('creating service account credentials provider for SA: %s (key ID: %s)', key.service_account_id, key.id)
8995
}
9096

9197
/**
@@ -161,12 +167,16 @@ export class ServiceAccountCredentialsProvider extends CredentialsProvider {
161167
return this.#promise
162168
}
163169

164-
dbg.log('fetching new IAM token (token expired or force=true)')
170+
dbg.log('fetching new IAM token (token expired or force=true, key ID: %s)', this.#key.id)
165171

166172
this.#promise = (async (): Promise<string> => {
167173
try {
168174
this.#token = await this.#fetchIamToken(signal)
169-
dbg.log('IAM token fetched successfully, expires at %s', new Date(this.#token.expires_at).toISOString())
175+
dbg.log(
176+
'IAM token fetched successfully, expires at %s (key ID: %s)',
177+
new Date(this.#token.expires_at).toISOString(),
178+
this.#key.id
179+
)
170180
return this.#token.value
171181
} finally {
172182
this.#promise = null
@@ -191,14 +201,15 @@ export class ServiceAccountCredentialsProvider extends CredentialsProvider {
191201
try {
192202
this.#token = await this.#fetchIamToken(signal)
193203
dbg.log(
194-
'background IAM token refresh successful, expires at %s',
195-
new Date(this.#token.expires_at).toISOString()
204+
'background IAM token refresh successful, expires at %s (key ID: %s)',
205+
new Date(this.#token.expires_at).toISOString(),
206+
this.#key.id
196207
)
197208
return this.#token.value
198209
} catch (error) {
199210
// Don't throw - failed background refresh will retry on next getToken call
200211
// Return existing token if available to avoid breaking ongoing requests
201-
dbg.log('background IAM token refresh failed: %O', error)
212+
dbg.log('background IAM token refresh failed: %O (key ID: %s)', error, this.#key.id)
202213
if (this.#token) {
203214
return this.#token.value
204215
}
@@ -296,7 +307,7 @@ export class ServiceAccountCredentialsProvider extends CredentialsProvider {
296307
// - https://datatracker.ietf.org/doc/html/rfc7518#section-3.5 (JWT PS256 algorithm specification)
297308
let signature = sign('sha256', Buffer.from(unsignedToken), {
298309
key: privateKey,
299-
padding: 1, // RSA_PKCS1_PSS_PADDING constant value
310+
padding: constants.RSA_PKCS1_PSS_PADDING,
300311
saltLength: 32,
301312
})
302313

@@ -325,7 +336,12 @@ export class ServiceAccountCredentialsProvider extends CredentialsProvider {
325336
return strategy ? strategy(ctx, cfg) : 0
326337
},
327338
onRetry: (ctx) => {
328-
dbg.log('retrying IAM token fetch, attempt %d, error: %O', ctx.attempt, ctx.error)
339+
dbg.log(
340+
'retrying IAM token fetch, attempt %d, error: %O (key ID: %s)',
341+
ctx.attempt,
342+
ctx.error,
343+
this.#key.id
344+
)
329345
},
330346
}
331347

0 commit comments

Comments
 (0)