@intentius/chant-lexicon-aws 0.44.8 → 0.44.10
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/api/read-client.d.ts +27 -5
- package/dist/api/read-client.d.ts.map +1 -1
- package/dist/api/sigv4.d.ts +82 -0
- package/dist/api/sigv4.d.ts.map +1 -0
- package/dist/identity-observe.d.ts +23 -0
- package/dist/identity-observe.d.ts.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +2 -2
- package/dist/manifest.json +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/api/read-client.test.ts +87 -4
- package/src/api/read-client.ts +89 -21
- package/src/api/sigv4.test.ts +260 -0
- package/src/api/sigv4.ts +243 -0
- package/src/identity-observe.test.ts +104 -0
- package/src/identity-observe.ts +114 -0
- package/src/index.ts +13 -0
- package/src/lifecycle-integration.test.ts +69 -0
- package/src/plugin.ts +19 -5
|
@@ -19,12 +19,22 @@
|
|
|
19
19
|
*
|
|
20
20
|
* ## Signing
|
|
21
21
|
*
|
|
22
|
-
* Requests are
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
22
|
+
* Requests are signed with SigV4 (#1686) when credentials resolve — see
|
|
23
|
+
* `./sigv4.ts`, which holds the implementation so that cedar's AVP client can
|
|
24
|
+
* adopt the same one rather than growing a second. Two cases stay unsigned, and
|
|
25
|
+
* both are deliberate:
|
|
26
|
+
*
|
|
27
|
+
* - **No credentials.** The caller's existing no-credentials path is
|
|
28
|
+
* untouched: the request still goes out carrying only the credential scope
|
|
29
|
+
* of {@link regionScope}, which is what the emulator lanes have always
|
|
30
|
+
* sent.
|
|
31
|
+
* - **An endpoint override.** Floci does not verify signatures, and signing
|
|
32
|
+
* against it would mean every local lane suddenly needs credentials to read
|
|
33
|
+
* what it just deployed. `signEndpointOverride` opts back in for an
|
|
34
|
+
* override that *is* real AWS — a VPC endpoint, a signing proxy.
|
|
27
35
|
*/
|
|
36
|
+
import { type AwsCredentialSource } from "./sigv4.js";
|
|
37
|
+
export type { AwsCredentials, AwsCredentialResolver, AwsCredentialSource } from "./sigv4.js";
|
|
28
38
|
/** Injectable HTTP, mirroring `AwsHttp` in the applier so tests avoid the network. */
|
|
29
39
|
export type AwsReadHttp = (url: string, init: {
|
|
30
40
|
headers: Record<string, string>;
|
|
@@ -49,6 +59,18 @@ export interface AwsReadClientOptions {
|
|
|
49
59
|
region?: string;
|
|
50
60
|
http?: AwsReadHttp;
|
|
51
61
|
signal?: AbortSignal;
|
|
62
|
+
/**
|
|
63
|
+
* What to sign with: literal credentials, or a resolver that decides. Omitted,
|
|
64
|
+
* the environment answers; when it has nothing, the request goes out unsigned
|
|
65
|
+
* exactly as it did before signing existed.
|
|
66
|
+
*/
|
|
67
|
+
credentials?: AwsCredentialSource;
|
|
68
|
+
/** Environment the credential fallback reads. Defaults to `process.env`; injectable for tests. */
|
|
69
|
+
env?: Record<string, string | undefined>;
|
|
70
|
+
/** Sign even against an endpoint override — for an override that is real AWS. */
|
|
71
|
+
signEndpointOverride?: boolean;
|
|
72
|
+
/** Signing clock. Injected by tests so a signature is reproducible. */
|
|
73
|
+
now?: Date;
|
|
52
74
|
}
|
|
53
75
|
/** Service host for `service`, honouring an endpoint override. */
|
|
54
76
|
export declare function serviceUrl(service: string, endpoint?: string, region?: string): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read-client.d.ts","sourceRoot":"","sources":["../../src/api/read-client.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"read-client.d.ts","sourceRoot":"","sources":["../../src/api/read-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/** SHA-256 of the empty string — the payload hash of every bodyless request. */
|
|
2
|
+
export declare const EMPTY_PAYLOAD_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
|
|
3
|
+
/** A resolved credential set. `sessionToken` is present for STS/role credentials. */
|
|
4
|
+
export interface AwsCredentials {
|
|
5
|
+
accessKeyId: string;
|
|
6
|
+
secretAccessKey: string;
|
|
7
|
+
sessionToken?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The credential injection seam: a function the caller supplies to decide, by
|
|
11
|
+
* whatever means it likes, what to sign with. Returning `undefined` means "this
|
|
12
|
+
* process has no credentials" and is respected as an answer, not overridden.
|
|
13
|
+
*/
|
|
14
|
+
export type AwsCredentialResolver = () => AwsCredentials | undefined;
|
|
15
|
+
/** Either literal credentials or a resolver for them. */
|
|
16
|
+
export type AwsCredentialSource = AwsCredentials | AwsCredentialResolver;
|
|
17
|
+
/**
|
|
18
|
+
* Credentials for a request: explicit → environment → absent.
|
|
19
|
+
*
|
|
20
|
+
* A resolver function is authoritative — if one is injected and it declines,
|
|
21
|
+
* the environment is not consulted behind its back, because the point of
|
|
22
|
+
* injecting one is to control the answer. Literal credentials are likewise
|
|
23
|
+
* final. Only the no-source case falls through to `AWS_ACCESS_KEY_ID` /
|
|
24
|
+
* `AWS_SECRET_ACCESS_KEY` (plus `AWS_SESSION_TOKEN` when set), and a half-set
|
|
25
|
+
* environment — a key id with no secret — is absent rather than a signature
|
|
26
|
+
* that cannot verify.
|
|
27
|
+
*
|
|
28
|
+
* Deliberately not implemented: the profile file, IMDS, and the container
|
|
29
|
+
* credential endpoints. Each is a separate transport with its own failure and
|
|
30
|
+
* caching story; a caller that has those can resolve them itself and pass the
|
|
31
|
+
* result in, which is what the resolver seam is for.
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveCredentials(source?: AwsCredentialSource, env?: Record<string, string | undefined>): AwsCredentials | undefined;
|
|
34
|
+
/** One request to sign. `headers` are the caller's; the signer adds its own. */
|
|
35
|
+
export interface SigV4Request {
|
|
36
|
+
method: string;
|
|
37
|
+
/** Absolute URL. Its host is signed and its path/query are canonicalized. */
|
|
38
|
+
url: string;
|
|
39
|
+
headers: Record<string, string>;
|
|
40
|
+
body: string;
|
|
41
|
+
/** Service name as it appears in the credential scope (`cloudformation`, `cloudcontrolapi`, …). */
|
|
42
|
+
service: string;
|
|
43
|
+
region: string;
|
|
44
|
+
credentials: AwsCredentials;
|
|
45
|
+
/** Signing clock. Injected by tests; otherwise now. */
|
|
46
|
+
now?: Date;
|
|
47
|
+
}
|
|
48
|
+
/** `YYYYMMDDTHHMMSSZ` — the `X-Amz-Date` format, which is ISO-8601 basic. */
|
|
49
|
+
export declare function amzDate(date: Date): string;
|
|
50
|
+
/** Lowercase hex SHA-256. */
|
|
51
|
+
export declare function sha256Hex(payload: string): string;
|
|
52
|
+
/**
|
|
53
|
+
* The signing key: four chained HMACs from the secret, so the key on the wire
|
|
54
|
+
* is scoped to one day, one region and one service rather than being the
|
|
55
|
+
* account secret itself.
|
|
56
|
+
*/
|
|
57
|
+
export declare function signingKey(secretAccessKey: string, day: string, region: string, service: string): Buffer;
|
|
58
|
+
/**
|
|
59
|
+
* Header names lowercased, values trimmed with internal whitespace runs
|
|
60
|
+
* collapsed, sorted by name. Returns the canonical block and the `;`-joined
|
|
61
|
+
* signed-header list that has to agree with it exactly.
|
|
62
|
+
*/
|
|
63
|
+
export declare function canonicalHeaders(headers: Record<string, string>): {
|
|
64
|
+
canonical: string;
|
|
65
|
+
signed: string;
|
|
66
|
+
};
|
|
67
|
+
/** The canonical request, verbatim as it is hashed into the string to sign. */
|
|
68
|
+
export declare function canonicalRequest(method: string, url: URL, headers: Record<string, string>, payloadHash: string): {
|
|
69
|
+
canonical: string;
|
|
70
|
+
signed: string;
|
|
71
|
+
};
|
|
72
|
+
/** The string to sign: algorithm, timestamp, scope, hashed canonical request. */
|
|
73
|
+
export declare function stringToSign(timestamp: string, scope: string, canonical: string): string;
|
|
74
|
+
/**
|
|
75
|
+
* The caller's headers plus everything SigV4 adds: `x-amz-date`,
|
|
76
|
+
* `x-amz-content-sha256`, `x-amz-security-token` when the credentials are
|
|
77
|
+
* temporary, and the `Authorization` header itself.
|
|
78
|
+
*
|
|
79
|
+
* `host` is signed (AWS requires it) but not returned — see the module header.
|
|
80
|
+
*/
|
|
81
|
+
export declare function signRequest(request: SigV4Request): Record<string, string>;
|
|
82
|
+
//# sourceMappingURL=sigv4.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sigv4.d.ts","sourceRoot":"","sources":["../../src/api/sigv4.ts"],"names":[],"mappings":"AAkCA,gFAAgF;AAChF,eAAO,MAAM,oBAAoB,qEAAqE,CAAC;AASvG,qFAAqF;AACrF,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAAG,MAAM,cAAc,GAAG,SAAS,CAAC;AAErE,yDAAyD;AACzD,MAAM,MAAM,mBAAmB,GAAG,cAAc,GAAG,qBAAqB,CAAC;AAEzE;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,CAAC,EAAE,mBAAmB,EAC5B,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GACpD,cAAc,GAAG,SAAS,CAW5B;AAED,gFAAgF;AAChF,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,mGAAmG;IACnG,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,cAAc,CAAC;IAC5B,uDAAuD;IACvD,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAED,6EAA6E;AAC7E,wBAAgB,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAE1C;AAED,6BAA6B;AAC7B,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEjD;AAMD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,eAAe,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAKxG;AAmCD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CASvG;AAED,+EAA+E;AAC/E,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,GAAG,EACR,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,WAAW,EAAE,MAAM,GAClB;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAavC;AAED,iFAAiF;AACjF,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAExF;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA0BzE"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type AwsReadClientOptions } from "./api/read-client.js";
|
|
2
|
+
import type { ResourceMetadata } from "@intentius/chant/lexicon";
|
|
3
|
+
/**
|
|
4
|
+
* The Cloud Control identifier the declared props spell, or undefined when the
|
|
5
|
+
* type has no primary identifier on record or any part of it is absent or
|
|
6
|
+
* non-scalar (a Ref, an intrinsic, a server-assigned name). Multi-part
|
|
7
|
+
* identifiers join with `|`, Cloud Control's own separator.
|
|
8
|
+
*/
|
|
9
|
+
export declare function declaredIdentifier(entityType: string, props: Record<string, unknown>): string | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Identity-read every entity the stack did not answer for and whose props
|
|
12
|
+
* spell an identifier. `already` is the stack's answer — an entity the stack
|
|
13
|
+
* DID return is never re-read. The `queried` map records the address each
|
|
14
|
+
* attempted read was issued against (#1620), whatever the verdict.
|
|
15
|
+
*/
|
|
16
|
+
export declare function observeByIdentity(entityNames: string[], entities: Map<string, {
|
|
17
|
+
entityType: string;
|
|
18
|
+
props: Record<string, unknown>;
|
|
19
|
+
}> | undefined, already: Record<string, ResourceMetadata>, client: AwsReadClientOptions): Promise<{
|
|
20
|
+
resources: Record<string, ResourceMetadata>;
|
|
21
|
+
queried: Record<string, string>;
|
|
22
|
+
}>;
|
|
23
|
+
//# sourceMappingURL=identity-observe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity-observe.d.ts","sourceRoot":"","sources":["../src/identity-observe.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAe,KAAK,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC3E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAmBjE;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,SAAS,CAazG;AAWD;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,WAAW,EAAE,MAAM,EAAE,EACrB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,GAAG,SAAS,EACzF,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,EACzC,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAAC,CA4B3F"}
|
package/dist/index.d.ts
CHANGED
|
@@ -14,7 +14,8 @@ export { output, isLexiconOutput } from "@intentius/chant/lexicon-output";
|
|
|
14
14
|
export type { LexiconOutput } from "@intentius/chant/lexicon-output";
|
|
15
15
|
export { awsPlugin } from "./plugin.js";
|
|
16
16
|
export { observeResourcesDeepAws, awsDeepNormalizationHooks, hasOwnershipMarker, DEEP_READABLE_TYPES, AWS_READ_ONLY_NAMES, AWS_SERVICE_DEFAULTS, } from "./deep-observe.js";
|
|
17
|
-
export { describeStackResources, describeStackOutputs, getResource, listResources, parseResourceDescription, AwsReadError, type AwsReadHttp, type AwsReadClientOptions, type CloudControlDescription, type StackResource, } from "./api/read-client.js";
|
|
17
|
+
export { describeStackResources, describeStackOutputs, getResource, listResources, parseResourceDescription, AwsReadError, type AwsReadHttp, type AwsReadClientOptions, type CloudControlDescription, type StackResource, type AwsCredentials, type AwsCredentialResolver, type AwsCredentialSource, } from "./api/read-client.js";
|
|
18
|
+
export { signRequest, resolveCredentials, EMPTY_PAYLOAD_SHA256, type SigV4Request, } from "./api/sigv4.js";
|
|
18
19
|
export { Sub, Ref, GetAtt, If, Join, Select, Split, Base64, GetAZs, SubIntrinsic, RefIntrinsic, GetAttIntrinsic, IfIntrinsic, JoinIntrinsic, SelectIntrinsic, SplitIntrinsic, Base64Intrinsic, GetAZsIntrinsic, } from "./intrinsics.js";
|
|
19
20
|
export { AWS, StackName, Region, AccountId, StackId, URLSuffix, NoValue, NotificationARNs, Partition, } from "./pseudo.js";
|
|
20
21
|
export * from "./generated/index.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGxC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACjF,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AACzG,YAAY,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAG9D,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAG7C,OAAO,EAAE,WAAW,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACvI,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAG9E,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AACtF,YAAY,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAChG,YAAY,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAC1E,YAAY,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAGrE,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAIrC,OAAO,EACL,uBAAuB,EACvB,yBAAyB,EACzB,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,gBAAgB,CAAC;AAOxB,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,WAAW,EACX,aAAa,EACb,wBAAwB,EACxB,YAAY,EACZ,KAAK,WAAW,EAChB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC5B,KAAK,aAAa,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/integrity.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "sha256",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
4
|
+
"manifest.json": "ba1ea326a8ff78358dba97679b751f9abf5a923977cb6cc52e759e19cae16b2f",
|
|
5
5
|
"meta.json": "2e4cae67cbc2eb2962c3dd0aa57008ae96fb725dd0118af7b04b2b44eab7722a",
|
|
6
6
|
"types/index.d.ts": "6d6b37bd14a9b40fb3ff5c66e1cb49be1ae1032de47a4e167cf5f411c943b7f5",
|
|
7
7
|
"rules/hardcoded-region.ts": "5a0eaf7ab391231fe6cd51426ece29539cb4b36f31c8dd060956638fed55722a",
|
|
@@ -62,5 +62,5 @@
|
|
|
62
62
|
"skills/chant-aws-eks.md": "8789255709ff004ad0a875fd5999edcdc66fc6e33d710db058d9f42703bcfdfe",
|
|
63
63
|
"skills/chant-aws-carve-terraform.md": "52dc7a8a10156d236266cc8dbf75614fabb6e2fa4caf31c13cca348e167a58b5"
|
|
64
64
|
},
|
|
65
|
-
"composite": "
|
|
65
|
+
"composite": "491effd79e8556535ecfaf370d32b01a5aec7540a31a03d6c2b268ec6bf81664"
|
|
66
66
|
}
|
package/dist/manifest.json
CHANGED
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAgL,MAAM,0BAA0B,CAAC;AAqC5O;yEACyE;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAWnD,eAAO,MAAM,SAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAgL,MAAM,0BAA0B,CAAC;AAqC5O;yEACyE;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAWnD,eAAO,MAAM,SAAS,EAAE,aAw6BvB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-aws",
|
|
3
|
-
"version": "0.44.
|
|
3
|
+
"version": "0.44.10",
|
|
4
4
|
"description": "AWS CloudFormation lexicon for chant — declarative IaC in TypeScript",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://intentius.io/chant",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"typescript": "^5.9.3"
|
|
81
81
|
},
|
|
82
82
|
"peerDependencies": {
|
|
83
|
-
"@intentius/chant": "^0.44.
|
|
83
|
+
"@intentius/chant": "^0.44.10",
|
|
84
84
|
"typescript": "^5.9.3"
|
|
85
85
|
}
|
|
86
86
|
}
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
xmlError,
|
|
20
20
|
type AwsReadHttp,
|
|
21
21
|
} from "./read-client";
|
|
22
|
+
import { EMPTY_PAYLOAD_SHA256 } from "./sigv4";
|
|
22
23
|
|
|
23
24
|
const respond = (text: string, status = 200): ReturnType<AwsReadHttp> => Promise.resolve({ status, text });
|
|
24
25
|
|
|
@@ -210,25 +211,107 @@ describe("the region an endpoint override cannot carry in its host", () => {
|
|
|
210
211
|
// snapshot recorded that region as holding nothing.
|
|
211
212
|
test("the request names the region in its credential scope", async () => {
|
|
212
213
|
const { http, calls } = recording(() => respond(stackXml));
|
|
213
|
-
await describeStackResources("web", { endpoint: "http://localhost:4566", region: "us-west-1", http });
|
|
214
|
+
await describeStackResources("web", { endpoint: "http://localhost:4566", region: "us-west-1", http, env: {} });
|
|
214
215
|
expect(calls[0].headers.authorization).toContain("/us-west-1/cloudformation/aws4_request");
|
|
215
216
|
});
|
|
216
217
|
|
|
217
218
|
test("Cloud Control carries it too, scoped to its own service", async () => {
|
|
218
219
|
const { http, calls } = recording(() => respond(JSON.stringify({ ResourceDescription: {} })));
|
|
219
|
-
await getResource("AWS::EC2::VPC", "vpc-01", { endpoint: "http://localhost:4566", region: "eu-west-1", http });
|
|
220
|
+
await getResource("AWS::EC2::VPC", "vpc-01", { endpoint: "http://localhost:4566", region: "eu-west-1", http, env: {} });
|
|
220
221
|
expect(calls[0].headers.authorization).toContain("/eu-west-1/cloudcontrolapi/aws4_request");
|
|
221
222
|
});
|
|
222
223
|
|
|
223
224
|
test("no region, no header — nothing invents one", async () => {
|
|
224
225
|
const { http, calls } = recording(() => respond(stackXml));
|
|
225
|
-
await describeStackResources("web", { endpoint: "http://localhost:4566", http });
|
|
226
|
+
await describeStackResources("web", { endpoint: "http://localhost:4566", http, env: {} });
|
|
226
227
|
expect(calls[0].headers.authorization).toBeUndefined();
|
|
227
228
|
});
|
|
228
229
|
|
|
229
230
|
test("it is a scope, not a signature — the placeholder says so", async () => {
|
|
230
231
|
const { http, calls } = recording(() => respond(stackXml));
|
|
231
|
-
await describeStackResources("web", { region: "us-west-1", http });
|
|
232
|
+
await describeStackResources("web", { region: "us-west-1", http, env: {} });
|
|
232
233
|
expect(calls[0].headers.authorization).toContain("Signature=unsigned");
|
|
233
234
|
});
|
|
234
235
|
});
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* The signing decision, as the client makes it. `./sigv4.test.ts` proves the
|
|
239
|
+
* signature is correct against AWS's own vectors; these prove the client only
|
|
240
|
+
* produces one when it should.
|
|
241
|
+
*/
|
|
242
|
+
describe("SigV4 on the read path", () => {
|
|
243
|
+
const credentials = { accessKeyId: "AKIDEXAMPLE", secretAccessKey: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY" };
|
|
244
|
+
const now = new Date("2015-08-30T12:36:00Z");
|
|
245
|
+
|
|
246
|
+
test("credentials and a real host produce a signed request, not the placeholder", async () => {
|
|
247
|
+
const { http, calls } = recording(() => respond(stackXml));
|
|
248
|
+
await describeStackResources("web", { region: "us-west-1", credentials, now, http, env: {} });
|
|
249
|
+
|
|
250
|
+
const auth = calls[0].headers.authorization;
|
|
251
|
+
expect(auth).toContain("Credential=AKIDEXAMPLE/20150830/us-west-1/cloudformation/aws4_request");
|
|
252
|
+
expect(auth).not.toContain("Signature=unsigned");
|
|
253
|
+
expect(auth).toMatch(/Signature=[0-9a-f]{64}$/);
|
|
254
|
+
expect(calls[0].headers["x-amz-date"]).toBe("20150830T123600Z");
|
|
255
|
+
// The body hash, not the empty-payload constant — the Query call has a body.
|
|
256
|
+
expect(calls[0].headers["x-amz-content-sha256"]).toMatch(/^[0-9a-f]{64}$/);
|
|
257
|
+
expect(calls[0].headers["x-amz-content-sha256"]).not.toBe(EMPTY_PAYLOAD_SHA256);
|
|
258
|
+
// `host` is signed but never emitted: fetch computes it and forbids the override.
|
|
259
|
+
expect(calls[0].headers.host).toBeUndefined();
|
|
260
|
+
expect(auth).toContain("SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date");
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("an endpoint override is not signed, so the emulator lanes need no credentials", async () => {
|
|
264
|
+
const { http, calls } = recording(() => respond(stackXml));
|
|
265
|
+
await describeStackResources("web", {
|
|
266
|
+
endpoint: "http://localhost:4566",
|
|
267
|
+
region: "us-west-1",
|
|
268
|
+
credentials,
|
|
269
|
+
http,
|
|
270
|
+
env: {},
|
|
271
|
+
});
|
|
272
|
+
expect(calls[0].headers.authorization).toContain("Signature=unsigned");
|
|
273
|
+
expect(calls[0].headers["x-amz-date"]).toBeUndefined();
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test("signEndpointOverride signs one anyway — for an override that is real AWS", async () => {
|
|
277
|
+
const { http, calls } = recording(() => respond(stackXml));
|
|
278
|
+
await describeStackResources("web", {
|
|
279
|
+
endpoint: "https://vpce-1234.cloudformation.us-west-1.vpce.amazonaws.com",
|
|
280
|
+
region: "us-west-1",
|
|
281
|
+
credentials,
|
|
282
|
+
signEndpointOverride: true,
|
|
283
|
+
now,
|
|
284
|
+
http,
|
|
285
|
+
env: {},
|
|
286
|
+
});
|
|
287
|
+
expect(calls[0].headers.authorization).toMatch(/Signature=[0-9a-f]{64}$/);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test("no credentials leaves the old unsigned path exactly as it was", async () => {
|
|
291
|
+
const { http, calls } = recording(() => respond(stackXml));
|
|
292
|
+
await describeStackResources("web", { region: "us-west-1", http, env: {} });
|
|
293
|
+
expect(calls[0].headers.authorization).toContain("Signature=unsigned");
|
|
294
|
+
expect(calls[0].headers["x-amz-date"]).toBeUndefined();
|
|
295
|
+
expect(calls[0].headers["x-amz-content-sha256"]).toBeUndefined();
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test("the environment is the fallback source, and Cloud Control signs from it too", async () => {
|
|
299
|
+
const { http, calls } = recording(() => respond(JSON.stringify({ ResourceDescription: {} })));
|
|
300
|
+
await getResource("AWS::EC2::VPC", "vpc-01", {
|
|
301
|
+
region: "eu-west-1",
|
|
302
|
+
now,
|
|
303
|
+
http,
|
|
304
|
+
env: { AWS_ACCESS_KEY_ID: "AKIDENV", AWS_SECRET_ACCESS_KEY: "s", AWS_SESSION_TOKEN: "tok" },
|
|
305
|
+
});
|
|
306
|
+
expect(calls[0].headers.authorization).toContain("Credential=AKIDENV/20150830/eu-west-1/cloudcontrolapi/");
|
|
307
|
+
expect(calls[0].headers["x-amz-security-token"]).toBe("tok");
|
|
308
|
+
expect(calls[0].headers["x-amz-target"]).toBe("CloudApiService.GetResource");
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test("signing without a named region falls back to the same default the host does", async () => {
|
|
312
|
+
const { http, calls } = recording(() => respond(stackXml));
|
|
313
|
+
await describeStackResources("web", { credentials, now, http, env: {} });
|
|
314
|
+
expect(calls[0].url).toBe("https://cloudformation.us-east-1.amazonaws.com/");
|
|
315
|
+
expect(calls[0].headers.authorization).toContain("/20150830/us-east-1/cloudformation/aws4_request");
|
|
316
|
+
});
|
|
317
|
+
});
|
package/src/api/read-client.ts
CHANGED
|
@@ -19,12 +19,23 @@
|
|
|
19
19
|
*
|
|
20
20
|
* ## Signing
|
|
21
21
|
*
|
|
22
|
-
* Requests are
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
22
|
+
* Requests are signed with SigV4 (#1686) when credentials resolve — see
|
|
23
|
+
* `./sigv4.ts`, which holds the implementation so that cedar's AVP client can
|
|
24
|
+
* adopt the same one rather than growing a second. Two cases stay unsigned, and
|
|
25
|
+
* both are deliberate:
|
|
26
|
+
*
|
|
27
|
+
* - **No credentials.** The caller's existing no-credentials path is
|
|
28
|
+
* untouched: the request still goes out carrying only the credential scope
|
|
29
|
+
* of {@link regionScope}, which is what the emulator lanes have always
|
|
30
|
+
* sent.
|
|
31
|
+
* - **An endpoint override.** Floci does not verify signatures, and signing
|
|
32
|
+
* against it would mean every local lane suddenly needs credentials to read
|
|
33
|
+
* what it just deployed. `signEndpointOverride` opts back in for an
|
|
34
|
+
* override that *is* real AWS — a VPC endpoint, a signing proxy.
|
|
27
35
|
*/
|
|
36
|
+
import { resolveCredentials, signRequest, type AwsCredentialSource } from "./sigv4";
|
|
37
|
+
|
|
38
|
+
export type { AwsCredentials, AwsCredentialResolver, AwsCredentialSource } from "./sigv4";
|
|
28
39
|
|
|
29
40
|
const DEFAULT_REGION = "us-east-1";
|
|
30
41
|
const CFN_API_VERSION = "2010-05-15";
|
|
@@ -62,6 +73,18 @@ export interface AwsReadClientOptions {
|
|
|
62
73
|
region?: string;
|
|
63
74
|
http?: AwsReadHttp;
|
|
64
75
|
signal?: AbortSignal;
|
|
76
|
+
/**
|
|
77
|
+
* What to sign with: literal credentials, or a resolver that decides. Omitted,
|
|
78
|
+
* the environment answers; when it has nothing, the request goes out unsigned
|
|
79
|
+
* exactly as it did before signing existed.
|
|
80
|
+
*/
|
|
81
|
+
credentials?: AwsCredentialSource;
|
|
82
|
+
/** Environment the credential fallback reads. Defaults to `process.env`; injectable for tests. */
|
|
83
|
+
env?: Record<string, string | undefined>;
|
|
84
|
+
/** Sign even against an endpoint override — for an override that is real AWS. */
|
|
85
|
+
signEndpointOverride?: boolean;
|
|
86
|
+
/** Signing clock. Injected by tests so a signature is reproducible. */
|
|
87
|
+
now?: Date;
|
|
65
88
|
}
|
|
66
89
|
|
|
67
90
|
/** Service host for `service`, honouring an endpoint override. */
|
|
@@ -84,15 +107,19 @@ export function serviceUrl(service: string, endpoint?: string, region = DEFAULT_
|
|
|
84
107
|
* an empty observation and the snapshot recorded the region as holding nothing.
|
|
85
108
|
* A three-region estate snapshotted as one region and nothing said so.
|
|
86
109
|
*
|
|
87
|
-
* This is NOT SigV4. The signature is a placeholder and real AWS rejects it
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
110
|
+
* This is NOT SigV4. The signature is a placeholder and real AWS rejects it. It
|
|
111
|
+
* is what an unsigned request still has to carry so an emulator learns the
|
|
112
|
+
* region, and it is only ever sent when no credentials resolved — the moment
|
|
113
|
+
* they do, {@link requestHeaders} sends a real signature instead.
|
|
91
114
|
*/
|
|
92
|
-
function regionScope(
|
|
115
|
+
function regionScope(
|
|
116
|
+
service: string,
|
|
117
|
+
region: string | undefined,
|
|
118
|
+
env: Record<string, string | undefined>,
|
|
119
|
+
): Record<string, string> {
|
|
93
120
|
if (!region) return {};
|
|
94
121
|
const day = new Date().toISOString().slice(0, 10).replace(/-/g, "");
|
|
95
|
-
const key =
|
|
122
|
+
const key = env.AWS_ACCESS_KEY_ID || "chant";
|
|
96
123
|
return {
|
|
97
124
|
authorization:
|
|
98
125
|
`AWS4-HMAC-SHA256 Credential=${key}/${day}/${region}/${service}/aws4_request, ` +
|
|
@@ -100,6 +127,38 @@ function regionScope(service: string, region?: string): Record<string, string> {
|
|
|
100
127
|
};
|
|
101
128
|
}
|
|
102
129
|
|
|
130
|
+
/**
|
|
131
|
+
* The headers one request goes out with — signed when there is something to
|
|
132
|
+
* sign with and the target is real AWS, scope-only otherwise.
|
|
133
|
+
*
|
|
134
|
+
* Signing needs a region even when the caller named none, because the scope
|
|
135
|
+
* string has a slot for one; it borrows the same `us-east-1` default that
|
|
136
|
+
* {@link serviceUrl} already used to build the host, so the signature agrees
|
|
137
|
+
* with the endpoint it is sent to.
|
|
138
|
+
*/
|
|
139
|
+
function requestHeaders(
|
|
140
|
+
service: string,
|
|
141
|
+
url: string,
|
|
142
|
+
body: string,
|
|
143
|
+
base: Record<string, string>,
|
|
144
|
+
options: AwsReadClientOptions,
|
|
145
|
+
): Record<string, string> {
|
|
146
|
+
const env = options.env ?? process.env;
|
|
147
|
+
const credentials = resolveCredentials(options.credentials, env);
|
|
148
|
+
const signable = credentials && (!options.endpoint || options.signEndpointOverride === true);
|
|
149
|
+
if (!signable) return { ...base, ...regionScope(service, options.region, env) };
|
|
150
|
+
return signRequest({
|
|
151
|
+
method: "POST",
|
|
152
|
+
url,
|
|
153
|
+
headers: base,
|
|
154
|
+
body,
|
|
155
|
+
service,
|
|
156
|
+
region: options.region ?? DEFAULT_REGION,
|
|
157
|
+
credentials,
|
|
158
|
+
...(options.now ? { now: options.now } : {}),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
103
162
|
/* ── CloudFormation Query ─────────────────────────────────────────────────── */
|
|
104
163
|
|
|
105
164
|
/**
|
|
@@ -162,10 +221,13 @@ export async function cfnQuery(
|
|
|
162
221
|
const res = await http(
|
|
163
222
|
url,
|
|
164
223
|
{
|
|
165
|
-
headers:
|
|
166
|
-
"
|
|
167
|
-
|
|
168
|
-
|
|
224
|
+
headers: requestHeaders(
|
|
225
|
+
"cloudformation",
|
|
226
|
+
url,
|
|
227
|
+
body,
|
|
228
|
+
{ "content-type": "application/x-www-form-urlencoded" },
|
|
229
|
+
options,
|
|
230
|
+
),
|
|
169
231
|
body,
|
|
170
232
|
},
|
|
171
233
|
options.signal,
|
|
@@ -238,15 +300,21 @@ async function cloudControl(
|
|
|
238
300
|
): Promise<Record<string, unknown>> {
|
|
239
301
|
const http = options.http ?? defaultHttp;
|
|
240
302
|
const url = serviceUrl("cloudcontrolapi", options.endpoint, options.region);
|
|
303
|
+
const payloadJson = JSON.stringify(payload);
|
|
241
304
|
const res = await http(
|
|
242
305
|
url,
|
|
243
306
|
{
|
|
244
|
-
headers:
|
|
245
|
-
"
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
307
|
+
headers: requestHeaders(
|
|
308
|
+
"cloudcontrolapi",
|
|
309
|
+
url,
|
|
310
|
+
payloadJson,
|
|
311
|
+
{
|
|
312
|
+
"content-type": "application/x-amz-json-1.0",
|
|
313
|
+
"x-amz-target": `${CLOUD_CONTROL_TARGET_PREFIX}.${operation}`,
|
|
314
|
+
},
|
|
315
|
+
options,
|
|
316
|
+
),
|
|
317
|
+
body: payloadJson,
|
|
250
318
|
},
|
|
251
319
|
options.signal,
|
|
252
320
|
);
|