@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.
Files changed (63) hide show
  1. package/dist/service/cloudformation/resource/cfn/sim-cfn-default-resource-value-adapter.d.ts +8 -1
  2. package/dist/service/cloudformation/resource/cfn/sim-cfn-default-resource-value-adapter.js +9 -2
  3. package/dist/service/cloudformation/resource/cfn/sim-cfn-resource-value-adapter.d.ts +6 -1
  4. package/dist/service/cloudformation/resource/cfn/sim-cfn-resource-value-adapter.js +2 -1
  5. package/dist/service/cloudformation/resource/create/sim-cfn-resource-create-operation.d.ts +4 -0
  6. package/dist/service/cloudformation/resource/create/sim-cfn-resource-create-operation.js +6 -0
  7. package/dist/service/cloudformation/resource/sim-cfn-resource-record.d.ts +19 -0
  8. package/dist/service/cloudformation/resource/sim-cfn-resource-record.js +26 -0
  9. package/dist/service/cloudformation/resource/sim-cfn-resource.js +1 -0
  10. package/dist/service/cloudformation/resource/unsupported/sim-cfn-named-skip.d.ts +26 -0
  11. package/dist/service/cloudformation/resource/unsupported/sim-cfn-named-skip.js +31 -0
  12. package/dist/service/cloudfront/cfn/cache-policy/sim-cfn-cf-cache-policy-creator.d.ts +5 -2
  13. package/dist/service/cloudfront/cfn/cache-policy/sim-cfn-cf-cache-policy-creator.js +10 -2
  14. package/dist/service/cloudfront/cfn/origin-access-control/sim-cfn-cf-oac-creator.d.ts +5 -2
  15. package/dist/service/cloudfront/cfn/origin-access-control/sim-cfn-cf-oac-creator.js +10 -2
  16. package/dist/service/cloudfront/cfn/origin-request-policy/sim-cfn-cf-orp-creator.d.ts +5 -2
  17. package/dist/service/cloudfront/cfn/origin-request-policy/sim-cfn-cf-orp-creator.js +10 -2
  18. package/dist/service/cloudfront/cfn/policy/sim-cfn-cf-policy-creators.d.ts +33 -0
  19. package/dist/service/cloudfront/cfn/policy/sim-cfn-cf-policy-creators.js +39 -0
  20. package/dist/service/cloudfront/cfn/response-headers-policy/sim-cfn-cf-rh-policy-creator.d.ts +5 -2
  21. package/dist/service/cloudfront/cfn/response-headers-policy/sim-cfn-cf-rh-policy-creator.js +10 -2
  22. package/dist/service/cloudfront/cfn/sim-cfn-cf-authorizer.d.ts +40 -0
  23. package/dist/service/cloudfront/cfn/sim-cfn-cf-authorizer.js +50 -0
  24. package/dist/service/cloudfront/cfn/sim-cfn-cloudfront-resource-factory.d.ts +1 -4
  25. package/dist/service/cloudfront/cfn/sim-cfn-cloudfront-resource-factory.js +14 -46
  26. package/dist/service/cloudfront/sim-cloudfront-commands.d.ts +6 -0
  27. package/dist/service/cloudfront/sim-cloudfront-commands.js +7 -0
  28. package/dist/service/cloudfront/sim-cloudfront.d.ts +6 -0
  29. package/dist/service/cloudfront/sim-cloudfront.js +7 -0
  30. package/dist/service/dynamodb/document/sim-dynamodb-document-command-paths.d.ts +8 -0
  31. package/dist/service/dynamodb/document/sim-dynamodb-document-command-paths.js +62 -0
  32. package/dist/service/dynamodb/document/sim-dynamodb-document-routes.d.ts +1 -1
  33. package/dist/service/dynamodb/document/sim-dynamodb-document-routes.js +3 -1
  34. package/dist/service/iam/authorize/sim-iam-action-authorizer.d.ts +8 -3
  35. package/dist/service/iam/authorize/sim-iam-action-authorizer.js +13 -4
  36. package/dist/service/iam/cfn/role/sim-cfn-iam-role-creator.js +1 -0
  37. package/dist/service/iam/cfn/role/sim-cfn-iam-role-properties-parser.d.ts +1 -0
  38. package/dist/service/iam/cfn/role/sim-cfn-iam-role-properties-parser.js +21 -14
  39. package/dist/service/iam/cfn/{sim-cfn-iam-optional-string.d.ts → sim-cfn-iam-property.d.ts} +10 -2
  40. package/dist/service/iam/cfn/sim-cfn-iam-property.js +31 -0
  41. package/dist/service/iam/cfn/user/sim-cfn-iam-user-properties-parser.js +1 -1
  42. package/dist/service/iam/command/role/create-role/create-role-record-factory.js +3 -0
  43. package/dist/service/iam/command/role/create-role/create-role.command.d.ts +10 -0
  44. package/dist/service/iam/command/role/get-role/get-role.command.d.ts +2 -0
  45. package/dist/service/iam/command/role/get-role/get-role.handler.js +2 -0
  46. package/dist/service/iam/command/role/sim-iam-role-command-handlers.js +15 -1
  47. package/dist/service/iam/role/sim-iam-role-boundary.d.ts +24 -0
  48. package/dist/service/iam/role/sim-iam-role-boundary.js +24 -0
  49. package/dist/service/iam/role/sim-iam-role-with-policy.factory.d.ts +1 -0
  50. package/dist/service/iam/role/sim-iam-role.d.ts +10 -0
  51. package/dist/service/iam/serve/sim-iam-query-entity.d.ts +6 -1
  52. package/dist/service/iam/serve/sim-iam-query-entity.js +10 -4
  53. package/dist/service/iam/serve/sim-iam-query-role-operations.js +15 -3
  54. package/dist/service/lambda/cfn/function/sim-cfn-lambda-runtime-skip.d.ts +6 -0
  55. package/dist/service/lambda/cfn/function/sim-cfn-lambda-runtime-skip.js +9 -2
  56. package/dist/service/route53/command/create-hosted-zone/sim-route53-zone-id.js +23 -1
  57. package/docs/services/cloudformation/README.md +88 -23
  58. package/docs/services/cloudfront/README.md +109 -0
  59. package/docs/services/dynamodb/README.md +128 -8
  60. package/docs/services/iam/README.md +125 -2
  61. package/docs/services/route53/README.md +11 -0
  62. package/package.json +1 -1
  63. 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
