@kensio/yulin 1.20.15 → 1.20.16

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 (95) hide show
  1. package/README.md +13 -0
  2. package/dist/sdk/module/sim-sdk-module-client-interceptor.d.ts +5 -0
  3. package/dist/sdk/module/sim-sdk-module-client-interceptor.js +14 -0
  4. package/dist/sdk/module/sim-sdk-static-client-factory.d.ts +26 -0
  5. package/dist/sdk/module/sim-sdk-static-client-factory.js +59 -0
  6. package/dist/sdk/send-patch.d.ts +8 -0
  7. package/dist/sdk/send-patch.js +11 -0
  8. package/dist/service/aws/caller/sim-aws-caller-resolver.d.ts +28 -6
  9. package/dist/service/aws/caller/sim-aws-caller-resolver.js +18 -11
  10. package/dist/service/aws/caller/sim-aws-caller.d.ts +9 -0
  11. package/dist/service/aws/caller/sim-aws-resolved-caller.d.ts +19 -0
  12. package/dist/service/aws/caller/sim-aws-resolved-caller.js +26 -0
  13. package/dist/service/aws/factory/sim-aws-account-region-service-builder.d.ts +6 -0
  14. package/dist/service/aws/factory/sim-aws-account-region-service-builder.js +3 -0
  15. package/dist/service/aws/factory/sim-aws-account-service-cache.d.ts +6 -0
  16. package/dist/service/aws/factory/sim-aws-account-service-cache.js +5 -0
  17. package/dist/service/aws/factory/sim-aws-service-factory.d.ts +5 -0
  18. package/dist/service/aws/factory/sim-aws-service-factory.js +6 -0
  19. package/dist/service/aws/sim-aws-properties.d.ts +15 -0
  20. package/dist/service/aws/sim-aws.js +2 -1
  21. package/dist/service/iam/authorize/context/sim-iam-auth-z-caller-context-builder.d.ts +30 -7
  22. package/dist/service/iam/authorize/context/sim-iam-auth-z-caller-context-builder.js +20 -11
  23. package/dist/service/iam/authorize/context/sim-iam-auth-z-context-builder.d.ts +2 -1
  24. package/dist/service/iam/authorize/context/sim-iam-auth-z-context-builder.js +5 -1
  25. package/dist/service/iam/authorize/match/sim-iam-policy-principal-matcher.d.ts +10 -0
  26. package/dist/service/iam/authorize/match/sim-iam-policy-principal-matcher.js +15 -3
  27. package/dist/service/iam/authorize/sim-iam-account-auth-z.d.ts +11 -4
  28. package/dist/service/iam/authorize/sim-iam-account-auth-z.js +5 -3
  29. package/dist/service/iam/authorize/sim-iam-authorizer.d.ts +2 -1
  30. package/dist/service/iam/authorize/sim-iam-authorizer.js +1 -0
  31. package/dist/service/iam/role/sim-iam-role-with-policy.factory.d.ts +9 -0
  32. package/dist/service/iam/role/sim-iam-role-with-policy.factory.js +4 -2
  33. package/dist/service/iam/sim-iam-account-parts.d.ts +8 -0
  34. package/dist/service/iam/sim-iam-account-parts.js +1 -0
  35. package/dist/service/sts/auth-z/assume-role-auth-z-coordinator.d.ts +9 -2
  36. package/dist/service/sts/auth-z/assume-role-auth-z-coordinator.js +2 -2
  37. package/dist/service/sts/auth-z/assume-role-source-account-auth-z.d.ts +10 -4
  38. package/dist/service/sts/auth-z/assume-role-source-account-auth-z.js +8 -7
  39. package/dist/service/sts/auth-z/assume-role-target-auth-z.d.ts +2 -2
  40. package/dist/service/sts/auth-z/assume-role-trust-policy-authorizer.d.ts +6 -2
  41. package/dist/service/sts/auth-z/assume-role-trust-policy-authorizer.js +14 -11
  42. package/dist/service/sts/command/assume-role/assume-role.handler.d.ts +5 -1
  43. package/dist/service/sts/command/assume-role/assume-role.handler.js +5 -2
  44. package/dist/service/sts/command/get-caller-identity/get-caller-identity.handler.d.ts +5 -1
  45. package/dist/service/sts/command/get-caller-identity/get-caller-identity.handler.js +4 -1
  46. package/dist/service/sts/service-role/sim-service-role.js +5 -1
  47. package/dist/service/sts/sim-sts.d.ts +9 -1
  48. package/dist/service/sts/sim-sts.js +4 -0
  49. package/docs/README.md +54 -0
  50. package/docs/ai-skill/README.md +69 -0
  51. package/docs/cli/README.md +284 -0
  52. package/docs/factories/README.md +140 -0
  53. package/docs/lint/README.md +176 -0
  54. package/docs/non-aws-dependencies/README.md +261 -0
  55. package/docs/sdk/README.md +272 -0
  56. package/docs/serve/README.md +1126 -0
  57. package/docs/services/acm/README.md +823 -0
  58. package/docs/services/apigateway/README.md +1919 -0
  59. package/docs/services/apigatewayv2/README.md +2678 -0
  60. package/docs/services/athena/README.md +1044 -0
  61. package/docs/services/bedrock/README.md +350 -0
  62. package/docs/services/cloudformation/README.md +3440 -0
  63. package/docs/services/cloudfront/README.md +2874 -0
  64. package/docs/services/cloudwatch/README.md +420 -0
  65. package/docs/services/cognito/README.md +4923 -0
  66. package/docs/services/dynamodb/README.md +3784 -0
  67. package/docs/services/ecr/README.md +302 -0
  68. package/docs/services/ecs/README.md +2010 -0
  69. package/docs/services/elbv2/README.md +1897 -0
  70. package/docs/services/eventbridge/README.md +1110 -0
  71. package/docs/services/firehose/README.md +805 -0
  72. package/docs/services/glue/README.md +501 -0
  73. package/docs/services/iam/README.md +1283 -0
  74. package/docs/services/kinesis/README.md +454 -0
  75. package/docs/services/kms/README.md +659 -0
  76. package/docs/services/lambda/README.md +3645 -0
  77. package/docs/services/logs/README.md +868 -0
  78. package/docs/services/organizations/README.md +574 -0
  79. package/docs/services/personalize/README.md +835 -0
  80. package/docs/services/rekognition/README.md +1070 -0
  81. package/docs/services/route53/README.md +1783 -0
  82. package/docs/services/s3/README.md +3024 -0
  83. package/docs/services/scheduler/README.md +806 -0
  84. package/docs/services/secretsmanager/README.md +667 -0
  85. package/docs/services/ses/README.md +1011 -0
  86. package/docs/services/sns/README.md +1904 -0
  87. package/docs/services/sqs/README.md +1185 -0
  88. package/docs/services/ssm/README.md +1072 -0
  89. package/docs/services/stepfunctions/README.md +1332 -0
  90. package/docs/services/sts/README.md +245 -0
  91. package/docs/services/wafv2/README.md +1378 -0
  92. package/docs/terraform/README.md +248 -0
  93. package/docs/time/README.md +284 -0
  94. package/llms.txt +59 -0
  95. package/package.json +7 -4
