@kustodian/plugin-1password 1.1.1 → 2.0.0
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/executor.d.ts +37 -0
- package/dist/executor.d.ts.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +442 -0
- package/dist/plugin.d.ts +12 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/resolver.d.ts +11 -0
- package/dist/resolver.d.ts.map +1 -0
- package/dist/types.d.ts +39 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +14 -11
- package/src/executor.ts +0 -190
- package/src/index.ts +0 -24
- package/src/plugin.ts +0 -118
- package/src/resolver.ts +0 -76
- package/src/types.ts +0 -78
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { KustodianErrorType } from '@kustodian/core';
|
|
2
|
+
import { type ResultType } from '@kustodian/core';
|
|
3
|
+
import { type OnePasswordPluginOptionsType } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Result of a command execution.
|
|
6
|
+
*/
|
|
7
|
+
export interface CommandResultType {
|
|
8
|
+
stdout: string;
|
|
9
|
+
stderr: string;
|
|
10
|
+
exit_code: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Options for command execution.
|
|
14
|
+
*/
|
|
15
|
+
export interface ExecOptionsType {
|
|
16
|
+
cwd?: string | undefined;
|
|
17
|
+
timeout?: number | undefined;
|
|
18
|
+
env?: Record<string, string> | undefined;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Executes a command and returns the result.
|
|
22
|
+
* Uses execFile instead of exec for security (no shell invocation).
|
|
23
|
+
*/
|
|
24
|
+
export declare function exec_command(command: string, args?: string[], options?: ExecOptionsType): Promise<ResultType<CommandResultType, KustodianErrorType>>;
|
|
25
|
+
/**
|
|
26
|
+
* Checks if op CLI is available in the system PATH.
|
|
27
|
+
*/
|
|
28
|
+
export declare function check_op_available(): Promise<ResultType<string, KustodianErrorType>>;
|
|
29
|
+
/**
|
|
30
|
+
* Reads a single secret from 1Password.
|
|
31
|
+
*/
|
|
32
|
+
export declare function op_read(ref: string, options?: OnePasswordPluginOptionsType): Promise<ResultType<string, KustodianErrorType>>;
|
|
33
|
+
/**
|
|
34
|
+
* Reads multiple secrets from 1Password in batch.
|
|
35
|
+
*/
|
|
36
|
+
export declare function op_read_batch(refs: string[], options?: OnePasswordPluginOptionsType): Promise<ResultType<Record<string, string>, KustodianErrorType>>;
|
|
37
|
+
//# sourceMappingURL=executor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../src/executor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAU,KAAK,UAAU,EAAoB,MAAM,iBAAiB,CAAC;AAE5E,OAAO,EAAmB,KAAK,4BAA4B,EAAE,MAAM,YAAY,CAAC;AAIhF;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;CAC1C;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,MAAM,EAAO,EACnB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,UAAU,CAAC,iBAAiB,EAAE,kBAAkB,CAAC,CAAC,CAwB5D;AA6BD;;GAEG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,UAAU,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAc1F;AAED;;GAEG;AACH,wBAAsB,OAAO,CAC3B,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,4BAAiC,GACzC,OAAO,CAAC,UAAU,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,CA4CjD;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,GAAE,4BAAiC,GACzC,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAuBjE"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { type CommandResultType, check_op_available, type ExecOptionsType, exec_command, op_read, op_read_batch, } from './executor.js';
|
|
2
|
+
export { create_onepassword_plugin, plugin, plugin as default } from './plugin.js';
|
|
3
|
+
export { resolve_onepassword_substitutions } from './resolver.js';
|
|
4
|
+
export { DEFAULT_TIMEOUT, type OnePasswordPluginOptionsType, type OnePasswordRefType, parse_onepassword_ref, } from './types.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,iBAAiB,EACtB,kBAAkB,EAClB,KAAK,eAAe,EACpB,YAAY,EACZ,OAAO,EACP,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,yBAAyB,EAAE,MAAM,EAAE,MAAM,IAAI,OAAO,EAAE,MAAM,aAAa,CAAC;AAGnF,OAAO,EAAE,iCAAiC,EAAE,MAAM,eAAe,CAAC;AAGlE,OAAO,EACL,eAAe,EACf,KAAK,4BAA4B,EACjC,KAAK,kBAAkB,EACvB,qBAAqB,GACtB,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
// src/executor.ts
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
|
|
5
|
+
// ../../packages/core/dist/index.js
|
|
6
|
+
function create_error(code, message, cause) {
|
|
7
|
+
return { code, message, cause };
|
|
8
|
+
}
|
|
9
|
+
var ErrorCodes = {
|
|
10
|
+
UNKNOWN: "UNKNOWN",
|
|
11
|
+
INVALID_ARGUMENT: "INVALID_ARGUMENT",
|
|
12
|
+
NOT_FOUND: "NOT_FOUND",
|
|
13
|
+
ALREADY_EXISTS: "ALREADY_EXISTS",
|
|
14
|
+
FILE_NOT_FOUND: "FILE_NOT_FOUND",
|
|
15
|
+
FILE_READ_ERROR: "FILE_READ_ERROR",
|
|
16
|
+
FILE_WRITE_ERROR: "FILE_WRITE_ERROR",
|
|
17
|
+
DIRECTORY_NOT_FOUND: "DIRECTORY_NOT_FOUND",
|
|
18
|
+
PARSE_ERROR: "PARSE_ERROR",
|
|
19
|
+
YAML_PARSE_ERROR: "YAML_PARSE_ERROR",
|
|
20
|
+
JSON_PARSE_ERROR: "JSON_PARSE_ERROR",
|
|
21
|
+
VALIDATION_ERROR: "VALIDATION_ERROR",
|
|
22
|
+
SCHEMA_VALIDATION_ERROR: "SCHEMA_VALIDATION_ERROR",
|
|
23
|
+
CONFIG_NOT_FOUND: "CONFIG_NOT_FOUND",
|
|
24
|
+
CONFIG_INVALID: "CONFIG_INVALID",
|
|
25
|
+
TEMPLATE_NOT_FOUND: "TEMPLATE_NOT_FOUND",
|
|
26
|
+
CLUSTER_NOT_FOUND: "CLUSTER_NOT_FOUND",
|
|
27
|
+
PROFILE_NOT_FOUND: "PROFILE_NOT_FOUND",
|
|
28
|
+
NETWORK_ERROR: "NETWORK_ERROR",
|
|
29
|
+
CONNECTION_REFUSED: "CONNECTION_REFUSED",
|
|
30
|
+
TIMEOUT: "TIMEOUT",
|
|
31
|
+
SSH_CONNECTION_ERROR: "SSH_CONNECTION_ERROR",
|
|
32
|
+
SSH_AUTH_ERROR: "SSH_AUTH_ERROR",
|
|
33
|
+
SSH_COMMAND_ERROR: "SSH_COMMAND_ERROR",
|
|
34
|
+
K8S_CONNECTION_ERROR: "K8S_CONNECTION_ERROR",
|
|
35
|
+
K8S_API_ERROR: "K8S_API_ERROR",
|
|
36
|
+
NODE_NOT_READY: "NODE_NOT_READY",
|
|
37
|
+
BOOTSTRAP_ERROR: "BOOTSTRAP_ERROR",
|
|
38
|
+
BOOTSTRAP_STATE_ERROR: "BOOTSTRAP_STATE_ERROR",
|
|
39
|
+
CLUSTER_PROVIDER_ERROR: "CLUSTER_PROVIDER_ERROR",
|
|
40
|
+
PLUGIN_NOT_FOUND: "PLUGIN_NOT_FOUND",
|
|
41
|
+
PLUGIN_LOAD_ERROR: "PLUGIN_LOAD_ERROR",
|
|
42
|
+
PLUGIN_EXECUTION_ERROR: "PLUGIN_EXECUTION_ERROR",
|
|
43
|
+
DEPENDENCY_CYCLE: "DEPENDENCY_CYCLE",
|
|
44
|
+
DEPENDENCY_MISSING: "DEPENDENCY_MISSING",
|
|
45
|
+
DEPENDENCY_SELF_REFERENCE: "DEPENDENCY_SELF_REFERENCE",
|
|
46
|
+
DEPENDENCY_VALIDATION_ERROR: "DEPENDENCY_VALIDATION_ERROR",
|
|
47
|
+
SECRET_CLI_NOT_FOUND: "SECRET_CLI_NOT_FOUND",
|
|
48
|
+
SECRET_NOT_FOUND: "SECRET_NOT_FOUND",
|
|
49
|
+
SECRET_AUTH_ERROR: "SECRET_AUTH_ERROR",
|
|
50
|
+
SECRET_TIMEOUT: "SECRET_TIMEOUT",
|
|
51
|
+
SOURCE_FETCH_ERROR: "SOURCE_FETCH_ERROR",
|
|
52
|
+
SOURCE_AUTH_ERROR: "SOURCE_AUTH_ERROR",
|
|
53
|
+
SOURCE_TIMEOUT: "SOURCE_TIMEOUT",
|
|
54
|
+
SOURCE_CHECKSUM_MISMATCH: "SOURCE_CHECKSUM_MISMATCH",
|
|
55
|
+
SOURCE_VERSION_NOT_FOUND: "SOURCE_VERSION_NOT_FOUND",
|
|
56
|
+
CACHE_READ_ERROR: "CACHE_READ_ERROR",
|
|
57
|
+
CACHE_WRITE_ERROR: "CACHE_WRITE_ERROR",
|
|
58
|
+
CACHE_CORRUPT: "CACHE_CORRUPT"
|
|
59
|
+
};
|
|
60
|
+
var Errors = {
|
|
61
|
+
unknown(message, cause) {
|
|
62
|
+
return create_error(ErrorCodes.UNKNOWN, message, cause);
|
|
63
|
+
},
|
|
64
|
+
invalid_argument(argument, reason) {
|
|
65
|
+
return create_error(ErrorCodes.INVALID_ARGUMENT, `Invalid argument '${argument}': ${reason}`);
|
|
66
|
+
},
|
|
67
|
+
not_found(resource, identifier) {
|
|
68
|
+
return create_error(ErrorCodes.NOT_FOUND, `${resource} '${identifier}' not found`);
|
|
69
|
+
},
|
|
70
|
+
already_exists(resource, identifier) {
|
|
71
|
+
return create_error(ErrorCodes.ALREADY_EXISTS, `${resource} '${identifier}' already exists`);
|
|
72
|
+
},
|
|
73
|
+
file_not_found(path) {
|
|
74
|
+
return create_error(ErrorCodes.FILE_NOT_FOUND, `File not found: ${path}`);
|
|
75
|
+
},
|
|
76
|
+
file_read_error(path, cause) {
|
|
77
|
+
return create_error(ErrorCodes.FILE_READ_ERROR, `Failed to read file: ${path}`, cause);
|
|
78
|
+
},
|
|
79
|
+
file_write_error(path, cause) {
|
|
80
|
+
return create_error(ErrorCodes.FILE_WRITE_ERROR, `Failed to write file: ${path}`, cause);
|
|
81
|
+
},
|
|
82
|
+
parse_error(format, message, cause) {
|
|
83
|
+
return create_error(ErrorCodes.PARSE_ERROR, `Failed to parse ${format}: ${message}`, cause);
|
|
84
|
+
},
|
|
85
|
+
yaml_parse_error(message, cause) {
|
|
86
|
+
return create_error(ErrorCodes.YAML_PARSE_ERROR, `YAML parse error: ${message}`, cause);
|
|
87
|
+
},
|
|
88
|
+
validation_error(message, cause) {
|
|
89
|
+
return create_error(ErrorCodes.VALIDATION_ERROR, message, cause);
|
|
90
|
+
},
|
|
91
|
+
schema_validation_error(errors) {
|
|
92
|
+
return create_error(ErrorCodes.SCHEMA_VALIDATION_ERROR, `Schema validation failed:
|
|
93
|
+
${errors.map((e) => ` - ${e}`).join(`
|
|
94
|
+
`)}`);
|
|
95
|
+
},
|
|
96
|
+
config_not_found(config_type, path) {
|
|
97
|
+
return create_error(ErrorCodes.CONFIG_NOT_FOUND, `${config_type} configuration not found at: ${path}`);
|
|
98
|
+
},
|
|
99
|
+
template_not_found(name) {
|
|
100
|
+
return create_error(ErrorCodes.TEMPLATE_NOT_FOUND, `Template '${name}' not found`);
|
|
101
|
+
},
|
|
102
|
+
cluster_not_found(name) {
|
|
103
|
+
return create_error(ErrorCodes.CLUSTER_NOT_FOUND, `Cluster '${name}' not found`);
|
|
104
|
+
},
|
|
105
|
+
profile_not_found(name) {
|
|
106
|
+
return create_error(ErrorCodes.PROFILE_NOT_FOUND, `Node profile '${name}' not found`);
|
|
107
|
+
},
|
|
108
|
+
ssh_connection_error(host, cause) {
|
|
109
|
+
return create_error(ErrorCodes.SSH_CONNECTION_ERROR, `Failed to connect to ${host} via SSH`, cause);
|
|
110
|
+
},
|
|
111
|
+
ssh_auth_error(host, cause) {
|
|
112
|
+
return create_error(ErrorCodes.SSH_AUTH_ERROR, `SSH authentication failed for ${host}`, cause);
|
|
113
|
+
},
|
|
114
|
+
bootstrap_error(message, cause) {
|
|
115
|
+
return create_error(ErrorCodes.BOOTSTRAP_ERROR, `Bootstrap failed: ${message}`, cause);
|
|
116
|
+
},
|
|
117
|
+
plugin_not_found(name) {
|
|
118
|
+
return create_error(ErrorCodes.PLUGIN_NOT_FOUND, `Plugin '${name}' not found`);
|
|
119
|
+
},
|
|
120
|
+
plugin_load_error(name, cause) {
|
|
121
|
+
return create_error(ErrorCodes.PLUGIN_LOAD_ERROR, `Failed to load plugin '${name}'`, cause);
|
|
122
|
+
},
|
|
123
|
+
dependency_cycle(cycle) {
|
|
124
|
+
const cycle_str = cycle.join(" → ");
|
|
125
|
+
return create_error(ErrorCodes.DEPENDENCY_CYCLE, `Dependency cycle detected: ${cycle_str}`);
|
|
126
|
+
},
|
|
127
|
+
dependency_missing(source, target) {
|
|
128
|
+
return create_error(ErrorCodes.DEPENDENCY_MISSING, `Kustomization '${source}' depends on '${target}' which does not exist`);
|
|
129
|
+
},
|
|
130
|
+
dependency_self_reference(node) {
|
|
131
|
+
return create_error(ErrorCodes.DEPENDENCY_SELF_REFERENCE, `Kustomization '${node}' cannot depend on itself`);
|
|
132
|
+
},
|
|
133
|
+
dependency_validation_error(errors) {
|
|
134
|
+
return create_error(ErrorCodes.DEPENDENCY_VALIDATION_ERROR, `Dependency validation failed:
|
|
135
|
+
${errors.map((e) => ` - ${e}`).join(`
|
|
136
|
+
`)}`);
|
|
137
|
+
},
|
|
138
|
+
secret_cli_not_found(provider, cli_name) {
|
|
139
|
+
return create_error(ErrorCodes.SECRET_CLI_NOT_FOUND, `${provider} CLI (${cli_name}) not found. Please install it first.`);
|
|
140
|
+
},
|
|
141
|
+
secret_not_found(provider, ref) {
|
|
142
|
+
return create_error(ErrorCodes.SECRET_NOT_FOUND, `Secret not found in ${provider}: ${ref}`);
|
|
143
|
+
},
|
|
144
|
+
secret_auth_error(provider, cause) {
|
|
145
|
+
return create_error(ErrorCodes.SECRET_AUTH_ERROR, `${provider} authentication failed. Check your credentials.`, cause);
|
|
146
|
+
},
|
|
147
|
+
secret_timeout(provider, timeout) {
|
|
148
|
+
return create_error(ErrorCodes.SECRET_TIMEOUT, `${provider} operation timed out after ${timeout}ms`);
|
|
149
|
+
},
|
|
150
|
+
source_fetch_error(source, cause) {
|
|
151
|
+
return create_error(ErrorCodes.SOURCE_FETCH_ERROR, `Failed to fetch template source '${source}'`, cause);
|
|
152
|
+
},
|
|
153
|
+
source_auth_error(source, cause) {
|
|
154
|
+
return create_error(ErrorCodes.SOURCE_AUTH_ERROR, `Authentication failed for source '${source}'`, cause);
|
|
155
|
+
},
|
|
156
|
+
source_timeout(source, timeout) {
|
|
157
|
+
return create_error(ErrorCodes.SOURCE_TIMEOUT, `Source '${source}' operation timed out after ${timeout}ms`);
|
|
158
|
+
},
|
|
159
|
+
source_checksum_mismatch(source, expected, actual) {
|
|
160
|
+
return create_error(ErrorCodes.SOURCE_CHECKSUM_MISMATCH, `Checksum mismatch for '${source}': expected ${expected}, got ${actual}`);
|
|
161
|
+
},
|
|
162
|
+
source_version_not_found(source, version) {
|
|
163
|
+
return create_error(ErrorCodes.SOURCE_VERSION_NOT_FOUND, `Version '${version}' not found for source '${source}'`);
|
|
164
|
+
},
|
|
165
|
+
cache_read_error(path, cause) {
|
|
166
|
+
return create_error(ErrorCodes.CACHE_READ_ERROR, `Failed to read cache at: ${path}`, cause);
|
|
167
|
+
},
|
|
168
|
+
cache_write_error(path, cause) {
|
|
169
|
+
return create_error(ErrorCodes.CACHE_WRITE_ERROR, `Failed to write cache at: ${path}`, cause);
|
|
170
|
+
},
|
|
171
|
+
cache_corrupt(path) {
|
|
172
|
+
return create_error(ErrorCodes.CACHE_CORRUPT, `Cache is corrupt at: ${path}`);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
function success(value) {
|
|
176
|
+
return { success: true, value };
|
|
177
|
+
}
|
|
178
|
+
function failure(error) {
|
|
179
|
+
return { success: false, error };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/types.ts
|
|
183
|
+
var DEFAULT_TIMEOUT = 30000;
|
|
184
|
+
function parse_onepassword_ref(ref) {
|
|
185
|
+
const match = ref.match(/^op:\/\/([^/]+)\/([^/]+)\/(?:([^/]+)\/)?([^/]+)$/);
|
|
186
|
+
if (!match) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const vault = match[1];
|
|
190
|
+
const item = match[2];
|
|
191
|
+
const section = match[3];
|
|
192
|
+
const field = match[4];
|
|
193
|
+
if (field === undefined) {
|
|
194
|
+
if (!vault || !item || !section) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
vault,
|
|
199
|
+
item,
|
|
200
|
+
field: section
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
if (!vault || !item || !field) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
vault,
|
|
208
|
+
item,
|
|
209
|
+
section,
|
|
210
|
+
field
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// src/executor.ts
|
|
215
|
+
var exec_file_async = promisify(execFile);
|
|
216
|
+
async function exec_command(command, args = [], options = {}) {
|
|
217
|
+
try {
|
|
218
|
+
const { stdout, stderr } = await exec_file_async(command, args, {
|
|
219
|
+
cwd: options.cwd,
|
|
220
|
+
timeout: options.timeout,
|
|
221
|
+
env: { ...process.env, ...options.env }
|
|
222
|
+
});
|
|
223
|
+
return success({
|
|
224
|
+
stdout,
|
|
225
|
+
stderr,
|
|
226
|
+
exit_code: 0
|
|
227
|
+
});
|
|
228
|
+
} catch (error) {
|
|
229
|
+
if (is_exec_error(error)) {
|
|
230
|
+
return success({
|
|
231
|
+
stdout: error.stdout ?? "",
|
|
232
|
+
stderr: error.stderr ?? "",
|
|
233
|
+
exit_code: error.code ?? 1
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
return failure(Errors.unknown(`Failed to execute command: ${command}`, error));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function is_exec_error(error) {
|
|
240
|
+
return typeof error === "object" && error !== null && (("stdout" in error) || ("stderr" in error) || ("code" in error));
|
|
241
|
+
}
|
|
242
|
+
function get_op_auth_env(options) {
|
|
243
|
+
const env = {};
|
|
244
|
+
const token = options.service_account_token ?? process.env["OP_SERVICE_ACCOUNT_TOKEN"];
|
|
245
|
+
if (token) {
|
|
246
|
+
env["OP_SERVICE_ACCOUNT_TOKEN"] = token;
|
|
247
|
+
}
|
|
248
|
+
return env;
|
|
249
|
+
}
|
|
250
|
+
async function check_op_available() {
|
|
251
|
+
const result = await exec_command("op", ["--version"]);
|
|
252
|
+
if (!result.success) {
|
|
253
|
+
return result;
|
|
254
|
+
}
|
|
255
|
+
if (result.value.exit_code !== 0) {
|
|
256
|
+
return failure(Errors.secret_cli_not_found("1Password", "op"));
|
|
257
|
+
}
|
|
258
|
+
const version = result.value.stdout.trim();
|
|
259
|
+
return success(version);
|
|
260
|
+
}
|
|
261
|
+
async function op_read(ref, options = {}) {
|
|
262
|
+
const timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
263
|
+
const env = get_op_auth_env(options);
|
|
264
|
+
const result = await exec_command("op", ["read", ref], {
|
|
265
|
+
timeout,
|
|
266
|
+
env
|
|
267
|
+
});
|
|
268
|
+
if (!result.success) {
|
|
269
|
+
return result;
|
|
270
|
+
}
|
|
271
|
+
const { stdout, stderr, exit_code } = result.value;
|
|
272
|
+
if (exit_code !== 0) {
|
|
273
|
+
const stderr_lower = stderr.toLowerCase();
|
|
274
|
+
if (stderr_lower.includes("not found") || stderr_lower.includes("doesn't exist") || stderr_lower.includes("no item")) {
|
|
275
|
+
return failure(Errors.secret_not_found("1Password", ref));
|
|
276
|
+
}
|
|
277
|
+
if (stderr_lower.includes("sign in") || stderr_lower.includes("authentication") || stderr_lower.includes("unauthorized") || stderr_lower.includes("not signed in")) {
|
|
278
|
+
return failure(Errors.secret_auth_error("1Password", stderr));
|
|
279
|
+
}
|
|
280
|
+
if (stderr_lower.includes("timeout")) {
|
|
281
|
+
return failure(Errors.secret_timeout("1Password", timeout));
|
|
282
|
+
}
|
|
283
|
+
return failure(Errors.unknown(`1Password error: ${stderr}`));
|
|
284
|
+
}
|
|
285
|
+
return success(stdout.trim());
|
|
286
|
+
}
|
|
287
|
+
async function op_read_batch(refs, options = {}) {
|
|
288
|
+
if (refs.length === 0) {
|
|
289
|
+
return success({});
|
|
290
|
+
}
|
|
291
|
+
const results = {};
|
|
292
|
+
for (const ref of refs) {
|
|
293
|
+
const result = await op_read(ref, options);
|
|
294
|
+
if (!result.success) {
|
|
295
|
+
if (options.fail_on_missing !== false) {
|
|
296
|
+
return result;
|
|
297
|
+
}
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
results[ref] = result.value;
|
|
301
|
+
}
|
|
302
|
+
return success(results);
|
|
303
|
+
}
|
|
304
|
+
// src/plugin.ts
|
|
305
|
+
var manifest = {
|
|
306
|
+
name: "@kustodian/plugin-1password",
|
|
307
|
+
version: "0.1.0",
|
|
308
|
+
description: "1Password secret provider for Kustodian",
|
|
309
|
+
capabilities: ["commands", "hooks"]
|
|
310
|
+
};
|
|
311
|
+
function create_onepassword_plugin(options = {}) {
|
|
312
|
+
return {
|
|
313
|
+
manifest,
|
|
314
|
+
async activate() {
|
|
315
|
+
const check_result = await check_op_available();
|
|
316
|
+
if (!check_result.success) {
|
|
317
|
+
console.warn("1Password CLI (op) not found - secret resolution may fail");
|
|
318
|
+
}
|
|
319
|
+
return success(undefined);
|
|
320
|
+
},
|
|
321
|
+
async deactivate() {
|
|
322
|
+
return success(undefined);
|
|
323
|
+
},
|
|
324
|
+
get_commands() {
|
|
325
|
+
const onepassword_command = {
|
|
326
|
+
name: "1password",
|
|
327
|
+
description: "1Password secret management commands",
|
|
328
|
+
subcommands: [
|
|
329
|
+
{
|
|
330
|
+
name: "check",
|
|
331
|
+
description: "Check 1Password CLI availability and authentication",
|
|
332
|
+
handler: async () => {
|
|
333
|
+
const result = await check_op_available();
|
|
334
|
+
if (result.success) {
|
|
335
|
+
console.log(`1Password CLI version: ${result.value}`);
|
|
336
|
+
return success(undefined);
|
|
337
|
+
}
|
|
338
|
+
console.error("1Password CLI not available");
|
|
339
|
+
return result;
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
name: "test",
|
|
344
|
+
description: "Test reading a secret reference",
|
|
345
|
+
arguments: [
|
|
346
|
+
{
|
|
347
|
+
name: "ref",
|
|
348
|
+
description: "Secret reference (op://vault/item/field)",
|
|
349
|
+
required: true
|
|
350
|
+
}
|
|
351
|
+
],
|
|
352
|
+
handler: async (ctx) => {
|
|
353
|
+
const ref = ctx.args[0];
|
|
354
|
+
if (!ref) {
|
|
355
|
+
console.error("Missing secret reference");
|
|
356
|
+
return success(undefined);
|
|
357
|
+
}
|
|
358
|
+
const result = await op_read(ref, options);
|
|
359
|
+
if (result.success) {
|
|
360
|
+
console.log("Secret retrieved successfully (value hidden)");
|
|
361
|
+
console.log(`Length: ${result.value.length} characters`);
|
|
362
|
+
return success(undefined);
|
|
363
|
+
}
|
|
364
|
+
console.error(`Failed to read secret: ${result.error.message}`);
|
|
365
|
+
return result;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
]
|
|
369
|
+
};
|
|
370
|
+
return [{ command: onepassword_command }];
|
|
371
|
+
},
|
|
372
|
+
get_hooks() {
|
|
373
|
+
return [
|
|
374
|
+
{
|
|
375
|
+
event: "generator:after_resolve",
|
|
376
|
+
priority: 50,
|
|
377
|
+
handler: async (_event, ctx) => {
|
|
378
|
+
return success(ctx);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
];
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
var plugin = create_onepassword_plugin();
|
|
386
|
+
// src/resolver.ts
|
|
387
|
+
async function resolve_onepassword_substitutions(substitutions, options = {}) {
|
|
388
|
+
if (substitutions.length === 0) {
|
|
389
|
+
return success({});
|
|
390
|
+
}
|
|
391
|
+
const results = {};
|
|
392
|
+
for (const sub of substitutions) {
|
|
393
|
+
let ref;
|
|
394
|
+
if (sub.ref) {
|
|
395
|
+
ref = sub.ref;
|
|
396
|
+
} else if (sub.item && sub.field) {
|
|
397
|
+
const vault = options.cluster_defaults?.vault;
|
|
398
|
+
if (!vault) {
|
|
399
|
+
return failure({
|
|
400
|
+
code: "MISSING_1PASSWORD_VAULT",
|
|
401
|
+
message: `1Password substitution '${sub.name}' uses shorthand but no cluster vault configured`
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
if (sub.section) {
|
|
405
|
+
ref = `op://${vault}/${sub.item}/${sub.section}/${sub.field}`;
|
|
406
|
+
} else {
|
|
407
|
+
ref = `op://${vault}/${sub.item}/${sub.field}`;
|
|
408
|
+
}
|
|
409
|
+
} else {
|
|
410
|
+
return failure({
|
|
411
|
+
code: "INVALID_1PASSWORD_REFERENCE",
|
|
412
|
+
message: `1Password substitution '${sub.name}' must specify either 'ref' or 'item'+'field'`
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
const result = await op_read(ref, options);
|
|
416
|
+
if (!result.success) {
|
|
417
|
+
if (sub.default !== undefined && options.fail_on_missing === false) {
|
|
418
|
+
results[sub.name] = sub.default;
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
if (sub.default !== undefined) {
|
|
422
|
+
results[sub.name] = sub.default;
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
return failure(result.error);
|
|
426
|
+
}
|
|
427
|
+
results[sub.name] = result.value;
|
|
428
|
+
}
|
|
429
|
+
return success(results);
|
|
430
|
+
}
|
|
431
|
+
export {
|
|
432
|
+
resolve_onepassword_substitutions,
|
|
433
|
+
plugin,
|
|
434
|
+
parse_onepassword_ref,
|
|
435
|
+
op_read_batch,
|
|
436
|
+
op_read,
|
|
437
|
+
exec_command,
|
|
438
|
+
plugin as default,
|
|
439
|
+
create_onepassword_plugin,
|
|
440
|
+
check_op_available,
|
|
441
|
+
DEFAULT_TIMEOUT
|
|
442
|
+
};
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { KustodianPluginType } from '@kustodian/plugins';
|
|
2
|
+
import type { OnePasswordPluginOptionsType } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Creates the 1Password plugin.
|
|
5
|
+
*/
|
|
6
|
+
export declare function create_onepassword_plugin(options?: OnePasswordPluginOptionsType): KustodianPluginType;
|
|
7
|
+
/**
|
|
8
|
+
* Default plugin export.
|
|
9
|
+
*/
|
|
10
|
+
export declare const plugin: KustodianPluginType;
|
|
11
|
+
export default plugin;
|
|
12
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAIV,mBAAmB,EAIpB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC;AAY/D;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,GAAE,4BAAiC,GACzC,mBAAmB,CAiFrB;AAED;;GAEG;AACH,eAAO,MAAM,MAAM,qBAA8B,CAAC;AAElD,eAAe,MAAM,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { KustodianErrorType } from '@kustodian/core';
|
|
2
|
+
import { type ResultType } from '@kustodian/core';
|
|
3
|
+
import type { OnePasswordSubstitutionType } from '@kustodian/schema';
|
|
4
|
+
import type { OnePasswordPluginOptionsType } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Resolves 1Password substitutions to actual secret values.
|
|
7
|
+
* Supports both full references and shorthand with cluster defaults.
|
|
8
|
+
* Returns a map from substitution name to resolved value.
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolve_onepassword_substitutions(substitutions: OnePasswordSubstitutionType[], options?: OnePasswordPluginOptionsType): Promise<ResultType<Record<string, string>, KustodianErrorType>>;
|
|
11
|
+
//# sourceMappingURL=resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../src/resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,KAAK,UAAU,EAAoB,MAAM,iBAAiB,CAAC;AACpE,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,mBAAmB,CAAC;AAGrE,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC;AAE/D;;;;GAIG;AACH,wBAAsB,iCAAiC,CACrD,aAAa,EAAE,2BAA2B,EAAE,EAC5C,OAAO,GAAE,4BAAiC,GACzC,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,kBAAkB,CAAC,CAAC,CA4DjE"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cluster-level 1Password defaults.
|
|
3
|
+
*/
|
|
4
|
+
export interface OnePasswordClusterDefaultsType {
|
|
5
|
+
/** Default vault name or ID */
|
|
6
|
+
vault?: string | undefined;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Options for the 1Password plugin.
|
|
10
|
+
*/
|
|
11
|
+
export interface OnePasswordPluginOptionsType {
|
|
12
|
+
/** Service account token (can also be set via OP_SERVICE_ACCOUNT_TOKEN env var) */
|
|
13
|
+
service_account_token?: string | undefined;
|
|
14
|
+
/** Timeout for CLI operations in milliseconds (default: 30000) */
|
|
15
|
+
timeout?: number | undefined;
|
|
16
|
+
/** Whether to fail on missing secrets (default: true) */
|
|
17
|
+
fail_on_missing?: boolean | undefined;
|
|
18
|
+
/** Cluster-level defaults for vault */
|
|
19
|
+
cluster_defaults?: OnePasswordClusterDefaultsType | undefined;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Parsed 1Password reference.
|
|
23
|
+
*/
|
|
24
|
+
export interface OnePasswordRefType {
|
|
25
|
+
vault: string;
|
|
26
|
+
item: string;
|
|
27
|
+
section?: string | undefined;
|
|
28
|
+
field: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Default timeout for 1Password CLI operations.
|
|
32
|
+
*/
|
|
33
|
+
export declare const DEFAULT_TIMEOUT = 30000;
|
|
34
|
+
/**
|
|
35
|
+
* Parses a 1Password secret reference.
|
|
36
|
+
* Format: op://vault/item[/section]/field
|
|
37
|
+
*/
|
|
38
|
+
export declare function parse_onepassword_ref(ref: string): OnePasswordRefType | undefined;
|
|
39
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC7C,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC3C,mFAAmF;IACnF,qBAAqB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,yDAAyD;IACzD,eAAe,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACtC,uCAAuC;IACvC,gBAAgB,CAAC,EAAE,8BAA8B,GAAG,SAAS,CAAC;CAC/D;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,eAAO,MAAM,eAAe,QAAQ,CAAC;AAErC;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAoCjF"}
|
package/package.json
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kustodian/plugin-1password",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "1Password secret provider plugin for Kustodian",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./
|
|
7
|
-
"types": "./
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
-
"types": "./
|
|
11
|
-
"import": "./
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
|
-
"
|
|
15
|
+
"dist"
|
|
16
16
|
],
|
|
17
17
|
"scripts": {
|
|
18
18
|
"test": "bun test",
|
|
19
19
|
"test:watch": "bun test --watch",
|
|
20
|
-
"typecheck": "bun run tsc --noEmit"
|
|
20
|
+
"typecheck": "bun run tsc --noEmit",
|
|
21
|
+
"build": "bun build src/index.ts --outdir dist --target node --format esm && tsc --emitDeclarationOnly --outDir dist",
|
|
22
|
+
"prepublishOnly": "bun run build"
|
|
21
23
|
},
|
|
22
24
|
"keywords": [
|
|
23
25
|
"kustodian",
|
|
@@ -34,12 +36,13 @@
|
|
|
34
36
|
"directory": "plugins/1password"
|
|
35
37
|
},
|
|
36
38
|
"publishConfig": {
|
|
37
|
-
"
|
|
39
|
+
"access": "public",
|
|
40
|
+
"registry": "https://registry.npmjs.org"
|
|
38
41
|
},
|
|
39
42
|
"dependencies": {
|
|
40
|
-
"@kustodian/core": "
|
|
41
|
-
"@kustodian/plugins": "
|
|
42
|
-
"@kustodian/schema": "
|
|
43
|
+
"@kustodian/core": "2.0.0",
|
|
44
|
+
"@kustodian/plugins": "2.0.0",
|
|
45
|
+
"@kustodian/schema": "2.0.0"
|
|
43
46
|
},
|
|
44
47
|
"devDependencies": {}
|
|
45
48
|
}
|
package/src/executor.ts
DELETED
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import { execFile } from 'node:child_process';
|
|
2
|
-
import { promisify } from 'node:util';
|
|
3
|
-
|
|
4
|
-
import { Errors, type ResultType, failure, success } from '@kustodian/core';
|
|
5
|
-
import type { KustodianErrorType } from '@kustodian/core';
|
|
6
|
-
|
|
7
|
-
import { DEFAULT_TIMEOUT, type OnePasswordPluginOptionsType } from './types.js';
|
|
8
|
-
|
|
9
|
-
const exec_file_async = promisify(execFile);
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Result of a command execution.
|
|
13
|
-
*/
|
|
14
|
-
export interface CommandResultType {
|
|
15
|
-
stdout: string;
|
|
16
|
-
stderr: string;
|
|
17
|
-
exit_code: number;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Options for command execution.
|
|
22
|
-
*/
|
|
23
|
-
export interface ExecOptionsType {
|
|
24
|
-
cwd?: string | undefined;
|
|
25
|
-
timeout?: number | undefined;
|
|
26
|
-
env?: Record<string, string> | undefined;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Executes a command and returns the result.
|
|
31
|
-
* Uses execFile instead of exec for security (no shell invocation).
|
|
32
|
-
*/
|
|
33
|
-
export async function exec_command(
|
|
34
|
-
command: string,
|
|
35
|
-
args: string[] = [],
|
|
36
|
-
options: ExecOptionsType = {},
|
|
37
|
-
): Promise<ResultType<CommandResultType, KustodianErrorType>> {
|
|
38
|
-
try {
|
|
39
|
-
const { stdout, stderr } = await exec_file_async(command, args, {
|
|
40
|
-
cwd: options.cwd,
|
|
41
|
-
timeout: options.timeout,
|
|
42
|
-
env: { ...process.env, ...options.env },
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
return success({
|
|
46
|
-
stdout,
|
|
47
|
-
stderr,
|
|
48
|
-
exit_code: 0,
|
|
49
|
-
});
|
|
50
|
-
} catch (error) {
|
|
51
|
-
if (is_exec_error(error)) {
|
|
52
|
-
return success({
|
|
53
|
-
stdout: error.stdout ?? '',
|
|
54
|
-
stderr: error.stderr ?? '',
|
|
55
|
-
exit_code: error.code ?? 1,
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return failure(Errors.unknown(`Failed to execute command: ${command}`, error));
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Type guard for exec errors.
|
|
65
|
-
*/
|
|
66
|
-
function is_exec_error(
|
|
67
|
-
error: unknown,
|
|
68
|
-
): error is { stdout?: string; stderr?: string; code?: number } {
|
|
69
|
-
return (
|
|
70
|
-
typeof error === 'object' &&
|
|
71
|
-
error !== null &&
|
|
72
|
-
('stdout' in error || 'stderr' in error || 'code' in error)
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Gets environment variables for 1Password CLI authentication.
|
|
78
|
-
*/
|
|
79
|
-
function get_op_auth_env(options: OnePasswordPluginOptionsType): Record<string, string> {
|
|
80
|
-
const env: Record<string, string> = {};
|
|
81
|
-
|
|
82
|
-
const token = options.service_account_token ?? process.env['OP_SERVICE_ACCOUNT_TOKEN'];
|
|
83
|
-
if (token) {
|
|
84
|
-
env['OP_SERVICE_ACCOUNT_TOKEN'] = token;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return env;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Checks if op CLI is available in the system PATH.
|
|
92
|
-
*/
|
|
93
|
-
export async function check_op_available(): Promise<ResultType<string, KustodianErrorType>> {
|
|
94
|
-
const result = await exec_command('op', ['--version']);
|
|
95
|
-
|
|
96
|
-
if (!result.success) {
|
|
97
|
-
return result;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (result.value.exit_code !== 0) {
|
|
101
|
-
return failure(Errors.secret_cli_not_found('1Password', 'op'));
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Parse version from output (e.g., "2.30.0")
|
|
105
|
-
const version = result.value.stdout.trim();
|
|
106
|
-
return success(version);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Reads a single secret from 1Password.
|
|
111
|
-
*/
|
|
112
|
-
export async function op_read(
|
|
113
|
-
ref: string,
|
|
114
|
-
options: OnePasswordPluginOptionsType = {},
|
|
115
|
-
): Promise<ResultType<string, KustodianErrorType>> {
|
|
116
|
-
const timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
117
|
-
const env = get_op_auth_env(options);
|
|
118
|
-
|
|
119
|
-
const result = await exec_command('op', ['read', ref], {
|
|
120
|
-
timeout,
|
|
121
|
-
env,
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
if (!result.success) {
|
|
125
|
-
return result;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const { stdout, stderr, exit_code } = result.value;
|
|
129
|
-
|
|
130
|
-
if (exit_code !== 0) {
|
|
131
|
-
// Parse specific error types from stderr
|
|
132
|
-
const stderr_lower = stderr.toLowerCase();
|
|
133
|
-
|
|
134
|
-
if (
|
|
135
|
-
stderr_lower.includes('not found') ||
|
|
136
|
-
stderr_lower.includes("doesn't exist") ||
|
|
137
|
-
stderr_lower.includes('no item')
|
|
138
|
-
) {
|
|
139
|
-
return failure(Errors.secret_not_found('1Password', ref));
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (
|
|
143
|
-
stderr_lower.includes('sign in') ||
|
|
144
|
-
stderr_lower.includes('authentication') ||
|
|
145
|
-
stderr_lower.includes('unauthorized') ||
|
|
146
|
-
stderr_lower.includes('not signed in')
|
|
147
|
-
) {
|
|
148
|
-
return failure(Errors.secret_auth_error('1Password', stderr));
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (stderr_lower.includes('timeout')) {
|
|
152
|
-
return failure(Errors.secret_timeout('1Password', timeout));
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return failure(Errors.unknown(`1Password error: ${stderr}`));
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return success(stdout.trim());
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Reads multiple secrets from 1Password in batch.
|
|
163
|
-
*/
|
|
164
|
-
export async function op_read_batch(
|
|
165
|
-
refs: string[],
|
|
166
|
-
options: OnePasswordPluginOptionsType = {},
|
|
167
|
-
): Promise<ResultType<Record<string, string>, KustodianErrorType>> {
|
|
168
|
-
if (refs.length === 0) {
|
|
169
|
-
return success({});
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const results: Record<string, string> = {};
|
|
173
|
-
|
|
174
|
-
// Read secrets sequentially for now
|
|
175
|
-
for (const ref of refs) {
|
|
176
|
-
const result = await op_read(ref, options);
|
|
177
|
-
|
|
178
|
-
if (!result.success) {
|
|
179
|
-
if (options.fail_on_missing !== false) {
|
|
180
|
-
return result;
|
|
181
|
-
}
|
|
182
|
-
// Skip missing secrets if fail_on_missing is false
|
|
183
|
-
continue;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
results[ref] = result.value;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return success(results);
|
|
190
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
// Plugin exports
|
|
2
|
-
export { create_onepassword_plugin, plugin } from './plugin.js';
|
|
3
|
-
export { plugin as default } from './plugin.js';
|
|
4
|
-
|
|
5
|
-
// Executor exports
|
|
6
|
-
export {
|
|
7
|
-
check_op_available,
|
|
8
|
-
exec_command,
|
|
9
|
-
op_read,
|
|
10
|
-
op_read_batch,
|
|
11
|
-
type CommandResultType,
|
|
12
|
-
type ExecOptionsType,
|
|
13
|
-
} from './executor.js';
|
|
14
|
-
|
|
15
|
-
// Resolver exports
|
|
16
|
-
export { resolve_onepassword_substitutions } from './resolver.js';
|
|
17
|
-
|
|
18
|
-
// Types
|
|
19
|
-
export {
|
|
20
|
-
parse_onepassword_ref,
|
|
21
|
-
DEFAULT_TIMEOUT,
|
|
22
|
-
type OnePasswordPluginOptionsType,
|
|
23
|
-
type OnePasswordRefType,
|
|
24
|
-
} from './types.js';
|
package/src/plugin.ts
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import { success } from '@kustodian/core';
|
|
2
|
-
import type {
|
|
3
|
-
CommandType,
|
|
4
|
-
HookContextType,
|
|
5
|
-
HookEventType,
|
|
6
|
-
KustodianPluginType,
|
|
7
|
-
PluginCommandContributionType,
|
|
8
|
-
PluginHookContributionType,
|
|
9
|
-
PluginManifestType,
|
|
10
|
-
} from '@kustodian/plugins';
|
|
11
|
-
|
|
12
|
-
import { check_op_available, op_read } from './executor.js';
|
|
13
|
-
import type { OnePasswordPluginOptionsType } from './types.js';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* 1Password plugin manifest.
|
|
17
|
-
*/
|
|
18
|
-
const manifest: PluginManifestType = {
|
|
19
|
-
name: '@kustodian/plugin-1password',
|
|
20
|
-
version: '0.1.0',
|
|
21
|
-
description: '1Password secret provider for Kustodian',
|
|
22
|
-
capabilities: ['commands', 'hooks'],
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Creates the 1Password plugin.
|
|
27
|
-
*/
|
|
28
|
-
export function create_onepassword_plugin(
|
|
29
|
-
options: OnePasswordPluginOptionsType = {},
|
|
30
|
-
): KustodianPluginType {
|
|
31
|
-
return {
|
|
32
|
-
manifest,
|
|
33
|
-
|
|
34
|
-
async activate() {
|
|
35
|
-
// Verify CLI availability on activation (warning only)
|
|
36
|
-
const check_result = await check_op_available();
|
|
37
|
-
if (!check_result.success) {
|
|
38
|
-
console.warn('1Password CLI (op) not found - secret resolution may fail');
|
|
39
|
-
}
|
|
40
|
-
return success(undefined);
|
|
41
|
-
},
|
|
42
|
-
|
|
43
|
-
async deactivate() {
|
|
44
|
-
return success(undefined);
|
|
45
|
-
},
|
|
46
|
-
|
|
47
|
-
get_commands(): PluginCommandContributionType[] {
|
|
48
|
-
const onepassword_command: CommandType = {
|
|
49
|
-
name: '1password',
|
|
50
|
-
description: '1Password secret management commands',
|
|
51
|
-
subcommands: [
|
|
52
|
-
{
|
|
53
|
-
name: 'check',
|
|
54
|
-
description: 'Check 1Password CLI availability and authentication',
|
|
55
|
-
handler: async () => {
|
|
56
|
-
const result = await check_op_available();
|
|
57
|
-
if (result.success) {
|
|
58
|
-
console.log(`1Password CLI version: ${result.value}`);
|
|
59
|
-
return success(undefined);
|
|
60
|
-
}
|
|
61
|
-
console.error('1Password CLI not available');
|
|
62
|
-
return result;
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
name: 'test',
|
|
67
|
-
description: 'Test reading a secret reference',
|
|
68
|
-
arguments: [
|
|
69
|
-
{
|
|
70
|
-
name: 'ref',
|
|
71
|
-
description: 'Secret reference (op://vault/item/field)',
|
|
72
|
-
required: true,
|
|
73
|
-
},
|
|
74
|
-
],
|
|
75
|
-
handler: async (ctx) => {
|
|
76
|
-
const ref = ctx.args[0];
|
|
77
|
-
if (!ref) {
|
|
78
|
-
console.error('Missing secret reference');
|
|
79
|
-
return success(undefined);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const result = await op_read(ref, options);
|
|
83
|
-
if (result.success) {
|
|
84
|
-
console.log('Secret retrieved successfully (value hidden)');
|
|
85
|
-
console.log(`Length: ${result.value.length} characters`);
|
|
86
|
-
return success(undefined);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
console.error(`Failed to read secret: ${result.error.message}`);
|
|
90
|
-
return result;
|
|
91
|
-
},
|
|
92
|
-
},
|
|
93
|
-
],
|
|
94
|
-
};
|
|
95
|
-
return [{ command: onepassword_command }];
|
|
96
|
-
},
|
|
97
|
-
|
|
98
|
-
get_hooks(): PluginHookContributionType[] {
|
|
99
|
-
return [
|
|
100
|
-
{
|
|
101
|
-
event: 'generator:after_resolve',
|
|
102
|
-
priority: 50, // Run before default (100) to inject secrets early
|
|
103
|
-
handler: async (_event: HookEventType, ctx: HookContextType) => {
|
|
104
|
-
// Hook for secret injection will be implemented in generator integration
|
|
105
|
-
return success(ctx);
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
|
-
];
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Default plugin export.
|
|
115
|
-
*/
|
|
116
|
-
export const plugin = create_onepassword_plugin();
|
|
117
|
-
|
|
118
|
-
export default plugin;
|
package/src/resolver.ts
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { type ResultType, failure, success } from '@kustodian/core';
|
|
2
|
-
import type { KustodianErrorType } from '@kustodian/core';
|
|
3
|
-
import type { OnePasswordSubstitutionType } from '@kustodian/schema';
|
|
4
|
-
|
|
5
|
-
import { op_read } from './executor.js';
|
|
6
|
-
import type { OnePasswordPluginOptionsType } from './types.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Resolves 1Password substitutions to actual secret values.
|
|
10
|
-
* Supports both full references and shorthand with cluster defaults.
|
|
11
|
-
* Returns a map from substitution name to resolved value.
|
|
12
|
-
*/
|
|
13
|
-
export async function resolve_onepassword_substitutions(
|
|
14
|
-
substitutions: OnePasswordSubstitutionType[],
|
|
15
|
-
options: OnePasswordPluginOptionsType = {},
|
|
16
|
-
): Promise<ResultType<Record<string, string>, KustodianErrorType>> {
|
|
17
|
-
if (substitutions.length === 0) {
|
|
18
|
-
return success({});
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const results: Record<string, string> = {};
|
|
22
|
-
|
|
23
|
-
for (const sub of substitutions) {
|
|
24
|
-
// Build the reference string
|
|
25
|
-
let ref: string;
|
|
26
|
-
|
|
27
|
-
if (sub.ref) {
|
|
28
|
-
// Full reference provided
|
|
29
|
-
ref = sub.ref;
|
|
30
|
-
} else if (sub.item && sub.field) {
|
|
31
|
-
// Shorthand reference - need cluster vault
|
|
32
|
-
const vault = options.cluster_defaults?.vault;
|
|
33
|
-
if (!vault) {
|
|
34
|
-
return failure({
|
|
35
|
-
code: 'MISSING_1PASSWORD_VAULT',
|
|
36
|
-
message: `1Password substitution '${sub.name}' uses shorthand but no cluster vault configured`,
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Build op:// reference
|
|
41
|
-
if (sub.section) {
|
|
42
|
-
ref = `op://${vault}/${sub.item}/${sub.section}/${sub.field}`;
|
|
43
|
-
} else {
|
|
44
|
-
ref = `op://${vault}/${sub.item}/${sub.field}`;
|
|
45
|
-
}
|
|
46
|
-
} else {
|
|
47
|
-
return failure({
|
|
48
|
-
code: 'INVALID_1PASSWORD_REFERENCE',
|
|
49
|
-
message: `1Password substitution '${sub.name}' must specify either 'ref' or 'item'+'field'`,
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const result = await op_read(ref, options);
|
|
54
|
-
|
|
55
|
-
if (!result.success) {
|
|
56
|
-
// If we have a default and fail_on_missing is false, use the default
|
|
57
|
-
if (sub.default !== undefined && options.fail_on_missing === false) {
|
|
58
|
-
results[sub.name] = sub.default;
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// If we have a default, use it
|
|
63
|
-
if (sub.default !== undefined) {
|
|
64
|
-
results[sub.name] = sub.default;
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Otherwise, propagate the error
|
|
69
|
-
return failure(result.error);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
results[sub.name] = result.value;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return success(results);
|
|
76
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cluster-level 1Password defaults.
|
|
3
|
-
*/
|
|
4
|
-
export interface OnePasswordClusterDefaultsType {
|
|
5
|
-
/** Default vault name or ID */
|
|
6
|
-
vault?: string | undefined;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Options for the 1Password plugin.
|
|
11
|
-
*/
|
|
12
|
-
export interface OnePasswordPluginOptionsType {
|
|
13
|
-
/** Service account token (can also be set via OP_SERVICE_ACCOUNT_TOKEN env var) */
|
|
14
|
-
service_account_token?: string | undefined;
|
|
15
|
-
/** Timeout for CLI operations in milliseconds (default: 30000) */
|
|
16
|
-
timeout?: number | undefined;
|
|
17
|
-
/** Whether to fail on missing secrets (default: true) */
|
|
18
|
-
fail_on_missing?: boolean | undefined;
|
|
19
|
-
/** Cluster-level defaults for vault */
|
|
20
|
-
cluster_defaults?: OnePasswordClusterDefaultsType | undefined;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Parsed 1Password reference.
|
|
25
|
-
*/
|
|
26
|
-
export interface OnePasswordRefType {
|
|
27
|
-
vault: string;
|
|
28
|
-
item: string;
|
|
29
|
-
section?: string | undefined;
|
|
30
|
-
field: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Default timeout for 1Password CLI operations.
|
|
35
|
-
*/
|
|
36
|
-
export const DEFAULT_TIMEOUT = 30000;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Parses a 1Password secret reference.
|
|
40
|
-
* Format: op://vault/item[/section]/field
|
|
41
|
-
*/
|
|
42
|
-
export function parse_onepassword_ref(ref: string): OnePasswordRefType | undefined {
|
|
43
|
-
// Match: op://vault/item/field or op://vault/item/section/field
|
|
44
|
-
const match = ref.match(/^op:\/\/([^/]+)\/([^/]+)\/(?:([^/]+)\/)?([^/]+)$/);
|
|
45
|
-
|
|
46
|
-
if (!match) {
|
|
47
|
-
return undefined;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const vault = match[1];
|
|
51
|
-
const item = match[2];
|
|
52
|
-
const section = match[3];
|
|
53
|
-
const field = match[4];
|
|
54
|
-
|
|
55
|
-
// If section is undefined, the field is in position 3
|
|
56
|
-
if (field === undefined) {
|
|
57
|
-
// When there's no section, match[3] contains the field
|
|
58
|
-
if (!vault || !item || !section) {
|
|
59
|
-
return undefined;
|
|
60
|
-
}
|
|
61
|
-
return {
|
|
62
|
-
vault,
|
|
63
|
-
item,
|
|
64
|
-
field: section,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (!vault || !item || !field) {
|
|
69
|
-
return undefined;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return {
|
|
73
|
-
vault,
|
|
74
|
-
item,
|
|
75
|
-
section,
|
|
76
|
-
field,
|
|
77
|
-
};
|
|
78
|
-
}
|