@evident-ai/runner-cdk 3.5.2-dev.2f7c636 → 3.5.2-dev.4f96188
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/README.md +80 -39
- package/dist/controller-lambda/handler.js +13 -13
- package/dist/evident-scale-to-zero-construct.js +7 -0
- package/dist/image-version-reporter-lambda/handler.js +12 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -2
- package/dist/microvm/constants.d.ts +1 -1
- package/dist/microvm/constants.js +7 -4
- package/dist/microvm/construct.d.ts +6 -8
- package/dist/microvm/construct.js +24 -33
- package/dist/microvm/controller/handle-doorbell.d.ts +2 -2
- package/dist/microvm/controller/handle-doorbell.js +2 -2
- package/dist/microvm/image/stage-context.d.ts +13 -38
- package/dist/microvm/image/stage-context.js +30 -120
- package/dist/microvm/image-version-reporter/construct.d.ts +1 -1
- package/dist/microvm/image-version-reporter/construct.js +3 -3
- package/dist/microvm/image-version-reporter/handler.d.ts +2 -2
- package/dist/microvm/image-version-reporter/handler.js +7 -7
- package/dist/microvm/shapes.d.ts +10 -21
- package/dist/microvm/shapes.js +10 -17
- package/package.json +3 -3
- package/dist/microvm-image-context/Dockerfile +0 -190
- package/dist/microvm-image-context/hook-server.js +0 -286
- package/dist/microvm-image-context/hooks/common.sh +0 -904
- package/dist/microvm-image-context/hooks/resume +0 -81
- package/dist/microvm-image-context/hooks/run +0 -95
- package/dist/microvm-image-context/hooks/suspend +0 -21
- package/dist/microvm-image-context/hooks/terminate +0 -36
package/README.md
CHANGED
|
@@ -9,9 +9,9 @@ Reusable AWS CDK constructs for running an [Evident](https://evident.run) agent
|
|
|
9
9
|
`desiredCount 0`, and an HMAC-authenticated waker Lambda scales it back up on the next
|
|
10
10
|
message. You pay for the time the agent is actually working.
|
|
11
11
|
- **`EvidentMicrovmConstruct`** — a per-session AWS Lambda MicroVM that boots on demand
|
|
12
|
-
and suspends between messages.
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
and suspends between messages. You provide a build-context directory and a full source
|
|
13
|
+
checkout to `stageMicrovmBuildContext`, then pass its returned directory as
|
|
14
|
+
`buildContextPath` (see [The MicroVM image](#the-microvm-image) below). See
|
|
15
15
|
[the AWS runner doc](https://evident.run/docs/aws-runner) for the full picture of both
|
|
16
16
|
strategies.
|
|
17
17
|
|
|
@@ -99,6 +99,25 @@ For the full end-to-end walkthrough for this construct, see
|
|
|
99
99
|
| `agentSecret`, `wakerSecret` | `secretsmanager.ISecret` | Any Secrets Manager secret. |
|
|
100
100
|
| `availableSecretKeys` | `Set<string>` | Keys present in the agent secret. Every listed key is injected; `EVIDENT_RUNNER_KEY` and `EVIDENT_AGENT_KEY` are injected from the `EVIDENT_AGENT_KEY` field, and `GH_TOKEN` is also injected for adopters that do not enumerate their secret. |
|
|
101
101
|
|
|
102
|
+
### MicroVM props
|
|
103
|
+
|
|
104
|
+
| Prop | Type | Notes |
|
|
105
|
+
| --- | --- | --- |
|
|
106
|
+
| `buildContextPath` | `string` | Directory containing your Dockerfile and every file it copies, normally the result of `stageMicrovmBuildContext()`. |
|
|
107
|
+
| `baseImageArn` / `baseImageVersion` | `string` | The AWS-managed base MicroVM image values from `aws lambda-microvms list-managed-microvm-images`. |
|
|
108
|
+
| `controllerSigningSecret` | `secretsmanager.ISecret` | Secret containing the shared HMAC key under the `CONTROLLER_SIGNING_SECRET` JSON field. |
|
|
109
|
+
| `hooksPort` / `hookTimeoutSeconds` | `number` | Values exported by the base image contract. |
|
|
110
|
+
| `shapes` | `readonly MicrovmShape[]?` | Shape catalogue to build. Defaults to the committed catalogue. |
|
|
111
|
+
| `runnerSecret` | `secretsmanager.ISecret?` | Optional JSON secret whose values are exported by `/run`. |
|
|
112
|
+
| `runnerOpencodeConfigPath` | `string?` | Optional baked OpenCode configuration path. |
|
|
113
|
+
| `gitUserName` / `gitUserEmail` | `string?` | Optional git identity for agent commits. |
|
|
114
|
+
| `extraImageEnvironment` | `Record<string,string>?` | Non-secret values baked into every VM launched from the image version. |
|
|
115
|
+
| `evidentApiUrl` / `evidentRunnerId` | `string?` | Supply both to report published image versions to Evident, or omit both. |
|
|
116
|
+
|
|
117
|
+
The `DoorbellSecret` CloudFormation logical ID remains stable for Evident's own
|
|
118
|
+
default stack. It identifies the deployed secret resource, not the customer-facing
|
|
119
|
+
name of the signing key.
|
|
120
|
+
|
|
102
121
|
### Two deliberate "no default" choices
|
|
103
122
|
|
|
104
123
|
- **The endpoints are required.** Defaulting them would either bake *our* dev environment
|
|
@@ -116,58 +135,65 @@ built from a `Dockerfile` you control. The generic runner image lives separately
|
|
|
116
135
|
|
|
117
136
|
## The MicroVM image
|
|
118
137
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
138
|
+
### Prune image versions
|
|
139
|
+
|
|
140
|
+
MicroVM image versions are quota-bound. `@evident-ai/lambda-microvm-cdk` ships the
|
|
141
|
+
`prune-microvm-image-versions` CLI; enable it with one `package.json` script line:
|
|
142
|
+
|
|
143
|
+
```json
|
|
144
|
+
"microvm:prune": "prune-microvm-image-versions --image-arn arn:aws:lambda:eu-west-1:000000000000:microvm-image:your-image"
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
See the `@evident-ai/lambda-microvm-cdk` package README for the CLI flags and safety semantics.
|
|
148
|
+
|
|
149
|
+
`EvidentMicrovmConstruct`'s `buildContextPath` is a **local directory** holding the
|
|
150
|
+
Dockerfile you control and everything it copies. `cdk deploy` zips and uploads that
|
|
151
|
+
directory, and the Lambda MicroVM service builds the image in *your* account. Start the
|
|
152
|
+
Dockerfile from a pinned `ghcr.io/sroze/evident-microvm-base:X.Y.Z` tag; the base image
|
|
153
|
+
contains the hook server, lifecycle hooks and common agent tooling.
|
|
123
154
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
`
|
|
127
|
-
and an optional overlay supplies deployment-specific build steps. `stageMicrovmImageContext()`
|
|
128
|
-
puts them together:
|
|
155
|
+
`stageMicrovmBuildContext()` combines your build-context directory with a full checkout
|
|
156
|
+
of the workspace in a fresh temporary directory. The workspace is written at
|
|
157
|
+
`workspace/`, and the returned path is the value for `buildContextPath`:
|
|
129
158
|
|
|
130
159
|
```ts
|
|
131
|
-
import path from 'node:path';
|
|
132
160
|
import {
|
|
133
161
|
EvidentMicrovmConstruct,
|
|
134
162
|
HOOKS_PORT,
|
|
135
163
|
HOOK_TIMEOUT_SECONDS,
|
|
136
|
-
|
|
164
|
+
stageMicrovmBuildContext,
|
|
137
165
|
} from '@evident-ai/runner-cdk';
|
|
138
166
|
|
|
167
|
+
const buildContextPath = stageMicrovmBuildContext({
|
|
168
|
+
// A full (non-shallow) clone: the agent branches, commits and opens PRs.
|
|
169
|
+
workspaceRepositoryPath: '/path/to/your/checkout',
|
|
170
|
+
// Credential-free: the token the agent pushes with arrives per-session.
|
|
171
|
+
workspaceOriginUrl: 'https://github.com/acme/widgets.git',
|
|
172
|
+
buildContextPath: '/path/to/your/microvm-build-context',
|
|
173
|
+
});
|
|
174
|
+
|
|
139
175
|
new EvidentMicrovmConstruct(this, 'Runner', {
|
|
140
|
-
|
|
141
|
-
// A full (non-shallow) clone: the agent branches, commits and opens PRs.
|
|
142
|
-
repositoryPath: '/path/to/your/checkout',
|
|
143
|
-
// Credential-free — this string ships inside the shared snapshot. The
|
|
144
|
-
// token the agent pushes with arrives per-session, never baked in.
|
|
145
|
-
originUrl: 'https://github.com/acme/widgets.git',
|
|
146
|
-
destination: path.join(__dirname, '..', 'build', 'image'),
|
|
147
|
-
// Optional deployment-specific installs and build warm-up.
|
|
148
|
-
overlayDir: '/path/to/microvm-overlay',
|
|
149
|
-
}),
|
|
176
|
+
buildContextPath,
|
|
150
177
|
// Must match the published image these were baked into, so export them
|
|
151
178
|
// rather than restating the numbers.
|
|
152
179
|
hooksPort: HOOKS_PORT,
|
|
153
180
|
hookTimeoutSeconds: HOOK_TIMEOUT_SECONDS,
|
|
154
181
|
baseImageArn,
|
|
155
182
|
baseImageVersion,
|
|
156
|
-
|
|
183
|
+
controllerSigningSecret,
|
|
157
184
|
runnerSecret,
|
|
158
185
|
runnerOpencodeConfigPath: 'opencode.runner.jsonc',
|
|
159
186
|
});
|
|
160
187
|
```
|
|
161
188
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
for the full overlay contract.
|
|
189
|
+
Write project-specific installation and build steps in your Dockerfile. The [MicroVM base
|
|
190
|
+
image README](../../runner/docker-images/microvm/README.md) documents the consumer
|
|
191
|
+
contract, including the required `USER`, `WORKDIR`, ownership and `git reset --quiet`
|
|
192
|
+
steps.
|
|
167
193
|
|
|
168
|
-
The image
|
|
169
|
-
|
|
170
|
-
|
|
194
|
+
The staged image includes your repository checkout, while your Dockerfile controls its
|
|
195
|
+
package manager and build warm-up. Do not copy credentials into the build context or bake
|
|
196
|
+
them into image environment variables.
|
|
171
197
|
|
|
172
198
|
| MicroVM prop | Omitted behavior |
|
|
173
199
|
| --- | --- |
|
|
@@ -175,12 +201,27 @@ its package manager. To speed up first boot, install dependencies from your over
|
|
|
175
201
|
| `runnerOpencodeConfigPath` | OpenCode uses the baked project configuration. Relative paths resolve from the workspace; absolute paths resolve in the image. |
|
|
176
202
|
| `extraImageEnvironment` | Non-secret values baked into every VM launched from this image version; every VM can read them, so never put secrets here. |
|
|
177
203
|
| `gitUserName` / `gitUserEmail` | The hook uses the Evident bot defaults for git identity. |
|
|
178
|
-
| `evidentApiUrl` / `evidentRunnerId` | No deploy-time report of the published image version — a byte-identical template to not having these props at all. Both non-secret (a URL, a public pool UUID) and both required together: set them if you have an Evident runner id configured for this provisioner, so each deploy that publishes a new image version tells Evident about it (a CloudFormation custom resource, HMAC-signed with `
|
|
179
|
-
|
|
180
|
-
`extraImageEnvironment` is the runtime
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
204
|
+
| `evidentApiUrl` / `evidentRunnerId` | No deploy-time report of the published image version — a byte-identical template to not having these props at all. Both non-secret (a URL, a public pool UUID) and both required together: set them if you have an Evident runner id configured for this provisioner, so each deploy that publishes a new image version tells Evident about it (a CloudFormation custom resource, HMAC-signed with `controllerSigningSecret`). A failure to report never fails your deploy — it warns and Evident reads that provisioner as *unknown*, never as up to date or stale. |
|
|
205
|
+
|
|
206
|
+
`extraImageEnvironment` is the runtime environment for the image. Values are baked into
|
|
207
|
+
every VM launched from that image version and are readable by all of them, so never put a
|
|
208
|
+
secret in this prop.
|
|
209
|
+
|
|
210
|
+
## Breaking changes in this release
|
|
211
|
+
|
|
212
|
+
- `stageMicrovmImageContext` is removed. Use `stageMicrovmBuildContext()` with your
|
|
213
|
+
Dockerfile and build context.
|
|
214
|
+
- `imageSource` is renamed to `buildContextPath`.
|
|
215
|
+
- `MicrovmShape.constructId` is removed. CloudFormation child identity derives from
|
|
216
|
+
the shape `name`.
|
|
217
|
+
- `doorbellSecret` is renamed to `controllerSigningSecret`.
|
|
218
|
+
- The default shape's `imageName` changes to `evident-microvm-standard`. The
|
|
219
|
+
`AWS::Lambda::MicrovmImage` resource, its auto-created build role and execution
|
|
220
|
+
role, and both roles' default policies are replaced with the new shape identity.
|
|
221
|
+
- The JSON field in the caller-supplied Secrets Manager secret changes from
|
|
222
|
+
`DOORBELL_SECRET` to `CONTROLLER_SIGNING_SECRET`. Rename that field in your own
|
|
223
|
+
secret. If you do not, the controller reads no signing key and every doorbell
|
|
224
|
+
returns HTTP 401 at runtime; TypeScript cannot catch this field-key change.
|
|
184
225
|
|
|
185
226
|
## Status / limitations
|
|
186
227
|
|
|
@@ -50036,13 +50036,13 @@ async function shouldRecreateForNewerImage(doorbellRequest, shape, runningVersio
|
|
|
50036
50036
|
async function handleDoorbell({
|
|
50037
50037
|
rawBody,
|
|
50038
50038
|
signatureHeader,
|
|
50039
|
-
|
|
50039
|
+
controllerSigningSecret,
|
|
50040
50040
|
shapes,
|
|
50041
50041
|
microvm,
|
|
50042
50042
|
sleep: sleep4,
|
|
50043
50043
|
random
|
|
50044
50044
|
}) {
|
|
50045
|
-
if (!verifyEvidentSignature(rawBody, signatureHeader,
|
|
50045
|
+
if (!verifyEvidentSignature(rawBody, signatureHeader, controllerSigningSecret)) {
|
|
50046
50046
|
return decide({
|
|
50047
50047
|
statusCode: 401,
|
|
50048
50048
|
action: "rejected",
|
|
@@ -50413,20 +50413,20 @@ function parseShapeCatalogue(raw) {
|
|
|
50413
50413
|
// src/microvm/controller/handler.ts
|
|
50414
50414
|
var microvms = new import_client_lambda_microvms.LambdaMicrovmsClient({});
|
|
50415
50415
|
var secrets = new import_client_secrets_manager.SecretsManagerClient({});
|
|
50416
|
-
var
|
|
50417
|
-
async function
|
|
50418
|
-
if (
|
|
50419
|
-
return
|
|
50416
|
+
var cachedControllerSigningSecret;
|
|
50417
|
+
async function getControllerSigningSecret(secretArn, key) {
|
|
50418
|
+
if (cachedControllerSigningSecret !== void 0) {
|
|
50419
|
+
return cachedControllerSigningSecret;
|
|
50420
50420
|
}
|
|
50421
50421
|
const { SecretString } = await secrets.send(new import_client_secrets_manager.GetSecretValueCommand({ SecretId: secretArn }));
|
|
50422
50422
|
if (!SecretString) {
|
|
50423
|
-
throw new Error(`
|
|
50423
|
+
throw new Error(`controller signing secret ${secretArn} has no SecretString`);
|
|
50424
50424
|
}
|
|
50425
50425
|
const value = JSON.parse(SecretString)[key];
|
|
50426
50426
|
if (typeof value !== "string" || value === "") {
|
|
50427
|
-
throw new Error(`
|
|
50427
|
+
throw new Error(`controller signing secret ${secretArn} is missing a non-empty '${key}' field`);
|
|
50428
50428
|
}
|
|
50429
|
-
|
|
50429
|
+
cachedControllerSigningSecret = value;
|
|
50430
50430
|
return value;
|
|
50431
50431
|
}
|
|
50432
50432
|
function createRuntimeMicrovm(executionRoleArn) {
|
|
@@ -50501,9 +50501,9 @@ function requireEnv(name) {
|
|
|
50501
50501
|
}
|
|
50502
50502
|
var sleep3 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
50503
50503
|
var handler = async (event) => {
|
|
50504
|
-
const
|
|
50505
|
-
requireEnv("
|
|
50506
|
-
requireEnv("
|
|
50504
|
+
const controllerSigningSecret = await getControllerSigningSecret(
|
|
50505
|
+
requireEnv("CONTROLLER_SIGNING_SECRET_ARN"),
|
|
50506
|
+
requireEnv("CONTROLLER_SIGNING_SECRET_KEY")
|
|
50507
50507
|
);
|
|
50508
50508
|
const shapes = parseShapeCatalogue(requireEnv("MICROVM_SHAPES"));
|
|
50509
50509
|
const executionRoleArn = requireEnv("MICROVM_EXECUTION_ROLE_ARN");
|
|
@@ -50511,7 +50511,7 @@ var handler = async (event) => {
|
|
|
50511
50511
|
const { statusCode, body } = await handleDoorbell({
|
|
50512
50512
|
rawBody,
|
|
50513
50513
|
signatureHeader: event.headers?.["x-evident-signature"],
|
|
50514
|
-
|
|
50514
|
+
controllerSigningSecret,
|
|
50515
50515
|
shapes,
|
|
50516
50516
|
microvm: createRuntimeMicrovm(executionRoleArn),
|
|
50517
50517
|
sleep: sleep3,
|
|
@@ -157,6 +157,13 @@ class EvidentScaleToZeroConstruct extends constructs_1.Construct {
|
|
|
157
157
|
// Object Get/Put/Delete scoped to this agent's prefix. DeleteObject is
|
|
158
158
|
// required: Litestream's retention/compaction prunes old LTX files.
|
|
159
159
|
replicaBucket.grantReadWrite(taskRole, `${this.replicaPrefix}/*`);
|
|
160
|
+
// Vault credentials are read and conditionally written, so this shared prefix
|
|
161
|
+
// needs no delete or list permission; conditional writes use request headers.
|
|
162
|
+
taskRole.addToPrincipalPolicy(new iam.PolicyStatement({
|
|
163
|
+
sid: 'ReadWriteCredentialVaultObjects',
|
|
164
|
+
actions: ['s3:GetObject', 's3:PutObject'],
|
|
165
|
+
resources: [replicaBucket.arnForObjects('credential-vaults/*')],
|
|
166
|
+
}));
|
|
160
167
|
this.service = new ecs.FargateService(this, 'Service', {
|
|
161
168
|
cluster,
|
|
162
169
|
taskDefinition,
|
|
@@ -29,15 +29,17 @@ var import_client_secrets_manager = require("@aws-sdk/client-secrets-manager");
|
|
|
29
29
|
var PHYSICAL_RESOURCE_ID = "evident-image-version-report";
|
|
30
30
|
var REPORT_TIMEOUT_MS = 5e3;
|
|
31
31
|
var secrets = new import_client_secrets_manager.SecretsManagerClient({});
|
|
32
|
-
async function
|
|
32
|
+
async function fetchControllerSigningSecret(secretArn, secretKey) {
|
|
33
33
|
const { SecretString } = await secrets.send(new import_client_secrets_manager.GetSecretValueCommand({ SecretId: secretArn }));
|
|
34
34
|
if (!SecretString) {
|
|
35
|
-
throw new Error(`
|
|
35
|
+
throw new Error(`controller signing secret ${secretArn} has no SecretString`);
|
|
36
36
|
}
|
|
37
37
|
const parsed = JSON.parse(SecretString);
|
|
38
38
|
const value = parsed !== null && typeof parsed === "object" ? parsed[secretKey] : void 0;
|
|
39
39
|
if (typeof value !== "string" || value === "") {
|
|
40
|
-
throw new Error(
|
|
40
|
+
throw new Error(
|
|
41
|
+
`controller signing secret ${secretArn} is missing a non-empty '${secretKey}' field`
|
|
42
|
+
);
|
|
41
43
|
}
|
|
42
44
|
return value;
|
|
43
45
|
}
|
|
@@ -63,7 +65,7 @@ function response() {
|
|
|
63
65
|
return { PhysicalResourceId: PHYSICAL_RESOURCE_ID };
|
|
64
66
|
}
|
|
65
67
|
var runtimeDependencies = {
|
|
66
|
-
getSecret:
|
|
68
|
+
getSecret: fetchControllerSigningSecret,
|
|
67
69
|
fetch: globalThis.fetch
|
|
68
70
|
};
|
|
69
71
|
async function handleImageVersionReport(event, getConfig, dependencies = runtimeDependencies) {
|
|
@@ -77,7 +79,10 @@ async function handleImageVersionReport(event, getConfig, dependencies = runtime
|
|
|
77
79
|
versions = readImageVersions(event.ResourceProperties?.imageVersions);
|
|
78
80
|
const config = getConfig();
|
|
79
81
|
runnerId = config.evidentRunnerId;
|
|
80
|
-
const secret = await dependencies.getSecret(
|
|
82
|
+
const secret = await dependencies.getSecret(
|
|
83
|
+
config.controllerSigningSecretArn,
|
|
84
|
+
config.controllerSigningSecretKey
|
|
85
|
+
);
|
|
81
86
|
const body = JSON.stringify({
|
|
82
87
|
type: "runner.microvm_image_versions_reported",
|
|
83
88
|
versions
|
|
@@ -117,8 +122,8 @@ async function handleImageVersionReport(event, getConfig, dependencies = runtime
|
|
|
117
122
|
return response();
|
|
118
123
|
}
|
|
119
124
|
var handler = (event) => handleImageVersionReport(event, () => ({
|
|
120
|
-
|
|
121
|
-
|
|
125
|
+
controllerSigningSecretArn: requireEnv("CONTROLLER_SIGNING_SECRET_ARN"),
|
|
126
|
+
controllerSigningSecretKey: requireEnv("CONTROLLER_SIGNING_SECRET_KEY"),
|
|
122
127
|
evidentApiUrl: requireEnv("EVIDENT_API_URL"),
|
|
123
128
|
evidentRunnerId: requireEnv("EVIDENT_RUNNER_ID")
|
|
124
129
|
}));
|
package/dist/index.d.ts
CHANGED
|
@@ -3,4 +3,4 @@ export { EvidentWaker, type EvidentWakerProps } from './waker/construct';
|
|
|
3
3
|
export { EvidentMicrovmConstruct, type EvidentMicrovmConstructProps } from './microvm/construct';
|
|
4
4
|
export { MICROVM_SHAPES, validateShapes, type MicrovmShape } from './microvm/shapes';
|
|
5
5
|
export { MICROVM_MAX_RUN_SECONDS, HOOKS_PORT, HOOK_TIMEOUT_SECONDS, } from './microvm/constants';
|
|
6
|
-
export {
|
|
6
|
+
export { MICROVM_WORKSPACE_DIRECTORY, stageMicrovmBuildContext, type StageMicrovmBuildContextOptions, } from './microvm/image/stage-context';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.stageMicrovmBuildContext = exports.MICROVM_WORKSPACE_DIRECTORY = exports.HOOK_TIMEOUT_SECONDS = exports.HOOKS_PORT = exports.MICROVM_MAX_RUN_SECONDS = exports.validateShapes = exports.MICROVM_SHAPES = exports.EvidentMicrovmConstruct = exports.EvidentWaker = exports.EvidentScaleToZeroConstruct = void 0;
|
|
4
4
|
var evident_scale_to_zero_construct_1 = require("./evident-scale-to-zero-construct");
|
|
5
5
|
Object.defineProperty(exports, "EvidentScaleToZeroConstruct", { enumerable: true, get: function () { return evident_scale_to_zero_construct_1.EvidentScaleToZeroConstruct; } });
|
|
6
6
|
var construct_1 = require("./waker/construct");
|
|
@@ -17,4 +17,5 @@ Object.defineProperty(exports, "MICROVM_MAX_RUN_SECONDS", { enumerable: true, ge
|
|
|
17
17
|
Object.defineProperty(exports, "HOOKS_PORT", { enumerable: true, get: function () { return constants_1.HOOKS_PORT; } });
|
|
18
18
|
Object.defineProperty(exports, "HOOK_TIMEOUT_SECONDS", { enumerable: true, get: function () { return constants_1.HOOK_TIMEOUT_SECONDS; } });
|
|
19
19
|
var stage_context_1 = require("./microvm/image/stage-context");
|
|
20
|
-
Object.defineProperty(exports, "
|
|
20
|
+
Object.defineProperty(exports, "MICROVM_WORKSPACE_DIRECTORY", { enumerable: true, get: function () { return stage_context_1.MICROVM_WORKSPACE_DIRECTORY; } });
|
|
21
|
+
Object.defineProperty(exports, "stageMicrovmBuildContext", { enumerable: true, get: function () { return stage_context_1.stageMicrovmBuildContext; } });
|
|
@@ -6,4 +6,4 @@ export declare const SUSPENDING_POLL_ATTEMPTS = 4;
|
|
|
6
6
|
export declare const SUSPENDING_POLL_INTERVAL_MS = 500;
|
|
7
7
|
export declare const MICROVM_MAX_RUN_SECONDS = 28800;
|
|
8
8
|
export declare const SESSION_CLEANUP_MAX_AGE = "24h";
|
|
9
|
-
export declare const
|
|
9
|
+
export declare const CONTROLLER_SIGNING_SECRET_KEY = "CONTROLLER_SIGNING_SECRET";
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// across them. The image template pins matching copies, guarded by
|
|
5
5
|
// `microvm/image/dockerfile.test.ts`.
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.
|
|
7
|
+
exports.CONTROLLER_SIGNING_SECRET_KEY = exports.SESSION_CLEANUP_MAX_AGE = exports.MICROVM_MAX_RUN_SECONDS = exports.SUSPENDING_POLL_INTERVAL_MS = exports.SUSPENDING_POLL_ATTEMPTS = exports.RUN_HOOK_PAYLOAD_MAX_BYTES = exports.HOOK_TIMEOUT_SECONDS = exports.HOOKS_DIR = exports.HOOKS_PORT = void 0;
|
|
8
8
|
// The port the image's hook server binds, baked into the published Dockerfile
|
|
9
9
|
// (`ENV HOOKS_PORT`) and passed to the construct as `hooksPort`. Exported from
|
|
10
10
|
// the package index because a consumer building the published image context
|
|
@@ -38,6 +38,9 @@ exports.MICROVM_MAX_RUN_SECONDS = 28800;
|
|
|
38
38
|
// The 24h window rather than 7d keeps the session database bounded at the growth
|
|
39
39
|
// rate observed in #537. The sweep runs on the default 1h interval.
|
|
40
40
|
exports.SESSION_CLEANUP_MAX_AGE = '24h';
|
|
41
|
-
// The JSON field inside the
|
|
42
|
-
// it and by whatever produces the secret; this is never the secret
|
|
43
|
-
|
|
41
|
+
// The JSON field inside the controller signing secret is shared by the Lambdas
|
|
42
|
+
// that name it and by whatever produces the secret; this is never the secret
|
|
43
|
+
// value. It is customer-facing and separate from persisted doorbell messages
|
|
44
|
+
// and telemetry. Changing the value silently stops an existing adopter's
|
|
45
|
+
// secret from being read.
|
|
46
|
+
exports.CONTROLLER_SIGNING_SECRET_KEY = 'CONTROLLER_SIGNING_SECRET';
|
|
@@ -4,12 +4,10 @@ import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
|
|
|
4
4
|
import { type MicrovmShape } from './shapes';
|
|
5
5
|
export interface EvidentMicrovmConstructProps {
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* stays a pure function of its inputs instead of depending on how the
|
|
10
|
-
* ambient checkout was cloned.
|
|
7
|
+
* Directory containing the consumer's Dockerfile and every file it copies,
|
|
8
|
+
* typically the return value of `stageMicrovmBuildContext()`.
|
|
11
9
|
*/
|
|
12
|
-
readonly
|
|
10
|
+
readonly buildContextPath: string;
|
|
13
11
|
/**
|
|
14
12
|
* ARN of the Lambda-managed base MicroVM image, from
|
|
15
13
|
* `aws lambda-microvms list-managed-microvm-images`. A string, not a `CfnParameter`
|
|
@@ -33,14 +31,14 @@ export interface EvidentMicrovmConstructProps {
|
|
|
33
31
|
readonly shapes?: readonly MicrovmShape[];
|
|
34
32
|
/**
|
|
35
33
|
* The secret holding the shared HMAC key the controller verifies every
|
|
36
|
-
* doorbell against, under the field `
|
|
34
|
+
* doorbell against, under the field `CONTROLLER_SIGNING_SECRET`. Any Secrets Manager
|
|
37
35
|
* secret, however the caller manages it — this construct has no opinion on
|
|
38
36
|
* SOPS, KMS, or any other production mechanism. Required (not defaulted
|
|
39
37
|
* here): a default would need a scope of its own, and a caller-owned
|
|
40
38
|
* default keeps its own logical id — the caller builds one exactly once,
|
|
41
39
|
* in its own scope, and passes the result in either case.
|
|
42
40
|
*/
|
|
43
|
-
readonly
|
|
41
|
+
readonly controllerSigningSecret: secretsmanager.ISecret;
|
|
44
42
|
/**
|
|
45
43
|
* Optional runner secret whose JSON values are exported by `/run`. Omitting it
|
|
46
44
|
* leaves GitHub and MCP credentials unavailable to a generic consumer.
|
|
@@ -65,7 +63,7 @@ export interface EvidentMicrovmConstructProps {
|
|
|
65
63
|
* TCP port the image's hook server listens on, baked into both the image
|
|
66
64
|
* (`HOOKS_PORT` env var) and the `MicrovmImage`'s `hooks.port` — a mismatch
|
|
67
65
|
* means AWS probes a port nothing binds. Caller-supplied because the hook
|
|
68
|
-
* server is part of
|
|
66
|
+
* server is part of the base image, which this construct does not build.
|
|
69
67
|
*/
|
|
70
68
|
readonly hooksPort: number;
|
|
71
69
|
/**
|
|
@@ -37,6 +37,7 @@ exports.EvidentMicrovmConstruct = void 0;
|
|
|
37
37
|
const path = __importStar(require("path"));
|
|
38
38
|
const cdk = __importStar(require("aws-cdk-lib"));
|
|
39
39
|
const constructs_1 = require("constructs");
|
|
40
|
+
const iam = __importStar(require("aws-cdk-lib/aws-iam"));
|
|
40
41
|
const lambda = __importStar(require("aws-cdk-lib/aws-lambda"));
|
|
41
42
|
const s3 = __importStar(require("aws-cdk-lib/aws-s3"));
|
|
42
43
|
const lambda_microvm_cdk_1 = require("@evident-ai/lambda-microvm-cdk");
|
|
@@ -145,42 +146,31 @@ class EvidentMicrovmConstruct extends constructs_1.Construct {
|
|
|
145
146
|
lifecycleTimeout: cdk.Duration.seconds(props.hookTimeoutSeconds),
|
|
146
147
|
buildTimeout: cdk.Duration.seconds(IMAGE_HOOK_TIMEOUT_SECONDS),
|
|
147
148
|
};
|
|
148
|
-
// Built first
|
|
149
|
-
//
|
|
150
|
-
// shape
|
|
151
|
-
|
|
152
|
-
// and current logical id — zero CloudFormation churn on the live,
|
|
153
|
-
// hand-deployed stack (D3/D9). Every additional shape below borrows this
|
|
154
|
-
// role rather than getting one of its own, which is why `validateShapes`
|
|
155
|
-
// requires the default shape to be `shapes[0]`: it must exist before any
|
|
156
|
-
// other shape can reference its execution role.
|
|
157
|
-
const defaultImage = new lambda_microvm_cdk_1.MicrovmImage(this, defaultShape.constructId, {
|
|
149
|
+
// Built first without an `executionRole` prop, so every additional shape
|
|
150
|
+
// can borrow this image's execution role. `validateShapes` requires the
|
|
151
|
+
// default shape to be `shapes[0]` because that role must exist first.
|
|
152
|
+
const defaultImage = new lambda_microvm_cdk_1.MicrovmImage(this, defaultShape.name, {
|
|
158
153
|
imageName: defaultShape.imageName,
|
|
159
154
|
description: defaultShape.description,
|
|
160
155
|
// A local directory, so the zip is uploaded by `cdk deploy` itself and the
|
|
161
156
|
// image ARN is a stack reference rather than a deploy-time input.
|
|
162
|
-
source: props.
|
|
157
|
+
source: props.buildContextPath,
|
|
163
158
|
baseImageArn: props.baseImageArn,
|
|
164
159
|
baseImageVersion: props.baseImageVersion,
|
|
165
160
|
memoryMiB: defaultShape.memoryMiB,
|
|
166
161
|
environment: imageEnvironment,
|
|
167
162
|
hooks: imageHooks,
|
|
168
163
|
});
|
|
169
|
-
// WART
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
// intentional — see D3 in the plan. Do NOT "fix" this with
|
|
174
|
-
// `overrideLogicalId`: renaming the logical id IS the CloudFormation
|
|
175
|
-
// churn this design exists to avoid (a delete-and-recreate of the role a
|
|
176
|
-
// running MicroVM has live session credentials from). The wart is one
|
|
177
|
-
// comment; the override would undo the point of D3.
|
|
164
|
+
// WART: every additional shape shares `defaultImage.executionRole`, so the
|
|
165
|
+
// role's path under the default image reads as if it belongs to one shape
|
|
166
|
+
// even though it serves all of them. Keeping one role avoids replacing a
|
|
167
|
+
// role that running MicroVMs may still have credentials from.
|
|
178
168
|
const images = new Map([[defaultShape.name, defaultImage]]);
|
|
179
169
|
for (const shape of additionalShapes) {
|
|
180
|
-
images.set(shape.name, new lambda_microvm_cdk_1.MicrovmImage(this, shape.
|
|
170
|
+
images.set(shape.name, new lambda_microvm_cdk_1.MicrovmImage(this, shape.name, {
|
|
181
171
|
imageName: shape.imageName,
|
|
182
172
|
description: shape.description,
|
|
183
|
-
source: props.
|
|
173
|
+
source: props.buildContextPath,
|
|
184
174
|
baseImageArn: props.baseImageArn,
|
|
185
175
|
baseImageVersion: props.baseImageVersion,
|
|
186
176
|
memoryMiB: shape.memoryMiB,
|
|
@@ -189,13 +179,14 @@ class EvidentMicrovmConstruct extends constructs_1.Construct {
|
|
|
189
179
|
hooks: imageHooks,
|
|
190
180
|
}));
|
|
191
181
|
}
|
|
192
|
-
//
|
|
193
|
-
// the
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
182
|
+
// The execution role serves every image shape, so per-session state is scoped
|
|
183
|
+
// to the `runners/` root; vault credentials need only GetObject and PutObject.
|
|
184
|
+
durableState.grantReadWrite(defaultImage.executionRole, 'runners/*');
|
|
185
|
+
defaultImage.executionRole.addToPrincipalPolicy(new iam.PolicyStatement({
|
|
186
|
+
sid: 'ReadWriteCredentialVaultObjects',
|
|
187
|
+
actions: ['s3:GetObject', 's3:PutObject'],
|
|
188
|
+
resources: [durableState.arnForObjects('credential-vaults/*')],
|
|
189
|
+
}));
|
|
199
190
|
// The image receives only an ARN; `/run` reads the secret value at runtime.
|
|
200
191
|
props.runnerSecret?.grantRead(defaultImage.executionRole);
|
|
201
192
|
// The shape catalogue the controller resolves a doorbell's `shape` field
|
|
@@ -235,8 +226,8 @@ class EvidentMicrovmConstruct extends constructs_1.Construct {
|
|
|
235
226
|
// The ARN and field name, never the value: the controller reads it from
|
|
236
227
|
// Secrets Manager at runtime, so the key is not exposed to anyone with
|
|
237
228
|
// lambda:GetFunctionConfiguration.
|
|
238
|
-
|
|
239
|
-
|
|
229
|
+
CONTROLLER_SIGNING_SECRET_ARN: props.controllerSigningSecret.secretArn,
|
|
230
|
+
CONTROLLER_SIGNING_SECRET_KEY: constants_1.CONTROLLER_SIGNING_SECRET_KEY,
|
|
240
231
|
MICROVM_SHAPES: shapeCatalogueJson,
|
|
241
232
|
// The role every shape's MicroVM shares (D3) — still one role, still
|
|
242
233
|
// this same expression, now documented as shared rather than singular.
|
|
@@ -251,7 +242,7 @@ class EvidentMicrovmConstruct extends constructs_1.Construct {
|
|
|
251
242
|
// emit N near-identical policy statements and break the "exactly one
|
|
252
243
|
// statement granting X" guarantees this construct's test encodes.
|
|
253
244
|
defaultImage.grantRun(controller);
|
|
254
|
-
props.
|
|
245
|
+
props.controllerSigningSecret.grantRead(controller);
|
|
255
246
|
const functionUrl = controller.addFunctionUrl({
|
|
256
247
|
// HMAC verification of the doorbell body IS the auth, as for the ECS waker.
|
|
257
248
|
authType: lambda.FunctionUrlAuthType.NONE,
|
|
@@ -261,7 +252,7 @@ class EvidentMicrovmConstruct extends constructs_1.Construct {
|
|
|
261
252
|
// no custom resource — this branch must not run for them.
|
|
262
253
|
if (props.evidentApiUrl && props.evidentRunnerId) {
|
|
263
254
|
new construct_1.ImageVersionReporter(this, 'ImageVersionReporter', {
|
|
264
|
-
|
|
255
|
+
controllerSigningSecret: props.controllerSigningSecret,
|
|
265
256
|
evidentApiUrl: props.evidentApiUrl,
|
|
266
257
|
evidentRunnerId: props.evidentRunnerId,
|
|
267
258
|
imageVersions: Object.fromEntries(shapes.map((shape) => [shape.name, images.get(shape.name).latestActiveImageVersion])),
|
|
@@ -17,10 +17,10 @@ export type DoorbellResult = {
|
|
|
17
17
|
kind: ActionKind;
|
|
18
18
|
};
|
|
19
19
|
};
|
|
20
|
-
export declare function handleDoorbell({ rawBody, signatureHeader,
|
|
20
|
+
export declare function handleDoorbell({ rawBody, signatureHeader, controllerSigningSecret, shapes, microvm, sleep, random, }: {
|
|
21
21
|
rawBody: string;
|
|
22
22
|
signatureHeader: string | undefined;
|
|
23
|
-
|
|
23
|
+
controllerSigningSecret: string;
|
|
24
24
|
shapes: ShapeCatalogue;
|
|
25
25
|
microvm: MicrovmClient;
|
|
26
26
|
} & Timing): Promise<DoorbellResult>;
|
|
@@ -174,8 +174,8 @@ async function shouldRecreateForNewerImage(doorbellRequest, shape, runningVersio
|
|
|
174
174
|
// been deactivated, and recreating would not roll it forward. Resume.
|
|
175
175
|
return compareImageVersions(running, latest) < 0;
|
|
176
176
|
}
|
|
177
|
-
async function handleDoorbell({ rawBody, signatureHeader,
|
|
178
|
-
if (!(0, sdk_1.verifyEvidentSignature)(rawBody, signatureHeader,
|
|
177
|
+
async function handleDoorbell({ rawBody, signatureHeader, controllerSigningSecret, shapes, microvm, sleep, random, }) {
|
|
178
|
+
if (!(0, sdk_1.verifyEvidentSignature)(rawBody, signatureHeader, controllerSigningSecret)) {
|
|
179
179
|
return decide({
|
|
180
180
|
statusCode: 401,
|
|
181
181
|
action: 'rejected',
|
|
@@ -1,42 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
readonly
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
* credential-free URL (typically `https://github.com/<owner>/<repo>.git`):
|
|
11
|
-
* this string ships inside the shared snapshot, so a token in it would be
|
|
12
|
-
* baked into every VM. The credential arrives per-VM instead.
|
|
13
|
-
*/
|
|
14
|
-
readonly originUrl: string;
|
|
15
|
-
/** Directory to write the build context to. Removed and recreated. */
|
|
16
|
-
readonly destination: string;
|
|
17
|
-
/**
|
|
18
|
-
* Overlay scripts the image build runs as root and as `runner`. The overlay
|
|
19
|
-
* ships inside the shared snapshot, so it may install software but must carry
|
|
20
|
-
* no credential. When omitted, deterministic no-op scripts are written.
|
|
21
|
-
*/
|
|
22
|
-
readonly overlayDir?: string;
|
|
23
|
-
/**
|
|
24
|
-
* The published template to copy from. Defaults to the one inside this
|
|
25
|
-
* package; named directly by the tests, which assert against a template they
|
|
26
|
-
* built themselves rather than whichever `dist/` happens to be present.
|
|
27
|
-
*/
|
|
28
|
-
readonly templateDir?: string;
|
|
1
|
+
/** The fixed directory that contains the workspace in a staged build context. */
|
|
2
|
+
export declare const MICROVM_WORKSPACE_DIRECTORY = "workspace";
|
|
3
|
+
export interface StageMicrovmBuildContextOptions {
|
|
4
|
+
/** The customer's directory containing their Dockerfile and COPY sources. */
|
|
5
|
+
readonly buildContextPath: string;
|
|
6
|
+
/** A full local checkout to bake into the image's `workspace/` directory. */
|
|
7
|
+
readonly workspaceRepositoryPath: string;
|
|
8
|
+
/** Credential-free origin URL carried by the baked checkout. */
|
|
9
|
+
readonly workspaceOriginUrl: string;
|
|
29
10
|
}
|
|
30
11
|
/**
|
|
31
|
-
*
|
|
32
|
-
* per-phase hook scripts, the deployment overlay and the repository — and
|
|
33
|
-
* returns its path. The result is what `EvidentMicrovmConstruct`'s `imageSource`
|
|
34
|
-
* takes.
|
|
12
|
+
* Stages customer-owned image inputs in a fresh temporary directory.
|
|
35
13
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* copy of those files. A caller may supply an overlay; the default is
|
|
39
|
-
* deterministic no-op scripts. `repositoryPath` is the only required caller
|
|
40
|
-
* input.
|
|
14
|
+
* Staging keeps the image asset hash a pure function of the workspace commit while
|
|
15
|
+
* leaving the customer in control of the Dockerfile and its build context.
|
|
41
16
|
*/
|
|
42
|
-
export declare function
|
|
17
|
+
export declare function stageMicrovmBuildContext(options: StageMicrovmBuildContextOptions): string;
|