@@ -0,0 +1,1072 @@
1
+ # Simulated SSM Parameter Store
2
+
3
+ Yulin includes a simulated AWS Systems Manager Parameter Store for tests and local development.
4
+ Parameters are stored in memory, versioned on every write, and every operation is authorized by
5
+ simulated IAM.
6
+
7
+ Only Parameter Store is simulated.
8
+
9
+ SSM-specific types are imported from the `@kensio/yulin/ssm` subpath.
10
+
11
+ ## Writing and reading a parameter
12
+
13
+ `PutParameter` needs a `Type` when the parameter is new. `GetParameter` returns the current version.
14
+
15
+ ```typescript sim-ssm-put-and-get
16
+ /**
17
+ * Writing a simulated parameter and reading it back.
18
+ */
19
+
20
+ import { GetParameterCommand, PutParameterCommand } from "@aws-sdk/client-ssm";
21
+
22
+ import { SimAws } from "@kensio/yulin";
23
+
24
+ const simAws = new SimAws();
25
+ const ssm = simAws.ssm();
26
+
27
+ await ssm.putParameter(
28
+ new PutParameterCommand({
29
+ Name: "/myapp/prod/db-host",
30
+ Type: "String",
31
+ Value: "db.internal",
32
+ }),
33
+ );
34
+
35
+ const read = await ssm.getParameter(
36
+ new GetParameterCommand({ Name: "/myapp/prod/db-host" }),
37
+ );
38
+
39
+ console.log(read.Parameter?.Value); // "db.internal"
40
+ console.log(read.Parameter?.Version); // 1
41
+ ```
42
+
43
+ A parameter written without a leading slash and one read with it are the same parameter, because
44
+ both name the same ARN.
45
+
46
+ ## Parameter ARNs and IAM policies
47
+
48
+ A parameter ARN drops the leading slash from the name. `/myapp/prod/db-host` becomes
49
+ `arn:aws:ssm:eu-west-2:111111111111:parameter/myapp/prod/db-host`, with one slash after `parameter`
50
+ rather than two. A policy written with the doubled slash matches nothing.
51
+
52
+ ```typescript sim-ssm-iam-policy
53
+ /**
54
+ * A simulated IAM policy allowing a Role to read one parameter.
55
+ */
56
+
57
+ import { CreateRoleCommand, PutRolePolicyCommand } from "@aws-sdk/client-iam";
58
+ import { GetParameterCommand, PutParameterCommand } from "@aws-sdk/client-ssm";
59
+
60
+ import { SimAws } from "@kensio/yulin";
61
+
62
+ const simAws = new SimAws();
63
+ const accountId = simAws.defaultAccountId;
64
+ const regionName = simAws.defaultRegionName;
65
+
66
+ const role = await simAws.iam().createRole(
67
+ new CreateRoleCommand({
68
+ RoleName: "ConfigReader",
69
+ AssumeRolePolicyDocument: JSON.stringify({
70
+ Version: "2012-10-17",
71
+ Statement: {
72
+ Effect: "Allow",
73
+ Principal: { AWS: `arn:aws:iam::${accountId}:root` },
74
+ Action: "sts:AssumeRole",
75
+ },
76
+ }),
77
+ }),
78
+ );
79
+
80
+ await simAws.iam().putRolePolicy(
81
+ new PutRolePolicyCommand({
82
+ RoleName: "ConfigReader",
83
+ PolicyName: "ReadDbHost",
84
+ PolicyDocument: JSON.stringify({
85
+ Version: "2012-10-17",
86
+ Statement: {
87
+ Effect: "Allow",
88
+ Action: "ssm:GetParameter",
89
+ // One slash after `parameter`, not two, whatever the name looks like.
90
+ Resource: `arn:aws:ssm:${regionName}:${accountId}:parameter/myapp/prod/db-host`,
91
+ },
92
+ }),
93
+ }),
94
+ );
95
+
96
+ await simAws.ssm().putParameter(
97
+ new PutParameterCommand({
98
+ Name: "/myapp/prod/db-host",
99
+ Type: "String",
100
+ Value: "db.internal",
101
+ }),
102
+ );
103
+
104
+ const read = await simAws
105
+ .ssm()
106
+ .getParameter(new GetParameterCommand({ Name: "/myapp/prod/db-host" }), {
107
+ caller: { kind: "arn", arn: role.Role.Arn },
108
+ });
109
+
110
+ console.log(read.Parameter?.Value); // "db.internal"
111
+ ```
112
+
113
+ `DescribeParameters` is the exception. Real Parameter Store gives that action no resource-level
114
+ permissions. It authorizes against `*` here, and a policy naming individual parameter ARNs grants
115
+ nothing.
116
+
117
+ `GetParametersByPath` authorizes against the path rather than against each parameter it returns.
118
+ Access to a path is access to everything under it. A recursive listing of `/myapp` returns
119
+ `/myapp/prod/db-host` even where a policy explicitly denies that parameter.
120
+
121
+ ## Versions
122
+
123
+ Every write makes a new version. `PutParameter` refuses a name that is already taken unless the
124
+ request sets `Overwrite`, and an earlier version stays readable by number.
125
+
126
+ ```typescript sim-ssm-parameter-versions
127
+ /**
128
+ * Overwriting a simulated parameter and reading an earlier version.
129
+ */
130
+
131
+ import { GetParameterCommand, PutParameterCommand } from "@aws-sdk/client-ssm";
132
+
133
+ import { SimAws } from "@kensio/yulin";
134
+
135
+ const simAws = new SimAws();
136
+ const ssm = simAws.ssm();
137
+
138
+ await ssm.putParameter(
139
+ new PutParameterCommand({
140
+ Name: "/myapp/prod/db-host",
141
+ Type: "String",
142
+ Value: "db.internal",
143
+ }),
144
+ );
145
+
146
+ const overwritten = await ssm.putParameter(
147
+ new PutParameterCommand({
148
+ Name: "/myapp/prod/db-host",
149
+ Value: "db2.internal",
150
+ Overwrite: true,
151
+ }),
152
+ );
153
+
154
+ console.log(overwritten.Version); // 2
155
+
156
+ const first = await ssm.getParameter(
157
+ new GetParameterCommand({ Name: "/myapp/prod/db-host:1" }),
158
+ );
159
+
160
+ console.log(first.Parameter?.Value); // "db.internal"
161
+ console.log(first.Parameter?.Selector); // ":1"
162
+ ```
163
+
164
+ A parameter's type cannot change. Overwriting a `String` parameter as a `StringList` fails with
165
+ `HierarchyTypeMismatchException`, as it does on real AWS. Delete the parameter and create a new one
166
+ instead.
167
+
168
+ ## Reading a hierarchy
169
+
170
+ `GetParametersByPath` reads a whole level of the hierarchy. Without `Recursive` it returns only the
171
+ level immediately below the path.
172
+
173
+ ```typescript sim-ssm-parameters-by-path
174
+ /**
175
+ * Reading a hierarchy of simulated parameters as application configuration.
176
+ */
177
+
178
+ import {
179
+ GetParametersByPathCommand,
180
+ PutParameterCommand,
181
+ } from "@aws-sdk/client-ssm";
182
+
183
+ import { SimAws } from "@kensio/yulin";
184
+
185
+ const simAws = new SimAws();
186
+ const ssm = simAws.ssm();
187
+
188
+ await ssm.putParameter(
189
+ new PutParameterCommand({
190
+ Name: "/myapp/prod/db-host",
191
+ Type: "String",
192
+ Value: "db.internal",
193
+ }),
194
+ );
195
+ await ssm.putParameter(
196
+ new PutParameterCommand({
197
+ Name: "/myapp/prod/db-port",
198
+ Type: "String",
199
+ Value: "5432",
200
+ }),
201
+ );
202
+ await ssm.putParameter(
203
+ new PutParameterCommand({
204
+ Name: "/myapp/test/db-host",
205
+ Type: "String",
206
+ Value: "db.test.internal",
207
+ }),
208
+ );
209
+
210
+ const listed = await ssm.getParametersByPath(
211
+ new GetParametersByPathCommand({ Path: "/myapp/prod" }),
212
+ );
213
+
214
+ console.log(listed.Parameters?.map((parameter) => parameter.Name));
215
+ // [ "/myapp/prod/db-host", "/myapp/prod/db-port" ]
216
+ ```
217
+
218
+ A page holds ten parameters, as it does on real AWS. Follow `NextToken` to read the rest.
219
+
220
+ ## Reading several parameters at once
221
+
222
+ `GetParameters` takes up to ten names. A name that resolves to nothing comes back in
223
+ `InvalidParameters`, and the request still succeeds. That is what makes a typo easy to miss.
224
+
225
+ ```typescript sim-ssm-get-parameters-batch
226
+ /**
227
+ * Reading several simulated parameters, including one name with a typo.
228
+ */
229
+
230
+ import { GetParametersCommand, PutParameterCommand } from "@aws-sdk/client-ssm";
231
+
232
+ import { SimAws } from "@kensio/yulin";
233
+
234
+ const simAws = new SimAws();
235
+ const ssm = simAws.ssm();
236
+
237
+ await ssm.putParameter(
238
+ new PutParameterCommand({
239
+ Name: "/myapp/prod/db-host",
240
+ Type: "String",
241
+ Value: "db.internal",
242
+ }),
243
+ );
244
+
245
+ const read = await ssm.getParameters(
246
+ new GetParametersCommand({
247
+ Names: ["/myapp/prod/db-host", "/myapp/prod/db-hostt"],
248
+ }),
249
+ );
250
+
251
+ console.log(read.Parameters?.map((parameter) => parameter.Name));
252
+ // [ "/myapp/prod/db-host" ]
253
+ console.log(read.InvalidParameters); // [ "/myapp/prod/db-hostt" ]
254
+ ```
255
+
256
+ `GetParameters` still authorizes each name. One name the caller may not read fails the whole
257
+ request.
258
+
259
+ ## String lists
260
+
261
+ A `StringList` value comes back as one comma-separated string, on read as well as on write, never
262
+ as an array.
263
+
264
+ ```typescript sim-ssm-string-list
265
+ /**
266
+ * Reading a simulated StringList parameter.
267
+ */
268
+
269
+ import { GetParameterCommand, PutParameterCommand } from "@aws-sdk/client-ssm";
270
+
271
+ import { SimAws } from "@kensio/yulin";
272
+
273
+ const simAws = new SimAws();
274
+ const ssm = simAws.ssm();
275
+
276
+ await ssm.putParameter(
277
+ new PutParameterCommand({
278
+ Name: "/myapp/prod/allowed-origins",
279
+ Type: "StringList",
280
+ Value: "https://one.example,https://two.example",
281
+ }),
282
+ );
283
+
284
+ const read = await ssm.getParameter(
285
+ new GetParameterCommand({ Name: "/myapp/prod/allowed-origins" }),
286
+ );
287
+
288
+ const origins = read.Parameter?.Value?.split(",") ?? [];
289
+
290
+ console.log(origins.length); // 2
291
+ ```
292
+
293
+ ## Parameter names
294
+
295
+ Name validation matches real Parameter Store, because a name it accepts and AWS refuses is a
296
+ deployment failure a passing test would have hidden. A name:
297
+
298
+ - may contain letters, digits, `_`, `.`, `-` and `/`
299
+ - must start with `/` if it contains a hierarchy at all
300
+ - may not have more than fifteen hierarchy levels
301
+ - may not start with `aws` or `ssm` in any case, which Parameter Store reserves
302
+ - may not contain spaces between characters, though surrounding spaces are stripped
303
+ - may not make an ARN longer than 1011 characters, counting the ARN prefix for the account and region
304
+
305
+ A `String` or `StringList` value holds at most 4KB, the standard tier limit. This is the one people
306
+ hit, usually by putting a whole JSON configuration blob in one parameter.
307
+
308
+ ## Deploying a parameter from CloudFormation
309
+
310
+ Simulated CloudFormation creates a parameter from an `AWS::SSM::Parameter` resource, in the stack's
311
+ account and region. The parameter is written through `PutParameter`. A template-created parameter is
312
+ the same thing an SDK caller would get, with the same name validation, the same ARN, and version 1.
313
+
314
+ `Ref` on the resource gives the parameter name, as it does on real AWS, and it can be handed straight
315
+ to `GetParameter`. `Fn::GetAtt … Type` and `Fn::GetAtt … Value` give those
316
+ properties.
317
+
318
+ ```typescript sim-ssm-cloudformation-parameter
319
+ /**
320
+ * Deploying a parameter from a CloudFormation template and reading it back.
321
+ */
322
+
323
+ import { GetParameterCommand } from "@aws-sdk/client-ssm";
324
+
325
+ import { SimAws } from "@kensio/yulin";
326
+
327
+ const simAws = new SimAws();
328
+
329
+ const stack = await simAws.cloudFormation().deployTemplate({
330
+ stackName: "config-stack",
331
+ template: {
332
+ Resources: {
333
+ DbHost: {
334
+ Type: "AWS::SSM::Parameter",
335
+ Properties: {
336
+ Name: "/myapp/prod/db-host",
337
+ Type: "String",
338
+ Value: "db.internal",
339
+ Description: "Where the application database lives",
340
+ },
341
+ },
342
+ },
343
+ Outputs: {
344
+ DbHostParameter: {
345
+ Value: { Ref: "DbHost" },
346
+ },
347
+ },
348
+ },
349
+ });
350
+
351
+ await stack.waitForDeployComplete();
352
+
353
+ // Ref resolves to the parameter name, so it works as a GetParameter Name.
354
+ const parameterName = stack.output("DbHostParameter");
355
+
356
+ const read = await simAws
357
+ .ssm()
358
+ .getParameter(new GetParameterCommand({ Name: parameterName }));
359
+
360
+ console.log(read.Parameter?.Value); // "db.internal"
361
+ console.log(read.Parameter?.Version); // 1
362
+ ```
363
+
364
+ A parameter with no `Name` is named after its logical ID. Real CloudFormation generates a name from
365
+ the stack name and its own random characters. A template cannot rely on the exact generated name
366
+ either way.
367
+
368
+ An IAM policy granting access to a template-created parameter needs the ARN rather than the `Ref`.
369
+ Build it with `Fn::Sub`, remembering that the ARN drops the name's leading slash:
370
+
371
+ ```yaml
372
+ Resource: !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/myapp/prod/db-host"
373
+ ```
374
+
375
+ CDK does this for you. `ssm.StringParameter` with `grantRead(fn)` synthesises a template that deploys
376
+ here without hand-editing.
377
+
378
+ ## Reading a parameter with a dynamic reference
379
+
380
+ A template reads a parameter that already exists through a `{{resolve:ssm:...}}` dynamic reference.
381
+ The reference is replaced with the parameter's value as the resource holding it is created.
382
+
383
+ `{{resolve:ssm:name}}` reads the current version, and `{{resolve:ssm:name:3}}` reads version 3. A
384
+ reference can sit inside a longer string, where only the reference itself is replaced.
385
+
386
+ ```typescript sim-ssm-dynamic-reference
387
+ /**
388
+ * Reading an existing parameter from a template with a dynamic reference.
389
+ */
390
+
391
+ import { GetParameterCommand, PutParameterCommand } from "@aws-sdk/client-ssm";
392
+
393
+ import { SimAws } from "@kensio/yulin";
394
+
395
+ const simAws = new SimAws();
396
+
397
+ await simAws.ssm().putParameter(
398
+ new PutParameterCommand({
399
+ Name: "/myapp/prod/db-host",
400
+ Type: "String",
401
+ Value: "db.internal",
402
+ }),
403
+ );
404
+
405
+ const stack = await simAws.cloudFormation().deployTemplate({
406
+ stackName: "app-stack",
407
+ template: {
408
+ Resources: {
409
+ DbUrl: {
410
+ Type: "AWS::SSM::Parameter",
411
+ Properties: {
412
+ Name: "/myapp/prod/db-url",
413
+ Type: "String",
414
+ Value: "postgres://{{resolve:ssm:/myapp/prod/db-host}}:5432/app",
415
+ },
416
+ },
417
+ },
418
+ },
419
+ });
420
+
421
+ await stack.waitForDeployComplete();
422
+
423
+ const read = await simAws
424
+ .ssm()
425
+ .getParameter(new GetParameterCommand({ Name: "/myapp/prod/db-url" }));
426
+
427
+ console.log(read.Parameter?.Value); // "postgres://db.internal:5432/app"
428
+ ```
429
+
430
+ CDK emits one of these from `ssm.StringParameter.valueForStringParameter` when a version is given,
431
+ and from `fromStringParameterAttributes` with `forceDynamicReference`.
432
+
433
+ A reference inside `Fn::Sub` is read after the variables around it are substituted, so
434
+ `!Sub "{{resolve:ssm:/myapp/${Environment}/db-host}}"` looks up the name the substitution produced.
435
+
436
+ A `StringList` parameter resolves to the comma-separated string Parameter Store holds, which
437
+ `Fn::Split` then splits.
438
+
439
+ Real CloudFormation makes no dependency out of a dynamic reference, and neither does this. A
440
+ parameter another resource of the same stack creates is only there in time when the template says
441
+ `DependsOn`.
442
+
443
+ The parameter is read with `GetParameter`, as the caller deploying the stack. A policy denying that
444
+ read fails the resource holding the reference, the way it fails a real deployment. A stack deployed
445
+ without a caller reads as the account root.
446
+
447
+ A `SecureString` parameter is read through `{{resolve:ssm-secure:...}}`, which the next section
448
+ covers. `{{resolve:secretsmanager:...}}` reads a secret, and
449
+ [simulated Secrets Manager](https://yulinsim.dev/services/secretsmanager/#reading-a-secret-with-a-dynamic-reference)
450
+ covers that one.
451
+
452
+ ### A reference the simulation cannot answer
453
+
454
+ Simulated CloudFormation deploys what it can. A reference naming a parameter that was never created
455
+ resolves to `dummy-value-for-<name>`, and the stack carries on deploying. A template reading
456
+ configuration a test does not care about is still worth deploying for everything else in it.
457
+
458
+ The substitution is recorded on
459
+ [`stack.ignoredProperties`](https://yulinsim.dev/services/cloudformation/#properties-a-resource-was-created-without),
460
+ naming the property that held the reference and why the value is a stand-in. A version the parameter
461
+ never had, a `SecureString`, and a body that is not a name and an optional integer version are all
462
+ recorded the same way.
463
+
464
+ ## Reading a SecureString with an ssm-secure reference
465
+
466
+ `{{resolve:ssm-secure:name}}` reads a `SecureString` parameter and resolves to its decrypted value.
467
+ `{{resolve:ssm-secure:name:3}}` reads version 3. The string scanning is the one plain `ssm`
468
+ references use, so a reference can sit inside a longer value and inside `Fn::Sub`.
469
+
470
+ Decryption goes to simulated KMS under the key the parameter was written with, as the caller
471
+ deploying the stack. A caller lacking `kms:Decrypt` on that key fails the resource with the
472
+ `AccessDenied` a decrypting `GetParameter` raises (see
473
+ [A customer managed key needs its own permission](#a-customer-managed-key-needs-its-own-permission)).
474
+
475
+ CDK writes one of these from `SecretValue.ssmSecure`, from
476
+ `ssm.StringParameter.fromSecureStringParameterAttributes` and from the deprecated
477
+ `valueForSecureStringParameter`.
478
+
479
+ ### Where a reference is accepted
480
+
481
+ Real CloudFormation reads an `ssm-secure` reference in eleven resource properties and refuses it in
482
+ every other one. Simulated CloudFormation holds a template to the same list.
483
+
484
+ | Resource | Property |
485
+ | -------------------------------------- | ------------------------------------------- |
486
+ | `AWS::DirectoryService::MicrosoftAD` | `Password` |
487
+ | `AWS::DirectoryService::SimpleAD` | `Password` |
488
+ | `AWS::ElastiCache::ReplicationGroup` | `AuthToken` |
489
+ | `AWS::IAM::User` | `LoginProfile.Password` |
490
+ | `AWS::KinesisFirehose::DeliveryStream` | `RedshiftDestinationConfiguration.Password` |
491
+ | `AWS::OpsWorks::App` | `Source.Password` |
492
+ | `AWS::OpsWorks::Stack` | `CustomCookbooksSource.Password` |
493
+ | `AWS::OpsWorks::Stack` | `RdsDbInstances.DbPassword` |
494
+ | `AWS::RDS::DBCluster` | `MasterUserPassword` |
495
+ | `AWS::RDS::DBInstance` | `MasterUserPassword` |
496
+ | `AWS::Redshift::Cluster` | `MasterUserPassword` |
497
+
498
+ A reference anywhere else fails the resource, naming the property that held it. A template breaking
499
+ this rule is broken on real CloudFormation too.
500
+
501
+ `AWS::IAM::User` `LoginProfile.Password` is the pair a simulated resource holds today, and it is
502
+ where CDK writes `SecretValue.ssmSecure`. The other ten name resource types this simulation has yet
503
+ to reach.
504
+
505
+ ```typescript sim-ssm-secure-dynamic-reference
506
+ /**
507
+ * Reading a SecureString parameter into a resource property from a template.
508
+ */
509
+
510
+ import { PutParameterCommand } from "@aws-sdk/client-ssm";
511
+
512
+ import { SimAws } from "@kensio/yulin";
513
+
514
+ const simAws = new SimAws();
515
+
516
+ await simAws.ssm().putParameter(
517
+ new PutParameterCommand({
518
+ Name: "/myapp/prod/console-password",
519
+ Type: "SecureString",
520
+ Value: "hunter2",
521
+ }),
522
+ );
523
+
524
+ const stack = await simAws.cloudFormation().deployTemplate({
525
+ stackName: "console-stack",
526
+ template: {
527
+ Resources: {
528
+ ConsoleUser: {
529
+ Type: "AWS::IAM::User",
530
+ Properties: {
531
+ UserName: "ConsoleUser",
532
+ LoginProfile: {
533
+ Password: "{{resolve:ssm-secure:/myapp/prod/console-password}}",
534
+ },
535
+ },
536
+ },
537
+ },
538
+ },
539
+ });
540
+
541
+ await stack.waitForDeployComplete();
542
+
543
+ const user = simAws
544
+ .iam()
545
+ .users.values()
546
+ .find((each) => each.userName === "ConsoleUser");
547
+
548
+ console.log(user?.loginProfile?.password); // "hunter2"
549
+ ```
550
+
551
+ A reference naming a `String` or a `StringList` parameter fails the resource, as real CloudFormation
552
+ fails it. Read a parameter stored in the clear with a plain `{{resolve:ssm:...}}` reference.
553
+
554
+ ### An ssm-secure reference the simulation cannot answer
555
+
556
+ The best-effort path is the one plain `ssm` references take. A reference naming a parameter that was
557
+ never created, or a version the parameter never had, resolves to `dummy-value-for-<name>` and the
558
+ stack carries on deploying. So does a body that is not a name and an optional integer version. Each
559
+ substitution is recorded on
560
+ [`stack.ignoredProperties`](https://yulinsim.dev/services/cloudformation/#properties-a-resource-was-created-without).
561
+
562
+ ## Reading a parameter through a template Parameter
563
+
564
+ A template `Parameters` entry declared as `AWS::SSM::Parameter::Value<String>` is given a parameter
565
+ name, and `Ref` on it gives the value held under that name in the stack's account and region. The
566
+ name comes from the value passed to `CreateStack`, and from the template `Default` when no value is
567
+ passed.
568
+
569
+ ```typescript sim-ssm-template-parameter-value
570
+ /**
571
+ * Reading configuration into a template through a Parameter Store value type.
572
+ */
573
+
574
+ import { PutParameterCommand } from "@aws-sdk/client-ssm";
575
+
576
+ import { SimAws } from "@kensio/yulin";
577
+
578
+ const simAws = new SimAws();
579
+
580
+ await simAws.ssm().putParameter(
581
+ new PutParameterCommand({
582
+ Name: "/myapp/prod/uploads-bucket",
583
+ Type: "String",
584
+ Value: "myapp-prod-uploads",
585
+ }),
586
+ );
587
+
588
+ const stack = await simAws.cloudFormation().deployTemplate({
589
+ stackName: "app-stack",
590
+ template: {
591
+ Parameters: {
592
+ UploadsBucketName: {
593
+ Type: "AWS::SSM::Parameter::Value<String>",
594
+ Default: "/myapp/prod/uploads-bucket",
595
+ },
596
+ },
597
+ Resources: {
598
+ UploadsBucket: {
599
+ Type: "AWS::S3::Bucket",
600
+ Properties: { BucketName: { Ref: "UploadsBucketName" } },
601
+ },
602
+ },
603
+ },
604
+ });
605
+
606
+ await stack.waitForDeployComplete();
607
+
608
+ // The Bucket was created under the name the parameter holds.
609
+ console.log(simAws.s3().getSimBucketByName("myapp-prod-uploads")?.bucketName);
610
+ // "myapp-prod-uploads"
611
+ ```
612
+
613
+ CDK emits this Parameter from `ssm.StringParameter.valueForStringParameter(scope, name)` called
614
+ without a version, carrying the name as the `Default`.
615
+
616
+ `AWS::SSM::Parameter::Value<List<String>>` resolves to the stored comma-separated string split into
617
+ a list, which `Fn::Select` then reads by index. `AWS::SSM::Parameter::Value<CommaDelimitedList>`
618
+ resolves the same way. `ssm.StringListParameter.fromListParameterAttributes` without a version emits
619
+ the first of the two.
620
+
621
+ The `Parameters` section is read before any resource is created, as it is on real AWS. A name a
622
+ resource of the same stack goes on to create is never there in time, whatever the template says
623
+ about `DependsOn`.
624
+
625
+ The stored value goes unchecked against the inner type. A name held against something other than an
626
+ image ID resolves under `AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>`, where real CloudFormation
627
+ refuses the stack.
628
+
629
+ ### A name the simulation cannot answer
630
+
631
+ The same best-effort answer a dynamic reference gets. A name Parameter Store has never held resolves
632
+ to `dummy-value-for-<name>`, and the stack carries on deploying. A `SecureString` resolves that way
633
+ too, since real CloudFormation refuses to read one into a template Parameter.
634
+
635
+ The substitution is recorded on
636
+ [`stack.ignoredProperties`](https://yulinsim.dev/services/cloudformation/#properties-a-resource-was-created-without).
637
+ The `logicalId` names the template Parameter, the `resourceType` gives its declared type, and the
638
+ `path` is `Parameters.<parameter name>`.
639
+
640
+ ## Reading configuration in a Lambda handler
641
+
642
+ Function code that reads its configuration on cold start needs no special treatment. Any
643
+ `@aws-sdk/client-ssm` client the handler creates is intercepted and dispatched with the function's
644
+ execution role as the caller. The role's policy decides whether the read succeeds.
645
+
646
+ ```typescript sim-ssm-lambda-handler-config
647
+ /**
648
+ * A simulated Lambda handler reading its configuration from Parameter Store.
649
+ */
650
+
651
+ import { CreateRoleCommand, PutRolePolicyCommand } from "@aws-sdk/client-iam";
652
+ import { CreateFunctionCommand, InvokeCommand } from "@aws-sdk/client-lambda";
653
+ import { PutParameterCommand } from "@aws-sdk/client-ssm";
654
+
655
+ import { SimAws } from "@kensio/yulin";
656
+ import { makeLambdaCodeZip } from "@kensio/yulin/lambda";
657
+
658
+ const simAws = new SimAws();
659
+ const accountId = simAws.defaultAccountId;
660
+ const regionName = simAws.defaultRegionName;
661
+
662
+ await simAws.ssm().putParameter(
663
+ new PutParameterCommand({
664
+ Name: "/myapp/prod/db-host",
665
+ Type: "String",
666
+ Value: "db.internal",
667
+ }),
668
+ );
669
+
670
+ const role = await simAws.iam().createRole(
671
+ new CreateRoleCommand({
672
+ RoleName: "ConfigReaderRole",
673
+ AssumeRolePolicyDocument: JSON.stringify({
674
+ Version: "2012-10-17",
675
+ Statement: {
676
+ Effect: "Allow",
677
+ Principal: { Service: "lambda.amazonaws.com" },
678
+ Action: "sts:AssumeRole",
679
+ },
680
+ }),
681
+ }),
682
+ );
683
+
684
+ await simAws.iam().putRolePolicy(
685
+ new PutRolePolicyCommand({
686
+ RoleName: "ConfigReaderRole",
687
+ PolicyName: "ReadConfig",
688
+ PolicyDocument: JSON.stringify({
689
+ Version: "2012-10-17",
690
+ Statement: {
691
+ Effect: "Allow",
692
+ Action: "ssm:GetParameter",
693
+ Resource: `arn:aws:ssm:${regionName}:${accountId}:parameter/myapp/prod/*`,
694
+ },
695
+ }),
696
+ }),
697
+ );
698
+
699
+ const handlerCode = [
700
+ 'const { SSMClient, GetParameterCommand } = require("@aws-sdk/client-ssm");',
701
+ "exports.handler = async () => {",
702
+ " const client = new SSMClient({});",
703
+ ' const command = new GetParameterCommand({ Name: "/myapp/prod/db-host" });',
704
+ " const out = await client.send(command);",
705
+ " return out.Parameter.Value;",
706
+ "};",
707
+ ].join("\n");
708
+
709
+ const zipFile = makeLambdaCodeZip({ "index.js": handlerCode });
710
+
711
+ await simAws.lambda().createFunction(
712
+ new CreateFunctionCommand({
713
+ FunctionName: "config-reader",
714
+ Role: role.Role.Arn,
715
+ Handler: "index.handler",
716
+ Code: { ZipFile: zipFile },
717
+ }),
718
+ );
719
+
720
+ await simAws.backgroundTasksComplete();
721
+
722
+ const invoked = await simAws
723
+ .lambda()
724
+ .invoke(new InvokeCommand({ FunctionName: "config-reader" }));
725
+
726
+ console.log(Buffer.from(invoked.Payload ?? []).toString("utf8")); // "db.internal"
727
+ ```
728
+
729
+ ## Account and region scoping
730
+
731
+ A parameter belongs to one account and region, as it does on real AWS. The same name in another
732
+ scope is another parameter.
733
+
734
+ ```typescript sim-ssm-scoping
735
+ /**
736
+ * The same simulated parameter name in two Account and Region scopes.
737
+ */
738
+
739
+ import { GetParameterCommand, PutParameterCommand } from "@aws-sdk/client-ssm";
740
+
741
+ import { SimAws } from "@kensio/yulin";
742
+
743
+ const simAws = new SimAws();
744
+
745
+ await simAws
746
+ .account("111111111111")
747
+ .region("eu-west-2")
748
+ .ssm()
749
+ .putParameter(
750
+ new PutParameterCommand({
751
+ Name: "/myapp/db-host",
752
+ Type: "String",
753
+ Value: "eu.db.internal",
754
+ }),
755
+ );
756
+
757
+ await simAws
758
+ .account("222222222222")
759
+ .region("us-east-1")
760
+ .ssm()
761
+ .putParameter(
762
+ new PutParameterCommand({
763
+ Name: "/myapp/db-host",
764
+ Type: "String",
765
+ Value: "us.db.internal",
766
+ }),
767
+ );
768
+
769
+ const read = await simAws
770
+ .account("111111111111")
771
+ .region("eu-west-2")
772
+ .ssm()
773
+ .getParameter(new GetParameterCommand({ Name: "/myapp/db-host" }));
774
+
775
+ console.log(read.Parameter?.ARN);
776
+ // "arn:aws:ssm:eu-west-2:111111111111:parameter/myapp/db-host"
777
+ ```
778
+
779
+ ## SecureString parameters
780
+
781
+ A `SecureString` value is encrypted through simulated KMS, under the `aws/ssm` AWS managed key unless
782
+ the request names a key of its own. Simulated KMS creates that managed key the first time something
783
+ asks for it, with no setup needed.
784
+
785
+ A read returns the ciphertext unless it asks for decryption. This is the mistake that is easy to make
786
+ and hard to see. A handler that forgets `WithDecryption` parses a base64 blob as if it were a
787
+ password.
788
+
789
+ ```typescript sim-ssm-secure-string
790
+ /**
791
+ * Writing and reading a simulated SecureString parameter.
792
+ */
793
+
794
+ import { GetParameterCommand, PutParameterCommand } from "@aws-sdk/client-ssm";
795
+
796
+ import { SimAws } from "@kensio/yulin";
797
+
798
+ const simAws = new SimAws();
799
+ const ssm = simAws.ssm();
800
+
801
+ await ssm.putParameter(
802
+ new PutParameterCommand({
803
+ Name: "/myapp/prod/db-password",
804
+ Type: "SecureString",
805
+ Value: "hunter2",
806
+ }),
807
+ );
808
+
809
+ const encrypted = await ssm.getParameter(
810
+ new GetParameterCommand({ Name: "/myapp/prod/db-password" }),
811
+ );
812
+
813
+ console.log(encrypted.Parameter?.Value); // a base64 ciphertext, not "hunter2"
814
+
815
+ const decrypted = await ssm.getParameter(
816
+ new GetParameterCommand({
817
+ Name: "/myapp/prod/db-password",
818
+ WithDecryption: true,
819
+ }),
820
+ );
821
+
822
+ console.log(decrypted.Parameter?.Value); // "hunter2"
823
+ ```
824
+
825
+ `WithDecryption` on a `String` or `StringList` parameter is ignored, as real Parameter Store ignores
826
+ it.
827
+
828
+ Pass `KeyId` to encrypt under a customer managed key instead. A `KeyId` naming a key that is absent,
829
+ disabled, or pending deletion fails with `InvalidKeyId`. That is how real Parameter Store reports
830
+ every KMS key problem.
831
+
832
+ Each value is bound to its own parameter's ARN as the KMS encryption context, under the
833
+ `PARAMETER_ARN` key. A ciphertext lifted out of one parameter cannot be decrypted as another.
834
+
835
+ ### A customer managed key needs its own permission
836
+
837
+ Encrypting and decrypting go to simulated KMS as the caller, not as the service. Under a customer
838
+ managed key a write needs `kms:Encrypt` on the key on top of `ssm:PutParameter` on the parameter, and
839
+ a decrypting read needs `kms:Decrypt` on top of `ssm:GetParameter`. A role granted one and not the
840
+ other fails here, ahead of a deployment.
841
+
842
+ ```typescript sim-ssm-secure-string-permissions
843
+ /**
844
+ * A Role allowed to read a simulated SecureString but not to decrypt it.
845
+ */
846
+
847
+ import { CreateRoleCommand, PutRolePolicyCommand } from "@aws-sdk/client-iam";
848
+ import { CreateKeyCommand } from "@aws-sdk/client-kms";
849
+ import { GetParameterCommand, PutParameterCommand } from "@aws-sdk/client-ssm";
850
+
851
+ import { SimAws } from "@kensio/yulin";
852
+
853
+ const simAws = new SimAws();
854
+ const accountId = simAws.defaultAccountId;
855
+
856
+ const key = await simAws
857
+ .kms()
858
+ .createKey(new CreateKeyCommand({ Description: "Parameter key" }));
859
+
860
+ await simAws.ssm().putParameter(
861
+ new PutParameterCommand({
862
+ Name: "/myapp/prod/db-password",
863
+ Type: "SecureString",
864
+ Value: "hunter2",
865
+ KeyId: key.KeyMetadata?.Arn,
866
+ }),
867
+ );
868
+
869
+ const role = await simAws.iam().createRole(
870
+ new CreateRoleCommand({
871
+ RoleName: "ConfigReader",
872
+ AssumeRolePolicyDocument: JSON.stringify({
873
+ Version: "2012-10-17",
874
+ Statement: {
875
+ Effect: "Allow",
876
+ Principal: { AWS: `arn:aws:iam::${accountId}:root` },
877
+ Action: "sts:AssumeRole",
878
+ },
879
+ }),
880
+ }),
881
+ );
882
+
883
+ // The parameter is allowed, the key is not.
884
+ await simAws.iam().putRolePolicy(
885
+ new PutRolePolicyCommand({
886
+ RoleName: "ConfigReader",
887
+ PolicyName: "ReadDbPassword",
888
+ PolicyDocument: JSON.stringify({
889
+ Version: "2012-10-17",
890
+ Statement: {
891
+ Effect: "Allow",
892
+ Action: "ssm:GetParameter",
893
+ Resource: "*",
894
+ },
895
+ }),
896
+ }),
897
+ );
898
+
899
+ const caller = { kind: "arn", arn: role.Role.Arn } as const;
900
+
901
+ try {
902
+ await simAws.ssm().getParameter(
903
+ new GetParameterCommand({
904
+ Name: "/myapp/prod/db-password",
905
+ WithDecryption: true,
906
+ }),
907
+ { caller },
908
+ );
909
+ } catch (error) {
910
+ console.log((error as Error).name); // "AccessDenied"
911
+ }
912
+ ```
913
+
914
+ ### The aws/ssm managed key needs no permission
915
+
916
+ A parameter naming no key is encrypted under the `aws/ssm` AWS managed key, and that key asks the
917
+ caller for nothing. Parameter Store supplies `kms:ViaService`, and the managed key's policy allows
918
+ the account's principals to use it through Systems Manager. A role holding only `ssm:GetParameter`
919
+ therefore reads the decrypted value, as it does on real AWS.
920
+
921
+ The same role cannot take the ciphertext to KMS itself, because that policy allows nothing to a
922
+ request arriving directly.
923
+
924
+ ```typescript sim-ssm-secure-string-managed-key
925
+ /**
926
+ * Reading a simulated SecureString under the aws/ssm managed key.
927
+ */
928
+
929
+ import { CreateRoleCommand, PutRolePolicyCommand } from "@aws-sdk/client-iam";
930
+ import { GetParameterCommand, PutParameterCommand } from "@aws-sdk/client-ssm";
931
+
932
+ import { SimAws } from "@kensio/yulin";
933
+
934
+ const simAws = new SimAws();
935
+ const accountId = simAws.defaultAccountId;
936
+
937
+ await simAws.ssm().putParameter(
938
+ new PutParameterCommand({
939
+ Name: "/myapp/prod/db-password",
940
+ Type: "SecureString",
941
+ Value: "hunter2",
942
+ }),
943
+ );
944
+
945
+ const role = await simAws.iam().createRole(
946
+ new CreateRoleCommand({
947
+ RoleName: "ConfigReader",
948
+ AssumeRolePolicyDocument: JSON.stringify({
949
+ Version: "2012-10-17",
950
+ Statement: {
951
+ Effect: "Allow",
952
+ Principal: { AWS: `arn:aws:iam::${accountId}:root` },
953
+ Action: "sts:AssumeRole",
954
+ },
955
+ }),
956
+ }),
957
+ );
958
+
959
+ // The parameter, and no KMS permission at all.
960
+ await simAws.iam().putRolePolicy(
961
+ new PutRolePolicyCommand({
962
+ RoleName: "ConfigReader",
963
+ PolicyName: "ReadDbPassword",
964
+ PolicyDocument: JSON.stringify({
965
+ Version: "2012-10-17",
966
+ Statement: {
967
+ Effect: "Allow",
968
+ Action: "ssm:GetParameter",
969
+ Resource: "*",
970
+ },
971
+ }),
972
+ }),
973
+ );
974
+
975
+ const read = await simAws.ssm().getParameter(
976
+ new GetParameterCommand({
977
+ Name: "/myapp/prod/db-password",
978
+ WithDecryption: true,
979
+ }),
980
+ { caller: { kind: "arn", arn: role.Role.Arn } },
981
+ );
982
+
983
+ console.log(read.Parameter?.Value); // "hunter2"
984
+ ```
985
+
986
+ `DescribeParameters` reports the key each `SecureString` is encrypted under as `KeyId`.
987
+
988
+ ## Available functionality
989
+
990
+ Sim SSM currently supports:
991
+
992
+ - `PutParameterCommand`, creating a parameter or overwriting one
993
+ - `GetParameterCommand`, by name, by `name:version` or by `name:label`
994
+ - `GetParametersCommand`, reporting names it could not resolve in `InvalidParameters`
995
+ - `GetParametersByPathCommand`, with and without `Recursive`
996
+ - `DeleteParameterCommand` and `DeleteParametersCommand`
997
+ - `DescribeParametersCommand`
998
+ - `String`, `StringList` and `SecureString` parameter types
999
+ - `SecureString` values encrypted through simulated KMS, decrypted only with `WithDecryption`
1000
+ - The `AWS::SSM::Parameter` CloudFormation resource, including `Ref` and `Fn::GetAtt`
1001
+ - `{{resolve:ssm:...}}` dynamic references in CloudFormation resource properties, by version or by
1002
+ current value, embedded in a longer string and inside `Fn::Sub`
1003
+ - `{{resolve:ssm-secure:...}}` dynamic references, decrypting the `SecureString` through the KMS key
1004
+ it was written under, in the eleven resource properties CloudFormation reads one in
1005
+ - `StringList` parameters read through a dynamic reference as the comma-separated string `Fn::Split`
1006
+ then splits
1007
+ - `AWS::SSM::Parameter::Value<String>` template parameters, resolving `Ref` to the stored value
1008
+ - `AWS::SSM::Parameter::Value<List<String>>` and `<CommaDelimitedList>` template parameters,
1009
+ resolving `Ref` to the stored string split into a list
1010
+ - Parameter name validation, including hierarchy depth and the reserved `aws` and `ssm` prefixes
1011
+ - Authorization of every operation by simulated IAM, against the real IAM action and ARN
1012
+ - Calls made from inside a simulated Lambda handler, authorized as the function's execution role
1013
+
1014
+ ## Limitations
1015
+
1016
+ Current documented limitations:
1017
+
1018
+ - Only standard tier `SecureString` encryption is simulated, which encrypts under the KMS key
1019
+ directly. The advanced tier's envelope encryption through the AWS Encryption SDK is left out, and
1020
+ `kms:GenerateDataKey` is never needed.
1021
+ - Parameter labels are left out. `LabelParameterVersion` is unimplemented, so a label can never be
1022
+ created, and a `name:label` selector is refused with an error saying so.
1023
+ - `GetParameterHistory` is absent, though earlier versions stay readable by number.
1024
+ - The advanced tier is left out. `Tier: Advanced` and `Tier: Intelligent-Tiering` are refused, and
1025
+ every parameter reports `Tier: Standard` with the 4KB standard tier value limit.
1026
+ - Parameter policies (expiration and notification) are left out. `Policies` is refused, and
1027
+ `DescribeParameters` always reports an empty `Policies` list.
1028
+ - Tags are left out. `Tags` on `PutParameter` is refused, and `AddTagsToResource`,
1029
+ `RemoveTagsFromResource` and `ListTagsForResource` are absent.
1030
+ - `AllowedPattern` is refused outright. Ignoring it would store a value it was meant to reject,
1031
+ without complaint.
1032
+ - `KeyId` on a `String` or `StringList` parameter is refused, since nothing would encrypt a value
1033
+ stored in the clear.
1034
+ - `DataType` other than `text` is refused. Real Parameter Store validates an `aws:ec2:image` value
1035
+ against EC2, which this simulation cannot do.
1036
+ - Filters are refused outright. `GetParametersByPath` refuses `ParameterFilters`, and
1037
+ `DescribeParameters` refuses `Filters` and `ParameterFilters`. Parameters are listed in name order.
1038
+ - `DescribeParameters` refuses `Shared`. Parameters cannot be shared between simulated accounts, and
1039
+ there is no resource policy support, so cross-account access to a parameter cannot be granted.
1040
+ - Every version is kept. Real Parameter Store keeps the hundred most recent versions and deletes the
1041
+ oldest as new ones are made, which can fail with `ParameterMaxVersionLimitExceeded`.
1042
+ - There is no per-account parameter count limit, so `ParameterLimitExceeded` never happens.
1043
+ - Deletion is immediate. Real Parameter Store asks for thirty seconds before a deleted name is
1044
+ reused, where here the name is free straight away.
1045
+ - `AWS::SSM::Parameter` supports `Name`, `Type`, `Value`, `Description` and `Tier`. `AllowedPattern`,
1046
+ `DataType`, `Policies` and `Tags` reach `PutParameter`, which refuses them for the reasons above.
1047
+ `Type: SecureString` is refused, as real CloudFormation refuses it for this resource type. The
1048
+ plaintext value would sit in the template.
1049
+ - The other `AWS::SSM::*` resource types (`Document`, `Association`, `MaintenanceWindow`,
1050
+ `PatchBaseline`, `ResourceDataSync` and the rest) are reported as unsupported and skipped.
1051
+ - Every deployment of an `AWS::SSM::Parameter` is a create. A name another stack already used is
1052
+ refused. A stack update that changes `Value` deletes the parameter and creates it again, where
1053
+ real CloudFormation overwrites it in place, so the parameter's version starts from 1 again.
1054
+ - A template parameter typed as `AWS::SSM::Parameter::Value<...>` is read once, while the
1055
+ `Parameters` section is. Real CloudFormation does the same, so a name that only exists once the
1056
+ stack has deployed resolves to a stand-in value here and fails the stack there.
1057
+ - The value a template parameter resolves to goes unvalidated against the inner type. The
1058
+ `AWS::EC2::*` and `AWS::Route53::HostedZone::Id` inner types name EC2 and Route53 resources, which
1059
+ this simulation holds none of.
1060
+ - A `{{resolve:ssm:...}}` or `{{resolve:ssm-secure:...}}` reference naming a parameter, or a
1061
+ version, that simulated Parameter Store has never held resolves to `dummy-value-for-<name>` and
1062
+ records the substitution. Real CloudFormation fails the stack. A template parameter naming one
1063
+ gets the same stand-in value.
1064
+ - An `ssm-secure` reference reads the stack's own account and region. A parameter in another account
1065
+ is out of reach on real CloudFormation as well.
1066
+ - Public parameters under `/aws/service/...` do not exist, and names under the reserved `aws` and
1067
+ `ssm` prefixes are refused, as they are on real AWS.
1068
+ - The Parameters and Secrets Lambda extension HTTP endpoint is absent. Handler code has to use the
1069
+ SDK.
1070
+ - Systems Manager is otherwise left out. Run Command, Session Manager, Patch Manager, State Manager,
1071
+ Automation, inventory and maintenance windows are all absent.
1072
+ - SSM is not served as an HTTP API by `serveSimAws`.