@vellumai/credential-executor 0.4.55
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/Dockerfile +55 -0
- package/bun.lock +37 -0
- package/package.json +32 -0
- package/src/__tests__/command-executor.test.ts +1333 -0
- package/src/__tests__/command-validator.test.ts +708 -0
- package/src/__tests__/command-workspace.test.ts +997 -0
- package/src/__tests__/grant-store.test.ts +467 -0
- package/src/__tests__/http-executor.test.ts +1251 -0
- package/src/__tests__/http-policy.test.ts +970 -0
- package/src/__tests__/local-materializers.test.ts +826 -0
- package/src/__tests__/managed-materializers.test.ts +961 -0
- package/src/__tests__/toolstore.test.ts +539 -0
- package/src/__tests__/transport.test.ts +388 -0
- package/src/audit/store.ts +188 -0
- package/src/commands/auth-adapters.ts +169 -0
- package/src/commands/executor.ts +840 -0
- package/src/commands/output-scan.ts +157 -0
- package/src/commands/profiles.ts +282 -0
- package/src/commands/validator.ts +438 -0
- package/src/commands/workspace.ts +512 -0
- package/src/grants/index.ts +17 -0
- package/src/grants/persistent-store.ts +247 -0
- package/src/grants/rpc-handlers.ts +269 -0
- package/src/grants/temporary-store.ts +219 -0
- package/src/http/audit.ts +84 -0
- package/src/http/executor.ts +540 -0
- package/src/http/path-template.ts +179 -0
- package/src/http/policy.ts +256 -0
- package/src/http/response-filter.ts +233 -0
- package/src/index.ts +106 -0
- package/src/main.ts +263 -0
- package/src/managed-main.ts +420 -0
- package/src/materializers/local.ts +300 -0
- package/src/materializers/managed-platform.ts +270 -0
- package/src/paths.ts +137 -0
- package/src/server.ts +636 -0
- package/src/subjects/local.ts +177 -0
- package/src/subjects/managed.ts +290 -0
- package/src/toolstore/integrity.ts +94 -0
- package/src/toolstore/manifest.ts +154 -0
- package/src/toolstore/publish.ts +342 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Managed CES entrypoint.
|
|
4
|
+
*
|
|
5
|
+
* In managed (sidecar) mode the CES container:
|
|
6
|
+
*
|
|
7
|
+
* 1. Ensures the CES-private data directories exist.
|
|
8
|
+
* 2. Binds a bootstrap Unix socket on the shared bootstrap volume.
|
|
9
|
+
* 3. Accepts exactly **one** assistant runtime connection.
|
|
10
|
+
* 4. Unlinks the socket path immediately after the connection is accepted,
|
|
11
|
+
* preventing any second process from connecting.
|
|
12
|
+
* 5. Serves RPC on the accepted stream only.
|
|
13
|
+
* 6. Simultaneously serves health probes (`/healthz`, `/readyz`) on a
|
|
14
|
+
* dedicated HTTP port for Kubernetes liveness/readiness checks.
|
|
15
|
+
*
|
|
16
|
+
* The managed entrypoint never opens a generic TCP or HTTP command API.
|
|
17
|
+
* All RPC traffic flows exclusively over the accepted Unix socket stream.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { mkdirSync, unlinkSync } from "node:fs";
|
|
21
|
+
import { createServer as createNetServer, type Socket } from "node:net";
|
|
22
|
+
import { dirname } from "node:path";
|
|
23
|
+
import { Readable, Writable } from "node:stream";
|
|
24
|
+
|
|
25
|
+
import { CES_PROTOCOL_VERSION, CesRpcMethod } from "@vellumai/ces-contracts";
|
|
26
|
+
|
|
27
|
+
import { AuditStore } from "./audit/store.js";
|
|
28
|
+
import { PersistentGrantStore } from "./grants/persistent-store.js";
|
|
29
|
+
import {
|
|
30
|
+
createListAuditRecordsHandler,
|
|
31
|
+
createListGrantsHandler,
|
|
32
|
+
createRecordGrantHandler,
|
|
33
|
+
createRevokeGrantHandler,
|
|
34
|
+
} from "./grants/rpc-handlers.js";
|
|
35
|
+
import { TemporaryGrantStore } from "./grants/temporary-store.js";
|
|
36
|
+
import {
|
|
37
|
+
getBootstrapSocketPath,
|
|
38
|
+
getCesAuditDir,
|
|
39
|
+
getCesDataRoot,
|
|
40
|
+
getCesGrantsDir,
|
|
41
|
+
getCesToolStoreDir,
|
|
42
|
+
getHealthPort,
|
|
43
|
+
} from "./paths.js";
|
|
44
|
+
import {
|
|
45
|
+
buildHandlersWithHttp,
|
|
46
|
+
CesRpcServer,
|
|
47
|
+
registerCommandExecutionHandler,
|
|
48
|
+
registerManageSecureCommandToolHandler,
|
|
49
|
+
type RpcHandlerRegistry,
|
|
50
|
+
} from "./server.js";
|
|
51
|
+
import { publishBundle } from "./toolstore/publish.js";
|
|
52
|
+
import type { ManagedSubjectResolverOptions } from "./subjects/managed.js";
|
|
53
|
+
import type { ManagedMaterializerOptions } from "./materializers/managed-platform.js";
|
|
54
|
+
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Logging (managed always logs to stderr)
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
const log = (msg: string) =>
|
|
60
|
+
process.stderr.write(`[ces-managed] ${msg}\n`);
|
|
61
|
+
|
|
62
|
+
const warn = (msg: string) =>
|
|
63
|
+
process.stderr.write(`[ces-managed] WARN: ${msg}\n`);
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Data directory bootstrap
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
function ensureDataDirs(): void {
|
|
70
|
+
const dirs = [
|
|
71
|
+
getCesDataRoot("managed"),
|
|
72
|
+
getCesGrantsDir("managed"),
|
|
73
|
+
getCesAuditDir("managed"),
|
|
74
|
+
getCesToolStoreDir("managed"),
|
|
75
|
+
];
|
|
76
|
+
for (const dir of dirs) {
|
|
77
|
+
mkdirSync(dir, { recursive: true });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// Build RPC handler registry (managed mode)
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
function buildHandlers(sessionId: string): RpcHandlerRegistry {
|
|
86
|
+
// -- Grant stores ----------------------------------------------------------
|
|
87
|
+
const persistentGrantStore = new PersistentGrantStore(
|
|
88
|
+
getCesGrantsDir("managed"),
|
|
89
|
+
);
|
|
90
|
+
persistentGrantStore.init();
|
|
91
|
+
|
|
92
|
+
const temporaryGrantStore = new TemporaryGrantStore();
|
|
93
|
+
|
|
94
|
+
// -- Audit store -----------------------------------------------------------
|
|
95
|
+
const auditStore = new AuditStore(getCesAuditDir("managed"));
|
|
96
|
+
auditStore.init();
|
|
97
|
+
|
|
98
|
+
// -- Managed credential options --------------------------------------------
|
|
99
|
+
// In managed mode, credentials are obtained from the platform via its
|
|
100
|
+
// token-materialization endpoint. The platform URL and API key are provided
|
|
101
|
+
// through environment variables set by the orchestration layer.
|
|
102
|
+
const platformBaseUrl = process.env["PLATFORM_BASE_URL"] ?? "";
|
|
103
|
+
const assistantApiKey = process.env["ASSISTANT_API_KEY"] ?? "";
|
|
104
|
+
|
|
105
|
+
const managedSubjectOptions: ManagedSubjectResolverOptions | undefined =
|
|
106
|
+
platformBaseUrl && assistantApiKey
|
|
107
|
+
? { platformBaseUrl, assistantApiKey }
|
|
108
|
+
: undefined;
|
|
109
|
+
|
|
110
|
+
const managedMaterializerOptions: ManagedMaterializerOptions | undefined =
|
|
111
|
+
platformBaseUrl && assistantApiKey
|
|
112
|
+
? { platformBaseUrl, assistantApiKey }
|
|
113
|
+
: undefined;
|
|
114
|
+
|
|
115
|
+
if (!managedSubjectOptions) {
|
|
116
|
+
warn(
|
|
117
|
+
"PLATFORM_BASE_URL and/or ASSISTANT_API_KEY not set. " +
|
|
118
|
+
"Managed credential materialisation will not be available.",
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// -- Build handler registry ------------------------------------------------
|
|
123
|
+
|
|
124
|
+
// In managed mode there is no local secure-key backend. The HTTP handler
|
|
125
|
+
// uses managed subject resolution and managed materialisation instead.
|
|
126
|
+
// The localMaterialiser and localSubjectDeps are required by HttpExecutorDeps
|
|
127
|
+
// but will only be reached for local_static/local_oauth handles (which are
|
|
128
|
+
// not expected in managed deployments). We stub them to fail closed.
|
|
129
|
+
const stubLocalMaterialiser = {
|
|
130
|
+
async materialise() {
|
|
131
|
+
return {
|
|
132
|
+
ok: false as const,
|
|
133
|
+
error: "Local credential materialisation is not available in managed mode.",
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
reset() {},
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const handlers = buildHandlersWithHttp(
|
|
140
|
+
{
|
|
141
|
+
persistentGrantStore,
|
|
142
|
+
temporaryGrantStore,
|
|
143
|
+
localMaterialiser: stubLocalMaterialiser as any,
|
|
144
|
+
localSubjectDeps: {
|
|
145
|
+
metadataStore: { getByServiceField: () => undefined } as any,
|
|
146
|
+
oauthConnections: { getById: () => undefined },
|
|
147
|
+
},
|
|
148
|
+
managedSubjectOptions,
|
|
149
|
+
managedMaterializerOptions,
|
|
150
|
+
sessionId,
|
|
151
|
+
},
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
// Register run_authenticated_command handler
|
|
155
|
+
registerCommandExecutionHandler(handlers, {
|
|
156
|
+
executorDeps: {
|
|
157
|
+
persistentStore: persistentGrantStore,
|
|
158
|
+
temporaryStore: temporaryGrantStore,
|
|
159
|
+
materializeCredential: async (_handle) => ({
|
|
160
|
+
ok: false as const,
|
|
161
|
+
error:
|
|
162
|
+
"Command credential materialisation in managed mode is not yet available.",
|
|
163
|
+
}),
|
|
164
|
+
cesMode: "managed",
|
|
165
|
+
},
|
|
166
|
+
defaultWorkspaceDir: "/workspace",
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Register manage_secure_command_tool handler
|
|
170
|
+
const toolRegistry = new Map<string, { toolName: string; credentialHandle: string; description: string; bundleDigest: string }>();
|
|
171
|
+
|
|
172
|
+
registerManageSecureCommandToolHandler(handlers, {
|
|
173
|
+
downloadBundle: async (sourceUrl: string) => {
|
|
174
|
+
const resp = await fetch(sourceUrl);
|
|
175
|
+
if (!resp.ok) {
|
|
176
|
+
throw new Error(`HTTP ${resp.status}: ${resp.statusText}`);
|
|
177
|
+
}
|
|
178
|
+
return Buffer.from(await resp.arrayBuffer());
|
|
179
|
+
},
|
|
180
|
+
publishBundle: (request) => publishBundle({ ...request, cesMode: "managed" }),
|
|
181
|
+
unregisterTool: (toolName: string) => {
|
|
182
|
+
return toolRegistry.delete(toolName);
|
|
183
|
+
},
|
|
184
|
+
registerTool: (entry) => {
|
|
185
|
+
toolRegistry.set(entry.toolName, entry);
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Register grant management handlers
|
|
190
|
+
handlers[CesRpcMethod.RecordGrant] = createRecordGrantHandler({
|
|
191
|
+
persistentGrantStore,
|
|
192
|
+
temporaryGrantStore,
|
|
193
|
+
}) as typeof handlers[string];
|
|
194
|
+
|
|
195
|
+
handlers[CesRpcMethod.ListGrants] = createListGrantsHandler({
|
|
196
|
+
persistentGrantStore,
|
|
197
|
+
sessionId,
|
|
198
|
+
}) as typeof handlers[string];
|
|
199
|
+
|
|
200
|
+
handlers[CesRpcMethod.RevokeGrant] = createRevokeGrantHandler({
|
|
201
|
+
persistentGrantStore,
|
|
202
|
+
}) as typeof handlers[string];
|
|
203
|
+
|
|
204
|
+
// Register audit record handler
|
|
205
|
+
handlers[CesRpcMethod.ListAuditRecords] = createListAuditRecordsHandler({
|
|
206
|
+
auditStore,
|
|
207
|
+
}) as typeof handlers[string];
|
|
208
|
+
|
|
209
|
+
return handlers;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
// Health server
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
|
|
216
|
+
let rpcConnected = false;
|
|
217
|
+
|
|
218
|
+
function startHealthServer(port: number, signal: AbortSignal): ReturnType<typeof Bun.serve> {
|
|
219
|
+
const server = Bun.serve({
|
|
220
|
+
port,
|
|
221
|
+
fetch(req) {
|
|
222
|
+
const url = new URL(req.url);
|
|
223
|
+
if (url.pathname === "/healthz") {
|
|
224
|
+
return new Response(
|
|
225
|
+
JSON.stringify({ status: "ok", version: CES_PROTOCOL_VERSION }),
|
|
226
|
+
{ headers: { "Content-Type": "application/json" } },
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
if (url.pathname === "/readyz") {
|
|
230
|
+
const ready = rpcConnected;
|
|
231
|
+
return new Response(
|
|
232
|
+
JSON.stringify({ ready, version: CES_PROTOCOL_VERSION }),
|
|
233
|
+
{
|
|
234
|
+
status: ready ? 200 : 503,
|
|
235
|
+
headers: { "Content-Type": "application/json" },
|
|
236
|
+
},
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
return new Response("Not Found", { status: 404 });
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
signal.addEventListener("abort", () => {
|
|
244
|
+
server.stop(true);
|
|
245
|
+
}, { once: true });
|
|
246
|
+
|
|
247
|
+
return server;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
// Bootstrap socket server (accepts exactly one connection)
|
|
252
|
+
// ---------------------------------------------------------------------------
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Listen on a Unix socket, accept exactly one connection, unlink the
|
|
256
|
+
* socket path, and return readable/writable streams for the accepted
|
|
257
|
+
* connection.
|
|
258
|
+
*/
|
|
259
|
+
function acceptOneConnection(
|
|
260
|
+
socketPath: string,
|
|
261
|
+
signal: AbortSignal,
|
|
262
|
+
): Promise<{ readable: Readable; writable: Writable; socket: Socket }> {
|
|
263
|
+
return new Promise((resolve, reject) => {
|
|
264
|
+
// Ensure the socket directory exists
|
|
265
|
+
mkdirSync(dirname(socketPath), { recursive: true });
|
|
266
|
+
|
|
267
|
+
// Clean up any stale socket file
|
|
268
|
+
try {
|
|
269
|
+
unlinkSync(socketPath);
|
|
270
|
+
} catch {
|
|
271
|
+
// Ignore — file may not exist
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const netServer = createNetServer();
|
|
275
|
+
|
|
276
|
+
const cleanup = () => {
|
|
277
|
+
netServer.close();
|
|
278
|
+
try {
|
|
279
|
+
unlinkSync(socketPath);
|
|
280
|
+
} catch {
|
|
281
|
+
// Already unlinked or never created
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
if (signal.aborted) {
|
|
286
|
+
reject(new Error("Aborted before listening"));
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
signal.addEventListener("abort", () => {
|
|
291
|
+
cleanup();
|
|
292
|
+
reject(new Error("Aborted while waiting for connection"));
|
|
293
|
+
}, { once: true });
|
|
294
|
+
|
|
295
|
+
netServer.on("error", (err) => {
|
|
296
|
+
cleanup();
|
|
297
|
+
reject(err);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
netServer.listen(socketPath, () => {
|
|
301
|
+
log(`Bootstrap socket listening at ${socketPath}`);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
netServer.on("connection", (socket: Socket) => {
|
|
305
|
+
// Accept exactly one connection, then close the listener and
|
|
306
|
+
// unlink the socket path so no other process can connect.
|
|
307
|
+
log("Assistant connected via bootstrap socket");
|
|
308
|
+
netServer.close();
|
|
309
|
+
try {
|
|
310
|
+
unlinkSync(socketPath);
|
|
311
|
+
} catch {
|
|
312
|
+
// Already unlinked
|
|
313
|
+
}
|
|
314
|
+
log("Bootstrap socket unlinked (single-connection enforced)");
|
|
315
|
+
|
|
316
|
+
const readable = new Readable({
|
|
317
|
+
read() {
|
|
318
|
+
// Data is pushed externally
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
const writable = new Writable({
|
|
323
|
+
write(chunk, _encoding, callback) {
|
|
324
|
+
if (socket.writable) {
|
|
325
|
+
socket.write(chunk, callback);
|
|
326
|
+
} else {
|
|
327
|
+
callback(new Error("Socket no longer writable"));
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
socket.on("data", (chunk) => {
|
|
333
|
+
readable.push(chunk);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
socket.on("end", () => {
|
|
337
|
+
readable.push(null);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
socket.on("error", (err) => {
|
|
341
|
+
readable.destroy(err);
|
|
342
|
+
writable.destroy(err);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
resolve({ readable, writable, socket });
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// ---------------------------------------------------------------------------
|
|
351
|
+
// Main
|
|
352
|
+
// ---------------------------------------------------------------------------
|
|
353
|
+
|
|
354
|
+
async function main(): Promise<void> {
|
|
355
|
+
ensureDataDirs();
|
|
356
|
+
|
|
357
|
+
log(`Starting CES v${CES_PROTOCOL_VERSION} (managed mode)`);
|
|
358
|
+
|
|
359
|
+
const controller = new AbortController();
|
|
360
|
+
|
|
361
|
+
// Graceful shutdown
|
|
362
|
+
const shutdown = () => {
|
|
363
|
+
log("Shutting down...");
|
|
364
|
+
controller.abort();
|
|
365
|
+
};
|
|
366
|
+
process.on("SIGTERM", shutdown);
|
|
367
|
+
process.on("SIGINT", shutdown);
|
|
368
|
+
|
|
369
|
+
// Start health server on dedicated port
|
|
370
|
+
const healthPort = getHealthPort();
|
|
371
|
+
const healthServer = startHealthServer(healthPort, controller.signal);
|
|
372
|
+
log(`Health server listening on port ${healthPort}`);
|
|
373
|
+
|
|
374
|
+
// Wait for exactly one assistant connection on the bootstrap socket
|
|
375
|
+
const socketPath = getBootstrapSocketPath();
|
|
376
|
+
log(`Waiting for assistant connection on ${socketPath}...`);
|
|
377
|
+
|
|
378
|
+
let connection: Awaited<ReturnType<typeof acceptOneConnection>>;
|
|
379
|
+
try {
|
|
380
|
+
connection = await acceptOneConnection(socketPath, controller.signal);
|
|
381
|
+
} catch (err) {
|
|
382
|
+
if (controller.signal.aborted) {
|
|
383
|
+
log("Shutdown before assistant connected.");
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
throw err;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
rpcConnected = true;
|
|
390
|
+
|
|
391
|
+
// Build the handler registry with all available RPC implementations
|
|
392
|
+
const sessionId = `ces-managed-${Date.now()}`;
|
|
393
|
+
const handlers = buildHandlers(sessionId);
|
|
394
|
+
|
|
395
|
+
const server = new CesRpcServer({
|
|
396
|
+
input: connection.readable,
|
|
397
|
+
output: connection.writable,
|
|
398
|
+
handlers,
|
|
399
|
+
logger: {
|
|
400
|
+
log: (msg: string, ...args: unknown[]) =>
|
|
401
|
+
process.stderr.write(`[ces-managed] ${msg} ${args.map(String).join(" ")}\n`),
|
|
402
|
+
warn: (msg: string, ...args: unknown[]) =>
|
|
403
|
+
process.stderr.write(`[ces-managed] WARN: ${msg} ${args.map(String).join(" ")}\n`),
|
|
404
|
+
error: (msg: string, ...args: unknown[]) =>
|
|
405
|
+
process.stderr.write(`[ces-managed] ERROR: ${msg} ${args.map(String).join(" ")}\n`),
|
|
406
|
+
},
|
|
407
|
+
signal: controller.signal,
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
await server.serve();
|
|
411
|
+
|
|
412
|
+
rpcConnected = false;
|
|
413
|
+
log("RPC session ended. Shutting down...");
|
|
414
|
+
controller.abort();
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
main().catch((err) => {
|
|
418
|
+
process.stderr.write(`[ces-managed] Fatal: ${err}\n`);
|
|
419
|
+
process.exit(1);
|
|
420
|
+
});
|