@kensio/yulin 1.21.5 → 1.21.6
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/service/cloudformation/resource/cfn/sim-cfn-default-resource-value-adapter.d.ts +8 -1
- package/dist/service/cloudformation/resource/cfn/sim-cfn-default-resource-value-adapter.js +9 -2
- package/dist/service/cloudformation/resource/cfn/sim-cfn-resource-value-adapter.d.ts +6 -1
- package/dist/service/cloudformation/resource/cfn/sim-cfn-resource-value-adapter.js +2 -1
- package/dist/service/cloudformation/resource/create/sim-cfn-resource-create-operation.d.ts +4 -0
- package/dist/service/cloudformation/resource/create/sim-cfn-resource-create-operation.js +6 -0
- package/dist/service/cloudformation/resource/sim-cfn-resource-record.d.ts +19 -0
- package/dist/service/cloudformation/resource/sim-cfn-resource-record.js +26 -0
- package/dist/service/cloudformation/resource/sim-cfn-resource.js +1 -0
- package/dist/service/cloudformation/resource/unsupported/sim-cfn-named-skip.d.ts +26 -0
- package/dist/service/cloudformation/resource/unsupported/sim-cfn-named-skip.js +31 -0
- package/dist/service/cloudfront/cfn/cache-policy/sim-cfn-cf-cache-policy-creator.d.ts +5 -2
- package/dist/service/cloudfront/cfn/cache-policy/sim-cfn-cf-cache-policy-creator.js +10 -2
- package/dist/service/cloudfront/cfn/origin-access-control/sim-cfn-cf-oac-creator.d.ts +5 -2
- package/dist/service/cloudfront/cfn/origin-access-control/sim-cfn-cf-oac-creator.js +10 -2
- package/dist/service/cloudfront/cfn/origin-request-policy/sim-cfn-cf-orp-creator.d.ts +5 -2
- package/dist/service/cloudfront/cfn/origin-request-policy/sim-cfn-cf-orp-creator.js +10 -2
- package/dist/service/cloudfront/cfn/policy/sim-cfn-cf-policy-creators.d.ts +33 -0
- package/dist/service/cloudfront/cfn/policy/sim-cfn-cf-policy-creators.js +39 -0
- package/dist/service/cloudfront/cfn/response-headers-policy/sim-cfn-cf-rh-policy-creator.d.ts +5 -2
- package/dist/service/cloudfront/cfn/response-headers-policy/sim-cfn-cf-rh-policy-creator.js +10 -2
- package/dist/service/cloudfront/cfn/sim-cfn-cf-authorizer.d.ts +40 -0
- package/dist/service/cloudfront/cfn/sim-cfn-cf-authorizer.js +50 -0
- package/dist/service/cloudfront/cfn/sim-cfn-cloudfront-resource-factory.d.ts +1 -4
- package/dist/service/cloudfront/cfn/sim-cfn-cloudfront-resource-factory.js +14 -46
- package/dist/service/cloudfront/sim-cloudfront-commands.d.ts +6 -0
- package/dist/service/cloudfront/sim-cloudfront-commands.js +7 -0
- package/dist/service/cloudfront/sim-cloudfront.d.ts +6 -0
- package/dist/service/cloudfront/sim-cloudfront.js +7 -0
- package/dist/service/dynamodb/document/sim-dynamodb-document-command-paths.d.ts +8 -0
- package/dist/service/dynamodb/document/sim-dynamodb-document-command-paths.js +62 -0
- package/dist/service/dynamodb/document/sim-dynamodb-document-routes.d.ts +1 -1
- package/dist/service/dynamodb/document/sim-dynamodb-document-routes.js +3 -1
- package/dist/service/iam/authorize/sim-iam-action-authorizer.d.ts +8 -3
- package/dist/service/iam/authorize/sim-iam-action-authorizer.js +13 -4
- package/dist/service/iam/cfn/role/sim-cfn-iam-role-creator.js +1 -0
- package/dist/service/iam/cfn/role/sim-cfn-iam-role-properties-parser.d.ts +1 -0
- package/dist/service/iam/cfn/role/sim-cfn-iam-role-properties-parser.js +21 -14
- package/dist/service/iam/cfn/{sim-cfn-iam-optional-string.d.ts → sim-cfn-iam-property.d.ts} +10 -2
- package/dist/service/iam/cfn/sim-cfn-iam-property.js +31 -0
- package/dist/service/iam/cfn/user/sim-cfn-iam-user-properties-parser.js +1 -1
- package/dist/service/iam/command/role/create-role/create-role-record-factory.js +3 -0
- package/dist/service/iam/command/role/create-role/create-role.command.d.ts +10 -0
- package/dist/service/iam/command/role/get-role/get-role.command.d.ts +2 -0
- package/dist/service/iam/command/role/get-role/get-role.handler.js +2 -0
- package/dist/service/iam/command/role/sim-iam-role-command-handlers.js +15 -1
- package/dist/service/iam/role/sim-iam-role-boundary.d.ts +24 -0
- package/dist/service/iam/role/sim-iam-role-boundary.js +24 -0
- package/dist/service/iam/role/sim-iam-role-with-policy.factory.d.ts +1 -0
- package/dist/service/iam/role/sim-iam-role.d.ts +10 -0
- package/dist/service/iam/serve/sim-iam-query-entity.d.ts +6 -1
- package/dist/service/iam/serve/sim-iam-query-entity.js +10 -4
- package/dist/service/iam/serve/sim-iam-query-role-operations.js +15 -3
- package/dist/service/lambda/cfn/function/sim-cfn-lambda-runtime-skip.d.ts +6 -0
- package/dist/service/lambda/cfn/function/sim-cfn-lambda-runtime-skip.js +9 -2
- package/dist/service/route53/command/create-hosted-zone/sim-route53-zone-id.js +23 -1
- package/docs/services/cloudformation/README.md +88 -23
- package/docs/services/cloudfront/README.md +109 -0
- package/docs/services/dynamodb/README.md +128 -8
- package/docs/services/iam/README.md +125 -2
- package/docs/services/route53/README.md +11 -0
- package/package.json +1 -1
- package/dist/service/iam/cfn/sim-cfn-iam-optional-string.js +0 -17
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BackgroundTasks, } from "../../../../../util/background/background.js";
|
|
2
2
|
import { SimIamNoSuchEntity } from "../../../error/sim-iam.error.js";
|
|
3
|
+
import { simIamAttachedPermissionsBoundary } from "../../../role/sim-iam-role-boundary.js";
|
|
3
4
|
/**
|
|
4
5
|
* IAM GetRoleCommand handler.
|
|
5
6
|
*
|
|
@@ -36,6 +37,7 @@ export class GetRoleCommandHandler {
|
|
|
36
37
|
CreateDate: role.creationDate,
|
|
37
38
|
AssumeRolePolicyDocument: role.assumeRolePolicyDocument,
|
|
38
39
|
Description: role.description,
|
|
40
|
+
PermissionsBoundary: simIamAttachedPermissionsBoundary(role),
|
|
39
41
|
},
|
|
40
42
|
};
|
|
41
43
|
}
|
|
@@ -31,7 +31,7 @@ export class SimIamRoleCommandHandlers {
|
|
|
31
31
|
* Handle a CreateRole command from the SDK.
|
|
32
32
|
*/
|
|
33
33
|
async createRole(command, options) {
|
|
34
|
-
this.authorizer.authorize("iam:CreateRole", this.roleArn(command.input.RoleName), options?.caller);
|
|
34
|
+
this.authorizer.authorize("iam:CreateRole", this.roleArn(command.input.RoleName), options?.caller, permissionsBoundaryCondition(command.input.PermissionsBoundary));
|
|
35
35
|
const handler = new CreateRoleCommandHandler({
|
|
36
36
|
accountId: this.accountId,
|
|
37
37
|
roles: this.roles,
|
|
@@ -123,3 +123,17 @@ export class SimIamRoleCommandHandlers {
|
|
|
123
123
|
return `arn:aws:iam::${this.accountId}:role/${roleName ?? "*"}`;
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* What a Role-creating request says about the boundary it declares.
|
|
128
|
+
*
|
|
129
|
+
* An account that requires a permissions boundary allows `iam:CreateRole`
|
|
130
|
+
* only under `StringEquals` on `iam:PermissionsBoundary`, and the CDK
|
|
131
|
+
* permissions-boundary guard is written that way. A request declaring no
|
|
132
|
+
* boundary leaves the key out, which is what refuses it: a positive operator
|
|
133
|
+
* over an absent key matches nothing.
|
|
134
|
+
*/
|
|
135
|
+
function permissionsBoundaryCondition(permissionsBoundary) {
|
|
136
|
+
return permissionsBoundary === undefined
|
|
137
|
+
? undefined
|
|
138
|
+
: { "iam:PermissionsBoundary": permissionsBoundary };
|
|
139
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { SimArn } from "../../aws/arn.js";
|
|
2
|
+
import type { SimIamRole } from "./sim-iam-role.js";
|
|
3
|
+
/**
|
|
4
|
+
* A permissions boundary attached to a Role, as IAM describes one.
|
|
5
|
+
*
|
|
6
|
+
* IAM answers with the attachment rather than the bare ARN, and the type has
|
|
7
|
+
* only ever taken one value.
|
|
8
|
+
*/
|
|
9
|
+
export interface SimIamAttachedPermissionsBoundary {
|
|
10
|
+
readonly PermissionsBoundaryType: "PermissionsBoundaryPolicy";
|
|
11
|
+
readonly PermissionsBoundaryArn: SimArn;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The member names IAM describes an attached boundary with.
|
|
15
|
+
*/
|
|
16
|
+
export declare const simIamAttachedBoundaryMembers: string[];
|
|
17
|
+
/**
|
|
18
|
+
* How a Role's boundary appears in a CreateRole or GetRole answer.
|
|
19
|
+
*
|
|
20
|
+
* A Role created without one has no attachment to describe, and IAM leaves
|
|
21
|
+
* the field out. `ListRoles` leaves it out whatever the Role carries, along
|
|
22
|
+
* with the Role's tags and last use, and points the caller at `GetRole`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function simIamAttachedPermissionsBoundary(role: SimIamRole): SimIamAttachedPermissionsBoundary | undefined;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The member names IAM describes an attached boundary with.
|
|
3
|
+
*/
|
|
4
|
+
export const simIamAttachedBoundaryMembers = [
|
|
5
|
+
"PermissionsBoundaryType",
|
|
6
|
+
"PermissionsBoundaryArn",
|
|
7
|
+
];
|
|
8
|
+
/**
|
|
9
|
+
* How a Role's boundary appears in a CreateRole or GetRole answer.
|
|
10
|
+
*
|
|
11
|
+
* A Role created without one has no attachment to describe, and IAM leaves
|
|
12
|
+
* the field out. `ListRoles` leaves it out whatever the Role carries, along
|
|
13
|
+
* with the Role's tags and last use, and points the caller at `GetRole`.
|
|
14
|
+
*/
|
|
15
|
+
export function simIamAttachedPermissionsBoundary(role) {
|
|
16
|
+
const permissionsBoundaryArn = role.permissionsBoundaryArn;
|
|
17
|
+
if (permissionsBoundaryArn === undefined) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
PermissionsBoundaryType: "PermissionsBoundaryPolicy",
|
|
22
|
+
PermissionsBoundaryArn: permissionsBoundaryArn,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -56,4 +56,5 @@ export declare const simIamRoleWithPolicyFactory: AsyncMappedFactory<SimIamRoleW
|
|
|
56
56
|
readonly CreateDate: Date;
|
|
57
57
|
readonly AssumeRolePolicyDocument?: string | undefined;
|
|
58
58
|
readonly Description?: string | undefined;
|
|
59
|
+
readonly PermissionsBoundary?: import("./sim-iam-role-boundary.js").SimIamAttachedPermissionsBoundary | undefined;
|
|
59
60
|
}, SimAws>;
|
|
@@ -19,6 +19,16 @@ export interface SimIamRole extends SimIamPrincipal {
|
|
|
19
19
|
readonly assumeRolePolicyDocument?: JSONString<SimIamPolicyDocument> | undefined;
|
|
20
20
|
readonly description?: string | undefined;
|
|
21
21
|
readonly creationDate: Date;
|
|
22
|
+
/**
|
|
23
|
+
* ARN of the managed policy attached to this Role as its permissions
|
|
24
|
+
* boundary, where the request creating it declared one.
|
|
25
|
+
*
|
|
26
|
+
* This records what the Role was created with, so that a policy conditioned
|
|
27
|
+
* on `iam:PermissionsBoundary` can be tested against the Roles a deployment
|
|
28
|
+
* makes. The boundary is not yet evaluated as a policy source, so it does
|
|
29
|
+
* not narrow what the Role itself may do.
|
|
30
|
+
*/
|
|
31
|
+
readonly permissionsBoundaryArn?: SimArn | undefined;
|
|
22
32
|
/**
|
|
23
33
|
* Inline identity-based permissions policies stored directly on this role.
|
|
24
34
|
*/
|
|
@@ -7,5 +7,10 @@ import type { SimQueryOutput } from "../../../serve/http/api/query/sim-query-res
|
|
|
7
7
|
* same structures are what a listing repeats. The Query layer writes the
|
|
8
8
|
* members of a structure and the items of a list, and this is the one shape
|
|
9
9
|
* left between them.
|
|
10
|
+
*
|
|
11
|
+
* An entity carrying a member of its own that is a structure passes `nested`,
|
|
12
|
+
* which writes whatever a scalar member cannot. A Role's permissions boundary
|
|
13
|
+
* is the case that exists for, and a listing that leaves it out passes
|
|
14
|
+
* nothing.
|
|
10
15
|
*/
|
|
11
|
-
export declare function iamQueryEntity(output: SimQueryOutput, name: string, members: readonly string[]): string;
|
|
16
|
+
export declare function iamQueryEntity(output: SimQueryOutput, name: string, members: readonly string[], nested?: (entity: SimQueryOutput) => string): string;
|
|
@@ -12,10 +12,16 @@ import { xmlElement } from "../../../util/xml/xml-writer.js";
|
|
|
12
12
|
* same structures are what a listing repeats. The Query layer writes the
|
|
13
13
|
* members of a structure and the items of a list, and this is the one shape
|
|
14
14
|
* left between them.
|
|
15
|
+
*
|
|
16
|
+
* An entity carrying a member of its own that is a structure passes `nested`,
|
|
17
|
+
* which writes whatever a scalar member cannot. A Role's permissions boundary
|
|
18
|
+
* is the case that exists for, and a listing that leaves it out passes
|
|
19
|
+
* nothing.
|
|
15
20
|
*/
|
|
16
|
-
export function iamQueryEntity(output, name, members) {
|
|
21
|
+
export function iamQueryEntity(output, name, members, nested) {
|
|
17
22
|
const entity = output[name];
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
if (!isRecord(entity)) {
|
|
24
|
+
return "";
|
|
25
|
+
}
|
|
26
|
+
return xmlElement(name, queryMembers(entity, members) + (nested?.(entity) ?? ""));
|
|
21
27
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { queryList, queryMembers, } from "../../../serve/http/api/query/sim-query-result.js";
|
|
1
|
+
import { queryList, queryMembers, queryStructure, } from "../../../serve/http/api/query/sim-query-result.js";
|
|
2
|
+
import { simIamAttachedBoundaryMembers } from "../role/sim-iam-role-boundary.js";
|
|
2
3
|
import { iamQueryEntity } from "./sim-iam-query-entity.js";
|
|
3
4
|
import { iamQueryListingInput, iamQueryListingResult, } from "./sim-iam-query-listing.js";
|
|
4
5
|
/**
|
|
@@ -13,6 +14,16 @@ const roleMembers = [
|
|
|
13
14
|
"AssumeRolePolicyDocument",
|
|
14
15
|
"Description",
|
|
15
16
|
];
|
|
17
|
+
/**
|
|
18
|
+
* Write the permissions boundary a Role carries, where it carries one.
|
|
19
|
+
*
|
|
20
|
+
* A boundary is a structure of its own, and `queryMembers` would write it as
|
|
21
|
+
* the JSON text of an object. `ListRoles` leaves the field out altogether, as
|
|
22
|
+
* IAM does, so only the create and the get pass this.
|
|
23
|
+
*/
|
|
24
|
+
function roleBoundary(role) {
|
|
25
|
+
return queryStructure(role, "PermissionsBoundary", (boundary) => queryMembers(boundary, simIamAttachedBoundaryMembers));
|
|
26
|
+
}
|
|
16
27
|
/**
|
|
17
28
|
* The Role operations simulated IAM serves over the Query protocol.
|
|
18
29
|
*
|
|
@@ -30,8 +41,9 @@ export function simIamQueryRoleOperations() {
|
|
|
30
41
|
Path: fields.text("Path"),
|
|
31
42
|
AssumeRolePolicyDocument: fields.text("AssumeRolePolicyDocument"),
|
|
32
43
|
Description: fields.text("Description"),
|
|
44
|
+
PermissionsBoundary: fields.text("PermissionsBoundary"),
|
|
33
45
|
}),
|
|
34
|
-
result: (output) => iamQueryEntity(output, "Role", roleMembers),
|
|
46
|
+
result: (output) => iamQueryEntity(output, "Role", roleMembers, roleBoundary),
|
|
35
47
|
},
|
|
36
48
|
],
|
|
37
49
|
[
|
|
@@ -40,7 +52,7 @@ export function simIamQueryRoleOperations() {
|
|
|
40
52
|
input: (fields) => ({
|
|
41
53
|
RoleName: fields.text("RoleName"),
|
|
42
54
|
}),
|
|
43
|
-
result: (output) => iamQueryEntity(output, "Role", roleMembers),
|
|
55
|
+
result: (output) => iamQueryEntity(output, "Role", roleMembers, roleBoundary),
|
|
44
56
|
},
|
|
45
57
|
],
|
|
46
58
|
[
|
|
@@ -26,6 +26,12 @@ import type { SimCfnLambdaFunctionProperties } from "./sim-cfn-lambda-function-p
|
|
|
26
26
|
* CloudFormation carries out itself is recorded as inert instead, because this
|
|
27
27
|
* message would otherwise tell a reader to bind a handler to a function that
|
|
28
28
|
* has nothing left to do.
|
|
29
|
+
*
|
|
30
|
+
* The refusal carries the function's name, which the properties parser has
|
|
31
|
+
* already worked out by the time this gate is asked. CDK writes a function's
|
|
32
|
+
* log group name as `/aws/lambda/` joined to a Ref of the function. A refusal
|
|
33
|
+
* carrying no name puts the log group under `/aws/lambda/` and a logical ID,
|
|
34
|
+
* a name no account would hold.
|
|
29
35
|
*/
|
|
30
36
|
export declare class SimCfnLambdaRuntimeSkip {
|
|
31
37
|
/**
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SimCfnNamedSkip } from "../../../cloudformation/resource/unsupported/sim-cfn-named-skip.js";
|
|
1
2
|
/**
|
|
2
3
|
* Sim Lambda runs handler modules in a Node.js vm, so it simulates the
|
|
3
4
|
* Node.js managed runtimes.
|
|
@@ -29,6 +30,12 @@ const nodeJsRuntimePrefix = "nodejs";
|
|
|
29
30
|
* CloudFormation carries out itself is recorded as inert instead, because this
|
|
30
31
|
* message would otherwise tell a reader to bind a handler to a function that
|
|
31
32
|
* has nothing left to do.
|
|
33
|
+
*
|
|
34
|
+
* The refusal carries the function's name, which the properties parser has
|
|
35
|
+
* already worked out by the time this gate is asked. CDK writes a function's
|
|
36
|
+
* log group name as `/aws/lambda/` joined to a Ref of the function. A refusal
|
|
37
|
+
* carrying no name puts the log group under `/aws/lambda/` and a logical ID,
|
|
38
|
+
* a name no account would hold.
|
|
32
39
|
*/
|
|
33
40
|
export class SimCfnLambdaRuntimeSkip {
|
|
34
41
|
/**
|
|
@@ -42,9 +49,9 @@ export class SimCfnLambdaRuntimeSkip {
|
|
|
42
49
|
runtimeName.startsWith(nodeJsRuntimePrefix)) {
|
|
43
50
|
return undefined;
|
|
44
51
|
}
|
|
45
|
-
return new
|
|
52
|
+
return new SimCfnNamedSkip(`Unsupported sim Lambda CloudFormation Resource ${resource.logicalId}: ` +
|
|
46
53
|
`sim Lambda simulates Node.js runtimes, and this function declares ` +
|
|
47
54
|
`Runtime ${runtimeName}. Bind a real in-process handler to this ` +
|
|
48
|
-
`function to simulate it
|
|
55
|
+
`function to simulate it.`, functionProperties.functionName);
|
|
49
56
|
}
|
|
50
57
|
}
|
|
@@ -34,10 +34,32 @@ export function makeSimRoute53HostedZoneId(existing = new Set()) {
|
|
|
34
34
|
export function normalizeSimRoute53HostedZoneId(value) {
|
|
35
35
|
const hostedZoneId = value?.replace(/^\/?hostedzone\//u, "");
|
|
36
36
|
if (!isSimRoute53HostedZoneId(hostedZoneId)) {
|
|
37
|
-
throw new SimRoute53InvalidInput(
|
|
37
|
+
throw new SimRoute53InvalidInput(rejectedSimRoute53HostedZoneId(value, hostedZoneId));
|
|
38
38
|
}
|
|
39
39
|
return hostedZoneId;
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* The literal CDK writes into a synthesized template when
|
|
43
|
+
* `HostedZone.fromLookup` runs against no credentials and no cached context.
|
|
44
|
+
*/
|
|
45
|
+
const cdkUnresolvedLookupHostedZoneId = "DUMMY";
|
|
46
|
+
/**
|
|
47
|
+
* The message for a Hosted Zone ID that fails the shape check.
|
|
48
|
+
*
|
|
49
|
+
* CDK writes its unresolved lookup stand-in into every RecordSet of the
|
|
50
|
+
* synthesized template, and that value arrives here looking like any other
|
|
51
|
+
* malformed ID. Reporting it as one sends the reader to check an ID they never
|
|
52
|
+
* wrote, so the stand-in is named and the synth that produced it is pointed at.
|
|
53
|
+
*/
|
|
54
|
+
function rejectedSimRoute53HostedZoneId(value, hostedZoneId) {
|
|
55
|
+
const reported = `Invalid Route53 Hosted Zone ID: ${value ?? "(missing)"}`;
|
|
56
|
+
if (hostedZoneId !== cdkUnresolvedLookupHostedZoneId) {
|
|
57
|
+
return reported;
|
|
58
|
+
}
|
|
59
|
+
return (`${reported}. CDK fills an unresolved \`HostedZone.fromLookup\` with this ` +
|
|
60
|
+
"stand-in. Run `cdk synth` with credentials for the account holding the " +
|
|
61
|
+
"zone, or commit the `cdk.context.json` a resolved lookup writes.");
|
|
62
|
+
}
|
|
41
63
|
/**
|
|
42
64
|
* Whether a value has the shape of a Route53 Hosted Zone ID.
|
|
43
65
|
*/
|
|
@@ -1055,8 +1055,15 @@ simulated CloudFront hostname, such as `e123example.cloudfront.net`.
|
|
|
1055
1055
|
#### Values from a skipped Resource
|
|
1056
1056
|
|
|
1057
1057
|
A Resource that was skipped, because its type is outside the simulation or because there is no
|
|
1058
|
-
simulated Resource to create at all, still answers both intrinsics.
|
|
1059
|
-
|
|
1058
|
+
simulated Resource to create at all, still answers both intrinsics.
|
|
1059
|
+
|
|
1060
|
+
What `Ref` answers with follows how far the refusal got. A service that had already worked out the
|
|
1061
|
+
Resource's name answers with that name. That is the name real CloudFormation would have produced,
|
|
1062
|
+
and the simulation holds it whether or not the Resource behind it was created. Sim Lambda declining
|
|
1063
|
+
a function on its `Runtime` is the case to know about, and it covers CDK's own Python providers. A
|
|
1064
|
+
Resource refused before any name existed answers with the logical ID.
|
|
1065
|
+
|
|
1066
|
+
`Fn::GetAtt` answers with the string `<logical ID>.<attribute name>` for either of them.
|
|
1060
1067
|
|
|
1061
1068
|
```typescript sim-cloudformation-skipped-resource-values
|
|
1062
1069
|
/**
|
|
@@ -1071,28 +1078,31 @@ const stack = await simAws.cloudFormation().deployTemplate({
|
|
|
1071
1078
|
stackName: "stand-in-stack",
|
|
1072
1079
|
template: {
|
|
1073
1080
|
Resources: {
|
|
1074
|
-
|
|
1075
|
-
Type: "AWS::
|
|
1081
|
+
SearchApi: {
|
|
1082
|
+
Type: "AWS::AppSync::GraphQLApi",
|
|
1083
|
+
Properties: {
|
|
1084
|
+
Name: "search",
|
|
1085
|
+
},
|
|
1076
1086
|
},
|
|
1077
1087
|
},
|
|
1078
1088
|
Outputs: {
|
|
1079
|
-
|
|
1080
|
-
|
|
1089
|
+
SearchApiRef: { Value: { Ref: "SearchApi" } },
|
|
1090
|
+
SearchApiArn: { Value: { "Fn::GetAtt": ["SearchApi", "Arn"] } },
|
|
1081
1091
|
},
|
|
1082
1092
|
},
|
|
1083
1093
|
});
|
|
1084
1094
|
|
|
1085
1095
|
await stack.waitForDeployComplete();
|
|
1086
1096
|
|
|
1087
|
-
console.log(stack.output("
|
|
1088
|
-
// "
|
|
1097
|
+
console.log(stack.output("SearchApiRef"));
|
|
1098
|
+
// "SearchApi"
|
|
1089
1099
|
|
|
1090
|
-
console.log(stack.output("
|
|
1091
|
-
// "
|
|
1100
|
+
console.log(stack.output("SearchApiArn"));
|
|
1101
|
+
// "SearchApi.Arn"
|
|
1092
1102
|
|
|
1093
1103
|
for (const skipped of stack.skippedResources) {
|
|
1094
1104
|
console.log(skipped.logicalId, skipped.skippedReason);
|
|
1095
|
-
// "
|
|
1105
|
+
// "SearchApi Unsupported sim CloudFormation Resource service AppSync"
|
|
1096
1106
|
}
|
|
1097
1107
|
```
|
|
1098
1108
|
|
|
@@ -1101,7 +1111,7 @@ every Resource holding a `Ref` or `Fn::GetAtt` to a skipped Resource would fail
|
|
|
1101
1111
|
every Resource depending on those, until one EventBridge rule took the whole stack down with it. The skip
|
|
1102
1112
|
stays where it happened.
|
|
1103
1113
|
|
|
1104
|
-
A
|
|
1114
|
+
A logical ID is deliberately shaped unlike an ARN. It fails closed wherever the simulator reads it.
|
|
1105
1115
|
|
|
1106
1116
|
- In an IAM policy `Resource` it matches no ARN, so a caller relying on that statement is denied.
|
|
1107
1117
|
- In a property that is parsed as an ARN it is refused as malformed, and that Resource fails.
|
|
@@ -1112,9 +1122,58 @@ A stand-in is deliberately shaped unlike an ARN. It fails closed wherever the si
|
|
|
1112
1122
|
SDK call fails the way a call for a missing resource does. A `PutItem` naming the skipped table
|
|
1113
1123
|
gets `ResourceNotFoundException: No DynamoDB Table named Orders`.
|
|
1114
1124
|
|
|
1115
|
-
A
|
|
1116
|
-
|
|
1117
|
-
|
|
1125
|
+
A generated name behaves differently, and is meant to. It is the name an account would have held, so
|
|
1126
|
+
anything reading it reads a well-formed name. CDK writes a function's log group name as
|
|
1127
|
+
`/aws/lambda/` joined to a `Ref` of the function, and the log group is created under
|
|
1128
|
+
`/aws/lambda/<stack>-<logical ID>-<suffix>` even where the function was declined. A deploy Role
|
|
1129
|
+
scoped to `<stack>-*` and `/aws/lambda/<stack>-*` covers the deployment, the way it does on AWS.
|
|
1130
|
+
|
|
1131
|
+
```typescript sim-cloudformation-skipped-resource-generated-name
|
|
1132
|
+
/**
|
|
1133
|
+
* A function skipped on its Runtime, and the Log Group named after it.
|
|
1134
|
+
*/
|
|
1135
|
+
|
|
1136
|
+
import { SimAws } from "@kensio/yulin";
|
|
1137
|
+
|
|
1138
|
+
const simAws = new SimAws();
|
|
1139
|
+
|
|
1140
|
+
const stack = await simAws.cloudFormation().deployTemplate({
|
|
1141
|
+
stackName: "ReportStack",
|
|
1142
|
+
template: {
|
|
1143
|
+
Resources: {
|
|
1144
|
+
ReportFunction: {
|
|
1145
|
+
Type: "AWS::Lambda::Function",
|
|
1146
|
+
Properties: {
|
|
1147
|
+
Role: "arn:aws:iam::111111111111:role/ReportRole",
|
|
1148
|
+
Handler: "index.handler",
|
|
1149
|
+
Runtime: "python3.13",
|
|
1150
|
+
Code: { ZipFile: "def handler(event, context): return 'report'" },
|
|
1151
|
+
},
|
|
1152
|
+
},
|
|
1153
|
+
ReportFunctionLogGroup: {
|
|
1154
|
+
Type: "AWS::Logs::LogGroup",
|
|
1155
|
+
Properties: {
|
|
1156
|
+
LogGroupName: {
|
|
1157
|
+
"Fn::Join": ["", ["/aws/lambda/", { Ref: "ReportFunction" }]],
|
|
1158
|
+
},
|
|
1159
|
+
},
|
|
1160
|
+
},
|
|
1161
|
+
},
|
|
1162
|
+
},
|
|
1163
|
+
});
|
|
1164
|
+
|
|
1165
|
+
await stack.waitForDeployComplete();
|
|
1166
|
+
|
|
1167
|
+
console.log(stack.getResource("ReportFunction")?.refValue);
|
|
1168
|
+
// "ReportStack-ReportFunction-42d643ca8338"
|
|
1169
|
+
|
|
1170
|
+
console.log(simAws.logs().allLogGroups()[0]?.logGroupName);
|
|
1171
|
+
// "/aws/lambda/ReportStack-ReportFunction-42d643ca8338"
|
|
1172
|
+
```
|
|
1173
|
+
|
|
1174
|
+
The Resource is absent all the same, and the name is no evidence that it is there. Invoking the
|
|
1175
|
+
function above fails with `ResourceNotFoundException`. `stack.skippedResources` is where to find out
|
|
1176
|
+
which Resources were left out and why, under
|
|
1118
1177
|
[Inspecting stacks and resources](#inspecting-stacks-and-resources).
|
|
1119
1178
|
|
|
1120
1179
|
### `Fn::Join`
|
|
@@ -3976,8 +4035,11 @@ const stack = await simAws.cloudFormation().deployTemplate({
|
|
|
3976
4035
|
BucketName: "skipped-site-bucket",
|
|
3977
4036
|
},
|
|
3978
4037
|
},
|
|
3979
|
-
|
|
3980
|
-
Type: "AWS::
|
|
4038
|
+
SearchApi: {
|
|
4039
|
+
Type: "AWS::AppSync::GraphQLApi",
|
|
4040
|
+
Properties: {
|
|
4041
|
+
Name: "search",
|
|
4042
|
+
},
|
|
3981
4043
|
},
|
|
3982
4044
|
},
|
|
3983
4045
|
},
|
|
@@ -3986,10 +4048,10 @@ const stack = await simAws.cloudFormation().deployTemplate({
|
|
|
3986
4048
|
await stack.waitForDeployComplete();
|
|
3987
4049
|
|
|
3988
4050
|
console.log(stack.skippedResources.map((resource) => resource.logicalId));
|
|
3989
|
-
// ["
|
|
4051
|
+
// ["SearchApi"]
|
|
3990
4052
|
|
|
3991
|
-
console.log(stack.getResource("
|
|
3992
|
-
// "Unsupported sim CloudFormation Resource service
|
|
4053
|
+
console.log(stack.getResource("SearchApi")?.skippedReason);
|
|
4054
|
+
// "Unsupported sim CloudFormation Resource service AppSync"
|
|
3993
4055
|
```
|
|
3994
4056
|
|
|
3995
4057
|
A skipped Resource is still there for `stack.getResource(...)`, and still answers `Ref` and `Fn::GetAtt` with
|
|
@@ -4033,8 +4095,11 @@ const stack = await simAws.cloudFormation().deployTemplate({
|
|
|
4033
4095
|
Description: "/opt/awscli/aws",
|
|
4034
4096
|
},
|
|
4035
4097
|
},
|
|
4036
|
-
|
|
4037
|
-
Type: "AWS::
|
|
4098
|
+
SearchApi: {
|
|
4099
|
+
Type: "AWS::AppSync::GraphQLApi",
|
|
4100
|
+
Properties: {
|
|
4101
|
+
Name: "search",
|
|
4102
|
+
},
|
|
4038
4103
|
},
|
|
4039
4104
|
},
|
|
4040
4105
|
},
|
|
@@ -4043,7 +4108,7 @@ const stack = await simAws.cloudFormation().deployTemplate({
|
|
|
4043
4108
|
await stack.waitForDeployComplete();
|
|
4044
4109
|
|
|
4045
4110
|
console.log(stack.skippedResources.map((resource) => resource.logicalId));
|
|
4046
|
-
// ["
|
|
4111
|
+
// ["SearchApi"]
|
|
4047
4112
|
|
|
4048
4113
|
console.log(stack.inertResources.map((resource) => resource.logicalId));
|
|
4049
4114
|
// ["AwsCliLayer"]
|
|
@@ -259,6 +259,11 @@ with a 200 for a URL the Bucket has no object for. It is one of the same error c
|
|
|
259
259
|
CloudFront allows. Where the response page is itself missing, the viewer gets the status from
|
|
260
260
|
fetching it, as in CloudFront.
|
|
261
261
|
|
|
262
|
+
Behind an origin access control, a response page the Bucket does not hold answers 403 where a public
|
|
263
|
+
Bucket answers 404. The Bucket policy an origin access control is written with grants `s3:GetObject`
|
|
264
|
+
and no `s3:ListBucket`, and S3 refuses a key whose absence it may not report. A test whose Bucket
|
|
265
|
+
was never loaded with the site's error page sees that refusal in place of the page.
|
|
266
|
+
|
|
262
267
|
A viewer-response function never sees a custom error page. CloudFront runs no viewer-response
|
|
263
268
|
function once the Origin has answered 400 or higher, and simulated CloudFront does the same, for a
|
|
264
269
|
CloudFront Function and a Lambda@Edge function alike. The status the Origin returned is what decides
|
|
@@ -3781,6 +3786,110 @@ A CDK stack needs no hand-editing.
|
|
|
3781
3786
|
other store. Handing back an empty store would let a Function that lost its association run to
|
|
3782
3787
|
completion and quietly take every default.
|
|
3783
3788
|
|
|
3789
|
+
## Permissions
|
|
3790
|
+
|
|
3791
|
+
Every CloudFront command goes through simulated IAM. So does every CloudFront Resource a
|
|
3792
|
+
CloudFormation Stack creates, decided as the principal the deployment runs as. Stand a project's own
|
|
3793
|
+
execution policy up as a deploy Role and the Stack finds out what the policy leaves out. A
|
|
3794
|
+
deployment naming no principal is decided as the Account root.
|
|
3795
|
+
|
|
3796
|
+
The action is the `cloudfront:` name of the operation. A Distribution action is decided against
|
|
3797
|
+
`arn:aws:cloudfront::<account>:distribution/<id>`, a Function action against `function/<name>` and a
|
|
3798
|
+
key value store action against `key-value-store/<id>`. A create action has nothing to name until it
|
|
3799
|
+
succeeds and is decided against `*`. A policy granting one has to write the wildcard.
|
|
3800
|
+
|
|
3801
|
+
These are the actions the simulation asks about:
|
|
3802
|
+
|
|
3803
|
+
- `CreateDistribution`, `GetDistribution`, `UpdateDistribution` and `DeleteDistribution`
|
|
3804
|
+
- `CreateFunction`, `ListFunctions`, `DescribeFunction`, `GetFunction` and `DeleteFunction`
|
|
3805
|
+
- `CreateInvalidation`, `GetInvalidation` and `ListInvalidations`
|
|
3806
|
+
- `CreateKeyValueStore`, `ListKeyValueStores`, `DescribeKeyValueStore`, `UpdateKeyValueStore` and
|
|
3807
|
+
`DeleteKeyValueStore`, alongside the data API's own `cloudfront-keyvaluestore:` actions on the
|
|
3808
|
+
keys inside a store
|
|
3809
|
+
- `CreateCachePolicy` and `DeleteCachePolicy`
|
|
3810
|
+
- `CreateOriginRequestPolicy` and `DeleteOriginRequestPolicy`
|
|
3811
|
+
- `CreateResponseHeadersPolicy` and `DeleteResponseHeadersPolicy`
|
|
3812
|
+
- `CreateOriginAccessControl` and `DeleteOriginAccessControl`
|
|
3813
|
+
|
|
3814
|
+
The last four pairs reach IAM through CloudFormation alone, since a template is the only way to make
|
|
3815
|
+
one of those four here. Each delete is decided against the ARN of the thing it names, such as
|
|
3816
|
+
`arn:aws:cloudfront::<account>:cache-policy/<id>`.
|
|
3817
|
+
|
|
3818
|
+
```typescript sim-cloudfront-deploy-permissions
|
|
3819
|
+
/**
|
|
3820
|
+
* A deploy Role refused the cache policy its Stack declares.
|
|
3821
|
+
*/
|
|
3822
|
+
|
|
3823
|
+
import { SimAws } from "@kensio/yulin";
|
|
3824
|
+
|
|
3825
|
+
const simAws = new SimAws();
|
|
3826
|
+
|
|
3827
|
+
// A deploy Role allowed CloudFormation and S3, and no CloudFront action.
|
|
3828
|
+
const { Role } = await simAws.iam().createRole({
|
|
3829
|
+
input: {
|
|
3830
|
+
RoleName: "DeployRole",
|
|
3831
|
+
AssumeRolePolicyDocument: JSON.stringify({
|
|
3832
|
+
Version: "2012-10-17",
|
|
3833
|
+
Statement: [
|
|
3834
|
+
{
|
|
3835
|
+
Effect: "Allow",
|
|
3836
|
+
Principal: { Service: "cloudformation.amazonaws.com" },
|
|
3837
|
+
Action: "sts:AssumeRole",
|
|
3838
|
+
},
|
|
3839
|
+
],
|
|
3840
|
+
}),
|
|
3841
|
+
},
|
|
3842
|
+
});
|
|
3843
|
+
|
|
3844
|
+
await simAws.iam().putRolePolicy({
|
|
3845
|
+
input: {
|
|
3846
|
+
RoleName: "DeployRole",
|
|
3847
|
+
PolicyName: "DeployPolicy",
|
|
3848
|
+
PolicyDocument: JSON.stringify({
|
|
3849
|
+
Version: "2012-10-17",
|
|
3850
|
+
Statement: [
|
|
3851
|
+
{
|
|
3852
|
+
Effect: "Allow",
|
|
3853
|
+
Action: ["cloudformation:*", "s3:*"],
|
|
3854
|
+
Resource: "*",
|
|
3855
|
+
},
|
|
3856
|
+
],
|
|
3857
|
+
}),
|
|
3858
|
+
},
|
|
3859
|
+
});
|
|
3860
|
+
|
|
3861
|
+
try {
|
|
3862
|
+
await simAws.cloudFormation().deployTemplate({
|
|
3863
|
+
stackName: "site-stack",
|
|
3864
|
+
caller: { kind: "arn", arn: Role.Arn },
|
|
3865
|
+
template: {
|
|
3866
|
+
Resources: {
|
|
3867
|
+
SiteCachePolicy: {
|
|
3868
|
+
Type: "AWS::CloudFront::CachePolicy",
|
|
3869
|
+
Properties: {
|
|
3870
|
+
CachePolicyConfig: {
|
|
3871
|
+
Name: "site-caching",
|
|
3872
|
+
MinTTL: 0,
|
|
3873
|
+
ParametersInCacheKeyAndForwardedToOrigin: {
|
|
3874
|
+
EnableAcceptEncodingGzip: false,
|
|
3875
|
+
CookiesConfig: { CookieBehavior: "none" },
|
|
3876
|
+
HeadersConfig: { HeaderBehavior: "none" },
|
|
3877
|
+
QueryStringsConfig: { QueryStringBehavior: "none" },
|
|
3878
|
+
},
|
|
3879
|
+
},
|
|
3880
|
+
},
|
|
3881
|
+
},
|
|
3882
|
+
},
|
|
3883
|
+
},
|
|
3884
|
+
});
|
|
3885
|
+
} catch (error) {
|
|
3886
|
+
// Sim CloudFormation Resource SiteCachePolicy creation failed: User:
|
|
3887
|
+
// arn:aws:iam::...:role/DeployRole is not authorized to perform:
|
|
3888
|
+
// cloudfront:CreateCachePolicy on resource: *
|
|
3889
|
+
console.log((error as Error).message);
|
|
3890
|
+
}
|
|
3891
|
+
```
|
|
3892
|
+
|
|
3784
3893
|
## Available functionality
|
|
3785
3894
|
|
|
3786
3895
|
Sim CloudFront currently supports:
|