@aws/nx-plugin-mcp 1.0.0-rc.76 → 1.0.0-rc.78
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/docs/get_started/tutorials/dungeon-game/1.mdx +8 -2
- package/docs/guides/connection/react-agui.mdx +19 -0
- package/docs/guides/py-agent.mdx +18 -1
- package/docs/guides/react-website-auth.mdx +36 -0
- package/docs/guides/react-website.mdx +317 -122
- package/docs/guides/ts-agent.mdx +18 -1
- package/docs/snippets/dynamodb/deploying-table.mdx +4 -25
- package/docs/snippets/dynamodb/encryption-options.mdx +168 -0
- package/package.json +1 -1
|
@@ -1072,11 +1072,17 @@ The `ts#website` generates these files. Let us examine some of the key files hig
|
|
|
1072
1072
|
// packages/common/constructs/src/app/static-websites/game-ui.ts
|
|
1073
1073
|
import * as url from 'url';
|
|
1074
1074
|
import { Construct } from 'constructs';
|
|
1075
|
-
import { StaticWebsite } from '../../core/index.js';
|
|
1075
|
+
import { StaticWebsite, StaticWebsiteProps } from '../../core/index.js';
|
|
1076
|
+
|
|
1077
|
+
export type GameUIProps = Omit<
|
|
1078
|
+
StaticWebsiteProps,
|
|
1079
|
+
'websiteName' | 'websiteFilePath'
|
|
1080
|
+
>;
|
|
1076
1081
|
|
|
1077
1082
|
export class GameUI extends StaticWebsite {
|
|
1078
|
-
constructor(scope: Construct, id: string) {
|
|
1083
|
+
constructor(scope: Construct, id: string, props?: GameUIProps) {
|
|
1079
1084
|
super(scope, id, {
|
|
1085
|
+
...props,
|
|
1080
1086
|
websiteName: 'GameUI',
|
|
1081
1087
|
websiteFilePath: url.fileURLToPath(
|
|
1082
1088
|
new URL(
|
|
@@ -92,6 +92,25 @@ The generated code handles authentication depending on your agent's configuratio
|
|
|
92
92
|
- **IAM** (default): uses AWS SigV4-signed HTTP requests. Credentials are obtained from the Cognito Identity Pool configured with your website's auth.
|
|
93
93
|
- **Cognito**: embeds the JWT access token in the `Authorization` header as a Bearer token.
|
|
94
94
|
|
|
95
|
+
### Sessions and Threads
|
|
96
|
+
|
|
97
|
+
AG-UI and AgentCore Runtime each identify a conversation differently, and the generated hook ties them together:
|
|
98
|
+
|
|
99
|
+
- **`threadId`** — the AG-UI conversation identifier, sent in the request body. CopilotKit generates a random UUID per chat unless you pass an explicit `threadId`.
|
|
100
|
+
- **Session ID** — the AgentCore Runtime session, sent in the `X-Amzn-Bedrock-AgentCore-Runtime-Session-Id` header. It selects the [microVM](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-how-it-works.html) serving the request, and is what your agent's `session.ts` / `session.py` keys conversation state on.
|
|
101
|
+
|
|
102
|
+
The hook derives the session ID from the thread ID, right-padding it to the 33 characters AgentCore Runtime requires:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
function agentCoreSessionId(input: RunAgentInput): string {
|
|
106
|
+
return (input.threadId ?? '').padEnd(33, '0');
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Leaving `threadId` unset is simplest — CopilotKit's generated UUID is already 36 characters. If you pass one explicitly, make it at least 33 characters, since padding maps thread IDs that differ only in trailing characters onto the same session.
|
|
111
|
+
|
|
112
|
+
Both Session ID and Thread ID are provided by the browser. To restrict each user to their own conversations, refer to the <Link path="guides/py-agent">`py#agent`</Link> or <Link path="guides/ts-agent">`ts#agent`</Link> guide.
|
|
113
|
+
|
|
95
114
|
## Infrastructure
|
|
96
115
|
|
|
97
116
|
<Snippet name="connection/react-agent-infrastructure" parentHeading="Infrastructure" />
|
package/docs/guides/py-agent.mdx
CHANGED
|
@@ -4,7 +4,7 @@ description: Generate a Python Agent for building AI agents with tools and deplo
|
|
|
4
4
|
generator: py#agent
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
import { FileTree, Tabs, TabItem, CardGrid } from '@astrojs/starlight/components';
|
|
7
|
+
import { FileTree, Tabs, TabItem, CardGrid, Steps } from '@astrojs/starlight/components';
|
|
8
8
|
import Astro from '@astrojs/react';
|
|
9
9
|
import ConnectionCard from '@components/connection-card.astro';
|
|
10
10
|
import RunGenerator from '@components/run-generator.astro';
|
|
@@ -516,6 +516,23 @@ The session ID itself comes from the AgentCore Runtime session (propagated via t
|
|
|
516
516
|
:::note[Local Development]
|
|
517
517
|
When running locally (`LOCAL_DEV=true`, set automatically by the `-dev` target), session data is always stored on disk under `tmp/agents/strands/<agent-name>` at the workspace root, regardless of the configured `session` option, for convenience.
|
|
518
518
|
:::
|
|
519
|
+
|
|
520
|
+
#### Restricting sessions to their owner
|
|
521
|
+
|
|
522
|
+
The session ID arrives from the caller, so on its own it identifies a conversation but not who the conversation belongs to. AgentCore Runtime authorizes an invocation against the agent runtime resource ARN rather than against an individual session, which leaves the agent free to decide what a session means to your application.
|
|
523
|
+
|
|
524
|
+
:::caution[The session also selects the microVM]
|
|
525
|
+
A session ID selects the [microVM](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-how-it-works.html) that serves the request, so its filesystem, `/tmp` and in-process caches are shared by every request resolving to that session. Two callers who send the same session ID share those resources even when their stored conversations are separate.
|
|
526
|
+
:::
|
|
527
|
+
|
|
528
|
+
To restrict each user to their own conversations:
|
|
529
|
+
|
|
530
|
+
<Steps>
|
|
531
|
+
1. Add an API to create a session, using <Link path="guides/trpc">tRPC</Link>, <Link path="guides/fastapi">FastAPI</Link> or <Link path="guides/ts-smithy-api">Smithy</Link>. Generate an opaque session ID (at least 33 characters) and store it alongside the calling user's ID — for example in a table created with the <Link path="guides/py-dynamodb">`py#dynamodb`</Link> generator. Each API guide shows how to retrieve the calling user's ID.
|
|
532
|
+
2. In your agent, look up the stored user ID for the session ID it was given, and reject the request when it does not match the caller. With `auth=cognito` the caller's JWT reaches your agent code, so its `sub` claim identifies them.
|
|
533
|
+
</Steps>
|
|
534
|
+
|
|
535
|
+
Generate the session ID rather than deriving it from user-supplied values such as a conversation name — anything a caller can predict, a caller can send.
|
|
519
536
|
</OptionFilter>
|
|
520
537
|
|
|
521
538
|
<OptionFilter when={{ framework: 'langchain' }} description="LangChain session management (session.py)">
|
|
@@ -119,6 +119,42 @@ Set `advanced_security_mode` to `ENFORCED` in the `user_pool_add_ons` block in `
|
|
|
119
119
|
</Fragment>
|
|
120
120
|
</Infrastructure>
|
|
121
121
|
|
|
122
|
+
#### Multi-factor authentication (MFA)
|
|
123
|
+
|
|
124
|
+
By default users must configure MFA (an SMS code or a time-based one time password) before they can sign in. You can make MFA optional, turn it off entirely, or restrict which second-factor methods are available:
|
|
125
|
+
|
|
126
|
+
<Infrastructure>
|
|
127
|
+
<Fragment slot="cdk">
|
|
128
|
+
```ts
|
|
129
|
+
import { Mfa } from 'aws-cdk-lib/aws-cognito';
|
|
130
|
+
|
|
131
|
+
new UserIdentity(this, 'Identity', {
|
|
132
|
+
mfa: Mfa.OPTIONAL,
|
|
133
|
+
mfaSecondFactor: { sms: false, otp: true },
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`mfa` accepts `Mfa.OFF` / `Mfa.OPTIONAL` / `Mfa.REQUIRED`. `mfaSecondFactor.sms` and `mfaSecondFactor.otp` enable or disable each second-factor method independently; they have no effect when `mfa` is `Mfa.OFF`. Setting `mfa: Mfa.REQUIRED` with both methods disabled is rejected at synth time, since nobody could then complete sign-in.
|
|
138
|
+
</Fragment>
|
|
139
|
+
<Fragment slot="terraform">
|
|
140
|
+
```hcl
|
|
141
|
+
module "user_identity" {
|
|
142
|
+
source = "../../common/terraform/src/core/user-identity"
|
|
143
|
+
|
|
144
|
+
mfa = "OPTIONAL"
|
|
145
|
+
mfa_second_factor_sms = false
|
|
146
|
+
mfa_second_factor_otp = true
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`mfa` accepts `"OFF"` / `"OPTIONAL"` / `"ON"`. `mfa_second_factor_sms` and `mfa_second_factor_otp` enable or disable each second-factor method independently; they have no effect when `mfa` is `"OFF"`.
|
|
151
|
+
</Fragment>
|
|
152
|
+
</Infrastructure>
|
|
153
|
+
|
|
154
|
+
:::caution[SMS MFA is tied to phone number verification]
|
|
155
|
+
Cognito rejects SMS-based phone number verification unless SMS is also an enabled MFA method whenever MFA is not off, in both CDK and Terraform. Disabling `mfaSecondFactor.sms` / `mfa_second_factor_sms` while `mfa` is on therefore also disables SMS-based phone number verification for the user pool.
|
|
156
|
+
:::
|
|
157
|
+
|
|
122
158
|
#### Web Application Firewall (WAF)
|
|
123
159
|
|
|
124
160
|
By default the User Pool is associated with an [AWS WAFv2](https://docs.aws.amazon.com/waf/latest/developerguide/waf-chapter.html) Web ACL using the `AWSManagedRulesCommonRuleSet` and `AWSManagedRulesKnownBadInputsRuleSet` managed rule groups. You can disable this if you wish to manage your own Web ACL or do not require one:
|
|
@@ -129,58 +129,7 @@ waf -> cloudfront
|
|
|
129
129
|
cloudfront -> s3
|
|
130
130
|
```
|
|
131
131
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
The CloudFront distribution applies a response headers policy that sets `Strict-Transport-Security`, `X-Content-Type-Options`, `X-Frame-Options: DENY`, `Referrer-Policy` and a `Content-Security-Policy` on all responses.
|
|
135
|
-
|
|
136
|
-
A default `Content-Security-Policy` is enforced. It restricts scripts and framing to mitigate XSS and clickjacking, while permitting HTTPS and WSS connections so the website can call AWS service endpoints (such as API Gateway, Cognito and Bedrock AgentCore) whose URLs are only known at deploy time. To adjust the policy (for example to tighten `connect-src` to your specific origins), edit the `content_security_policy` value in your generated `static-website.ts` (CDK) or `static-website.tf` (Terraform).
|
|
137
|
-
|
|
138
|
-
`runtime-config.json` is served with `Cache-Control: no-cache` so that browsers always fetch the latest configuration after a redeploy, rather than using a stale cached copy.
|
|
139
|
-
|
|
140
|
-
#### Custom Domain & TLS
|
|
141
|
-
|
|
142
|
-
By default the distribution uses the default CloudFront domain name (`*.cloudfront.net`) and its default certificate, which does not support enforcing a minimum TLS version of 1.2. To serve your website from your own domain, supply an [ACM certificate](https://docs.aws.amazon.com/acm/latest/userguide/acm-overview.html) (which must reside in `us-east-1` for use with CloudFront) and your domain names — a minimum TLS version of 1.2 is then enforced for viewers:
|
|
143
|
-
|
|
144
|
-
<Infrastructure>
|
|
145
|
-
<Fragment slot="cdk">
|
|
146
|
-
Pass the `certificate` and `domainNames` props through in your generated website construct in `packages/common/constructs/src/app/static-websites`:
|
|
147
|
-
|
|
148
|
-
```ts {6-8}
|
|
149
|
-
export class MyWebsite extends StaticWebsite {
|
|
150
|
-
constructor(scope: Construct, id: string) {
|
|
151
|
-
super(scope, id, {
|
|
152
|
-
websiteName: 'MyWebsite',
|
|
153
|
-
websiteFilePath: ...,
|
|
154
|
-
domainNames: ['www.example.com'],
|
|
155
|
-
certificate: Certificate.fromCertificateArn(scope, 'Cert',
|
|
156
|
-
'arn:aws:acm:us-east-1:123456789012:certificate/...'),
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
```
|
|
161
|
-
</Fragment>
|
|
162
|
-
<Fragment slot="terraform">
|
|
163
|
-
Set the `custom_domain_names` and `acm_certificate_arn` variables in your generated website module in `packages/common/terraform/src/app/static-websites`:
|
|
164
|
-
|
|
165
|
-
```hcl {5-6}
|
|
166
|
-
module "static_website" {
|
|
167
|
-
source = "../../../core/static-website"
|
|
168
|
-
website_name = "my-website"
|
|
169
|
-
website_file_path = ...
|
|
170
|
-
custom_domain_names = ["www.example.com"]
|
|
171
|
-
acm_certificate_arn = "arn:aws:acm:us-east-1:123456789012:certificate/..."
|
|
172
|
-
|
|
173
|
-
providers = {
|
|
174
|
-
aws.us_east_1 = aws.us_east_1
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
```
|
|
178
|
-
</Fragment>
|
|
179
|
-
</Infrastructure>
|
|
180
|
-
|
|
181
|
-
You will also need to create DNS records (for example in Route 53) pointing your domain at the CloudFront distribution.
|
|
182
|
-
|
|
183
|
-
## Implementing your React Website
|
|
132
|
+
## Implementing your Website
|
|
184
133
|
|
|
185
134
|
The [React documentation](https://react.dev/learn) is a good place to start to learn the basics of building with React.
|
|
186
135
|
|
|
@@ -231,6 +180,321 @@ export const MyComponent = () => {
|
|
|
231
180
|
|
|
232
181
|
For more details, check out the [TanStack Router](https://tanstack.com/router/latest/docs/framework/react/overview) documentation.
|
|
233
182
|
|
|
183
|
+
## Deploying your Website
|
|
184
|
+
|
|
185
|
+
The React website generator creates CDK or Terraform infrastructure as code based on your selected `iac`. You can use this to deploy your website.
|
|
186
|
+
|
|
187
|
+
<Infrastructure>
|
|
188
|
+
<Fragment slot="cdk">
|
|
189
|
+
To deploy your website, we recommend using the <Link path="guides/typescript-infrastructure">`ts#infra` generator</Link> to create a CDK application.
|
|
190
|
+
|
|
191
|
+
You can use the CDK construct generated for you in `packages/common/constructs` to deploy your website.
|
|
192
|
+
|
|
193
|
+
```ts title="packages/infra/src/stacks/application-stack.ts" {3, 9}
|
|
194
|
+
import { Stack } from 'aws-cdk-lib';
|
|
195
|
+
import { Construct } from 'constructs';
|
|
196
|
+
import { MyWebsite } from '@my-scope/common-constructs';
|
|
197
|
+
|
|
198
|
+
export class ApplicationStack extends Stack {
|
|
199
|
+
constructor(scope: Construct, id: string) {
|
|
200
|
+
super(scope, id);
|
|
201
|
+
|
|
202
|
+
new MyWebsite(this, 'MyWebsite');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
This sets up:
|
|
208
|
+
|
|
209
|
+
1. An S3 bucket for hosting your static website files
|
|
210
|
+
2. CloudFront distribution for global content delivery
|
|
211
|
+
3. WAF Web ACL for security protection
|
|
212
|
+
4. Origin Access Control for secure S3 access
|
|
213
|
+
5. Automatic deployment of website files and runtime configuration
|
|
214
|
+
</Fragment>
|
|
215
|
+
<Fragment slot="terraform">
|
|
216
|
+
To deploy your website, we recommend using the <Link path="/guides/terraform-project">`terraform#project` generator</Link> to create a Terraform project.
|
|
217
|
+
|
|
218
|
+
You can use the Terraform module generated for you in `packages/common/terraform` to deploy your website.
|
|
219
|
+
|
|
220
|
+
```hcl title="packages/infra/src/main.tf" {3}
|
|
221
|
+
# Deploy website
|
|
222
|
+
module "my_website" {
|
|
223
|
+
source = "../../common/terraform/src/app/static-websites/my-website"
|
|
224
|
+
|
|
225
|
+
providers = {
|
|
226
|
+
aws.us_east_1 = aws.us_east_1
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
This sets up:
|
|
232
|
+
|
|
233
|
+
1. An S3 bucket for hosting your static website files
|
|
234
|
+
2. CloudFront distribution for global content delivery
|
|
235
|
+
3. WAF Web ACL for security protection (deployed in us-east-1)
|
|
236
|
+
4. Origin Access Control for secure S3 access
|
|
237
|
+
5. Automatic deployment of website files and runtime configuration
|
|
238
|
+
|
|
239
|
+
:::note[WAF Provider Region]
|
|
240
|
+
The `aws.us_east_1` provider is required for CloudFront and WAF resources, which must be deployed in the us-east-1 region. Make sure your Terraform configuration includes this provider:
|
|
241
|
+
|
|
242
|
+
```hcl title="packages/infra/src/providers.tf"
|
|
243
|
+
provider "aws" {
|
|
244
|
+
alias = "us_east_1"
|
|
245
|
+
region = "us-east-1"
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
:::
|
|
249
|
+
</Fragment>
|
|
250
|
+
</Infrastructure>
|
|
251
|
+
|
|
252
|
+
### Security Headers
|
|
253
|
+
|
|
254
|
+
The CloudFront distribution applies a response headers policy that sets `Strict-Transport-Security`, `X-Content-Type-Options`, `X-Frame-Options: DENY`, `Referrer-Policy` and a `Content-Security-Policy` on all responses.
|
|
255
|
+
|
|
256
|
+
A default `Content-Security-Policy` is enforced. It restricts scripts and framing to mitigate XSS and clickjacking, while permitting HTTPS and WSS connections so the website can call AWS service endpoints (such as API Gateway, Cognito and Bedrock AgentCore) whose URLs are only known at deploy time. To adjust the policy (for example to tighten `connect-src` to your specific origins), edit the `content_security_policy` value in your generated `static-website.ts` (CDK) or `static-website.tf` (Terraform).
|
|
257
|
+
|
|
258
|
+
`runtime-config.json` is served with `Cache-Control: no-cache` so that browsers always fetch the latest configuration after a redeploy, rather than using a stale cached copy.
|
|
259
|
+
|
|
260
|
+
### WAF
|
|
261
|
+
|
|
262
|
+
The CloudFront distribution is protected by an [AWS WAFv2](https://docs.aws.amazon.com/waf/latest/developerguide/waf-chapter.html) Web ACL by default. The Web ACL uses the AWS managed default ruleset ([`AWSManagedRulesCommonRuleSet`](https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-baseline.html#aws-managed-rule-groups-baseline-crs) and [`AWSManagedRulesKnownBadInputsRuleSet`](https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-baseline.html#aws-managed-rule-groups-baseline-known-bad-inputs)), providing protection against common web exploits including the OWASP Top 10.
|
|
263
|
+
|
|
264
|
+
<Infrastructure>
|
|
265
|
+
<Fragment slot="cdk">
|
|
266
|
+
To opt out, set `enableWaf` to `false` when you create your website:
|
|
267
|
+
|
|
268
|
+
```ts title="packages/infra/src/stacks/application-stack.ts" {10,15-18}
|
|
269
|
+
import { Stack } from 'aws-cdk-lib';
|
|
270
|
+
import { Construct } from 'constructs';
|
|
271
|
+
import { MyWebsite, suppressRules } from '@my-scope/common-constructs';
|
|
272
|
+
|
|
273
|
+
export class ApplicationStack extends Stack {
|
|
274
|
+
constructor(scope: Construct, id: string) {
|
|
275
|
+
super(scope, id);
|
|
276
|
+
|
|
277
|
+
const website = new MyWebsite(this, 'MyWebsite', {
|
|
278
|
+
enableWaf: false,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Disabling WAF fails the checkov CKV_AWS_68 check ("CloudFront
|
|
282
|
+
// Distribution should have WAF enabled"). Suppress it explicitly.
|
|
283
|
+
suppressRules(
|
|
284
|
+
website.cloudFrontDistribution,
|
|
285
|
+
['CKV_AWS_68'],
|
|
286
|
+
'WAF is intentionally disabled for this distribution',
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
:::caution[Suppress CKV_AWS_68 when disabling WAF]
|
|
293
|
+
Disabling WAF means the synthesized CloudFront distribution has no Web ACL associated, which fails the `checkov` security scan with `CKV_AWS_68: "CloudFront Distribution should have WAF enabled"` unless suppressed as shown above.
|
|
294
|
+
:::
|
|
295
|
+
</Fragment>
|
|
296
|
+
<Fragment slot="terraform">
|
|
297
|
+
To opt out, set `enable_waf` to `false`:
|
|
298
|
+
|
|
299
|
+
```hcl title="packages/infra/src/main.tf" {7}
|
|
300
|
+
module "my_website" {
|
|
301
|
+
source = "../../common/terraform/src/app/static-websites/my-website"
|
|
302
|
+
|
|
303
|
+
providers = {
|
|
304
|
+
aws.us_east_1 = aws.us_east_1
|
|
305
|
+
}
|
|
306
|
+
enable_waf = false
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
</Fragment>
|
|
310
|
+
</Infrastructure>
|
|
311
|
+
|
|
312
|
+
### Bucket Encryption
|
|
313
|
+
|
|
314
|
+
The website bucket, the CloudFront distribution log bucket, and the CloudWatch Logs group receiving their server access logs are encrypted with a customer-managed [AWS KMS](https://aws.amazon.com/kms/) key by default. This key is created for you automatically, with key rotation enabled.
|
|
315
|
+
|
|
316
|
+
<Infrastructure>
|
|
317
|
+
<Fragment slot="cdk">
|
|
318
|
+
If you want to use a different encryption configuration, pass the `encryption`, `encryptionKey` and `enableKeyRotation` props when you create your website:
|
|
319
|
+
|
|
320
|
+
```ts title="packages/infra/src/stacks/application-stack.ts" {11}
|
|
321
|
+
import { Stack } from 'aws-cdk-lib';
|
|
322
|
+
import { Construct } from 'constructs';
|
|
323
|
+
import { BucketEncryption } from 'aws-cdk-lib/aws-s3';
|
|
324
|
+
import { MyWebsite } from '@my-scope/common-constructs';
|
|
325
|
+
|
|
326
|
+
export class ApplicationStack extends Stack {
|
|
327
|
+
constructor(scope: Construct, id: string) {
|
|
328
|
+
super(scope, id);
|
|
329
|
+
|
|
330
|
+
new MyWebsite(this, 'MyWebsite', {
|
|
331
|
+
encryption: BucketEncryption.S3_MANAGED,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
:::caution[Suppress CKV_AWS_158 when using S3_MANAGED]
|
|
338
|
+
Choosing `BucketEncryption.S3_MANAGED` means the access log group is no longer KMS encrypted, which fails the `checkov` security scan with `CKV_AWS_158`. Suppress it:
|
|
339
|
+
|
|
340
|
+
```ts title="packages/infra/src/stacks/application-stack.ts"
|
|
341
|
+
import { LogGroup } from 'aws-cdk-lib/aws-logs';
|
|
342
|
+
import { suppressRules } from '@my-scope/common-constructs';
|
|
343
|
+
|
|
344
|
+
suppressRules(
|
|
345
|
+
website,
|
|
346
|
+
['CKV_AWS_158'],
|
|
347
|
+
'Access log group is not KMS encrypted when encryption is S3_MANAGED',
|
|
348
|
+
(c) => c instanceof LogGroup,
|
|
349
|
+
);
|
|
350
|
+
```
|
|
351
|
+
:::
|
|
352
|
+
|
|
353
|
+
To use your own KMS key instead of one created automatically, pass `encryptionKey`:
|
|
354
|
+
|
|
355
|
+
```ts {2}
|
|
356
|
+
new MyWebsite(this, 'MyWebsite', {
|
|
357
|
+
encryptionKey: myKey,
|
|
358
|
+
});
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
:::caution[Imported keys need their own permissions]
|
|
362
|
+
A key imported via `Key.fromKeyArn` must already grant the CloudWatch Logs, S3 and CloudFront service principals the permissions they need in its own key policy. `addToResourcePolicy` is a no-op on an imported key, so this construct can't grant them for you, and `cdk synth` won't warn you - the failure only shows up at deploy time. A key created in the same app (`new Key(this, 'WebsiteKey')`) doesn't have this problem, since CDK can update its policy directly.
|
|
363
|
+
:::
|
|
364
|
+
|
|
365
|
+
`enableKeyRotation` (default `true`) only applies to the automatically created key, i.e. when `encryption` is `BucketEncryption.KMS` (the default) and no `encryptionKey` is supplied:
|
|
366
|
+
|
|
367
|
+
```ts {2}
|
|
368
|
+
new MyWebsite(this, 'MyWebsite', {
|
|
369
|
+
enableKeyRotation: false,
|
|
370
|
+
});
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
:::caution[Suppress CKV_AWS_7 when disabling key rotation]
|
|
374
|
+
Disabling key rotation on the automatically created key fails the `checkov` security scan with `CKV_AWS_7`. Suppress it:
|
|
375
|
+
|
|
376
|
+
```ts title="packages/infra/src/stacks/application-stack.ts"
|
|
377
|
+
import { Key } from 'aws-cdk-lib/aws-kms';
|
|
378
|
+
import { suppressRules } from '@my-scope/common-constructs';
|
|
379
|
+
|
|
380
|
+
suppressRules(
|
|
381
|
+
website,
|
|
382
|
+
['CKV_AWS_7'],
|
|
383
|
+
'Key rotation is managed externally',
|
|
384
|
+
(c) => c instanceof Key,
|
|
385
|
+
);
|
|
386
|
+
```
|
|
387
|
+
:::
|
|
388
|
+
</Fragment>
|
|
389
|
+
<Fragment slot="terraform">
|
|
390
|
+
If you want to use a different encryption configuration, set the `encryption`, `kms_key_arn`, `create_kms_key` and `enable_key_rotation` variables:
|
|
391
|
+
|
|
392
|
+
```hcl title="packages/infra/src/main.tf" {7}
|
|
393
|
+
module "my_website" {
|
|
394
|
+
source = "../../common/terraform/src/app/static-websites/my-website"
|
|
395
|
+
|
|
396
|
+
providers = {
|
|
397
|
+
aws.us_east_1 = aws.us_east_1
|
|
398
|
+
}
|
|
399
|
+
encryption = "S3_MANAGED"
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
:::caution[Checkov: S3_MANAGED]
|
|
404
|
+
Choosing `S3_MANAGED` fails the `checkov` security scan with `CKV_AWS_145` on the website and distribution log buckets. The vended `test` target doesn't accept a `--config-file`, but `checkov` auto-discovers a `.checkov.yaml` in its working directory, so drop one in `packages/infra/src`:
|
|
405
|
+
|
|
406
|
+
```yaml title="packages/infra/src/.checkov.yaml"
|
|
407
|
+
skip-check:
|
|
408
|
+
- CKV_AWS_145
|
|
409
|
+
```
|
|
410
|
+
:::
|
|
411
|
+
|
|
412
|
+
To use your own KMS key instead of one created automatically, pass `kms_key_arn` and set `create_kms_key` to `false`:
|
|
413
|
+
|
|
414
|
+
```hcl title="packages/infra/src/main.tf" {7-8}
|
|
415
|
+
module "my_website" {
|
|
416
|
+
source = "../../common/terraform/src/app/static-websites/my-website"
|
|
417
|
+
|
|
418
|
+
providers = {
|
|
419
|
+
aws.us_east_1 = aws.us_east_1
|
|
420
|
+
}
|
|
421
|
+
kms_key_arn = aws_kms_key.website.arn
|
|
422
|
+
create_kms_key = false
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
A customer-supplied key must already grant the CloudWatch Logs, S3 and CloudFront service principals the permissions they need in its own key policy.
|
|
427
|
+
|
|
428
|
+
`enable_key_rotation` (default `true`) only applies to the automatically created key, i.e. when `encryption` is `"KMS"` (the default) and `create_kms_key` is `true`:
|
|
429
|
+
|
|
430
|
+
```hcl title="packages/infra/src/main.tf" {7}
|
|
431
|
+
module "my_website" {
|
|
432
|
+
source = "../../common/terraform/src/app/static-websites/my-website"
|
|
433
|
+
|
|
434
|
+
providers = {
|
|
435
|
+
aws.us_east_1 = aws.us_east_1
|
|
436
|
+
}
|
|
437
|
+
enable_key_rotation = false
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
:::caution[Checkov: key rotation disabled]
|
|
442
|
+
Disabling key rotation on the automatically created key fails the `checkov` security scan with `CKV_AWS_7`. Add it to the same `.checkov.yaml`:
|
|
443
|
+
|
|
444
|
+
```yaml title="packages/infra/src/.checkov.yaml"
|
|
445
|
+
skip-check:
|
|
446
|
+
- CKV_AWS_7
|
|
447
|
+
```
|
|
448
|
+
:::
|
|
449
|
+
</Fragment>
|
|
450
|
+
</Infrastructure>
|
|
451
|
+
|
|
452
|
+
### Custom Domain & TLS
|
|
453
|
+
|
|
454
|
+
By default the distribution uses the default CloudFront domain name (`*.cloudfront.net`) and its default certificate, which does not support enforcing a minimum TLS version of 1.2. To serve your website from your own domain, supply an [ACM certificate](https://docs.aws.amazon.com/acm/latest/userguide/acm-overview.html) (which must reside in `us-east-1` for use with CloudFront) and your domain names. A minimum TLS version of 1.2 is then enforced for viewers:
|
|
455
|
+
|
|
456
|
+
<Infrastructure>
|
|
457
|
+
<Fragment slot="cdk">
|
|
458
|
+
Pass the `certificate` and `domainNames` props when you create your website:
|
|
459
|
+
|
|
460
|
+
```ts title="packages/infra/src/stacks/application-stack.ts" {11-13}
|
|
461
|
+
import { Stack } from 'aws-cdk-lib';
|
|
462
|
+
import { Construct } from 'constructs';
|
|
463
|
+
import { Certificate } from 'aws-cdk-lib/aws-certificatemanager';
|
|
464
|
+
import { MyWebsite } from '@my-scope/common-constructs';
|
|
465
|
+
|
|
466
|
+
export class ApplicationStack extends Stack {
|
|
467
|
+
constructor(scope: Construct, id: string) {
|
|
468
|
+
super(scope, id);
|
|
469
|
+
|
|
470
|
+
new MyWebsite(this, 'MyWebsite', {
|
|
471
|
+
domainNames: ['www.example.com'],
|
|
472
|
+
certificate: Certificate.fromCertificateArn(this, 'Cert',
|
|
473
|
+
'arn:aws:acm:us-east-1:123456789012:certificate/...'),
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
```
|
|
478
|
+
</Fragment>
|
|
479
|
+
<Fragment slot="terraform">
|
|
480
|
+
Set the `custom_domain_names` and `acm_certificate_arn` variables:
|
|
481
|
+
|
|
482
|
+
```hcl title="packages/infra/src/main.tf" {7-8}
|
|
483
|
+
module "my_website" {
|
|
484
|
+
source = "../../common/terraform/src/app/static-websites/my-website"
|
|
485
|
+
|
|
486
|
+
providers = {
|
|
487
|
+
aws.us_east_1 = aws.us_east_1
|
|
488
|
+
}
|
|
489
|
+
custom_domain_names = ["www.example.com"]
|
|
490
|
+
acm_certificate_arn = "arn:aws:acm:us-east-1:123456789012:certificate/..."
|
|
491
|
+
}
|
|
492
|
+
```
|
|
493
|
+
</Fragment>
|
|
494
|
+
</Infrastructure>
|
|
495
|
+
|
|
496
|
+
You will also need to create DNS records (for example in Route 53) pointing your domain at the CloudFront distribution.
|
|
497
|
+
|
|
234
498
|
## Runtime Configuration
|
|
235
499
|
|
|
236
500
|
Configuration from your infrastructure is provided to your website via <Link href="guides/runtime-config">Runtime Configuration</Link>. This allows your website to access details such as API URLs which are not known until your application is deployed.
|
|
@@ -252,7 +516,7 @@ export class ApplicationStack extends Stack {
|
|
|
252
516
|
constructor(scope: Construct, id: string) {
|
|
253
517
|
super(scope, id);
|
|
254
518
|
|
|
255
|
-
// Website can be declared at any point
|
|
519
|
+
// Website can be declared at any point, since runtime config is resolved lazily
|
|
256
520
|
new MyWebsite(this, 'MyWebsite');
|
|
257
521
|
|
|
258
522
|
// Automatically adds values to the RuntimeConfig
|
|
@@ -415,75 +679,6 @@ You can run your tests using the `test` target:
|
|
|
415
679
|
|
|
416
680
|
<NxCommands commands={['test <my-website>']} />
|
|
417
681
|
|
|
418
|
-
## Deploying Your Website
|
|
419
|
-
|
|
420
|
-
The React website generator creates CDK or Terraform infrastructure as code based on your selected `iac`. You can use this to deploy your website.
|
|
421
|
-
|
|
422
|
-
<Infrastructure>
|
|
423
|
-
<Fragment slot="cdk">
|
|
424
|
-
To deploy your website, we recommend using the <Link path="guides/typescript-infrastructure">`ts#infra` generator</Link> to create a CDK application.
|
|
425
|
-
|
|
426
|
-
You can use the CDK construct generated for you in `packages/common/constructs` to deploy your website.
|
|
427
|
-
|
|
428
|
-
```ts title="packages/infra/src/stacks/application-stack.ts" {3, 9}
|
|
429
|
-
import { Stack } from 'aws-cdk-lib';
|
|
430
|
-
import { Construct } from 'constructs';
|
|
431
|
-
import { MyWebsite } from '@my-scope/common-constructs';
|
|
432
|
-
|
|
433
|
-
export class ApplicationStack extends Stack {
|
|
434
|
-
constructor(scope: Construct, id: string) {
|
|
435
|
-
super(scope, id);
|
|
436
|
-
|
|
437
|
-
new MyWebsite(this, 'MyWebsite');
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
```
|
|
441
|
-
|
|
442
|
-
This sets up:
|
|
443
|
-
|
|
444
|
-
1. An S3 bucket for hosting your static website files
|
|
445
|
-
2. CloudFront distribution for global content delivery
|
|
446
|
-
3. WAF Web ACL for security protection
|
|
447
|
-
4. Origin Access Control for secure S3 access
|
|
448
|
-
5. Automatic deployment of website files and runtime configuration
|
|
449
|
-
</Fragment>
|
|
450
|
-
<Fragment slot="terraform">
|
|
451
|
-
To deploy your website, we recommend using the <Link path="/guides/terraform-project">`terraform#project` generator</Link> to create a Terraform project.
|
|
452
|
-
|
|
453
|
-
You can use the Terraform module generated for you in `packages/common/terraform` to deploy your website.
|
|
454
|
-
|
|
455
|
-
```hcl title="packages/infra/src/main.tf" {3}
|
|
456
|
-
# Deploy website
|
|
457
|
-
module "my_website" {
|
|
458
|
-
source = "../../common/terraform/src/app/static-websites/my-website"
|
|
459
|
-
|
|
460
|
-
providers = {
|
|
461
|
-
aws.us_east_1 = aws.us_east_1
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
```
|
|
465
|
-
|
|
466
|
-
This sets up:
|
|
467
|
-
|
|
468
|
-
1. An S3 bucket for hosting your static website files
|
|
469
|
-
2. CloudFront distribution for global content delivery
|
|
470
|
-
3. WAF Web ACL for security protection (deployed in us-east-1)
|
|
471
|
-
4. Origin Access Control for secure S3 access
|
|
472
|
-
5. Automatic deployment of website files and runtime configuration
|
|
473
|
-
|
|
474
|
-
:::note[WAF Provider Region]
|
|
475
|
-
The `aws.us_east_1` provider is required for CloudFront and WAF resources, which must be deployed in the us-east-1 region. Make sure your Terraform configuration includes this provider:
|
|
476
|
-
|
|
477
|
-
```hcl title="packages/infra/src/providers.tf"
|
|
478
|
-
provider "aws" {
|
|
479
|
-
alias = "us_east_1"
|
|
480
|
-
region = "us-east-1"
|
|
481
|
-
}
|
|
482
|
-
```
|
|
483
|
-
:::
|
|
484
|
-
</Fragment>
|
|
485
|
-
</Infrastructure>
|
|
486
|
-
|
|
487
682
|
## Connections
|
|
488
683
|
|
|
489
684
|
Use the <Link path="guides/connection">`connection`</Link> generator to integrate this project with others in your workspace. The following connections involve this project:
|
package/docs/guides/ts-agent.mdx
CHANGED
|
@@ -4,7 +4,7 @@ description: Generate a TypeScript Agent for building AI agents with tools and d
|
|
|
4
4
|
generator: ts#agent
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
import { FileTree, Tabs, TabItem, CardGrid } from '@astrojs/starlight/components';
|
|
7
|
+
import { FileTree, Tabs, TabItem, CardGrid, Steps } from '@astrojs/starlight/components';
|
|
8
8
|
import Astro from '@astrojs/react';
|
|
9
9
|
import ConnectionCard from '@components/connection-card.astro';
|
|
10
10
|
import RunGenerator from '@components/run-generator.astro';
|
|
@@ -365,6 +365,23 @@ The session ID itself comes from the AgentCore Runtime session (propagated via t
|
|
|
365
365
|
:::note[Local Development]
|
|
366
366
|
When running locally (`LOCAL_DEV=true`, set automatically by the `-dev` target), session data is always stored on disk under `tmp/agents/strands/<agent-name>` at the workspace root, regardless of the configured `session` option, for convenience.
|
|
367
367
|
:::
|
|
368
|
+
|
|
369
|
+
#### Restricting sessions to their owner
|
|
370
|
+
|
|
371
|
+
The session ID arrives from the caller, so on its own it identifies a conversation but not who the conversation belongs to. AgentCore Runtime authorizes an invocation against the agent runtime resource ARN rather than against an individual session, which leaves the agent free to decide what a session means to your application.
|
|
372
|
+
|
|
373
|
+
:::caution[The session also selects the microVM]
|
|
374
|
+
A session ID selects the [microVM](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-how-it-works.html) that serves the request, so its filesystem, `/tmp` and in-process caches are shared by every request resolving to that session. Two callers who send the same session ID share those resources even when their stored conversations are separate.
|
|
375
|
+
:::
|
|
376
|
+
|
|
377
|
+
To restrict each user to their own conversations:
|
|
378
|
+
|
|
379
|
+
<Steps>
|
|
380
|
+
1. Add an API to create a session, using <Link path="guides/trpc">tRPC</Link>, <Link path="guides/fastapi">FastAPI</Link> or <Link path="guides/ts-smithy-api">Smithy</Link>. Generate an opaque session ID (at least 33 characters) and store it alongside the calling user's ID — for example in a table created with the <Link path="guides/ts-dynamodb">`ts#dynamodb`</Link> generator. Each API guide shows how to retrieve the calling user's ID.
|
|
381
|
+
2. In your agent, look up the stored user ID for the session ID it was given, and reject the request when it does not match the caller. With `auth=cognito` the caller's JWT reaches your agent code, so its `sub` claim identifies them.
|
|
382
|
+
</Steps>
|
|
383
|
+
|
|
384
|
+
Generate the session ID rather than deriving it from user-supplied values such as a conversation name — anything a caller can predict, a caller can send.
|
|
368
385
|
</OptionFilter>
|
|
369
386
|
|
|
370
387
|
## Invoking your Agent
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
title: Deploying your DynamoDB Table
|
|
3
3
|
---
|
|
4
4
|
import Infrastructure from '@components/infrastructure.astro';
|
|
5
|
+
import Snippet from '@components/snippet.astro';
|
|
5
6
|
|
|
6
7
|
The DynamoDB generator creates CDK or Terraform infrastructure based on your selected `iac`.
|
|
7
8
|
|
|
@@ -137,30 +138,8 @@ module "my_table" {
|
|
|
137
138
|
</Fragment>
|
|
138
139
|
</Infrastructure>
|
|
139
140
|
|
|
140
|
-
### Encryption
|
|
141
|
+
### Encryption
|
|
141
142
|
|
|
142
|
-
The KMS key
|
|
143
|
+
The table is encrypted with a customer-managed KMS key by default, created automatically for you. Switch to an AWS managed key, the AWS owned key, or bring your own KMS key, if you manage encryption differently.
|
|
143
144
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
<Infrastructure>
|
|
147
|
-
<Fragment slot="cdk">
|
|
148
|
-
|
|
149
|
-
```ts title="packages/infra/src/stacks/application-stack.ts"
|
|
150
|
-
import { MyTable } from '@my-scope/common-constructs';
|
|
151
|
-
|
|
152
|
-
const table = new MyTable(this, 'Table', {
|
|
153
|
-
enableKeyRotation: false,
|
|
154
|
-
});
|
|
155
|
-
```
|
|
156
|
-
</Fragment>
|
|
157
|
-
<Fragment slot="terraform">
|
|
158
|
-
|
|
159
|
-
```hcl title="packages/infra/src/main.tf"
|
|
160
|
-
module "my_table" {
|
|
161
|
-
source = "../../common/terraform/src/app/dynamodb/my-table"
|
|
162
|
-
enable_key_rotation = false
|
|
163
|
-
}
|
|
164
|
-
```
|
|
165
|
-
</Fragment>
|
|
166
|
-
</Infrastructure>
|
|
145
|
+
<Snippet name="dynamodb/encryption-options" parentHeading="Encryption" />
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Encryption Options
|
|
3
|
+
---
|
|
4
|
+
import Infrastructure from '@components/infrastructure.astro';
|
|
5
|
+
import OptionFilter from '@components/option-filter.astro';
|
|
6
|
+
|
|
7
|
+
#### Use an AWS Managed Key
|
|
8
|
+
|
|
9
|
+
Uses the shared `aws/dynamodb` KMS key that AWS manages on your behalf. It's visible in your account's KMS console and billed per request, but there's no key for you to create, rotate or delete.
|
|
10
|
+
|
|
11
|
+
<Infrastructure>
|
|
12
|
+
<Fragment slot="cdk">
|
|
13
|
+
|
|
14
|
+
```ts title="packages/infra/src/stacks/application-stack.ts"
|
|
15
|
+
import { TableEncryption } from 'aws-cdk-lib/aws-dynamodb';
|
|
16
|
+
import { MyTable } from '@my-scope/common-constructs';
|
|
17
|
+
|
|
18
|
+
const table = new MyTable(this, 'Table', {
|
|
19
|
+
encryption: TableEncryption.AWS_MANAGED,
|
|
20
|
+
});
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
:::tip[Checkov]
|
|
24
|
+
Choosing `AWS_MANAGED` means the table is no longer encrypted with a customer-managed key, which fails Checkov's `CKV_AWS_119`. Suppress it on the table:
|
|
25
|
+
|
|
26
|
+
```ts title="packages/infra/src/stacks/application-stack.ts"
|
|
27
|
+
import { suppressRules } from '@my-scope/common-constructs';
|
|
28
|
+
|
|
29
|
+
suppressRules(table.table, ['CKV_AWS_119'], 'Using an AWS managed key rather than a customer-managed CMK');
|
|
30
|
+
```
|
|
31
|
+
:::
|
|
32
|
+
</Fragment>
|
|
33
|
+
<Fragment slot="terraform">
|
|
34
|
+
|
|
35
|
+
```hcl title="packages/infra/src/main.tf"
|
|
36
|
+
module "my_table" {
|
|
37
|
+
source = "../../common/terraform/src/app/dynamodb/my-table"
|
|
38
|
+
encryption = "AWS_MANAGED"
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
</Fragment>
|
|
42
|
+
</Infrastructure>
|
|
43
|
+
|
|
44
|
+
#### Use the AWS Owned Key
|
|
45
|
+
|
|
46
|
+
Uses a key fully owned and managed by AWS — free, with no key visible in your account at all. The simplest option when you don't need a customer- or account-visible key for compliance reasons.
|
|
47
|
+
|
|
48
|
+
<Infrastructure>
|
|
49
|
+
<Fragment slot="cdk">
|
|
50
|
+
|
|
51
|
+
```ts title="packages/infra/src/stacks/application-stack.ts"
|
|
52
|
+
import { TableEncryption } from 'aws-cdk-lib/aws-dynamodb';
|
|
53
|
+
import { MyTable } from '@my-scope/common-constructs';
|
|
54
|
+
|
|
55
|
+
const table = new MyTable(this, 'Table', {
|
|
56
|
+
encryption: TableEncryption.DEFAULT,
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
:::tip[Checkov]
|
|
61
|
+
Choosing `DEFAULT` means the table is no longer encrypted with a customer-managed key, which fails Checkov's `CKV_AWS_119`. Suppress it on the table:
|
|
62
|
+
|
|
63
|
+
```ts title="packages/infra/src/stacks/application-stack.ts"
|
|
64
|
+
import { suppressRules } from '@my-scope/common-constructs';
|
|
65
|
+
|
|
66
|
+
suppressRules(table.table, ['CKV_AWS_119'], 'Using the AWS owned key rather than a customer-managed CMK');
|
|
67
|
+
```
|
|
68
|
+
:::
|
|
69
|
+
</Fragment>
|
|
70
|
+
<Fragment slot="terraform">
|
|
71
|
+
|
|
72
|
+
```hcl title="packages/infra/src/main.tf"
|
|
73
|
+
module "my_table" {
|
|
74
|
+
source = "../../common/terraform/src/app/dynamodb/my-table"
|
|
75
|
+
encryption = "DEFAULT"
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
</Fragment>
|
|
79
|
+
</Infrastructure>
|
|
80
|
+
|
|
81
|
+
<OptionFilter when={{ iac: 'terraform' }} description="Switching an already-deployed table away from CUSTOMER_MANAGED">
|
|
82
|
+
#### Switching away from CUSTOMER_MANAGED
|
|
83
|
+
|
|
84
|
+
On an **already-deployed** table, changing `encryption` away from `CUSTOMER_MANAGED` (to either `AWS_MANAGED` or `DEFAULT`) in a single `terraform apply` fails: Terraform destroys the customer-managed key before updating the table, and DynamoDB then rejects the update because the key is already pending deletion.
|
|
85
|
+
|
|
86
|
+
Work around it by updating the table's encryption directly via the AWS CLI first, then letting Terraform catch up and clean up the orphaned key:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# For AWS_MANAGED:
|
|
90
|
+
aws dynamodb update-table --table-name <table-name> \
|
|
91
|
+
--sse-specification Enabled=true,SSEType=KMS,KMSMasterKeyId=alias/aws/dynamodb
|
|
92
|
+
|
|
93
|
+
# For DEFAULT:
|
|
94
|
+
aws dynamodb update-table --table-name <table-name> --sse-specification Enabled=false
|
|
95
|
+
|
|
96
|
+
# Then wait for this to report ENABLED (or for SSEDescription to disappear, for DEFAULT):
|
|
97
|
+
aws dynamodb describe-table --table-name <table-name> --query Table.SSEDescription.Status
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Then update `encryption` in your Terraform config and run `terraform apply` as normal — Terraform now only needs to destroy the already-unused key, with nothing left depending on it.
|
|
101
|
+
</OptionFilter>
|
|
102
|
+
|
|
103
|
+
#### Use Your Own KMS Key
|
|
104
|
+
|
|
105
|
+
Provide an existing customer-managed key instead of having one created for you. The key must already grant the DynamoDB service the permissions it needs in its own key policy.
|
|
106
|
+
|
|
107
|
+
<Infrastructure>
|
|
108
|
+
<Fragment slot="cdk">
|
|
109
|
+
|
|
110
|
+
```ts title="packages/infra/src/stacks/application-stack.ts"
|
|
111
|
+
import { Key } from 'aws-cdk-lib/aws-kms';
|
|
112
|
+
import { MyTable } from '@my-scope/common-constructs';
|
|
113
|
+
|
|
114
|
+
const key = Key.fromKeyArn(this, 'Key', 'arn:aws:kms:us-east-1:111111111111:key/my-key-id');
|
|
115
|
+
|
|
116
|
+
const table = new MyTable(this, 'Table', {
|
|
117
|
+
encryptionKey: key,
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
</Fragment>
|
|
121
|
+
<Fragment slot="terraform">
|
|
122
|
+
|
|
123
|
+
```hcl title="packages/infra/src/main.tf"
|
|
124
|
+
module "my_table" {
|
|
125
|
+
source = "../../common/terraform/src/app/dynamodb/my-table"
|
|
126
|
+
kms_key_arn = "arn:aws:kms:us-east-1:111111111111:key/my-key-id"
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
</Fragment>
|
|
130
|
+
</Infrastructure>
|
|
131
|
+
|
|
132
|
+
#### Encryption Key Rotation
|
|
133
|
+
|
|
134
|
+
When the table creates its own customer-managed KMS key (the default, and only when you haven't provided your own key), that key has automatic key rotation enabled by default. Disable it if your security policy manages rotation externally.
|
|
135
|
+
|
|
136
|
+
##### Disable Encryption Key Rotation
|
|
137
|
+
|
|
138
|
+
<Infrastructure>
|
|
139
|
+
<Fragment slot="cdk">
|
|
140
|
+
|
|
141
|
+
```ts title="packages/infra/src/stacks/application-stack.ts"
|
|
142
|
+
import { MyTable } from '@my-scope/common-constructs';
|
|
143
|
+
|
|
144
|
+
const table = new MyTable(this, 'Table', {
|
|
145
|
+
enableKeyRotation: false,
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
:::tip[Checkov]
|
|
150
|
+
Disabling key rotation, or bringing your own key that doesn't rotate, fails Checkov's `CKV_AWS_7`. Suppress it on the key:
|
|
151
|
+
|
|
152
|
+
```ts title="packages/infra/src/stacks/application-stack.ts"
|
|
153
|
+
import { suppressRules } from '@my-scope/common-constructs';
|
|
154
|
+
|
|
155
|
+
suppressRules(table.table.encryptionKey!, ['CKV_AWS_7'], 'Key rotation is managed externally');
|
|
156
|
+
```
|
|
157
|
+
:::
|
|
158
|
+
</Fragment>
|
|
159
|
+
<Fragment slot="terraform">
|
|
160
|
+
|
|
161
|
+
```hcl title="packages/infra/src/main.tf"
|
|
162
|
+
module "my_table" {
|
|
163
|
+
source = "../../common/terraform/src/app/dynamodb/my-table"
|
|
164
|
+
enable_key_rotation = false
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
</Fragment>
|
|
168
|
+
</Infrastructure>
|