@agent-vm/secret-management 0.0.94 → 0.0.96
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/dist/index.d.ts +24 -13
- package/dist/index.js +217 -98
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,17 @@ import { ResolveAllResponse } from "@1password/sdk";
|
|
|
6
6
|
type SecretEnvironment = Readonly<Record<string, string | undefined>>;
|
|
7
7
|
declare function createCompositeSecretResolver(onePasswordResolver: SecretResolver | null, env?: SecretEnvironment): SecretResolver;
|
|
8
8
|
//#endregion
|
|
9
|
+
//#region src/redacted-exec-file.d.ts
|
|
10
|
+
interface ExecFileOptions {
|
|
11
|
+
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
12
|
+
readonly input?: string | undefined;
|
|
13
|
+
readonly redactErrorOutput?: boolean | undefined;
|
|
14
|
+
}
|
|
15
|
+
interface ExecFileResult {
|
|
16
|
+
readonly stdout: string;
|
|
17
|
+
readonly stderr: string;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
9
20
|
//#region src/onepassword-secret-resolver.d.ts
|
|
10
21
|
interface SecretResolverClient {
|
|
11
22
|
readonly secrets: {
|
|
@@ -13,10 +24,11 @@ interface SecretResolverClient {
|
|
|
13
24
|
resolveAll(secretReferences: readonly string[]): Promise<ResolveAllResponse>;
|
|
14
25
|
};
|
|
15
26
|
}
|
|
27
|
+
interface OnePasswordServiceAccountHeadlessAuthProbeResult {
|
|
28
|
+
readonly hint: string;
|
|
29
|
+
readonly ok: boolean;
|
|
30
|
+
}
|
|
16
31
|
type TokenSource = {
|
|
17
|
-
readonly type: 'op-cli';
|
|
18
|
-
readonly ref: string;
|
|
19
|
-
} | {
|
|
20
32
|
readonly type: 'env';
|
|
21
33
|
readonly envVar?: string | undefined;
|
|
22
34
|
} | {
|
|
@@ -24,15 +36,6 @@ type TokenSource = {
|
|
|
24
36
|
readonly service: string;
|
|
25
37
|
readonly account: string;
|
|
26
38
|
};
|
|
27
|
-
interface ExecFileOptions {
|
|
28
|
-
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
29
|
-
readonly input?: string | undefined;
|
|
30
|
-
readonly redactErrorOutput?: boolean | undefined;
|
|
31
|
-
}
|
|
32
|
-
interface ExecFileResult {
|
|
33
|
-
readonly stdout: string;
|
|
34
|
-
readonly stderr: string;
|
|
35
|
-
}
|
|
36
39
|
declare function resolveServiceAccountToken(source: TokenSource, dependencies?: {
|
|
37
40
|
readonly execFileAsync?: (command: string, args: readonly string[], options?: ExecFileOptions) => Promise<ExecFileResult>;
|
|
38
41
|
}): Promise<string>;
|
|
@@ -46,6 +49,11 @@ interface CreateSecretResolverDependencies {
|
|
|
46
49
|
readonly integrationName?: string;
|
|
47
50
|
readonly integrationVersion?: string;
|
|
48
51
|
}
|
|
52
|
+
declare function probeOnePasswordServiceAccountHeadlessAuth(options: {
|
|
53
|
+
readonly serviceAccountToken: string;
|
|
54
|
+
}, dependencies?: {
|
|
55
|
+
readonly execFileAsync?: (command: string, args: readonly string[], options?: ExecFileOptions) => Promise<ExecFileResult>;
|
|
56
|
+
}): Promise<OnePasswordServiceAccountHeadlessAuthProbeResult>;
|
|
49
57
|
declare function createSecretResolver(options: {
|
|
50
58
|
readonly serviceAccountToken: string;
|
|
51
59
|
}, dependencies?: CreateSecretResolverDependencies): Promise<SecretResolver>;
|
|
@@ -63,4 +71,7 @@ type OnePasswordE2eTestEnvironment = Partial<Record<'AGENT_VM_TEST_OP_REFS' | 'A
|
|
|
63
71
|
declare const defaultOnePasswordE2eVaultPrefix = "op://agent-vm-testing/";
|
|
64
72
|
declare function readOnePasswordE2eTestConfig(env?: OnePasswordE2eTestEnvironment): OnePasswordE2eTestConfig;
|
|
65
73
|
//#endregion
|
|
66
|
-
|
|
74
|
+
//#region src/secret-redaction.d.ts
|
|
75
|
+
declare function redactOnePasswordReferences(text: string): string;
|
|
76
|
+
//#endregion
|
|
77
|
+
export { CreateSecretResolverDependencies, type ExecFileOptions, type ExecFileResult, MediatedSecretSpec, OnePasswordE2eTestConfig, OnePasswordE2eTestEnvironment, OnePasswordServiceAccountHeadlessAuthProbeResult, SecretRef, SecretResolver, SecretResolverClient, type TokenSource, createCompositeSecretResolver, createOpCliSecretResolver, createSecretResolver, createStaticSecretResolver, defaultOnePasswordE2eVaultPrefix, probeOnePasswordServiceAccountHeadlessAuth, readOnePasswordE2eTestConfig, redactOnePasswordReferences, resolveServiceAccountToken };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import "./contracts.js";
|
|
2
2
|
import { createStaticSecretResolver } from "./testing.js";
|
|
3
|
-
import { execFile } from "node:child_process";
|
|
4
3
|
import { randomUUID } from "node:crypto";
|
|
5
4
|
import { createClient } from "@1password/sdk";
|
|
5
|
+
import { mkdir, mkdtemp, rm } from "node:fs/promises";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { execFile } from "node:child_process";
|
|
9
|
+
//#region src/secret-redaction.ts
|
|
10
|
+
const quotedOnePasswordReferencePattern = /(["'`])(?:(?!\1)[\s\S])*?op:\/\/(?:(?!\1)[\s\S])*?\1/giu;
|
|
11
|
+
const unquotedOnePasswordReferencePattern = /op:\/\/[^\r\n]*/giu;
|
|
12
|
+
function redactOnePasswordReferences(text) {
|
|
13
|
+
return text.replaceAll(quotedOnePasswordReferencePattern, (quotedReference) => {
|
|
14
|
+
const quote = quotedReference.slice(0, 1);
|
|
15
|
+
return `${quote}<1password-ref>${quote}`;
|
|
16
|
+
}).replaceAll(unquotedOnePasswordReferencePattern, "<1password-ref>");
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
6
19
|
//#region src/composite-secret-resolver.ts
|
|
7
20
|
function resolveEnvironmentSecret(ref, env) {
|
|
8
21
|
const value = env[ref.ref];
|
|
@@ -14,25 +27,25 @@ function resolveConfigSecret(ref) {
|
|
|
14
27
|
if (ref.value.trim().length === 0) throw new Error("Config secret value is empty.");
|
|
15
28
|
return ref.value;
|
|
16
29
|
}
|
|
17
|
-
function formatUnknownError$
|
|
18
|
-
return error instanceof Error ? error.message : String(error);
|
|
30
|
+
function formatUnknownError$2(error) {
|
|
31
|
+
return redactOnePasswordReferences(error instanceof Error ? error.message : String(error));
|
|
19
32
|
}
|
|
20
33
|
function describeSecretRef(ref) {
|
|
21
34
|
switch (ref.source) {
|
|
22
|
-
case "1password":
|
|
35
|
+
case "1password": return "<1password-ref>";
|
|
23
36
|
case "environment": return ref.ref;
|
|
24
37
|
case "config": return "<config>";
|
|
25
38
|
default: return JSON.stringify(ref);
|
|
26
39
|
}
|
|
27
40
|
}
|
|
28
41
|
function createSecretResolutionError(options) {
|
|
29
|
-
return new Error(`Failed to resolve secret '${options.secretName}' from '${describeSecretRef(options.ref)}': ${formatUnknownError$
|
|
42
|
+
return new Error(`Failed to resolve secret '${options.secretName}' from '${describeSecretRef(options.ref)}': ${formatUnknownError$2(options.cause)}`, { cause: options.cause });
|
|
30
43
|
}
|
|
31
44
|
function throwAggregateSecretResolutionError(failures) {
|
|
32
45
|
if (failures.length > 0) throw new AggregateError(failures, `Failed to resolve ${String(failures.length)} secret(s).`);
|
|
33
46
|
}
|
|
34
47
|
function extractAggregateErrors(error) {
|
|
35
|
-
return (Array.isArray(error.errors) ? error.errors : [error]).map((failure) =>
|
|
48
|
+
return (Array.isArray(error.errors) ? error.errors : [error]).map((failure) => new Error(formatUnknownError$2(failure)));
|
|
36
49
|
}
|
|
37
50
|
function createCompositeSecretResolver(onePasswordResolver, env = process.env) {
|
|
38
51
|
return {
|
|
@@ -102,16 +115,74 @@ function createCompositeSecretResolver(onePasswordResolver, env = process.env) {
|
|
|
102
115
|
};
|
|
103
116
|
}
|
|
104
117
|
//#endregion
|
|
105
|
-
//#region src/
|
|
106
|
-
|
|
118
|
+
//#region src/op-cli-service-account-env.ts
|
|
119
|
+
const opCliProcessPlumbingEnvNames = [
|
|
120
|
+
"APPDATA",
|
|
121
|
+
"ALL_PROXY",
|
|
122
|
+
"all_proxy",
|
|
123
|
+
"COMSPEC",
|
|
124
|
+
"HOME",
|
|
125
|
+
"HTTP_PROXY",
|
|
126
|
+
"http_proxy",
|
|
127
|
+
"HTTPS_PROXY",
|
|
128
|
+
"https_proxy",
|
|
129
|
+
"LANG",
|
|
130
|
+
"LC_ALL",
|
|
131
|
+
"LC_CTYPE",
|
|
132
|
+
"LOCALAPPDATA",
|
|
133
|
+
"NO_PROXY",
|
|
134
|
+
"no_proxy",
|
|
135
|
+
"PATH",
|
|
136
|
+
"SSL_CERT_DIR",
|
|
137
|
+
"SSL_CERT_FILE",
|
|
138
|
+
"TEMP",
|
|
139
|
+
"TMP",
|
|
140
|
+
"TMPDIR",
|
|
141
|
+
"TZ",
|
|
142
|
+
"USERPROFILE",
|
|
143
|
+
"WINDIR",
|
|
144
|
+
"XDG_RUNTIME_DIR"
|
|
145
|
+
];
|
|
146
|
+
function createOpCliServiceAccountEnv(serviceAccountToken, opConfigDir) {
|
|
147
|
+
const env = {};
|
|
148
|
+
for (const envName of opCliProcessPlumbingEnvNames) {
|
|
149
|
+
const envValue = process.env[envName];
|
|
150
|
+
if (envValue !== void 0) env[envName] = envValue;
|
|
151
|
+
}
|
|
152
|
+
env.OP_BIOMETRIC_UNLOCK_ENABLED = "false";
|
|
153
|
+
env.OP_CACHE = "false";
|
|
154
|
+
env.OP_CONFIG_DIR = opConfigDir;
|
|
155
|
+
env.OP_SERVICE_ACCOUNT_TOKEN = serviceAccountToken;
|
|
156
|
+
return env;
|
|
157
|
+
}
|
|
158
|
+
async function withOpCliServiceAccountEnv(serviceAccountToken, callback) {
|
|
159
|
+
const opConfigDirParent = tmpdir();
|
|
160
|
+
await mkdir(opConfigDirParent, { recursive: true });
|
|
161
|
+
const opConfigDir = await mkdtemp(path.join(opConfigDirParent, "agent-vm-op-config-"));
|
|
162
|
+
try {
|
|
163
|
+
return await callback(createOpCliServiceAccountEnv(serviceAccountToken, opConfigDir));
|
|
164
|
+
} finally {
|
|
165
|
+
await rm(opConfigDir, {
|
|
166
|
+
force: true,
|
|
167
|
+
recursive: true
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region src/redacted-exec-file.ts
|
|
173
|
+
function formatUnknownError$1(error) {
|
|
107
174
|
if (error instanceof AggregateError) {
|
|
108
|
-
const childMessages = readAggregateErrorChildren(error).map(formatUnknownError);
|
|
175
|
+
const childMessages = readAggregateErrorChildren$1(error).map(formatUnknownError$1);
|
|
109
176
|
if (childMessages.length === 0) return error.message;
|
|
110
177
|
const separator = error.message.endsWith(".") ? "" : ".";
|
|
111
178
|
return `${error.message}${separator} Details: ${childMessages.join("; ")}`;
|
|
112
179
|
}
|
|
113
180
|
return error instanceof Error ? error.message : String(error);
|
|
114
181
|
}
|
|
182
|
+
function readAggregateErrorChildren$1(error) {
|
|
183
|
+
const errorChildren = error.errors;
|
|
184
|
+
return Array.isArray(errorChildren) ? errorChildren : [];
|
|
185
|
+
}
|
|
115
186
|
var RedactedExecFileError = class extends Error {
|
|
116
187
|
safeDetail;
|
|
117
188
|
constructor(message, safeDetail, options) {
|
|
@@ -120,12 +191,6 @@ var RedactedExecFileError = class extends Error {
|
|
|
120
191
|
this.name = "RedactedExecFileError";
|
|
121
192
|
}
|
|
122
193
|
};
|
|
123
|
-
var OpInjectOutputError = class extends Error {
|
|
124
|
-
constructor(message) {
|
|
125
|
-
super(message);
|
|
126
|
-
this.name = "OpInjectOutputError";
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
194
|
function formatErrorMetadataValue(value) {
|
|
130
195
|
if (typeof value === "number" || typeof value === "string") return String(value);
|
|
131
196
|
}
|
|
@@ -137,14 +202,48 @@ function readErrorSignal(error) {
|
|
|
137
202
|
if (!("signal" in error)) return;
|
|
138
203
|
return formatErrorMetadataValue(error.signal);
|
|
139
204
|
}
|
|
140
|
-
function
|
|
205
|
+
function readErrorKilled(error) {
|
|
206
|
+
if (!("killed" in error) || typeof error.killed !== "boolean") return;
|
|
207
|
+
return error.killed;
|
|
208
|
+
}
|
|
209
|
+
function hasEnvironmentPrefix(env, prefix) {
|
|
210
|
+
return Object.keys(env).some((envName) => envName.startsWith(prefix));
|
|
211
|
+
}
|
|
212
|
+
function formatOpCliAuthContext(env) {
|
|
213
|
+
if (env === void 0) return ["opEnvIsolation=disabled", "opAuth=ambient-process"];
|
|
214
|
+
return [
|
|
215
|
+
"opEnvIsolation=enabled",
|
|
216
|
+
`opAuth=${env.OP_SERVICE_ACCOUNT_TOKEN === void 0 ? "missing" : "service-account-token"}`,
|
|
217
|
+
`opConfig=${env.OP_CONFIG_DIR === void 0 ? "default" : "isolated"}`,
|
|
218
|
+
`opBiometricUnlock=${env.OP_BIOMETRIC_UNLOCK_ENABLED ?? "unset"}`,
|
|
219
|
+
`opCache=${env.OP_CACHE ?? "unset"}`,
|
|
220
|
+
`opConnectEnv=${env.OP_CONNECT_HOST !== void 0 || env.OP_CONNECT_TOKEN !== void 0 ? "present" : "absent"}`,
|
|
221
|
+
`opSessionEnv=${hasEnvironmentPrefix(env, "OP_SESSION") ? "present" : "absent"}`,
|
|
222
|
+
`opAccountEnv=${env.OP_ACCOUNT === void 0 ? "absent" : "present"}`
|
|
223
|
+
];
|
|
224
|
+
}
|
|
225
|
+
function formatRedactedExecErrorDetail(options) {
|
|
226
|
+
const error = options.error;
|
|
141
227
|
const exitCode = readErrorCode(error) ?? "unknown";
|
|
142
228
|
const signal = readErrorSignal(error);
|
|
143
|
-
|
|
229
|
+
const details = [
|
|
230
|
+
signal === void 0 ? `exit code ${exitCode}` : `exit code ${exitCode}, signal ${signal}`,
|
|
231
|
+
`elapsedMs=${String(options.elapsedMs)}`,
|
|
232
|
+
"output=redacted"
|
|
233
|
+
];
|
|
234
|
+
const killed = readErrorKilled(error);
|
|
235
|
+
if (killed !== void 0) details.push(`killed=${String(killed)}`);
|
|
236
|
+
if (options.command === "op") details.push(...formatOpCliAuthContext(options.env));
|
|
237
|
+
return details.join("; ");
|
|
144
238
|
}
|
|
145
239
|
function createExecFileError(options) {
|
|
146
240
|
if (options.redactErrorOutput) {
|
|
147
|
-
const safeDetail = formatRedactedExecErrorDetail(
|
|
241
|
+
const safeDetail = formatRedactedExecErrorDetail({
|
|
242
|
+
command: options.command,
|
|
243
|
+
elapsedMs: options.elapsedMs,
|
|
244
|
+
...options.env ? { env: options.env } : {},
|
|
245
|
+
error: options.error
|
|
246
|
+
});
|
|
148
247
|
return new RedactedExecFileError(`${options.command} failed: ${safeDetail}`, safeDetail);
|
|
149
248
|
}
|
|
150
249
|
const errorDetail = options.stderr.trim() || options.error.message;
|
|
@@ -157,15 +256,13 @@ function formatStdinWriteErrorDetail(error) {
|
|
|
157
256
|
function createStdinWriteError(command, error, redactErrorOutput) {
|
|
158
257
|
if (redactErrorOutput) {
|
|
159
258
|
const safeDetail = formatStdinWriteErrorDetail(error);
|
|
160
|
-
return new RedactedExecFileError(`${command} failed writing stdin: ${safeDetail}`, safeDetail
|
|
259
|
+
return new RedactedExecFileError(`${command} failed writing stdin: ${safeDetail}`, safeDetail);
|
|
161
260
|
}
|
|
162
|
-
return new Error(`${command} failed writing stdin: ${formatUnknownError(error)}`, { cause: error });
|
|
163
|
-
}
|
|
164
|
-
function ensureMacOsForKeychain() {
|
|
165
|
-
if (process.platform !== "darwin") throw new Error("Keychain token source is only supported on macOS. Use an env or op-cli token source on this platform so cmd-ts can surface a clear startup error.");
|
|
261
|
+
return new Error(`${command} failed writing stdin: ${formatUnknownError$1(error)}`, { cause: error });
|
|
166
262
|
}
|
|
167
263
|
function execFileAsync(command, args, options) {
|
|
168
264
|
return new Promise((resolve, reject) => {
|
|
265
|
+
const startedAtMs = Date.now();
|
|
169
266
|
let hasSettled = false;
|
|
170
267
|
const rejectOnce = (error) => {
|
|
171
268
|
if (hasSettled) return;
|
|
@@ -184,6 +281,8 @@ function execFileAsync(command, args, options) {
|
|
|
184
281
|
if (error) {
|
|
185
282
|
rejectOnce(createExecFileError({
|
|
186
283
|
command,
|
|
284
|
+
elapsedMs: Date.now() - startedAtMs,
|
|
285
|
+
env: options?.env,
|
|
187
286
|
error,
|
|
188
287
|
redactErrorOutput: options?.redactErrorOutput,
|
|
189
288
|
stderr
|
|
@@ -209,15 +308,39 @@ function execFileAsync(command, args, options) {
|
|
|
209
308
|
}
|
|
210
309
|
});
|
|
211
310
|
}
|
|
311
|
+
//#endregion
|
|
312
|
+
//#region src/onepassword-secret-resolver.ts
|
|
313
|
+
function formatUnknownError(error) {
|
|
314
|
+
if (error instanceof AggregateError) {
|
|
315
|
+
const childMessages = readAggregateErrorChildren(error).map(formatUnknownError);
|
|
316
|
+
if (childMessages.length === 0) return error.message;
|
|
317
|
+
const separator = error.message.endsWith(".") ? "" : ".";
|
|
318
|
+
return `${error.message}${separator} Details: ${childMessages.join("; ")}`;
|
|
319
|
+
}
|
|
320
|
+
return error instanceof Error ? error.message : String(error);
|
|
321
|
+
}
|
|
322
|
+
function redactKnownSecretValues(message, secretValues) {
|
|
323
|
+
return secretValues.reduce((redactedMessage, secretValue) => {
|
|
324
|
+
if (secretValue.length === 0) return redactedMessage;
|
|
325
|
+
return redactedMessage.replaceAll(secretValue, "<redacted>");
|
|
326
|
+
}, message);
|
|
327
|
+
}
|
|
328
|
+
function formatUnknownErrorWithRedactions(error, secretValues) {
|
|
329
|
+
return redactOnePasswordReferences(redactKnownSecretValues(formatUnknownError(error), secretValues));
|
|
330
|
+
}
|
|
331
|
+
var OpInjectOutputError = class extends Error {
|
|
332
|
+
constructor(message) {
|
|
333
|
+
super(message);
|
|
334
|
+
this.name = "OpInjectOutputError";
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
function ensureMacOsForKeychain() {
|
|
338
|
+
if (process.platform !== "darwin") throw new Error("Keychain token source is only supported on macOS. Use an env token source on this platform so cmd-ts can surface a clear startup error.");
|
|
339
|
+
}
|
|
212
340
|
const SAFE_IDENTIFIER_PATTERN = /^[\w.@-]+$/u;
|
|
213
341
|
async function resolveServiceAccountToken(source, dependencies) {
|
|
214
342
|
const exec = dependencies?.execFileAsync ?? execFileAsync;
|
|
215
343
|
switch (source.type) {
|
|
216
|
-
case "op-cli": {
|
|
217
|
-
const token = (await exec("op", ["read", source.ref], { redactErrorOutput: true })).stdout.trim();
|
|
218
|
-
if (token.length === 0) throw new Error("op-cli token resolution returned empty value");
|
|
219
|
-
return token;
|
|
220
|
-
}
|
|
221
344
|
case "env": {
|
|
222
345
|
const envVar = source.envVar ?? "OP_SERVICE_ACCOUNT_TOKEN";
|
|
223
346
|
const token = process.env[envVar]?.trim();
|
|
@@ -277,54 +400,43 @@ function mergeResolvedSecrets(resolvedSecrets, onePasswordSecrets) {
|
|
|
277
400
|
};
|
|
278
401
|
}
|
|
279
402
|
async function resolveSecretWithOpCli(serviceAccountToken, secretReference, exec) {
|
|
280
|
-
return
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
403
|
+
return await withOpCliServiceAccountEnv(serviceAccountToken, async (env) => {
|
|
404
|
+
return stripOpReadStdoutTerminator((await exec("op", ["read", secretReference], {
|
|
405
|
+
env,
|
|
406
|
+
redactErrorOutput: true
|
|
407
|
+
})).stdout);
|
|
408
|
+
});
|
|
284
409
|
}
|
|
285
410
|
function stripOpReadStdoutTerminator(stdout) {
|
|
286
411
|
if (stdout.endsWith("\r\n")) return stdout.slice(0, -2);
|
|
287
412
|
if (stdout.endsWith("\n")) return stdout.slice(0, -1);
|
|
288
413
|
return stdout;
|
|
289
414
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
"WINDIR",
|
|
315
|
-
"XDG_CACHE_HOME",
|
|
316
|
-
"XDG_CONFIG_HOME",
|
|
317
|
-
"XDG_DATA_HOME",
|
|
318
|
-
"XDG_RUNTIME_DIR"
|
|
319
|
-
];
|
|
320
|
-
function createOpCliServiceAccountEnv(serviceAccountToken) {
|
|
321
|
-
const env = {};
|
|
322
|
-
for (const envName of opCliProcessPlumbingEnvNames) {
|
|
323
|
-
const envValue = process.env[envName];
|
|
324
|
-
if (envValue !== void 0) env[envName] = envValue;
|
|
415
|
+
function opWhoamiReportsServiceAccount(stdout) {
|
|
416
|
+
return /^User Type:\s*SERVICE_ACCOUNT\s*$/imu.test(stdout);
|
|
417
|
+
}
|
|
418
|
+
async function probeOnePasswordServiceAccountHeadlessAuth(options, dependencies = {}) {
|
|
419
|
+
const exec = dependencies.execFileAsync ?? execFileAsync;
|
|
420
|
+
try {
|
|
421
|
+
return await withOpCliServiceAccountEnv(options.serviceAccountToken, async (env) => {
|
|
422
|
+
if (opWhoamiReportsServiceAccount((await exec("op", ["whoami"], {
|
|
423
|
+
env,
|
|
424
|
+
redactErrorOutput: true
|
|
425
|
+
})).stdout)) return {
|
|
426
|
+
hint: "op whoami returned SERVICE_ACCOUNT with isolated service-account env",
|
|
427
|
+
ok: true
|
|
428
|
+
};
|
|
429
|
+
return {
|
|
430
|
+
hint: "op whoami did not report SERVICE_ACCOUNT with isolated service-account env",
|
|
431
|
+
ok: false
|
|
432
|
+
};
|
|
433
|
+
});
|
|
434
|
+
} catch (error) {
|
|
435
|
+
return {
|
|
436
|
+
hint: `op whoami failed with isolated service-account env: ${formatUnknownErrorWithRedactions(error, [options.serviceAccountToken])}`,
|
|
437
|
+
ok: false
|
|
438
|
+
};
|
|
325
439
|
}
|
|
326
|
-
env.OP_SERVICE_ACCOUNT_TOKEN = serviceAccountToken;
|
|
327
|
-
return env;
|
|
328
440
|
}
|
|
329
441
|
const opInjectTemplateDelimiterPattern = /(?:\{\{|\}\})/u;
|
|
330
442
|
function assertOpInjectTemplateSafeReference(entry) {
|
|
@@ -343,18 +455,21 @@ function createAggregateErrorWithCause(options) {
|
|
|
343
455
|
aggregateError.cause = options.cause;
|
|
344
456
|
return aggregateError;
|
|
345
457
|
}
|
|
346
|
-
function createFallbackStageError(stage, error) {
|
|
347
|
-
|
|
458
|
+
function createFallbackStageError(stage, error, secretValues = []) {
|
|
459
|
+
const message = `${stage} failed before op CLI fallback: ${formatUnknownErrorWithRedactions(error, secretValues)}`;
|
|
460
|
+
return secretValues.length === 0 ? new Error(message, { cause: error }) : new Error(message);
|
|
348
461
|
}
|
|
349
462
|
function createFallbackFailureError(options) {
|
|
463
|
+
const fallbackMessage = formatUnknownErrorWithRedactions(options.fallbackError, options.secretValues ?? []);
|
|
464
|
+
const redactedFallbackError = options.secretValues === void 0 || options.secretValues.length === 0 ? new Error(fallbackMessage, { cause: options.fallbackError }) : new Error(fallbackMessage);
|
|
350
465
|
if (options.fallbackError instanceof AggregateError) return createAggregateErrorWithCause({
|
|
351
|
-
cause:
|
|
352
|
-
errors: [options.stageError,
|
|
353
|
-
message:
|
|
466
|
+
cause: redactedFallbackError,
|
|
467
|
+
errors: [options.stageError, redactedFallbackError],
|
|
468
|
+
message: fallbackMessage
|
|
354
469
|
});
|
|
355
470
|
return createAggregateErrorWithCause({
|
|
356
|
-
cause:
|
|
357
|
-
errors: [options.stageError,
|
|
471
|
+
cause: redactedFallbackError,
|
|
472
|
+
errors: [options.stageError, redactedFallbackError],
|
|
358
473
|
message: options.message
|
|
359
474
|
});
|
|
360
475
|
}
|
|
@@ -383,8 +498,8 @@ function buildOpInjectTemplate(entries) {
|
|
|
383
498
|
}
|
|
384
499
|
function findUniqueOpInjectMarker(options) {
|
|
385
500
|
const markerIndex = options.output.indexOf(options.marker);
|
|
386
|
-
if (markerIndex === -1) throw new OpInjectOutputError(`op inject output omitted ${options.markerDescription} marker for secret '${options.secretName}'
|
|
387
|
-
if (options.output.indexOf(options.marker, markerIndex + options.marker.length) !== -1) throw new OpInjectOutputError(`op inject output for secret '${options.secretName}'
|
|
501
|
+
if (markerIndex === -1) throw new OpInjectOutputError(`op inject output omitted ${options.markerDescription} marker for secret '${options.secretName}'.`);
|
|
502
|
+
if (options.output.indexOf(options.marker, markerIndex + options.marker.length) !== -1) throw new OpInjectOutputError(`op inject output for secret '${options.secretName}' contained repeated ${options.markerDescription} marker.`);
|
|
388
503
|
return markerIndex;
|
|
389
504
|
}
|
|
390
505
|
function extractInjectedSecret(options) {
|
|
@@ -394,15 +509,13 @@ function extractInjectedSecret(options) {
|
|
|
394
509
|
marker: startToken,
|
|
395
510
|
markerDescription: "start",
|
|
396
511
|
output: options.output,
|
|
397
|
-
secretName: options.entry.secretName
|
|
398
|
-
secretReference: options.entry.secretRef.ref
|
|
512
|
+
secretName: options.entry.secretName
|
|
399
513
|
}) + startToken.length;
|
|
400
514
|
const secretEndIndex = findUniqueOpInjectMarker({
|
|
401
515
|
marker: endToken,
|
|
402
516
|
markerDescription: "end",
|
|
403
517
|
output: options.output,
|
|
404
|
-
secretName: options.entry.secretName
|
|
405
|
-
secretReference: options.entry.secretRef.ref
|
|
518
|
+
secretName: options.entry.secretName
|
|
406
519
|
});
|
|
407
520
|
return options.output.slice(secretStartIndex, secretEndIndex);
|
|
408
521
|
}
|
|
@@ -415,25 +528,27 @@ function mapOpInjectOutput(entries, output) {
|
|
|
415
528
|
async function resolveAllSecretsWithOpInject(serviceAccountToken, refs, exec) {
|
|
416
529
|
const entries = createOpInjectEntries(refs);
|
|
417
530
|
if (entries.length === 0) return {};
|
|
418
|
-
return
|
|
419
|
-
"
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
531
|
+
return await withOpCliServiceAccountEnv(serviceAccountToken, async (env) => {
|
|
532
|
+
return mapOpInjectOutput(entries, (await exec("op", [
|
|
533
|
+
"inject",
|
|
534
|
+
"--in-file",
|
|
535
|
+
"/dev/stdin"
|
|
536
|
+
], {
|
|
537
|
+
env,
|
|
538
|
+
input: buildOpInjectTemplate(entries),
|
|
539
|
+
redactErrorOutput: true
|
|
540
|
+
})).stdout);
|
|
541
|
+
});
|
|
427
542
|
}
|
|
428
543
|
function formatResolveReferenceError(error) {
|
|
429
544
|
return "message" in error && typeof error.message === "string" ? `${error.type}: ${error.message}` : error.type;
|
|
430
545
|
}
|
|
431
546
|
function readSdkBatchSecret(options) {
|
|
432
547
|
const individualResponse = options.response.individualResponses[options.secretReference];
|
|
433
|
-
if (!individualResponse) throw new Error(`1Password SDK resolveAll response omitted '${options.secretName}'
|
|
548
|
+
if (!individualResponse) throw new Error(`1Password SDK resolveAll response omitted '${options.secretName}'.`);
|
|
434
549
|
if (individualResponse.content !== void 0) return individualResponse.content.secret;
|
|
435
|
-
if (individualResponse.error !== void 0) throw new Error(`1Password SDK resolveAll failed for '${options.secretName}'
|
|
436
|
-
throw new Error(`1Password SDK resolveAll returned neither content nor error for '${options.secretName}'
|
|
550
|
+
if (individualResponse.error !== void 0) throw new Error(`1Password SDK resolveAll failed for '${options.secretName}': ${formatResolveReferenceError(individualResponse.error)}`);
|
|
551
|
+
throw new Error(`1Password SDK resolveAll returned neither content nor error for '${options.secretName}'.`);
|
|
437
552
|
}
|
|
438
553
|
function mapSdkResolveAllResponse(refs, response) {
|
|
439
554
|
return Object.fromEntries(Object.entries(refs).map(([secretName, secretRef]) => [secretName, readSdkBatchSecret({
|
|
@@ -461,13 +576,14 @@ async function createSecretResolver(options, dependencies = {}) {
|
|
|
461
576
|
try {
|
|
462
577
|
return await client.secrets.resolve(ref.ref);
|
|
463
578
|
} catch (error) {
|
|
464
|
-
const sdkResolveError = createFallbackStageError("1Password SDK resolve", error);
|
|
579
|
+
const sdkResolveError = createFallbackStageError("1Password SDK resolve", error, [options.serviceAccountToken]);
|
|
465
580
|
try {
|
|
466
581
|
return await resolveSecretWithOpCli(options.serviceAccountToken, ref.ref, exec);
|
|
467
582
|
} catch (fallbackError) {
|
|
468
583
|
throw createFallbackFailureError({
|
|
469
584
|
fallbackError,
|
|
470
585
|
message: "1Password SDK resolve and op CLI fallback both failed.",
|
|
586
|
+
secretValues: [options.serviceAccountToken],
|
|
471
587
|
stageError: sdkResolveError
|
|
472
588
|
});
|
|
473
589
|
}
|
|
@@ -480,13 +596,14 @@ async function createSecretResolver(options, dependencies = {}) {
|
|
|
480
596
|
const response = await client.secrets.resolveAll(Object.values(splitRefs.onePasswordRefs).map((secretRef) => secretRef.ref));
|
|
481
597
|
return mergeResolvedSecrets(splitRefs.resolvedSecrets, mapSdkResolveAllResponse(splitRefs.onePasswordRefs, response));
|
|
482
598
|
} catch (error) {
|
|
483
|
-
const sdkResolveAllError = createFallbackStageError("1Password SDK resolveAll", error);
|
|
599
|
+
const sdkResolveAllError = createFallbackStageError("1Password SDK resolveAll", error, [options.serviceAccountToken]);
|
|
484
600
|
try {
|
|
485
601
|
return mergeResolvedSecrets(splitRefs.resolvedSecrets, await resolveAllSecretsWithOpCli(options.serviceAccountToken, splitRefs.onePasswordRefs, exec));
|
|
486
602
|
} catch (fallbackError) {
|
|
487
603
|
throw createFallbackFailureError({
|
|
488
604
|
fallbackError,
|
|
489
605
|
message: "1Password SDK resolveAll and op CLI fallback both failed.",
|
|
606
|
+
secretValues: [options.serviceAccountToken],
|
|
490
607
|
stageError: sdkResolveAllError
|
|
491
608
|
});
|
|
492
609
|
}
|
|
@@ -494,7 +611,7 @@ async function createSecretResolver(options, dependencies = {}) {
|
|
|
494
611
|
}
|
|
495
612
|
};
|
|
496
613
|
} catch (error) {
|
|
497
|
-
const sdkClientCreationError = createFallbackStageError("1Password SDK client creation", error);
|
|
614
|
+
const sdkClientCreationError = createFallbackStageError("1Password SDK client creation", error, [options.serviceAccountToken]);
|
|
498
615
|
return {
|
|
499
616
|
resolve: async (ref) => {
|
|
500
617
|
switch (ref.source) {
|
|
@@ -509,6 +626,7 @@ async function createSecretResolver(options, dependencies = {}) {
|
|
|
509
626
|
throw createFallbackFailureError({
|
|
510
627
|
fallbackError,
|
|
511
628
|
message: "1Password SDK client creation and op CLI fallback both failed.",
|
|
629
|
+
secretValues: [options.serviceAccountToken],
|
|
512
630
|
stageError: sdkClientCreationError
|
|
513
631
|
});
|
|
514
632
|
}
|
|
@@ -522,6 +640,7 @@ async function createSecretResolver(options, dependencies = {}) {
|
|
|
522
640
|
throw createFallbackFailureError({
|
|
523
641
|
fallbackError,
|
|
524
642
|
message: "1Password SDK client creation and op CLI fallback both failed.",
|
|
643
|
+
secretValues: [options.serviceAccountToken],
|
|
525
644
|
stageError: sdkClientCreationError
|
|
526
645
|
});
|
|
527
646
|
}
|
|
@@ -571,4 +690,4 @@ function readOnePasswordE2eTestConfig(env = process.env) {
|
|
|
571
690
|
};
|
|
572
691
|
}
|
|
573
692
|
//#endregion
|
|
574
|
-
export { createCompositeSecretResolver, createOpCliSecretResolver, createSecretResolver, createStaticSecretResolver, defaultOnePasswordE2eVaultPrefix, readOnePasswordE2eTestConfig, resolveServiceAccountToken };
|
|
693
|
+
export { createCompositeSecretResolver, createOpCliSecretResolver, createSecretResolver, createStaticSecretResolver, defaultOnePasswordE2eVaultPrefix, probeOnePasswordServiceAccountHeadlessAuth, readOnePasswordE2eTestConfig, redactOnePasswordReferences, resolveServiceAccountToken };
|
package/package.json
CHANGED