maxpool 1.24.9 → 1.24.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/secret-resolver.js +79 -0
package/package.json
CHANGED
package/src/secret-resolver.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* broken provider degrades to "disabled" instead of crashing the proxy.
|
|
10
10
|
*/
|
|
11
11
|
import { execFile } from 'node:child_process';
|
|
12
|
+
import https from 'node:https';
|
|
12
13
|
import { promisify } from 'node:util';
|
|
13
14
|
import { readFileSync } from 'node:fs';
|
|
14
15
|
import { homedir } from 'node:os';
|
|
@@ -57,12 +58,90 @@ function fromCache(secretName) {
|
|
|
57
58
|
return (typeof v === 'string' && v.trim()) ? v.trim() : null;
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
// REST PATH FIRST (2026-10-08). `gcloud secrets versions access` hung on this machine
|
|
62
|
+
// (>60s, 21 piled-up gcloud processes) while a plain Secret Manager REST call with the
|
|
63
|
+
// same user's token answered in 0.75s. Every startup resolve AND every 5-minute
|
|
64
|
+
// re-resolve went through the hung CLI, so all z.ai providers stayed
|
|
65
|
+
// "secret-unresolved" until a manual restart happened to land on a good moment.
|
|
66
|
+
// The token itself comes from `gcloud auth print-access-token` (fast — it reads the
|
|
67
|
+
// stored credential, no Secret Manager round-trip) and is reused for ~45 min.
|
|
68
|
+
let tokenMemo = { value: null, at: 0 };
|
|
69
|
+
const TOKEN_TTL_MS = 45 * 60_000;
|
|
70
|
+
|
|
71
|
+
async function accessToken(timeoutMs) {
|
|
72
|
+
if (tokenMemo.value && Date.now() - tokenMemo.at < TOKEN_TTL_MS) return tokenMemo.value;
|
|
73
|
+
const { stdout } = await execFileAsync('gcloud', ['auth', 'print-access-token'],
|
|
74
|
+
{ timeout: Math.min(timeoutMs, 20_000), maxBuffer: 64 * 1024 });
|
|
75
|
+
const t = stdout.trim();
|
|
76
|
+
if (!t) throw new Error('empty token');
|
|
77
|
+
tokenMemo = { value: t, at: Date.now() };
|
|
78
|
+
return t;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// IPv4-PINNED GET (2026-10-08). On this network IPv6 to Google is black-holed:
|
|
82
|
+
// secretmanager.googleapis.com advertises AAAA records, an IPv6 connect hangs until
|
|
83
|
+
// timeout, and IPv4 answers fine (measured: `curl -6` 8s timeout, `curl -4` OK). gcloud's
|
|
84
|
+
// own HTTP stack tries IPv6 first and HANGS — that is why every CLI resolve timed out.
|
|
85
|
+
// `family: 4` makes this path immune; the CLI fallback below stays as a last resort.
|
|
86
|
+
function ipv4Get(url, { headers, signal }) {
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
const req = https.get(url, { headers, family: 4, signal }, res => {
|
|
89
|
+
const chunks = [];
|
|
90
|
+
res.on('data', c => chunks.push(c));
|
|
91
|
+
res.on('end', () => {
|
|
92
|
+
const body = Buffer.concat(chunks).toString('utf8');
|
|
93
|
+
resolve({ status: res.statusCode, ok: res.statusCode >= 200 && res.statusCode < 300, json: async () => JSON.parse(body) });
|
|
94
|
+
});
|
|
95
|
+
res.on('error', reject);
|
|
96
|
+
});
|
|
97
|
+
req.on('error', reject);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Test seam: inject a fetch + token source. */
|
|
102
|
+
let restDeps = { fetch: ipv4Get, token: accessToken };
|
|
103
|
+
export function __setRestDeps(d) { restDeps = { ...restDeps, ...d }; }
|
|
104
|
+
export function __resetRestDeps() { restDeps = { fetch: ipv4Get, token: accessToken }; tokenMemo = { value: null, at: 0 }; }
|
|
105
|
+
|
|
106
|
+
async function viaRest(secretName, project, timeoutMs) {
|
|
107
|
+
const tok = await restDeps.token(timeoutMs);
|
|
108
|
+
const url = `https://secretmanager.googleapis.com/v1/projects/${encodeURIComponent(project)}`
|
|
109
|
+
+ `/secrets/${encodeURIComponent(secretName)}/versions/latest:access`;
|
|
110
|
+
const ctl = new AbortController();
|
|
111
|
+
const t = setTimeout(() => ctl.abort(), Math.min(timeoutMs, 15_000));
|
|
112
|
+
try {
|
|
113
|
+
const res = await restDeps.fetch(url, { headers: { Authorization: `Bearer ${tok}` }, signal: ctl.signal });
|
|
114
|
+
if (res.status === 401) { tokenMemo = { value: null, at: 0 }; return null; } // stale token → CLI fallback
|
|
115
|
+
// 403/404: the server answered definitively — the CLI would get the same answer.
|
|
116
|
+
if (res.status === 403 || res.status === 404) return { value: null, definitive: true };
|
|
117
|
+
if (!res.ok) return null;
|
|
118
|
+
const j = await res.json();
|
|
119
|
+
const b64 = j?.payload?.data;
|
|
120
|
+
if (!b64) return { value: null, definitive: true };
|
|
121
|
+
const v = Buffer.from(b64, 'base64').toString('utf8').trim();
|
|
122
|
+
return { value: v || null, definitive: true };
|
|
123
|
+
} finally { clearTimeout(t); }
|
|
124
|
+
}
|
|
125
|
+
|
|
60
126
|
export async function resolveSecret(secretName, { project = DEFAULT_PROJECT, timeoutMs = 45_000, useCache = true } = {}) {
|
|
61
127
|
if (!secretName || typeof secretName !== 'string') return null;
|
|
62
128
|
if (useCache) {
|
|
63
129
|
const cached = fromCache(secretName);
|
|
64
130
|
if (cached) return cached;
|
|
65
131
|
}
|
|
132
|
+
// ONE time budget for the whole lookup: REST first, then the CLI with whatever is
|
|
133
|
+
// left. A definitive REST answer (the secret does not exist / no access) skips the
|
|
134
|
+
// CLI entirely — the CLI would only re-ask the same server, and on an IPv6-broken
|
|
135
|
+
// network it hangs for the full timeout anyway.
|
|
136
|
+
const deadline = Date.now() + timeoutMs;
|
|
137
|
+
try {
|
|
138
|
+
const r = await viaRest(secretName, project, timeoutMs);
|
|
139
|
+
if (r?.value) return r.value;
|
|
140
|
+
if (r?.definitive) return null;
|
|
141
|
+
} catch { /* transport failure — fall through to the CLI path */ }
|
|
142
|
+
const remaining = deadline - Date.now();
|
|
143
|
+
if (remaining < 1000) return null;
|
|
144
|
+
timeoutMs = remaining;
|
|
66
145
|
try {
|
|
67
146
|
const { stdout } = await execFileAsync(
|
|
68
147
|
'gcloud',
|