cdk-local 0.149.5 → 0.149.7
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/cli.js +2 -2
- package/dist/{docker-cmd-B5yU__zC.js → docker-cmd-leyvDn1u.js} +232 -2
- package/dist/{docker-cmd-B5yU__zC.js.map → docker-cmd-leyvDn1u.js.map} +1 -1
- package/dist/index.js +2 -2
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +2 -2
- package/dist/local-studio-D-j3TZWX.d.ts.map +1 -1
- package/dist/{local-studio-CV21zLBM.js → local-studio-U-gD2HSf.js} +364 -101
- package/dist/local-studio-U-gD2HSf.js.map +1 -0
- package/package.json +1 -1
- package/dist/local-studio-CV21zLBM.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as
|
|
1
|
+
import { C as stringifyThrown, E as getEmbedConfig, O as setEmbedConfig, S as sanitizeServiceExceptionMessage, T as resolveConfiguredLogLevel, _ as clampErrorName, a as isDockerClientEnvKey, b as flattenToOneLine, c as runDockerStreaming, d as absoluteAssemblyPathEscape, f as assemblyPathEscape, g as resolveAssemblyPath, h as renderAssemblyPathEscape, i as getDockerCmd, l as spawnStreaming, m as namesTheSameDirectory, n as finchSecretArgvRefusal, o as isMalformedEnvKey, p as displayUntrustedValue, r as formatDockerLoginError, s as runDockerForeground, u as warnFinchArgvExposure, v as describeAwsFailureForWarn, w as getLogger, x as isAwsServiceException, y as describeCredentialLoadFailure } from "./docker-cmd-leyvDn1u.js";
|
|
2
2
|
import { chmodSync, cpSync, createWriteStream, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, readlinkSync, realpathSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { homedir, tmpdir } from "node:os";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -6358,6 +6358,36 @@ function toFlagName(field) {
|
|
|
6358
6358
|
return "--" + field.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
6359
6359
|
}
|
|
6360
6360
|
|
|
6361
|
+
//#endregion
|
|
6362
|
+
//#region src/utils/own-keys.ts
|
|
6363
|
+
/**
|
|
6364
|
+
* Write `key` as an OWN, ordinary data property of `target`.
|
|
6365
|
+
*
|
|
6366
|
+
* Plain assignment (`target[key] = value`) on an object literal routes the
|
|
6367
|
+
* key `__proto__` through `Object.prototype`'s `__proto__` accessor: a string
|
|
6368
|
+
* value is silently ignored and an object value REPLACES the target's
|
|
6369
|
+
* prototype. For maps keyed by user-supplied names (env vars, secrets) that
|
|
6370
|
+
* means a variable literally named `__proto__` vanishes without a warning
|
|
6371
|
+
* (issue #769). `Object.defineProperty` never consults the accessor, so the
|
|
6372
|
+
* key lands as an own property like any other.
|
|
6373
|
+
*
|
|
6374
|
+
* The descriptor matches what an ordinary assignment creates (writable,
|
|
6375
|
+
* enumerable, configurable), so `Object.keys` / `Object.entries` / spread /
|
|
6376
|
+
* `JSON.stringify` / `delete` treat the key exactly like its siblings.
|
|
6377
|
+
*
|
|
6378
|
+
* Readers of such a map must test membership with `Object.hasOwn`, never
|
|
6379
|
+
* `in` or a bare `map[key] !== undefined`: both see the inherited
|
|
6380
|
+
* `__proto__` accessor on a map that does not carry the key.
|
|
6381
|
+
*/
|
|
6382
|
+
function defineOwnKey(target, key, value) {
|
|
6383
|
+
Object.defineProperty(target, key, {
|
|
6384
|
+
value,
|
|
6385
|
+
writable: true,
|
|
6386
|
+
enumerable: true,
|
|
6387
|
+
configurable: true
|
|
6388
|
+
});
|
|
6389
|
+
}
|
|
6390
|
+
|
|
6361
6391
|
//#endregion
|
|
6362
6392
|
//#region src/local/state-resolver.ts
|
|
6363
6393
|
/**
|
|
@@ -7020,12 +7050,12 @@ function substituteEnvVarsFromState(templateEnv, contextOrResources) {
|
|
|
7020
7050
|
const context = isContext(contextOrResources) ? contextOrResources : { resources: contextOrResources };
|
|
7021
7051
|
for (const [key, value] of Object.entries(templateEnv)) {
|
|
7022
7052
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
7023
|
-
env
|
|
7053
|
+
defineOwnKey(env, key, value);
|
|
7024
7054
|
continue;
|
|
7025
7055
|
}
|
|
7026
7056
|
const { result, consumedSensitive } = resolveWithSensitivity(value, context);
|
|
7027
7057
|
if (result.kind === "literal") {
|
|
7028
|
-
env
|
|
7058
|
+
defineOwnKey(env, key, result.value);
|
|
7029
7059
|
audit.resolvedKeys.push(key);
|
|
7030
7060
|
if (consumedSensitive) audit.sensitiveKeys.push(key);
|
|
7031
7061
|
} else audit.unresolved.push({
|
|
@@ -7113,12 +7143,12 @@ async function substituteEnvVarsFromStateAsync(templateEnv, contextOrResources)
|
|
|
7113
7143
|
const context = isContext(contextOrResources) ? contextOrResources : { resources: contextOrResources };
|
|
7114
7144
|
for (const [key, value] of Object.entries(templateEnv)) {
|
|
7115
7145
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
7116
|
-
env
|
|
7146
|
+
defineOwnKey(env, key, value);
|
|
7117
7147
|
continue;
|
|
7118
7148
|
}
|
|
7119
7149
|
const { result, consumedSensitive } = await resolveWithSensitivityAsync(value, context);
|
|
7120
7150
|
if (result.kind === "literal") {
|
|
7121
|
-
env
|
|
7151
|
+
defineOwnKey(env, key, result.value);
|
|
7122
7152
|
audit.resolvedKeys.push(key);
|
|
7123
7153
|
if (consumedSensitive) audit.sensitiveKeys.push(key);
|
|
7124
7154
|
} else audit.unresolved.push({
|
|
@@ -7162,9 +7192,9 @@ function applyDeployedEnvFallback(resolvedEnv, unresolved, deployedEnv) {
|
|
|
7162
7192
|
const filled = [];
|
|
7163
7193
|
const stillUnresolved = [];
|
|
7164
7194
|
for (const item of unresolved) {
|
|
7165
|
-
const deployedValue = deployedEnv[item.key];
|
|
7195
|
+
const deployedValue = Object.hasOwn(deployedEnv, item.key) ? deployedEnv[item.key] : void 0;
|
|
7166
7196
|
if (deployedValue !== void 0) {
|
|
7167
|
-
env
|
|
7197
|
+
defineOwnKey(env, item.key, deployedValue);
|
|
7168
7198
|
filled.push(item.key);
|
|
7169
7199
|
} else stillUnresolved.push({
|
|
7170
7200
|
key: item.key,
|
|
@@ -7178,6 +7208,166 @@ function applyDeployedEnvFallback(resolvedEnv, unresolved, deployedEnv) {
|
|
|
7178
7208
|
};
|
|
7179
7209
|
}
|
|
7180
7210
|
|
|
7211
|
+
//#endregion
|
|
7212
|
+
//#region src/local/ecr-uri.ts
|
|
7213
|
+
/**
|
|
7214
|
+
* Every ECR private-registry host form, read off the AWS-published `ecr`
|
|
7215
|
+
* endpoint list (https://docs.aws.amazon.com/general/latest/gr/ecr.html):
|
|
7216
|
+
*
|
|
7217
|
+
* - `<acct>.dkr.ecr.<region>.<partition suffix>` — the plain IPv4 endpoint.
|
|
7218
|
+
* - `<acct>.dkr.ecr-fips.<region>.<partition suffix>` — FIPS IPv4.
|
|
7219
|
+
* - `<acct>.dkr-ecr.<region>.on.aws` — dual-stack (IPv4 + IPv6).
|
|
7220
|
+
* - `<acct>.dkr-ecr-fips.<region>.on.aws` — dual-stack FIPS.
|
|
7221
|
+
*
|
|
7222
|
+
* Every form except the plain one is scoped to the `aws` and `aws-us-gov`
|
|
7223
|
+
* partitions, the only ones that endpoint list shows it in: both FIPS forms are
|
|
7224
|
+
* listed for `us-east-1` / `us-east-2` / `us-west-1` / `us-west-2` /
|
|
7225
|
+
* `us-gov-east-1` / `us-gov-west-1` only, and `on.aws` is the dual-stack DNS of
|
|
7226
|
+
* those two partitions (the others serve dual-stack under their own suffixes,
|
|
7227
|
+
* which are not listed here). A FIPS or dual-stack host in any other partition
|
|
7228
|
+
* is therefore not recognized and is pulled as a public image, as before issue
|
|
7229
|
+
* #760. The scope is by PARTITION, not by region: a commercial region AWS adds
|
|
7230
|
+
* later is accepted, and a login to an unserved region's FIPS host only fails,
|
|
7231
|
+
* because the suffix is still AWS-owned.
|
|
7232
|
+
*/
|
|
7233
|
+
const FIPS_AND_DUAL_STACK_PARTITIONS = ["aws", "aws-us-gov"];
|
|
7234
|
+
const ECR_REGISTRY_HOST_FORMS = [
|
|
7235
|
+
{ labels: "dkr.ecr" },
|
|
7236
|
+
{
|
|
7237
|
+
labels: "dkr.ecr-fips",
|
|
7238
|
+
partitions: FIPS_AND_DUAL_STACK_PARTITIONS
|
|
7239
|
+
},
|
|
7240
|
+
{
|
|
7241
|
+
labels: "dkr-ecr",
|
|
7242
|
+
fixedUrlSuffix: "on.aws",
|
|
7243
|
+
partitions: FIPS_AND_DUAL_STACK_PARTITIONS
|
|
7244
|
+
},
|
|
7245
|
+
{
|
|
7246
|
+
labels: "dkr-ecr-fips",
|
|
7247
|
+
fixedUrlSuffix: "on.aws",
|
|
7248
|
+
partitions: FIPS_AND_DUAL_STACK_PARTITIONS
|
|
7249
|
+
}
|
|
7250
|
+
];
|
|
7251
|
+
const ECR_HOST_FORM_BY_LABELS = new Map(ECR_REGISTRY_HOST_FORMS.map((f) => [f.labels, f]));
|
|
7252
|
+
function escapeRegExp(value) {
|
|
7253
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7254
|
+
}
|
|
7255
|
+
/**
|
|
7256
|
+
* `<acct>.<labels>.<region>.<suffix>/` — ANCHORED at the start, so nothing
|
|
7257
|
+
* (userinfo, a scheme, another host) can precede the account id, and ending at
|
|
7258
|
+
* the first `/`, so the captured suffix is the rest of the host.
|
|
7259
|
+
*
|
|
7260
|
+
* The labels alternation is built from {@link ECR_REGISTRY_HOST_FORMS},
|
|
7261
|
+
* ESCAPED (an unescaped `dkr.ecr` would match `dkrxecr`) and longest first, so
|
|
7262
|
+
* `dkr.ecr-fips` is tried before its own `dkr.ecr` prefix. The `i` flag makes
|
|
7263
|
+
* the labels case-insensitive, as DNS is; it is non-unicode, so it does NOT
|
|
7264
|
+
* treat U+212A (Kelvin sign) as `k`, and `\d` is ASCII-only.
|
|
7265
|
+
*/
|
|
7266
|
+
const ECR_URI_HOST_REGEX = new RegExp(`^(\\d{12})\\.(${[...ECR_REGISTRY_HOST_FORMS].sort((a, b) => b.labels.length - a.labels.length).map((f) => escapeRegExp(f.labels)).join("|")})\\.([^./]+)\\.([^/]+)/`, "i");
|
|
7267
|
+
/**
|
|
7268
|
+
* A region segment is ASCII alphanumerics plus `-`. Tested on the RAW capture,
|
|
7269
|
+
* BEFORE any case fold: `String.prototype.toLowerCase` folds U+212A onto an
|
|
7270
|
+
* ASCII `k`, so testing a folded value would admit `us-eKst-1` (Kelvin sign) as
|
|
7271
|
+
* the region `us-ekst-1` — a region the host does not name, which then seeds
|
|
7272
|
+
* the ECR client and the login endpoint. Today {@link REGION_SHAPE} also
|
|
7273
|
+
* refuses that input, because the fold is ASCII-only and the shape is ASCII
|
|
7274
|
+
* only, so this guard is DEFENSE IN DEPTH: it keeps the region safe if the fold
|
|
7275
|
+
* is ever widened to full Unicode folding.
|
|
7276
|
+
*/
|
|
7277
|
+
const CANONICAL_REGION_SEGMENT = /^[A-Za-z0-9][A-Za-z0-9-]*$/;
|
|
7278
|
+
/**
|
|
7279
|
+
* The SHAPE of an AWS region id, tested on the FOLDED region after the charset
|
|
7280
|
+
* guard above. One alternative per partition, transcribed from the
|
|
7281
|
+
* `regionRegex` of each partition in the AWS SDK / botocore `partitions.json`
|
|
7282
|
+
* (`\w+` written as `[a-z0-9]+`):
|
|
7283
|
+
*
|
|
7284
|
+
* - aws: `(us|eu|ap|sa|ca|me|af|il|mx)-X-N`
|
|
7285
|
+
* - aws-cn: `cn-X-N`; aws-us-gov: `us-gov-X-N`
|
|
7286
|
+
* - aws-iso / -b / -e / -f: `us-iso-X-N`, `us-isob-X-N`, `eu-isoe-X-N`,
|
|
7287
|
+
* `us-isof-X-N`
|
|
7288
|
+
* - aws-eusc: `eusc-<cc>-X-N` — broader than upstream's `eusc-(de)-`, matching
|
|
7289
|
+
* `PARTITION_TABLE`'s `eusc-` prefix so a future country is not refused here
|
|
7290
|
+
* while the partition lookup accepts it.
|
|
7291
|
+
*
|
|
7292
|
+
* It refuses a label that is not a region — `s3`, `lambda-url`,
|
|
7293
|
+
* `s3-external-1`, `fips-us-east-1` — so `<acct>.dkr.ecr.s3.amazonaws.com` or
|
|
7294
|
+
* `<acct>.dkr-ecr.lambda-url.on.aws` never becomes a login target. Those hosts
|
|
7295
|
+
* are AWS-owned, but they are not registries, and only TLS would stop a token
|
|
7296
|
+
* sent there (an `insecure-registries` daemon setting turns that off).
|
|
7297
|
+
*/
|
|
7298
|
+
const REGION_SHAPE = /^(?:(?:us|eu|ap|sa|ca|me|af|il|mx)-[a-z0-9]+-[0-9]+|cn-[a-z0-9]+-[0-9]+|us-gov-[a-z0-9]+-[0-9]+|us-iso-[a-z0-9]+-[0-9]+|us-isob-[a-z0-9]+-[0-9]+|eu-isoe-[a-z0-9]+-[0-9]+|us-isof-[a-z0-9]+-[0-9]+|eusc-[a-z]+-[a-z0-9]+-[0-9]+)$/;
|
|
7299
|
+
/**
|
|
7300
|
+
* A URL suffix is a dotted name of ASCII alphanumerics and `-`, tested on the
|
|
7301
|
+
* raw capture like the region. Today it is DEFENSE IN DEPTH, not the load-
|
|
7302
|
+
* bearing check: the fold below is ASCII-only, so `cloud.adc-e.uK` (Kelvin
|
|
7303
|
+
* sign) never becomes the `aws-iso-e` suffix, and the exact-suffix comparison
|
|
7304
|
+
* already refuses a port (`:`), userinfo (`@`) or any look-alike. It keeps
|
|
7305
|
+
* the suffix safe if that fold is ever widened to full Unicode folding.
|
|
7306
|
+
*/
|
|
7307
|
+
const CANONICAL_URL_SUFFIX = /^[A-Za-z0-9][A-Za-z0-9.-]*$/;
|
|
7308
|
+
/**
|
|
7309
|
+
* Lower-case ONLY `A`-`Z`. DNS case-insensitivity is ASCII-only, and a full
|
|
7310
|
+
* `toLowerCase` would fold U+212A onto `k` and hand the charset guards above an
|
|
7311
|
+
* already-ASCII string, defeating them.
|
|
7312
|
+
*/
|
|
7313
|
+
function foldAsciiUpperCase(value) {
|
|
7314
|
+
return value.replace(/[A-Z]+/g, (run) => run.toLowerCase());
|
|
7315
|
+
}
|
|
7316
|
+
/** Docker's rule for "component 1 is a registry host, not a repository segment". */
|
|
7317
|
+
function isRegistryHostComponent(component) {
|
|
7318
|
+
return component.includes(".") || component.includes(":") || component === "localhost";
|
|
7319
|
+
}
|
|
7320
|
+
/**
|
|
7321
|
+
* Lower-case the REGISTRY HOST of an image reference (ASCII letters only) and
|
|
7322
|
+
* leave the repository path and tag byte-identical.
|
|
7323
|
+
*
|
|
7324
|
+
* Docker's credential store is keyed on the hostname VERBATIM, so a login to
|
|
7325
|
+
* one spelling and a pull from another sends no credentials; every
|
|
7326
|
+
* docker-facing use of an ECR reference goes through this one spelling. The
|
|
7327
|
+
* path is not folded: docker requires it lower-case already, and rewriting it
|
|
7328
|
+
* would name a different image. A first component that is not a registry host
|
|
7329
|
+
* (`MyOrg/MyRepo:tag` is a Docker Hub path) is left alone for the same reason.
|
|
7330
|
+
*/
|
|
7331
|
+
function canonicalizeImageUriHost(imageUri) {
|
|
7332
|
+
const slash = imageUri.indexOf("/");
|
|
7333
|
+
if (slash < 0) return imageUri;
|
|
7334
|
+
const host = imageUri.slice(0, slash);
|
|
7335
|
+
if (!isRegistryHostComponent(host)) return imageUri;
|
|
7336
|
+
return foldAsciiUpperCase(host) + imageUri.slice(slash);
|
|
7337
|
+
}
|
|
7338
|
+
/**
|
|
7339
|
+
* The ECR registry an image reference names, or `undefined` when its host is
|
|
7340
|
+
* not one of {@link ECR_REGISTRY_HOST_FORMS} with the suffix that form carries
|
|
7341
|
+
* for the region it names.
|
|
7342
|
+
*
|
|
7343
|
+
* The suffix check is what keeps a `docker login` from being pointed at a host
|
|
7344
|
+
* cdk-local does not own: `<acct>.dkr.ecr.<region>.example.com`, a look-alike
|
|
7345
|
+
* `amazonaws.com.evil.example`, a port or userinfo after the suffix, and a
|
|
7346
|
+
* form/suffix mispairing (`dkr-ecr` + `amazonaws.com`, `dkr.ecr` + `on.aws`,
|
|
7347
|
+
* an ISO region + `amazonaws.com`) are all refused.
|
|
7348
|
+
*/
|
|
7349
|
+
function parseEcrRegistryHost(imageUri) {
|
|
7350
|
+
const m = ECR_URI_HOST_REGEX.exec(imageUri);
|
|
7351
|
+
if (!m) return void 0;
|
|
7352
|
+
const [, accountId, rawLabels, rawRegion, rawSuffix] = m;
|
|
7353
|
+
if (!CANONICAL_REGION_SEGMENT.test(rawRegion)) return void 0;
|
|
7354
|
+
if (!CANONICAL_URL_SUFFIX.test(rawSuffix)) return void 0;
|
|
7355
|
+
const form = ECR_HOST_FORM_BY_LABELS.get(foldAsciiUpperCase(rawLabels));
|
|
7356
|
+
if (!form) return void 0;
|
|
7357
|
+
const region = foldAsciiUpperCase(rawRegion);
|
|
7358
|
+
if (!REGION_SHAPE.test(region)) return void 0;
|
|
7359
|
+
const suffix = foldAsciiUpperCase(rawSuffix);
|
|
7360
|
+
const partition = derivePartitionAndUrlSuffix(region);
|
|
7361
|
+
if (form.partitions && !form.partitions.includes(partition.partition)) return void 0;
|
|
7362
|
+
if (suffix !== (form.fixedUrlSuffix ?? partition.urlSuffix)) return void 0;
|
|
7363
|
+
return {
|
|
7364
|
+
accountId,
|
|
7365
|
+
region,
|
|
7366
|
+
registryHost: `${accountId}.${form.labels}.${region}.${suffix}`,
|
|
7367
|
+
form
|
|
7368
|
+
};
|
|
7369
|
+
}
|
|
7370
|
+
|
|
7181
7371
|
//#endregion
|
|
7182
7372
|
//#region src/local/ecs-task-resolver.ts
|
|
7183
7373
|
var EcsTaskResolutionError = class EcsTaskResolutionError extends Error {
|
|
@@ -7523,13 +7713,13 @@ function parseContainerDefinition(raw, idx, taskLogicalId, resources, stack, con
|
|
|
7523
7713
|
const value = e["Value"];
|
|
7524
7714
|
if (!key) continue;
|
|
7525
7715
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
7526
|
-
environment
|
|
7716
|
+
defineOwnKey(environment, key, String(value));
|
|
7527
7717
|
continue;
|
|
7528
7718
|
}
|
|
7529
7719
|
if (subContext) {
|
|
7530
7720
|
const { result: sub, consumedSensitive } = resolveWithSensitivity(value, subContext);
|
|
7531
7721
|
if (sub.kind === "literal") {
|
|
7532
|
-
environment
|
|
7722
|
+
defineOwnKey(environment, key, String(sub.value));
|
|
7533
7723
|
if (consumedSensitive) sensitiveEnvKeys.push(key);
|
|
7534
7724
|
continue;
|
|
7535
7725
|
}
|
|
@@ -7692,9 +7882,10 @@ function buildSubstitutionContextFromImageContext(context) {
|
|
|
7692
7882
|
* Parse the `Image` field of an ECS container definition.
|
|
7693
7883
|
*
|
|
7694
7884
|
* Three shapes:
|
|
7695
|
-
* - `<account>.dkr.ecr.<region>.amazonaws.com/<repo>:<tag>` —
|
|
7696
|
-
*
|
|
7697
|
-
*
|
|
7885
|
+
* - `<account>.dkr.ecr.<region>.amazonaws.com/<repo>:<tag>` — ECR, in any
|
|
7886
|
+
* host form `ECR_REGISTRY_HOST_FORMS` lists (FIPS / dual-stack too) and any
|
|
7887
|
+
* partition. Pulled through `pullEcrImage` (cross-account / cross-region
|
|
7888
|
+
* below).
|
|
7698
7889
|
* - `Fn::Sub` / `Fn::Join` / `Ref` referencing a `Code.fromAsset`-style
|
|
7699
7890
|
* CDK asset image. Surfaces `kind: 'cdk-asset'` with the optional
|
|
7700
7891
|
* asset hash so the runner can route through `docker-build.ts`.
|
|
@@ -7741,12 +7932,12 @@ function parseContainerImage(raw, containerName, taskLogicalId, resources, _stac
|
|
|
7741
7932
|
if (substituted.includes("AWS::")) throw new EcsTaskResolutionError(`Container '${containerName}' in task '${taskLogicalId}' has an Image that references AWS pseudo parameters (${substituted}). ${getEmbedConfig().productName} could not resolve them: confirm AWS credentials are configured so STS GetCallerIdentity succeeds, and that --region / AWS_REGION names the target region. Workaround: build the image locally (ContainerImage.fromAsset) or pin a public image.`);
|
|
7742
7933
|
throw new EcsTaskResolutionError(`Container '${containerName}' in task '${taskLogicalId}' has an Image with unresolved \${...} placeholders (${substituted}). ${getEmbedConfig().cliName} only resolves AWS pseudo parameters and same-stack AWS::ECR::Repository refs.`);
|
|
7743
7934
|
}
|
|
7744
|
-
const
|
|
7745
|
-
if (
|
|
7935
|
+
const ecrHost = parseEcrRegistryHost(substituted);
|
|
7936
|
+
if (ecrHost) return {
|
|
7746
7937
|
kind: "ecr",
|
|
7747
7938
|
uri: substituted,
|
|
7748
|
-
account:
|
|
7749
|
-
region:
|
|
7939
|
+
account: ecrHost.accountId,
|
|
7940
|
+
region: ecrHost.region
|
|
7750
7941
|
};
|
|
7751
7942
|
return {
|
|
7752
7943
|
kind: "public",
|
|
@@ -7782,12 +7973,12 @@ function classifyResolvedImage(uri) {
|
|
|
7782
7973
|
if (hashMatch) out.assetHash = hashMatch[1];
|
|
7783
7974
|
return out;
|
|
7784
7975
|
}
|
|
7785
|
-
const
|
|
7786
|
-
if (
|
|
7976
|
+
const ecrHost = parseEcrRegistryHost(uri);
|
|
7977
|
+
if (ecrHost) return {
|
|
7787
7978
|
kind: "ecr",
|
|
7788
7979
|
uri,
|
|
7789
|
-
account:
|
|
7790
|
-
region:
|
|
7980
|
+
account: ecrHost.accountId,
|
|
7981
|
+
region: ecrHost.region
|
|
7791
7982
|
};
|
|
7792
7983
|
return {
|
|
7793
7984
|
kind: "public",
|
|
@@ -8046,11 +8237,11 @@ async function applyCrossStackResolverToTask(task, context) {
|
|
|
8046
8237
|
const value = e["Value"];
|
|
8047
8238
|
if (!key) continue;
|
|
8048
8239
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") continue;
|
|
8049
|
-
if (
|
|
8240
|
+
if (Object.hasOwn(container.environment, key)) continue;
|
|
8050
8241
|
if (!isCrossStackIntrinsic(value)) continue;
|
|
8051
8242
|
const { result: sub, consumedSensitive } = await resolveWithSensitivityAsync(value, context);
|
|
8052
8243
|
if (sub.kind === "literal") {
|
|
8053
|
-
container.environment
|
|
8244
|
+
defineOwnKey(container.environment, key, String(sub.value));
|
|
8054
8245
|
resolvedEnvKeys.add(key);
|
|
8055
8246
|
if (consumedSensitive && !container.sensitiveEnvKeys.includes(key)) container.sensitiveEnvKeys.push(key);
|
|
8056
8247
|
}
|
|
@@ -8805,49 +8996,27 @@ function buildDockerBuildCommand(source, tag, platformOverride) {
|
|
|
8805
8996
|
//#endregion
|
|
8806
8997
|
//#region src/local/ecr-puller.ts
|
|
8807
8998
|
/**
|
|
8808
|
-
* ECR pull fallback for `cdkl invoke` / `cdkl start-api` /
|
|
8809
|
-
* `cdkl run-task`. When the image URI resolves to an ECR repo but
|
|
8810
|
-
* doesn't match any cdk.out asset (typical when invoking a stack
|
|
8811
|
-
* deployed elsewhere or sharing a centralized registry), cdk-local
|
|
8812
|
-
* authenticates against the target registry and runs `docker pull`.
|
|
8813
|
-
*
|
|
8814
|
-
* **Cross-account / cross-region** (#455):
|
|
8815
|
-
* - Same-account, same-region: fast path. No STS hop. The default
|
|
8816
|
-
* credential chain is used directly for `ecr:GetAuthorizationToken`.
|
|
8817
|
-
* - `ecrRoleArn` is provided: `sts:AssumeRole` is issued via the
|
|
8818
|
-
* default credential chain to obtain temporary credentials for the
|
|
8819
|
-
* target account. The resulting credentials authenticate the ECR
|
|
8820
|
-
* client (regardless of region — the ECR client is built for the
|
|
8821
|
-
* URI's region, which can differ from the caller's profile region).
|
|
8822
|
-
* - Cross-account, NO `ecrRoleArn`: cdk-local falls through to the
|
|
8823
|
-
* default credential chain. This works when the caller has been
|
|
8824
|
-
* granted cross-account `ecr:GetAuthorizationToken` +
|
|
8825
|
-
* `ecr:BatchGetImage` permissions on the target repository via an
|
|
8826
|
-
* IAM policy; otherwise AWS rejects the call with `AccessDenied`
|
|
8827
|
-
* and the user is pointed at `--ecr-role-arn`.
|
|
8828
|
-
*
|
|
8829
|
-
* The `--no-pull` semantics (C3 in the design doc):
|
|
8830
|
-
* - When NOT set: `ecrLogin` + `docker pull <uri>`.
|
|
8831
|
-
* - When set: skip `docker pull`. If the image isn't in the local
|
|
8832
|
-
* cache, the subsequent `docker run` will fail; we surface a clearer
|
|
8833
|
-
* "image not in local cache" error here so the user knows to drop
|
|
8834
|
-
* `--no-pull` or pre-pull manually.
|
|
8835
|
-
*/
|
|
8836
|
-
/** Regex matching the `<acct>.dkr.ecr.<region>.amazonaws.com/<repo>:<tag>` shape. */
|
|
8837
|
-
const ECR_URI_REGEX = /^(\d{12})\.dkr\.ecr\.([^.]+)\.amazonaws\.com(?:\.cn)?\/([^:]+):(.+)$/;
|
|
8838
|
-
/**
|
|
8839
8999
|
* Parse an ECR image URI. Returns `undefined` for non-ECR URIs (typically:
|
|
8840
9000
|
* Docker Hub, public.ecr.aws, gcr.io, ...) — those are user-managed
|
|
8841
9001
|
* images we don't try to authenticate against.
|
|
9002
|
+
*
|
|
9003
|
+
* Recognizes every host form in `ECR_REGISTRY_HOST_FORMS` (plain, FIPS,
|
|
9004
|
+
* dual-stack, dual-stack FIPS) in every partition, each with the suffix that
|
|
9005
|
+
* form carries for its region (issue #760) — see `parseEcrRegistryHost`.
|
|
8842
9006
|
*/
|
|
8843
9007
|
function parseEcrUri(imageUri) {
|
|
8844
|
-
const
|
|
9008
|
+
const host = parseEcrRegistryHost(imageUri);
|
|
9009
|
+
if (!host) return void 0;
|
|
9010
|
+
const canonicalUri = canonicalizeImageUriHost(imageUri);
|
|
9011
|
+
const m = /^([^:]+):(.+)$/.exec(canonicalUri.slice(canonicalUri.indexOf("/") + 1));
|
|
8845
9012
|
if (!m) return void 0;
|
|
8846
9013
|
return {
|
|
8847
|
-
accountId:
|
|
8848
|
-
region:
|
|
8849
|
-
repository: m[
|
|
8850
|
-
tag: m[
|
|
9014
|
+
accountId: host.accountId,
|
|
9015
|
+
region: host.region,
|
|
9016
|
+
repository: m[1],
|
|
9017
|
+
tag: m[2],
|
|
9018
|
+
canonicalUri,
|
|
9019
|
+
registryHost: host.registryHost
|
|
8851
9020
|
};
|
|
8852
9021
|
}
|
|
8853
9022
|
/**
|
|
@@ -8887,17 +9056,20 @@ function isCredentialFresh(creds) {
|
|
|
8887
9056
|
*
|
|
8888
9057
|
* Auto-detects cross-account from `STS:GetCallerIdentity` and assumes
|
|
8889
9058
|
* the supplied role when set. Returns the image URI the caller should
|
|
8890
|
-
* pass to `docker run
|
|
9059
|
+
* pass to `docker run`: the input with its registry host ASCII-lower-cased
|
|
9060
|
+
* (`ParsedEcrUri.canonicalUri`), so the run names the same spelling the pull
|
|
9061
|
+
* and the login used; the repository path and tag are unchanged.
|
|
8891
9062
|
*/
|
|
8892
9063
|
async function pullEcrImage(imageUri, options) {
|
|
8893
9064
|
const logger = getLogger().child("ecr-puller");
|
|
8894
9065
|
const parsed = parseEcrUri(imageUri);
|
|
8895
9066
|
if (!parsed) throw new LocalInvokeBuildError(`Image URI '${imageUri}' is not an ECR URI. ${getEmbedConfig().cliName} invoke v1 only authenticates against ECR for the deployed-image fallback path.`);
|
|
9067
|
+
const canonicalUri = parsed.canonicalUri;
|
|
8896
9068
|
const callerRegion = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"];
|
|
8897
9069
|
if (options.skipPull) {
|
|
8898
|
-
logger.info(`Skipping ECR pull (--no-pull). Verifying ${
|
|
8899
|
-
await verifyImageInLocalCache(
|
|
8900
|
-
return
|
|
9070
|
+
logger.info(`Skipping ECR pull (--no-pull). Verifying ${canonicalUri} is in local cache...`);
|
|
9071
|
+
await verifyImageInLocalCache(canonicalUri);
|
|
9072
|
+
return canonicalUri;
|
|
8901
9073
|
}
|
|
8902
9074
|
const callerIdentityKey = `${options.profile ?? "_noprofile"}|${callerRegion ?? "_unset"}`;
|
|
8903
9075
|
let callerAccount = CALLER_IDENTITY_CACHE.get(callerIdentityKey);
|
|
@@ -8940,17 +9112,21 @@ async function pullEcrImage(imageUri, options) {
|
|
|
8940
9112
|
...assumed ? { credentials: assumed } : options.profile ? { profile: options.profile } : {}
|
|
8941
9113
|
});
|
|
8942
9114
|
try {
|
|
8943
|
-
await ecrLogin(ecr,
|
|
9115
|
+
await ecrLogin(ecr, {
|
|
9116
|
+
accountId: parsed.accountId,
|
|
9117
|
+
region: parsed.region,
|
|
9118
|
+
registryHost: parsed.registryHost
|
|
9119
|
+
});
|
|
8944
9120
|
} finally {
|
|
8945
9121
|
ecr.destroy();
|
|
8946
9122
|
}
|
|
8947
|
-
logger.info(`Pulling ${
|
|
9123
|
+
logger.info(`Pulling ${canonicalUri}...`);
|
|
8948
9124
|
try {
|
|
8949
|
-
await runDockerForeground(["pull",
|
|
9125
|
+
await runDockerForeground(["pull", canonicalUri]);
|
|
8950
9126
|
} catch (err) {
|
|
8951
|
-
throw new LocalInvokeBuildError(`docker pull ${
|
|
9127
|
+
throw new LocalInvokeBuildError(`docker pull ${canonicalUri} failed: ${err.message}`);
|
|
8952
9128
|
}
|
|
8953
|
-
return
|
|
9129
|
+
return canonicalUri;
|
|
8954
9130
|
}
|
|
8955
9131
|
/**
|
|
8956
9132
|
* Assume the supplied role via the SDK default credential chain and
|
|
@@ -8991,16 +9167,29 @@ async function assumeRoleForEcr(roleArn, callerRegion, profile, logger) {
|
|
|
8991
9167
|
* Authenticate the local docker daemon against the target ECR registry.
|
|
8992
9168
|
* Self-contained in this module so the local-invoke path stays
|
|
8993
9169
|
* independent of any full asset-publish path's larger surface area.
|
|
8994
|
-
|
|
8995
|
-
|
|
8996
|
-
|
|
9170
|
+
*
|
|
9171
|
+
* The login endpoint is ALWAYS the host the PULL targets (issue #760):
|
|
9172
|
+
* docker's credential store is keyed on the hostname verbatim, so a login to
|
|
9173
|
+
* any other host leaves the pull with no credentials (`no basic auth
|
|
9174
|
+
* credentials`). `GetAuthorizationToken`'s `proxyEndpoint` is therefore NOT
|
|
9175
|
+
* used: it names the CALLER's default registry on the plain host, which is the
|
|
9176
|
+
* wrong host for a FIPS / dual-stack pull and the wrong ACCOUNT for a
|
|
9177
|
+
* cross-account pull made on the caller's own credentials (a repository policy
|
|
9178
|
+
* grant, no `--ecr-role-arn`). The token is principal-scoped, not host-scoped:
|
|
9179
|
+
* measured on real ECR, one token authenticates the plain, FIPS and dual-stack
|
|
9180
|
+
* hosts alike. The password reaches docker only over `--password-stdin`, never
|
|
9181
|
+
* argv.
|
|
9182
|
+
*/
|
|
9183
|
+
async function ecrLogin(client, target) {
|
|
9184
|
+
const { accountId, region, registryHost } = target;
|
|
9185
|
+
getLogger().child("ecr-puller").debug(`ECR login (account=${accountId}, region=${region}, host=${registryHost})`);
|
|
8997
9186
|
const authData = (await client.send(new GetAuthorizationTokenCommand({})).catch((err) => {
|
|
8998
9187
|
throw new LocalInvokeBuildError(`ECR GetAuthorizationToken failed (account=${accountId}, region=${region}): ${describeAwsFailureForWarn(err, "ECR GetAuthorizationToken")}. Verify the credentials can call ecr:GetAuthorizationToken.`);
|
|
8999
9188
|
})).authorizationData?.[0];
|
|
9000
9189
|
if (!authData?.authorizationToken) throw new LocalInvokeBuildError("Failed to get ECR authorization token");
|
|
9001
9190
|
const [username, password] = Buffer.from(authData.authorizationToken, "base64").toString().split(":");
|
|
9002
9191
|
if (!username || password === void 0) throw new LocalInvokeBuildError("ECR authorization token has unexpected shape (missing username/password)");
|
|
9003
|
-
const endpoint =
|
|
9192
|
+
const endpoint = `https://${registryHost}`;
|
|
9004
9193
|
try {
|
|
9005
9194
|
await runDockerStreaming([
|
|
9006
9195
|
"login",
|
|
@@ -9298,7 +9487,7 @@ async function runDetached(opts) {
|
|
|
9298
9487
|
args.push("-v", `${mount.hostPath}:${mount.containerPath}${ro}`);
|
|
9299
9488
|
}
|
|
9300
9489
|
const sensitiveKeys = opts.sensitiveEnvKeys && opts.sensitiveEnvKeys.size > 0 ? /* @__PURE__ */ new Set([...SENSITIVE_ENV_KEYS, ...opts.sensitiveEnvKeys]) : SENSITIVE_ENV_KEYS;
|
|
9301
|
-
const passthroughEnv = appendEnvFlags(args, opts.env, sensitiveKeys);
|
|
9490
|
+
const { passthrough: passthroughEnv, collisions } = appendEnvFlags(args, opts.env, sensitiveKeys);
|
|
9302
9491
|
if (opts.tmpfs) args.push("--tmpfs", `${opts.tmpfs.target}:rw,size=${opts.tmpfs.sizeMb}m`);
|
|
9303
9492
|
if (opts.workingDir) args.push("--workdir", opts.workingDir);
|
|
9304
9493
|
let entryPointTail = [];
|
|
@@ -9307,7 +9496,9 @@ async function runDetached(opts) {
|
|
|
9307
9496
|
entryPointTail = opts.entryPoint.slice(1);
|
|
9308
9497
|
}
|
|
9309
9498
|
args.push(opts.image, ...entryPointTail, ...opts.cmd);
|
|
9310
|
-
getLogger().child("docker")
|
|
9499
|
+
const logger = getLogger().child("docker");
|
|
9500
|
+
logger.debug(`${getDockerCmd()} ${redactAwsCredentialsInArgs(args).join(" ")}`);
|
|
9501
|
+
if (collisions.length > 0) logger.warn(describeRefusedSensitiveEnvKeys(`Container for image ${displayUntrustedValue(opts.image)}`, collisions));
|
|
9311
9502
|
const forwardedKeys = Object.keys(passthroughEnv);
|
|
9312
9503
|
const refusal = finchSecretArgvRefusal(forwardedKeys.filter((k) => opts.sensitiveEnvKeys?.has(k)), `Container for image ${opts.image}`);
|
|
9313
9504
|
if (refusal !== void 0) throw new DockerRunnerError(refusal);
|
|
@@ -9432,37 +9623,72 @@ const SENSITIVE_ENV_KEYS = /* @__PURE__ */ new Set([
|
|
|
9432
9623
|
* shell history. Non-sensitive keys keep the inline `-e KEY=VALUE` form
|
|
9433
9624
|
* (fine for non-secret config, and keeps `--debug` output readable).
|
|
9434
9625
|
*
|
|
9435
|
-
* Returns the `{ KEY: value }` map of routed-through keys; the
|
|
9436
|
-
* merge it into the spawned docker process's `env` (see
|
|
9626
|
+
* Returns `passthrough`, the `{ KEY: value }` map of routed-through keys; the
|
|
9627
|
+
* caller MUST merge it into the spawned docker process's `env` (see
|
|
9437
9628
|
* {@link execEnvForSecrets}) so docker can resolve each passed-through
|
|
9438
9629
|
* key. Values still appear in `docker inspect` Config.Env — that is
|
|
9439
9630
|
* inherent to container env and matches production behavior.
|
|
9440
9631
|
*
|
|
9632
|
+
* A sensitive key whose NAME is a variable the container client (or the
|
|
9633
|
+
* dynamic loader / a helper it execs) reads ({@link isDockerClientEnvKey}), or
|
|
9634
|
+
* that is MALFORMED ({@link isMalformedEnvKey}: empty, or containing `=` /
|
|
9635
|
+
* NUL), gets NO flag and no passthrough entry, and is returned in
|
|
9636
|
+
* `collisions` so the caller can warn by NAME (issue #772). Written into the
|
|
9637
|
+
* client's spawn env it would hijack the client (`DOCKER_HOST` redirects the
|
|
9638
|
+
* daemon, `PATH` / `LD_PRELOAD` choose what code runs); and a value-less
|
|
9639
|
+
* `-e KEY` the spawn env refuses to set would make docker resolve it against
|
|
9640
|
+
* its OWN environment, handing the container the HOST's value. A key
|
|
9641
|
+
* containing `=` is refused because the OS parses the environ entry's name as
|
|
9642
|
+
* everything before the first `=`, so `PATH=/tmp/evil:` would poison `PATH`
|
|
9643
|
+
* while slipping past the name denylist. A NON-sensitive key is container
|
|
9644
|
+
* env only (`-e KEY=VALUE` on the argv) and is unaffected.
|
|
9645
|
+
*
|
|
9441
9646
|
* Unlike `--env-file`, this handles multi-line values (e.g. PEM secrets)
|
|
9442
9647
|
* and needs no temp file on disk.
|
|
9443
9648
|
*/
|
|
9444
9649
|
function appendEnvFlags(args, env, sensitiveKeys) {
|
|
9445
9650
|
const passthrough = {};
|
|
9651
|
+
const collisions = [];
|
|
9446
9652
|
for (const [k, v] of Object.entries(env)) if (sensitiveKeys.has(k)) {
|
|
9653
|
+
if (isMalformedEnvKey(k) || isDockerClientEnvKey(k)) {
|
|
9654
|
+
collisions.push(k);
|
|
9655
|
+
continue;
|
|
9656
|
+
}
|
|
9447
9657
|
args.push("-e", k);
|
|
9448
|
-
passthrough
|
|
9658
|
+
defineOwnKey(passthrough, k, v);
|
|
9449
9659
|
} else args.push("-e", `${k}=${v}`);
|
|
9450
|
-
return
|
|
9660
|
+
return {
|
|
9661
|
+
passthrough,
|
|
9662
|
+
collisions
|
|
9663
|
+
};
|
|
9664
|
+
}
|
|
9665
|
+
/**
|
|
9666
|
+
* The warning for keys {@link appendEnvFlags} refused. Names each key
|
|
9667
|
+
* display-safe (a key is template-chosen) and never a value.
|
|
9668
|
+
*/
|
|
9669
|
+
function describeRefusedSensitiveEnvKeys(subject, keys) {
|
|
9670
|
+
return `${subject}: sensitive env var(s) ${keys.map(displayUntrustedValue).join(", ")} share a name with a variable the container client reads (docker, or the CDK_DOCKER binary) or have a malformed name (empty, or containing '=' / NUL), and were NOT passed to the container at all (a colliding name would hijack that client; a malformed name cannot form a valid environment variable). Rename the variable if the container needs it.`;
|
|
9451
9671
|
}
|
|
9452
9672
|
/**
|
|
9453
9673
|
* Build the `env` option for an `execFile` / `spawn` call that runs a
|
|
9454
9674
|
* `docker run` whose args include passed-through sensitive keys (from
|
|
9455
|
-
* {@link appendEnvFlags}). Returns `{ env
|
|
9456
|
-
*
|
|
9457
|
-
*
|
|
9458
|
-
* the default
|
|
9675
|
+
* {@link appendEnvFlags}). Returns `{ env }`: a copy of `process.env` with the
|
|
9676
|
+
* accepted passthrough keys written onto it as own keys, so docker inherits
|
|
9677
|
+
* the normal environment PLUS the sensitive values it must resolve. Returns
|
|
9678
|
+
* `{}` when no key is left to pass through (preserving the default
|
|
9679
|
+
* inherited-environment behavior).
|
|
9680
|
+
*
|
|
9681
|
+
* The client's own variables stay authoritative: a passthrough key that names
|
|
9682
|
+
* one ({@link isDockerClientEnvKey}) or is malformed ({@link isMalformedEnvKey})
|
|
9683
|
+
* is never written (issue #772). {@link appendEnvFlags} already keeps such a
|
|
9684
|
+
* key out of `passthrough`; this is defence in depth for any other caller.
|
|
9459
9685
|
*/
|
|
9460
9686
|
function execEnvForSecrets(passthrough) {
|
|
9461
|
-
|
|
9462
|
-
|
|
9463
|
-
|
|
9464
|
-
|
|
9465
|
-
|
|
9687
|
+
const keys = Object.keys(passthrough).filter((k) => !isMalformedEnvKey(k) && !isDockerClientEnvKey(k));
|
|
9688
|
+
if (keys.length === 0) return {};
|
|
9689
|
+
const env = { ...process.env };
|
|
9690
|
+
for (const k of keys) defineOwnKey(env, k, passthrough[k]);
|
|
9691
|
+
return { env };
|
|
9466
9692
|
}
|
|
9467
9693
|
/**
|
|
9468
9694
|
* Returns a copy of `args` with any `-e <KEY>=<value>` pair whose KEY is
|
|
@@ -17287,6 +17513,40 @@ function availableApiIdentifiers(routes) {
|
|
|
17287
17513
|
//#endregion
|
|
17288
17514
|
//#region src/local/env-resolver.ts
|
|
17289
17515
|
/**
|
|
17516
|
+
* Resolve a Lambda function's `Properties.Environment.Variables` for
|
|
17517
|
+
* `cdkl invoke`.
|
|
17518
|
+
*
|
|
17519
|
+
* Per the issue scope, v1 supports **literal values only**. Intrinsic
|
|
17520
|
+
* functions (`Ref` / `Fn::GetAtt` / `Fn::Sub` / etc.) in env vars are
|
|
17521
|
+
* unresolvable without state — they would substitute to whatever the
|
|
17522
|
+
* deployed value is, and we have no source for that here. Each unresolved
|
|
17523
|
+
* key is recorded in `unresolved` so the caller can warn the user; the
|
|
17524
|
+
* variable is **dropped** rather than silently substituted with garbage
|
|
17525
|
+
* (a bad env var that "exists" is harder to debug than one that's missing).
|
|
17526
|
+
*
|
|
17527
|
+
* `--env-vars <file>` overrides match SAM's shape (D5), with the
|
|
17528
|
+
* additional CDK ergonomics that a function-specific entry can be keyed
|
|
17529
|
+
* by either the CloudFormation logical ID or the CDK display path
|
|
17530
|
+
* (`Metadata['aws:cdk:path']`):
|
|
17531
|
+
*
|
|
17532
|
+
* {
|
|
17533
|
+
* "Parameters": { "GLOBAL_KEY": "value" },
|
|
17534
|
+
* "MyHandlerLogicalId": { "FUNCTION_KEY": "value" },
|
|
17535
|
+
* "MyStack/MyHandler": { "FUNCTION_KEY": "value" }
|
|
17536
|
+
* }
|
|
17537
|
+
*
|
|
17538
|
+
* Override merge order (lowest to highest priority):
|
|
17539
|
+
* 1. Template literal env vars
|
|
17540
|
+
* 2. `Parameters` (global, applied to every invoke)
|
|
17541
|
+
* 3. Function-specific entries (logical-ID or display-path keyed)
|
|
17542
|
+
* applied in JSON insertion order, so a later key wins on conflict.
|
|
17543
|
+
* This matches SAM's apply-in-order semantics; pick one form per
|
|
17544
|
+
* function to avoid surprises.
|
|
17545
|
+
*
|
|
17546
|
+
* The override file may also clear a variable by setting it to `null`
|
|
17547
|
+
* (matches SAM behavior).
|
|
17548
|
+
*/
|
|
17549
|
+
/**
|
|
17290
17550
|
* Resolve Lambda env vars for local invocation.
|
|
17291
17551
|
*
|
|
17292
17552
|
* @param logicalId The function's CloudFormation logical ID. Used
|
|
@@ -17310,7 +17570,7 @@ function availableApiIdentifiers(routes) {
|
|
|
17310
17570
|
function resolveEnvVars$1(logicalId, displayPath, templateEnv, overrides) {
|
|
17311
17571
|
const resolved = {};
|
|
17312
17572
|
const unresolved = [];
|
|
17313
|
-
if (templateEnv) for (const [key, value] of Object.entries(templateEnv)) if (isLiteralEnvValue(value)) resolved
|
|
17573
|
+
if (templateEnv) for (const [key, value] of Object.entries(templateEnv)) if (isLiteralEnvValue(value)) defineOwnKey(resolved, key, String(value));
|
|
17314
17574
|
else unresolved.push(key);
|
|
17315
17575
|
if (overrides) {
|
|
17316
17576
|
applyEnvOverrideMap(resolved, overrides.Parameters);
|
|
@@ -17345,7 +17605,7 @@ function resolveEnvVars$1(logicalId, displayPath, templateEnv, overrides) {
|
|
|
17345
17605
|
function applyEnvOverrideMap(acc, map) {
|
|
17346
17606
|
if (!map) return;
|
|
17347
17607
|
for (const [key, value] of Object.entries(map)) if (value === null) delete acc[key];
|
|
17348
|
-
else if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") acc
|
|
17608
|
+
else if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") defineOwnKey(acc, key, String(value));
|
|
17349
17609
|
}
|
|
17350
17610
|
/**
|
|
17351
17611
|
* A value is "literal" iff it's a string / number / boolean. CFn intrinsic
|
|
@@ -23471,7 +23731,8 @@ async function createNetworkAndSidecar(args) {
|
|
|
23471
23731
|
if (credentials.sessionToken) sidecarEnv["AWS_SESSION_TOKEN"] = credentials.sessionToken;
|
|
23472
23732
|
}
|
|
23473
23733
|
if (cluster) sidecarEnv["CLUSTER"] = cluster;
|
|
23474
|
-
const sidecarPassthroughEnv = appendEnvFlags(sidecarArgs, sidecarEnv, SENSITIVE_ENV_KEYS);
|
|
23734
|
+
const { passthrough: sidecarPassthroughEnv, collisions: sidecarCollisions } = appendEnvFlags(sidecarArgs, sidecarEnv, SENSITIVE_ENV_KEYS);
|
|
23735
|
+
if (sidecarCollisions.length > 0) logger.warn(describeRefusedSensitiveEnvKeys("Metadata sidecar", sidecarCollisions));
|
|
23475
23736
|
sidecarArgs.push(METADATA_ENDPOINT_IMAGE);
|
|
23476
23737
|
warnFinchArgvExposure(Object.keys(sidecarPassthroughEnv));
|
|
23477
23738
|
logger.info(`Starting ECS local-container-endpoints sidecar at ${sidecarIp}...`);
|
|
@@ -23786,7 +24047,7 @@ async function runEcsTask(task, options, state) {
|
|
|
23786
24047
|
const startOrder = topoSort(buildDependencyGraph(task.containers), task.containers);
|
|
23787
24048
|
const finchRefusals = [];
|
|
23788
24049
|
for (const c of task.containers) {
|
|
23789
|
-
const refusal = finchSecretArgvRefusal([...c.secrets.map((s) => s.name), ...c.sensitiveEnvKeys], `Container '${c.name}'`);
|
|
24050
|
+
const refusal = finchSecretArgvRefusal([...c.secrets.map((s) => s.name), ...c.sensitiveEnvKeys].filter((n) => !isMalformedEnvKey(n) && !isDockerClientEnvKey(n)), `Container '${c.name}'`);
|
|
23790
24051
|
if (refusal !== void 0) finchRefusals.push(refusal);
|
|
23791
24052
|
}
|
|
23792
24053
|
if (finchRefusals.length > 0) throw new EcsTaskRunnerError(finchRefusals.join("\n"));
|
|
@@ -23865,6 +24126,7 @@ async function runEcsTask(task, options, state) {
|
|
|
23865
24126
|
args: built.args,
|
|
23866
24127
|
sensitiveEnv: built.sensitiveEnv
|
|
23867
24128
|
});
|
|
24129
|
+
if (built.collisions.length > 0) logger.warn(describeRefusedSensitiveEnvKeys(`Container ${displayUntrustedValue(container.name)}`, built.collisions));
|
|
23868
24130
|
for (const ep of built.publishedEndpoints) state.publishedEndpoints.push(ep);
|
|
23869
24131
|
}
|
|
23870
24132
|
const startedByName = /* @__PURE__ */ new Map();
|
|
@@ -24308,8 +24570,8 @@ function buildDockerRunArgs(opts) {
|
|
|
24308
24570
|
finalEnv["AWS_SHARED_CREDENTIALS_FILE"] = opts.profileCredentialsFile.containerPath;
|
|
24309
24571
|
finalEnv["AWS_PROFILE"] = opts.profileCredentialsFile.profileName;
|
|
24310
24572
|
}
|
|
24311
|
-
Object.
|
|
24312
|
-
for (const s of secrets) finalEnv
|
|
24573
|
+
for (const [k, v] of Object.entries(container.environment)) defineOwnKey(finalEnv, k, v);
|
|
24574
|
+
for (const s of secrets) defineOwnKey(finalEnv, s.name, s.value);
|
|
24313
24575
|
const overrides = opts.envOverrides;
|
|
24314
24576
|
if (overrides) {
|
|
24315
24577
|
applyEnvOverrideMap(finalEnv, overrides["Parameters"]);
|
|
@@ -24318,7 +24580,7 @@ function buildDockerRunArgs(opts) {
|
|
|
24318
24580
|
const sensitiveEnvKeys = new Set(SENSITIVE_ENV_KEYS);
|
|
24319
24581
|
for (const s of secrets) sensitiveEnvKeys.add(s.name);
|
|
24320
24582
|
for (const k of container.sensitiveEnvKeys) sensitiveEnvKeys.add(k);
|
|
24321
|
-
const sensitiveEnv = appendEnvFlags(args, finalEnv, sensitiveEnvKeys);
|
|
24583
|
+
const { passthrough: sensitiveEnv, collisions } = appendEnvFlags(args, finalEnv, sensitiveEnvKeys);
|
|
24322
24584
|
if (container.user) args.push("--user", container.user);
|
|
24323
24585
|
if (container.privileged) args.push("--privileged");
|
|
24324
24586
|
if (container.readonlyRootFilesystem) args.push("--read-only");
|
|
@@ -24341,6 +24603,7 @@ function buildDockerRunArgs(opts) {
|
|
|
24341
24603
|
return {
|
|
24342
24604
|
args,
|
|
24343
24605
|
sensitiveEnv,
|
|
24606
|
+
collisions,
|
|
24344
24607
|
publishedEndpoints
|
|
24345
24608
|
};
|
|
24346
24609
|
}
|
|
@@ -25514,7 +25777,7 @@ async function softReloadReplica(args) {
|
|
|
25514
25777
|
const defaultDockerInspectWorkdirImpl = async (containerId) => {
|
|
25515
25778
|
const { execFile } = await import("node:child_process");
|
|
25516
25779
|
const { promisify } = await import("node:util");
|
|
25517
|
-
const { getDockerCmd } = await import("./docker-cmd-
|
|
25780
|
+
const { getDockerCmd } = await import("./docker-cmd-leyvDn1u.js").then((n) => n.t);
|
|
25518
25781
|
const { stdout } = await promisify(execFile)(getDockerCmd(), [
|
|
25519
25782
|
"inspect",
|
|
25520
25783
|
"--format",
|
|
@@ -25533,7 +25796,7 @@ let dockerInspectWorkdirImpl = defaultDockerInspectWorkdirImpl;
|
|
|
25533
25796
|
const defaultDockerCpImpl = async (src, dst) => {
|
|
25534
25797
|
const { execFile } = await import("node:child_process");
|
|
25535
25798
|
const { promisify } = await import("node:util");
|
|
25536
|
-
const { getDockerCmd } = await import("./docker-cmd-
|
|
25799
|
+
const { getDockerCmd } = await import("./docker-cmd-leyvDn1u.js").then((n) => n.t);
|
|
25537
25800
|
await promisify(execFile)(getDockerCmd(), [
|
|
25538
25801
|
"cp",
|
|
25539
25802
|
src,
|
|
@@ -25548,7 +25811,7 @@ let dockerCpImpl = defaultDockerCpImpl;
|
|
|
25548
25811
|
const defaultDockerRestartImpl = async (containerId) => {
|
|
25549
25812
|
const { execFile } = await import("node:child_process");
|
|
25550
25813
|
const { promisify } = await import("node:util");
|
|
25551
|
-
const { getDockerCmd } = await import("./docker-cmd-
|
|
25814
|
+
const { getDockerCmd } = await import("./docker-cmd-leyvDn1u.js").then((n) => n.t);
|
|
25552
25815
|
await promisify(execFile)(getDockerCmd(), ["restart", containerId]);
|
|
25553
25816
|
};
|
|
25554
25817
|
let dockerRestartImpl = defaultDockerRestartImpl;
|
|
@@ -25588,7 +25851,7 @@ async function disconnectOldFromSharedNetwork(oldInstance) {
|
|
|
25588
25851
|
const defaultDockerNetworkDisconnectImpl = async (networkName, containerId) => {
|
|
25589
25852
|
const { execFile } = await import("node:child_process");
|
|
25590
25853
|
const { promisify } = await import("node:util");
|
|
25591
|
-
const { getDockerCmd } = await import("./docker-cmd-
|
|
25854
|
+
const { getDockerCmd } = await import("./docker-cmd-leyvDn1u.js").then((n) => n.t);
|
|
25592
25855
|
await promisify(execFile)(getDockerCmd(), [
|
|
25593
25856
|
"network",
|
|
25594
25857
|
"disconnect",
|
|
@@ -25765,7 +26028,7 @@ function pickEssentialContainerId(instance, service) {
|
|
|
25765
26028
|
const defaultWaitForExitImpl = async (containerId) => {
|
|
25766
26029
|
const { execFile } = await import("node:child_process");
|
|
25767
26030
|
const { promisify } = await import("node:util");
|
|
25768
|
-
const { getDockerCmd } = await import("./docker-cmd-
|
|
26031
|
+
const { getDockerCmd } = await import("./docker-cmd-leyvDn1u.js").then((n) => n.t);
|
|
25769
26032
|
const { stdout } = await promisify(execFile)(getDockerCmd(), ["wait", containerId], { maxBuffer: 1024 * 1024 });
|
|
25770
26033
|
const code = parseInt(stdout.trim(), 10);
|
|
25771
26034
|
return Number.isFinite(code) ? code : -1;
|
|
@@ -25785,7 +26048,7 @@ const EXIT_LOG_TAIL_LINES = 50;
|
|
|
25785
26048
|
const defaultReadContainerLogsImpl = async (containerId) => {
|
|
25786
26049
|
const { execFile } = await import("node:child_process");
|
|
25787
26050
|
const { promisify } = await import("node:util");
|
|
25788
|
-
const { getDockerCmd } = await import("./docker-cmd-
|
|
26051
|
+
const { getDockerCmd } = await import("./docker-cmd-leyvDn1u.js").then((n) => n.t);
|
|
25789
26052
|
const { stdout, stderr } = await promisify(execFile)(getDockerCmd(), [
|
|
25790
26053
|
"logs",
|
|
25791
26054
|
"--tail",
|
|
@@ -35092,7 +35355,7 @@ function resolveEnvVars(kind, values) {
|
|
|
35092
35355
|
const flat = {};
|
|
35093
35356
|
for (const row of value) {
|
|
35094
35357
|
const { left, right } = row ?? {};
|
|
35095
|
-
if (nonEmptyStr(left)) flat
|
|
35358
|
+
if (nonEmptyStr(left)) defineOwnKey(flat, left.trim(), typeof right === "string" ? right : "");
|
|
35096
35359
|
}
|
|
35097
35360
|
return Object.keys(flat).length > 0 ? { Parameters: flat } : void 0;
|
|
35098
35361
|
}
|
|
@@ -40920,4 +41183,4 @@ function addStudioSpecificOptions(cmd) {
|
|
|
40920
41183
|
|
|
40921
41184
|
//#endregion
|
|
40922
41185
|
export { applyEdgeResponseResult as $, buildJwksUrlFromIssuer as $n, resolveCfnStackName as $r, buildCloudMapIndex as $t, startAgentCoreHttpServer as A, resolveProfileCredentials as Ai, classifySourceChange as An, ConnectionRegistry as Ar, addRunTaskSpecificOptions as At, idFromArn as B, buildStageMap as Bn, resolveRuntimeFileExtension as Br, resolveEcsAssumeRoleOption as Bt, addListSpecificOptions as C, resolveAgentCoreTarget as Ci, waitForAgentCorePing as Cn, tryParseStatus as Cr, parseLbPortOverrides as Ct, createLocalStartAgentCoreCommand as D, tryResolveImageFnJoin as Di, computeCodeImageTag as Dn, probeHostGatewaySupport as Dr, addStartServiceSpecificOptions as Dt, addStartAgentCoreSpecificOptions as E, substituteImagePlaceholders as Ei, buildAgentCoreCodeImage as En, HOST_GATEWAY_MIN_VERSION as Er, resolveAlbFrontDoor as Et, createLocalStartCloudFrontCommand as F, createWatchPredicates as Fn, buildDisconnectEvent as Fr, addImageOverrideOptions as Ft, classifyS3Error as G, filterRoutesByApiIdentifiers as Gn, substituteEnvVarsFromState as Gr, enforceImageOverrideOrphans as Gt, createDeployedKvsDataSource as H, resolveEnvVars$1 as Hn, EcsTaskResolutionError as Hr, runEcsServiceEmulator as Ht, normalizeKvsFileKeys as I, resolveApiTargetSubset as In, buildMessageEvent as Ir, buildEcsImageResolutionContext$1 as It, startCloudFrontServer as J, startApiServer as Jn, createLocalStateProvider as Jr, resolveImageOverrides as Jt, createS3OriginReader as K, groupRoutesByServer as Kn, substituteEnvVarsFromStateAsync as Kr, mergeForService as Kt, parseKvsFileOverrides as L, createAuthorizerCache as Ln, architectureToPlatform as Lr, ecsClusterOption as Lt, startAgentCoreWsBridge as M, isProxyEnvConfigured as Mi, createLocalInvokeCommand as Mn, handleConnectionsRequest as Mr, MAX_TASKS_SUBNET_RANGE_CAP as Mt, LocalStartCloudFrontError as N, addStartApiSpecificOptions as Nn, parseConnectionsPath as Nr, addCommonEcsServiceOptions as Nt, buildAgentCoreServeAuthCheck as O, LocalInvokeBuildError as Oi, renderCodeDockerfile as On, resolveHostGatewayExtraHosts as Or, createLocalStartServiceCommand as Ot, addStartCloudFrontSpecificOptions as P, createLocalStartApiCommand as Pn, buildConnectEvent as Pr, addEcsAssumeRoleOptions as Pt, applyEdgeRequestResult as Q, buildCognitoJwksUrl as Qn, resolveCfnRegion as Qr, listPinnedTargets as Qt, parseOriginOverrides as R, createFileWatcher as Rn, buildContainerImage as Rr, parseMaxTasks as Rt, StudioEventBus as S, pickAgentCoreCandidateStack as Si, waitForAgentCoreHttpReady as Sn, selectIntegrationResponse as Sr, createLocalStartAlbCommand as St, formatTargetListing as T, formatStateRemedy as Ti, SUPPORTED_CODE_RUNTIMES as Tn, HOST_DOCKER_INTERNAL_GATEWAY as Tr, isApplicationLoadBalancer as Tt, resolveDeployedKvsArnByName as U, availableApiIdentifiers as Un, substituteAgainstState as Ur, ImageOverrideError as Ut, resolveKvsModulesForDistribution as V, materializeLayerFromArn as Vn, resolveRuntimeImage as Vr, resolveSharedSidecarCredentials as Vt, resolveDeployedOriginBucket as W, filterRoutesByApiIdentifier as Wn, substituteAgainstStateAsync as Wr, buildImageOverrideTag as Wt, serveFromStaticOrigin as X, resolveServiceIntegrationParameters as Xn, rejectExplicitCfnStackWithMultipleStacks as Xr, describePinnedImageUri as Xt, resolveErrorResponseCandidates as Y, resolveSelectionExpression as Yn, isCfnFlagPresent as Yr, runImageOverrideBuilds as Yt, serveLambdaUrlOrigin as Z, defaultCredentialsLoader as Zn, resolveCfnFallbackRegion as Zr, isLocalCdkAssetImage as Zt, filterStudioTargetGroups as _, AGENTCORE_AGUI_PROTOCOL as _i, parseSseForJsonRpc as _n, applyAuthorizerOverlay as _r, createCloudFrontModule as _t, createLocalStudioCommand as a, countTargets as ai, attachContainerLogStreamer as an, computeRequestIdentityHash as ar, describeS3OriginDomain as at, renderStudioHtml as b, AGENTCORE_RUNTIME_TYPE as bi, AGENTCORE_SESSION_ID_HEADER as bn, evaluateResponseParameters as br, addAlbSpecificOptions as bt, startStudioProxy as c, discoverWebSocketApis as ci, bridgeAgentCoreWs as cn, invokeTokenAuthorizer as cr, pickFunctionUrlLogicalIdFromOrigin as ct, createStudioDispatcher as d, parseSelectionExpressionPath as di, A2A_PATH as dn, buildCorsConfigByApiId as dr, pickTargetFunctionLogicalId as dt, CfnLocalStateProvider as ei, CloudMapRegistry as en, createJwksCache as er, buildEdgeRequestEvent as et, filterStudioCustomResources as f, webSocketApiMatchesIdentifier as fi, a2aInvokeOnce as fn, buildCorsConfigFromCloudFrontChain as fr, resolveCloudFrontDistribution as ft, annotatePinnedEcsTargets as g, AGENTCORE_A2A_PROTOCOL as gi, mcpInvokeOnce as gn, translateLambdaResponse as gr, stripCloudFrontImport as gt, annotateEcsTaskPinnedTargets as h, resolveLambdaArnIntrinsic as hi, MCP_PROTOCOL_VERSION as hn, matchRoute as hr, runViewerResponse as ht, coerceStopRequest as i, resolveSingleTarget as ii, getContainerNetworkIp as in, buildMethodArn as ir, CLOUDFRONT_DISTRIBUTION_TYPE as it, attachAgentCoreWsBridge as j, buildProxyClientConfig as ji, addInvokeSpecificOptions as jn, buildMgmtEndpointEnvUrl as jr, createLocalRunTaskCommand as jt, selectServeInboundAuth as k, buildStsClientConfig as ki, toCmdArgv as kn, bufferToBody as kr, serviceStrategy as kt, relayServeRequest as l, discoverWebSocketApisOrThrow as li, invokeAgentCoreWs as ln, attachAuthorizers as lr, pickKvsLogicalIdFromArn as lt, annotateAlbPinnedBackingServices as m, pickRefLogicalId as mi, MCP_PATH as mn, matchPreflight as mr, runViewerRequest as mt, coerceRunRequest as n, resolveSsmParameters as ni, SOFT_RELOAD_COMPLETION_LOG_SUFFIX as nn, verifyJwtAuthorizer as nr, edgeHeadersToHttp as nt, resolveServeBaseUrl as o, listTargets as oi, addInvokeAgentCoreSpecificOptions as on, evaluateCachedLambdaPolicy as or, extractKvsAssociations as ot, isCustomResourceLambdaTarget as p, discoverRoutes as pi, MCP_CONTAINER_PORT as pn, isFunctionUrlOacFronted as pr, compileCloudFrontFunction as pt, matchBehavior as q, readMtlsMaterialsFromDisk as qn, LocalStateSourceError as qr, parseImageOverrideFlags as qt, coerceServeRequest as r, resolveWatchConfig as ri, setShadowReadyTimeoutMs as rn, verifyJwtViaDiscovery as rr, httpHeadersToEdge as rt, createStudioServeManager as s, availableWebSocketApiIdentifiers as si, createLocalInvokeAgentCoreCommand as sn, invokeRequestAuthorizer as sr, isCloudFrontDistribution as st, addStudioSpecificOptions as t, collectSsmParameterRefs as ti, DEFAULT_SHADOW_READY_TIMEOUT_MS as tn, verifyCognitoJwt as tr, buildEdgeResponseEvent as tt, reinvoke as u, filterWebSocketApisByIdentifiers as ui, A2A_CONTAINER_PORT as un, applyCorsResponseHeaders as ur, pickLambdaEdgeFunctionLogicalId as ut, startStudioServer as v, AGENTCORE_HTTP_PROTOCOL as vi, AGENTCORE_SIGV4_SERVICE as vn, buildHttpApiV2Event as vr, createLocalFileKvsDataSource as vt, createLocalListCommand as w, derivePseudoParametersFromRegion as wi, downloadAndExtractS3Bundle as wn, VtlEvaluationError as wr, resolveAlbTarget as wt, createStudioStore as x, AgentCoreResolutionError as xi, invokeAgentCore as xn, pickResponseTemplate as xr, albStrategy as xt, toStudioTargetGroups as y, AGENTCORE_MCP_PROTOCOL as yi, signAgentCoreInvocation as yn, buildRestV1Event as yr, createUnboundCloudFrontModule as yt, resolveCloudFrontTarget as z, attachStageContext as zn, resolveRuntimeCodeMountPath as zr, parseRestartPolicy as zt };
|
|
40923
|
-
//# sourceMappingURL=local-studio-
|
|
41186
|
+
//# sourceMappingURL=local-studio-U-gD2HSf.js.map
|