- return isRecord(entity)
19
- ? xmlElement(name, queryMembers(entity, members))
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 Error(`Unsupported sim Lambda CloudFormation Resource ${resource.logicalId}: ` +
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(`Invalid Route53 Hosted Zone ID: ${value ?? "(missing)"}`);
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. `Ref` returns the logical ID, and
1059
- `Fn::GetAtt` returns the string `<logical ID>.<attribute name>`.
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
- AlarmRule: {
1075
- Type: "AWS::CloudWatch::Alarm",
1081
+ SearchApi: {
1082
+ Type: "AWS::AppSync::GraphQLApi",
1083
+ Properties: {
1084
+ Name: "search",
1085
+ },
1076
1086
  },
1077
1087
  },
1078
1088
  Outputs: {
1079
- AlarmRef: { Value: { Ref: "AlarmRule" } },
1080
- AlarmArn: { Value: { "Fn::GetAtt": ["AlarmRule", "Arn"] } },
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("AlarmRef"));
1088
- // "AlarmRule"
1097
+ console.log(stack.output("SearchApiRef"));
1098
+ // "SearchApi"
1089
1099
 
1090
- console.log(stack.output("AlarmArn"));
1091
- // "AlarmRule.Arn"
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
- // "AlarmRule Unsupported sim CloudFormation Resource service CloudWatch"
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 stand-in is deliberately shaped unlike an ARN. It fails closed wherever the simulator reads it.
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 stand-in stands in for something absent, and is never a value to rely on. A test asserting against
1116
- one is asserting on a Resource that was never created. `stack.skippedResources` is where to find out
1117
- which Resources those are and why, under
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
- AlarmRule: {
3980
- Type: "AWS::CloudWatch::Alarm",
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
- // ["AlarmRule"]
4051
+ // ["SearchApi"]
3990
4052
 
3991
- console.log(stack.getResource("AlarmRule")?.skippedReason);
3992
- // "Unsupported sim CloudFormation Resource service CloudWatch"
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
- AlarmRule: {
4037
- Type: "AWS::CloudWatch::Alarm",
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
- // ["AlarmRule"]
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: