@fjall/deploy-core 17.0.0 → 19.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.build-source-hash +6 -6
- package/dist/.minified +1 -1
- package/dist/src/orchestration/application/buildSecretSession.d.ts +24 -6
- package/dist/src/orchestration/application/buildSecretSession.js +1 -1
- package/dist/src/orchestration/application/deploySessionPolicy.d.ts +27 -2
- package/dist/src/orchestration/application/deploySessionPolicy.js +1 -1
- package/dist/src/orchestration/domain/domainSessionPolicy.d.ts +11 -9
- package/dist/src/orchestration/domain/domainSessionPolicy.js +1 -1
- package/package.json +4 -4
package/dist/.build-source-hash
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"package.json",
|
|
10
10
|
"tsconfig.json"
|
|
11
11
|
],
|
|
12
|
-
"hash": "
|
|
12
|
+
"hash": "f152a2d7b6a075a5791d9b321acb4761dda16eba6a24856717bf5143f1651236",
|
|
13
13
|
"files": {
|
|
14
14
|
"src/__test-utils__/awsMockHelpers.ts": "fea371c1b1869678",
|
|
15
15
|
"src/__test-utils__/index.ts": "e03cf35821157dc4",
|
|
@@ -63,10 +63,10 @@
|
|
|
63
63
|
"src/orchestration/application/buildArgResolver.ts": "47142177df453d9c",
|
|
64
64
|
"src/orchestration/application/buildGroupValidator.ts": "0f0e1f142ee31fe0",
|
|
65
65
|
"src/orchestration/application/buildSecretResolver.ts": "bdb48c28857188ba",
|
|
66
|
-
"src/orchestration/application/buildSecretSession.ts": "
|
|
66
|
+
"src/orchestration/application/buildSecretSession.ts": "ff89426b0093bfd3",
|
|
67
67
|
"src/orchestration/application/codeOnlyDeploy.ts": "062b0acdad1dcf08",
|
|
68
68
|
"src/orchestration/application/databaseEndpointReconcile.ts": "27b14ee41524c632",
|
|
69
|
-
"src/orchestration/application/deploySessionPolicy.ts": "
|
|
69
|
+
"src/orchestration/application/deploySessionPolicy.ts": "6b70e135dd6e71f4",
|
|
70
70
|
"src/orchestration/application/destroyPreFlight.ts": "cc4978dec60b033e",
|
|
71
71
|
"src/orchestration/application/destructionGate.ts": "5fbaa6ef69eb3ae1",
|
|
72
72
|
"src/orchestration/application/detectionPipeline.ts": "69b7dfcedf22fe01",
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
"src/orchestration/domain/delegatedDomainDeploy.ts": "b5e5c2db59250b8a",
|
|
114
114
|
"src/orchestration/domain/delegationDestroyMirror.ts": "939f6c3cc3293d8b",
|
|
115
115
|
"src/orchestration/domain/delegationResolution.ts": "f2b91a8aaf284dfa",
|
|
116
|
-
"src/orchestration/domain/domainSessionPolicy.ts": "
|
|
116
|
+
"src/orchestration/domain/domainSessionPolicy.ts": "8dadfa02d5b5702e",
|
|
117
117
|
"src/orchestration/domain/domainTakeoverGate.ts": "ecd9694cebc7be6c",
|
|
118
118
|
"src/orchestration/domain/index.ts": "ec421e83dd0afbd6",
|
|
119
119
|
"src/orchestration/domain/nsPropagationGate.ts": "c7a443e7048d4430",
|
|
@@ -271,9 +271,9 @@
|
|
|
271
271
|
"src/util/sleepAbortable.ts": "f9cac3bf8a79a476",
|
|
272
272
|
"src/version.ts": "1811a4475823b27e",
|
|
273
273
|
"../scripts/minify-dist.mjs": "6b2e4b0df8aec601",
|
|
274
|
-
"package.json": "
|
|
274
|
+
"package.json": "093d8bddf1ead2cf",
|
|
275
275
|
"tsconfig.json": "1929cf3f99fe9b1e"
|
|
276
276
|
},
|
|
277
|
-
"outputHash": "
|
|
277
|
+
"outputHash": "03b3796b0ca16efc9a18b001a6b318007f2a8be0c75ebb0c93420710158e11a7",
|
|
278
278
|
"outputFileCount": 511
|
|
279
279
|
}
|
package/dist/.minified
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
255 files minified at 2026-08-
|
|
1
|
+
255 files minified at 2026-08-24T11:07:21.862Z
|
|
@@ -17,13 +17,31 @@ export interface BuildSecretSessionContext {
|
|
|
17
17
|
partition: string;
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
|
-
* The
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
20
|
+
* The binding STS limit on an inline session `Policy` is NOT the documented
|
|
21
|
+
* 2048-char plaintext cap — it is the PACKED (compressed binary) budget that
|
|
22
|
+
* the policy shares with session tags, and for high-entropy ARN content it
|
|
23
|
+
* binds far earlier. Measured against STS on 2026-08-23 (dev account,
|
|
24
|
+
* us-east-2): 117 chars packed to 9%, 720 chars to 64%, and the
|
|
25
|
+
* `AssumeRoleWithWebIdentity` boundary for `buildDeploySessionPolicy` output
|
|
26
|
+
* sat between 1255 chars (accepted) and 1258 chars (PackedPolicyTooLarge).
|
|
27
|
+
* That knife edge is what silently killed every nightly deploy-test mint from
|
|
28
|
+
* 2026-08-10 — the policy passed the old 2048 plaintext guard and died at STS.
|
|
29
|
+
*
|
|
30
|
+
* 1190 leaves ~5% headroom under the measured boundary while admitting the
|
|
31
|
+
* maximal CANONICAL app name: the deploy policy serialises to BASE +
|
|
32
|
+
* pascalLen + 2 * kebabLen chars, and over the kebab namespace the generator
|
|
33
|
+
* scaffolds that maximises at the 50-char hyphen-free name (~1164 chars with
|
|
34
|
+
* the account-pinned ECR entries). Degenerate legacy-Pascal names whose kebab
|
|
35
|
+
* expansion overflows this (e.g. "aB" * 25 → ~1214) fail closed with the
|
|
36
|
+
* budget named — they could never deploy anyway (pre-fix they died at STS
|
|
37
|
+
* with an opaque PackedPolicyTooLarge), and admitting them would mean
|
|
38
|
+
* sailing ~3% under the measured cliff. We fail closed here, before minting
|
|
39
|
+
* a session whose policy STS would reject, rather than ship a
|
|
40
|
+
* silently-broadened or truncated policy. Shared by all three session-policy
|
|
41
|
+
* builders (`deploySessionPolicy.ts`, `domainSessionPolicy.ts`, and this one)
|
|
42
|
+
* — they guard the same STS budget.
|
|
25
43
|
*/
|
|
26
|
-
export declare const
|
|
44
|
+
export declare const MAX_PACKED_SAFE_POLICY_CHARS = 1190;
|
|
27
45
|
/**
|
|
28
46
|
* Derive the `aws` / `aws-cn` partition from a region. China regions use the
|
|
29
47
|
* `aws-cn` partition; everything else (incl. GovCloud `us-gov-*`, which is
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var g=Object.defineProperty;var
|
|
1
|
+
var g=Object.defineProperty;var a=(s,m)=>g(s,"name",{value:m,configurable:!0});import{success as p,failure as S}from"@fjall/generator";import{toKebab as A}from"@fjall/util";const u=1190;function x(s){return s.startsWith("cn-")?"aws-cn":"aws"}a(x,"partitionForRegion");function y(s,m){const{region:r,accountId:n,partition:c}=s,i=A(s.appName),d=`arn:${c}:ssm:${r}:${n}:parameter/${i}/*`,f=`arn:${c}:secretsmanager:${r}:${n}:secret:fjall-${i}-*`,t=new Set;for(const e of m){if(e.ssm!==void 0&&e.ssm!==""){const o=e.ssm.startsWith("/")?e.ssm.slice(1):e.ssm;o.startsWith(`${i}/`)||t.add(`arn:${c}:ssm:${r}:${n}:parameter/${o}`)}if(e.secretsManagerArn!==void 0&&e.secretsManagerArn!==""){const o=f.replace(/\*$/,"");e.secretsManagerArn.startsWith(o)||t.add(e.secretsManagerArn)}e.secretsManagerName!==void 0&&e.secretsManagerName!==""&&!e.secretsManagerName.startsWith(`fjall-${i}-`)&&t.add(`arn:${c}:secretsmanager:${r}:${n}:secret:${e.secretsManagerName}-*`)}const $=[{Sid:"FjallBuildSsmRead",Effect:"Allow",Action:["ssm:GetParameter","ssm:GetParameters"],Resource:[d,...[...t].filter(h)]},{Sid:"FjallBuildSecretsManagerRead",Effect:"Allow",Action:["secretsmanager:GetSecretValue"],Resource:[f,...[...t].filter(M)]},{Sid:"FjallBuildKmsDecrypt",Effect:"Allow",Action:["kms:Decrypt"],Resource:"*",Condition:{StringEquals:{"kms:ViaService":[`ssm.${r}.amazonaws.com`,`secretsmanager.${r}.amazonaws.com`]}}}],l=JSON.stringify({Version:"2012-10-17",Statement:$});return l.length>u?S(new Error(`Build-secret session policy is ${l.length} chars, exceeding the ${u}-char packed-safe STS session-policy budget. Reduce the number of out-of-prefix secret/parameter references for this build.`)):p(l)}a(y,"buildBuildSecretSessionPolicy");function h(s){return s.includes(":ssm:")}a(h,"isSsmArn");function M(s){return s.includes(":secretsmanager:")}a(M,"isSecretsArn");export{u as MAX_PACKED_SAFE_POLICY_CHARS,y as buildBuildSecretSessionPolicy,x as partitionForRegion};
|
|
@@ -2,7 +2,10 @@ import { type Result } from "@fjall/generator";
|
|
|
2
2
|
/**
|
|
3
3
|
* The AWS-account/region context a deploy session's inline policy is scoped to.
|
|
4
4
|
* `appName` is the deploy target; `partition` is derived via `partitionForRegion`
|
|
5
|
-
* (re-exported from `buildSecretSession.js`).
|
|
5
|
+
* (re-exported from `buildSecretSession.js`). `region` and `accountId` are
|
|
6
|
+
* deliberately NOT pinned into the policy's ARNs (see the packed-budget note on
|
|
7
|
+
* `buildDeploySessionPolicy`) — they remain in the context so call sites keep
|
|
8
|
+
* compiling and the caller's intent stays explicit.
|
|
6
9
|
*/
|
|
7
10
|
export interface DeploySessionContext {
|
|
8
11
|
appName: string;
|
|
@@ -62,6 +65,28 @@ export declare const ECR_MUTATION_ACTIONS: string[];
|
|
|
62
65
|
* ECR repo is `toKebab(appName)` (matching the ECR construct at
|
|
63
66
|
* components/infrastructure/lib/app.ts) — never a re-implemented `.toLowerCase()`.
|
|
64
67
|
*
|
|
65
|
-
*
|
|
68
|
+
* The REGION segment of every ARN is wildcarded, NOT pinned from `ctx` — a
|
|
69
|
+
* pinned region wrongly DENIED an app's own stacks in any second region, and
|
|
70
|
+
* every pinned segment spends packed budget: STS compresses the policy into a
|
|
71
|
+
* packed binary whose real ceiling sits near 1256 chars for this content class
|
|
72
|
+
* (measured 2026-08-23 — see MAX_PACKED_SAFE_POLICY_CHARS), and the fully
|
|
73
|
+
* pinned form crossed it for app names of 19+ chars, killing the mint with
|
|
74
|
+
* PackedPolicyTooLarge. The ACCOUNT segment is asymmetric by design:
|
|
75
|
+
* - CloudFormation entries wildcard it. CFN has no resource-based policies, so
|
|
76
|
+
* cross-account stack mutation would require an `sts:AssumeRole` this policy
|
|
77
|
+
* documentedly does not survive — pinning the account there restated what
|
|
78
|
+
* the role boundary already enforces, for 12 chars apiece.
|
|
79
|
+
* - ECR entries PIN it. ECR repositories carry resource-based policies: a
|
|
80
|
+
* foreign account's repo policy (e.g. an `aws:PrincipalOrgID` grant on a
|
|
81
|
+
* shared mirror) can admit this identity with NO AssumeRole, so an
|
|
82
|
+
* account-wildcarded NotResource would exempt same-named foreign repos —
|
|
83
|
+
* `cdk-*` would exempt every bootstrap asset repo in every account — from
|
|
84
|
+
* the one Deny whose job is fencing foreign ECR mutation. The account pin
|
|
85
|
+
* here is the load-bearing cross-account fence, worth 4 × 11 packed chars.
|
|
86
|
+
* Statements carry no Sids for the same budget reason — they are
|
|
87
|
+
* documentation, and this policy's documentation lives here.
|
|
88
|
+
*
|
|
89
|
+
* Fails closed when the serialised policy would exceed the packed-safe STS
|
|
90
|
+
* session-policy budget.
|
|
66
91
|
*/
|
|
67
92
|
export declare function buildDeploySessionPolicy(ctx: DeploySessionContext): Result<string>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var s=Object.defineProperty;var
|
|
1
|
+
var s=Object.defineProperty;var c=(e,o)=>s(e,"name",{value:o,configurable:!0});import{success as p,failure as m}from"@fjall/generator";import{toKebab as f,toPascalCase as u}from"@fjall/util";import{buildCacheRepositoryName as d}from"@fjall/util/docker";import{SCHEMA_GATE_ECR_REPO_NAME as y}from"@fjall/util/migration";import{MAX_PACKED_SAFE_POLICY_CHARS as i}from"./buildSecretSession.js";const S=["cloudformation:CreateStack","cloudformation:UpdateStack","cloudformation:DeleteStack","cloudformation:CreateChangeSet","cloudformation:ExecuteChangeSet","cloudformation:DeleteChangeSet","cloudformation:CancelUpdateStack","cloudformation:ContinueUpdateRollback","cloudformation:RollbackStack","cloudformation:SetStackPolicy","cloudformation:UpdateTerminationProtection"],C=["ecr:PutImage","ecr:InitiateLayerUpload","ecr:UploadLayerPart","ecr:CompleteLayerUpload","ecr:BatchDeleteImage","ecr:DeleteRepository","ecr:SetRepositoryPolicy","ecr:DeleteRepositoryPolicy"];function P(e){const{partition:o,accountId:t}=e,n=u(e.appName),a=f(e.appName),l=[{Effect:"Allow",Action:"*",Resource:"*"},{Effect:"Deny",Action:S,NotResource:[`arn:${o}:cloudformation:*:*:stack/${n}*/*`,`arn:${o}:cloudformation:*:*:stack/CDKToolkit/*`]},{Effect:"Deny",Action:C,NotResource:[`arn:${o}:ecr:*:${t}:repository/${a}`,`arn:${o}:ecr:*:${t}:repository/${d(a)}`,`arn:${o}:ecr:*:${t}:repository/cdk-*`,`arn:${o}:ecr:*:${t}:repository/${y}`]}],r=JSON.stringify({Version:"2012-10-17",Statement:l});return r.length>i?m(new Error(`Deploy session policy is ${r.length} chars, exceeding the ${i}-char packed-safe STS session-policy budget.`)):p(r)}c(P,"buildDeploySessionPolicy");export{S as CFN_MUTATION_ACTIONS,C as ECR_MUTATION_ACTIONS,P as buildDeploySessionPolicy};
|
|
@@ -7,7 +7,8 @@ import { type Result } from "@fjall/generator";
|
|
|
7
7
|
* case-sensitive, so a caller-normalised or user-typed variant would deny
|
|
8
8
|
* the zone's own stack (fail-closed, but loud at deploy time instead of
|
|
9
9
|
* here). Callers necessarily hold the record already: `accountId`/`region`
|
|
10
|
-
* are its home pin
|
|
10
|
+
* are its home pin — carried for caller-intent clarity, though the policy's
|
|
11
|
+
* ARNs deliberately wildcard both segments (see the builder body).
|
|
11
12
|
*/
|
|
12
13
|
export interface DomainSessionContext {
|
|
13
14
|
zoneName: string;
|
|
@@ -35,12 +36,13 @@ export interface DomainSessionContext {
|
|
|
35
36
|
* Differences from the application builder, each derived from what a domain
|
|
36
37
|
* deploy actually does:
|
|
37
38
|
* - The CFN NotResource names the zone's stacks EXACTLY (no prefix wildcard):
|
|
38
|
-
* `getDomainStackName(zone)`
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
39
|
+
* `getDomainStackName(zone)` plus its paired us-east-1 certificates stack
|
|
40
|
+
* (`getDomainUsEast1CertificatesStackName`) — CloudFront viewer certificates
|
|
41
|
+
* only mint in us-east-1, so the pair spans two regions by construction —
|
|
42
|
+
* plus CDKToolkit. The ARNs' region/account segments are wildcarded (see the
|
|
43
|
+
* builder body), so each stack needs exactly one entry regardless of which
|
|
44
|
+
* regions it spans. Exact names close the app builder's prefix-wildcard
|
|
45
|
+
* class of over-grant across DISTINCT stacks. (getDomainStackName is not injective
|
|
44
46
|
* — it strips dots before PascalCasing, so `dev.fjall.io` and
|
|
45
47
|
* `devfjall.io` share one stack name — but two such zones in one
|
|
46
48
|
* account+region share the PHYSICAL stack too, which no ARN-scoped policy
|
|
@@ -56,7 +58,7 @@ export interface DomainSessionContext {
|
|
|
56
58
|
* lookup before the session exists — accepted v1 limitation, same class as
|
|
57
59
|
* the app builder's.
|
|
58
60
|
*
|
|
59
|
-
* Fails closed when the serialised policy would exceed the
|
|
60
|
-
*
|
|
61
|
+
* Fails closed when the serialised policy would exceed the packed-safe STS
|
|
62
|
+
* session-policy budget.
|
|
61
63
|
*/
|
|
62
64
|
export declare function buildDomainSessionPolicy(ctx: DomainSessionContext): Result<string>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var f=Object.defineProperty;var s=(t,e)=>f(t,"name",{value:e,configurable:!0});import{success as l,failure as A}from"@fjall/generator";import{getDomainStackName as S,getDomainUsEast1CertificatesStackName as N}from"@fjall/util";import{MAX_PACKED_SAFE_POLICY_CHARS as i}from"../application/buildSecretSession.js";import{CFN_MUTATION_ACTIONS as p,ECR_MUTATION_ACTIONS as u}from"../application/deploySessionPolicy.js";function _(t){const{partition:e}=t,c=S(t.zoneName),r=N(c),o=s(m=>`arn:${e}:cloudformation:*:*:stack/${m}/*`,"stackArn"),a=[o(c),o(r),o("CDKToolkit")],n=JSON.stringify({Version:"2012-10-17",Statement:[{Effect:"Allow",Action:"*",Resource:"*"},{Effect:"Deny",Action:p,NotResource:a},{Effect:"Deny",Action:u,Resource:"*"}]});return n.length>i?A(new Error(`Domain deploy session policy is ${n.length} chars, exceeding the ${i}-char packed-safe STS session-policy budget.`)):l(n)}s(_,"buildDomainSessionPolicy");export{_ as buildDomainSessionPolicy};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fjall/deploy-core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "19.0.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/fjall-tech/fjall.git",
|
|
@@ -96,15 +96,15 @@
|
|
|
96
96
|
"@aws-sdk/client-ssm": "^3.1098.0",
|
|
97
97
|
"@aws-sdk/client-sso-admin": "^3.1098.0",
|
|
98
98
|
"@aws-sdk/client-sts": "^3.1098.0",
|
|
99
|
-
"@fjall/generator": "^
|
|
100
|
-
"@fjall/util": "^
|
|
99
|
+
"@fjall/generator": "^19.0.0",
|
|
100
|
+
"@fjall/util": "^19.0.0",
|
|
101
101
|
"@smithy/node-http-handler": "^4.9.13",
|
|
102
102
|
"aws-cdk": "^2.1134.0",
|
|
103
103
|
"tsx": "^4.23.1",
|
|
104
104
|
"zod": "^4.4.3"
|
|
105
105
|
},
|
|
106
106
|
"peerDependencies": {
|
|
107
|
-
"@fjall/components-infrastructure": ">=
|
|
107
|
+
"@fjall/components-infrastructure": ">=19.0.0 <20.0.0"
|
|
108
108
|
},
|
|
109
109
|
"peerDependenciesMeta": {
|
|
110
110
|
"@fjall/components-infrastructure": {
|