Skip to content

Commit 1263fa2

Browse files
committed
Merge branch 'v3' into refactor-strands
2 parents c8614f7 + 88d703f commit 1263fa2

File tree

9 files changed

+182
-31
lines changed

9 files changed

+182
-31
lines changed

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,26 @@ By default, this sample does not restrict the domains for sign-up email addresse
500500

501501
This sample supports external identity provider. Currently we support [Google](./docs/idp/SET_UP_GOOGLE.md) and [custom OIDC provider](./docs/idp/SET_UP_CUSTOM_OIDC.md).
502502

503+
### Optional Frontend WAF
504+
505+
For CloudFront distributions, AWS WAF WebACLs must be created in the us-east-1 region. In some organizations, creating resources outside the primary region is restricted by policy. In such environments, CDK deployment can fail when attempting to provision the Frontend WAF in us-east-1.
506+
507+
To accommodate these restrictions, the Frontend WAF stack is optional. When disabled, the CloudFront distribution is deployed without a WebACL. This means you won’t have IP allow/deny controls at the frontend edge. Authentication and all other application controls continue to work as usual. Note that this setting only affects the Frontend WAF (CloudFront scope); the Published API WAF (regional) remains unaffected.
508+
509+
To disable the Frontend WAF set the following in `parameter.ts` (Recommended Type-Safe Method):
510+
511+
```ts
512+
bedrockChatParams.set("default", {
513+
enableFrontendWaf: false
514+
});
515+
```
516+
517+
Or if using the legacy `cdk/cdk.json` set the following:
518+
519+
```json
520+
"enableFrontendWaf": false
521+
```
522+
503523
### Add new users to groups automatically
504524

505525
This sample has the following groups to give permissions to users:
@@ -609,6 +629,18 @@ bedrockChatParams.set("default", {
609629
});
610630
```
611631

632+
### Disable IPv6 support
633+
634+
The frontend gets both IP and IPv6 addresses by default. In some rare
635+
circumstances, you may need to disable IPv6 support explicitly. To do this, set
636+
the following parameter in [parameter.ts](./cdk/parameter.ts) or similarly in [cdk.json](./cdk/cdk.json):
637+
638+
```ts
639+
"enableFrontendIpv6": false
640+
```
641+
642+
If left unset the IPv6 support will be enabled by default.
643+
612644
### Local Development
613645

614646
See [LOCAL DEVELOPMENT](./docs/LOCAL_DEVELOPMENT.md).

cdk/bin/bedrock-chat.ts

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,28 @@ const params = getBedrockChatParameters(
2828

2929
const sepHyphen = params.envPrefix ? "-" : "";
3030

31-
// WAF for frontend
32-
// 2023/9: Currently, the WAF for CloudFront needs to be created in the North America region (us-east-1), so the stacks are separated
33-
// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wafv2-webacl.html
34-
const waf = new FrontendWafStack(
35-
app,
36-
`${params.envPrefix}${sepHyphen}FrontendWafStack`,
37-
{
38-
env: {
39-
// account: process.env.CDK_DEFAULT_ACCOUNT,
40-
region: "us-east-1",
41-
},
42-
envPrefix: params.envPrefix,
43-
allowedIpV4AddressRanges: params.allowedIpV4AddressRanges,
44-
allowedIpV6AddressRanges: params.allowedIpV6AddressRanges,
45-
}
46-
);
31+
// Enable or disable creating the Frontend WAF via context 'enableFrontendWaf' (defaults to true)
32+
const enableFrontendWaf = (app.node.tryGetContext("enableFrontendWaf") as boolean | undefined) ?? true;
33+
34+
let waf: FrontendWafStack | undefined;
35+
if (enableFrontendWaf) {
36+
// WAF for frontend
37+
// 2023/9: Currently, the WAF for CloudFront needs to be created in the North America region (us-east-1), so the stacks are separated
38+
// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wafv2-webacl.html
39+
waf = new FrontendWafStack(
40+
app,
41+
`${params.envPrefix}${sepHyphen}FrontendWafStack`,
42+
{
43+
env: {
44+
// account: process.env.CDK_DEFAULT_ACCOUNT,
45+
region: "us-east-1",
46+
},
47+
envPrefix: params.envPrefix,
48+
allowedIpV4AddressRanges: params.allowedIpV4AddressRanges,
49+
allowedIpV6AddressRanges: params.allowedIpV6AddressRanges,
50+
}
51+
);
52+
}
4753

4854
// The region of the LLM model called by the converse API and the region of Guardrail must be in the same region.
4955
// CustomBotStack contains Knowledge Bases is deployed in the same region as the LLM model, and source bucket must be in the same region as Knowledge Bases.
@@ -73,8 +79,8 @@ const chat = new BedrockChatStack(
7379
envPrefix: params.envPrefix,
7480
crossRegionReferences: true,
7581
bedrockRegion: params.bedrockRegion,
76-
webAclId: waf.webAclArn.value,
77-
enableIpV6: waf.ipV6Enabled,
82+
webAclId: waf ? waf.webAclArn.value : '',
83+
enableIpV6: params.enableFrontendIpv6 && params.allowedIpV6AddressRanges.length > 0,
7884
identityProviders: params.identityProviders,
7985
userPoolDomainPrefix: params.userPoolDomainPrefix,
8086
publishedApiAllowedIpV4AddressRanges:
@@ -101,7 +107,9 @@ const chat = new BedrockChatStack(
101107
allowedCountries: params.allowedCountries,
102108
}
103109
);
104-
chat.addDependency(waf);
110+
if (waf) {
111+
chat.addDependency(waf);
112+
}
105113
chat.addDependency(bedrockRegionResources);
106114

107115
cdk.Aspects.of(chat).add(new LogRetentionChecker());

cdk/cdk.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@
5252
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
5353
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
5454
"bedrockRegion": "us-east-1",
55+
"enableFrontendIpv6": true,
56+
"enableFrontendWaf": true,
5557
"allowedIpV4AddressRanges": ["0.0.0.0/1", "128.0.0.0/1"],
5658
"allowedIpV6AddressRanges": [
5759
"0000:0000:0000:0000:0000:0000:0000:0000/1",

cdk/lib/constructs/frontend.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import * as targets from "aws-cdk-lib/aws-route53-targets";
2222
import * as acm from "aws-cdk-lib/aws-certificatemanager";
2323

2424
export interface FrontendProps {
25-
readonly webAclId: string;
25+
readonly webAclId?: string;
2626
readonly accessLogBucket?: IBucket;
2727
readonly enableIpV6: boolean;
2828
/**
@@ -115,7 +115,7 @@ export class Frontend extends Construct {
115115
logBucket: props.accessLogBucket,
116116
logFilePrefix: "Frontend/",
117117
}),
118-
webAclId: props.webAclId,
118+
...(props.webAclId ? { webAclId: props.webAclId } : {}),
119119
enableIpv6: props.enableIpV6,
120120
});
121121

cdk/lib/frontend-waf-stack.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,13 @@ export class FrontendWafStack extends Stack {
1818
*/
1919
public readonly webAclArn: CfnOutput;
2020

21-
/**
22-
* Whether IPv6 is used or not
23-
*/
24-
public readonly ipV6Enabled: boolean;
25-
2621
constructor(scope: Construct, id: string, props: FrontendWafStackProps) {
2722
super(scope, id, props);
2823

2924
const sepHyphen = props.envPrefix ? "-" : "";
3025
const rules: wafv2.CfnWebACL.RuleProperty[] = [];
3126

32-
// create Ipset for ACL
27+
// Prepare IPv4 ACL
3328
if (props.allowedIpV4AddressRanges.length > 0) {
3429
const ipV4SetReferenceStatement = new wafv2.CfnIPSet(
3530
this,
@@ -54,6 +49,8 @@ export class FrontendWafStack extends Stack {
5449
},
5550
});
5651
}
52+
53+
// Prepare IPv6 ACL
5754
if (props.allowedIpV6AddressRanges.length > 0) {
5855
const ipV6SetReferenceStatement = new wafv2.CfnIPSet(
5956
this,
@@ -77,11 +74,9 @@ export class FrontendWafStack extends Stack {
7774
ipSetReferenceStatement: { arn: ipV6SetReferenceStatement.attrArn },
7875
},
7976
});
80-
this.ipV6Enabled = true;
81-
} else {
82-
this.ipV6Enabled = false;
8377
}
8478

79+
// Attach the IP-based ACL rules
8580
if (rules.length > 0) {
8681
const webAcl = new wafv2.CfnWebACL(this, "WebAcl", {
8782
defaultAction: { block: {} },

cdk/lib/utils/parameter-models.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,14 @@ function getEnvVar(name: string, defaultValue?: string): string | undefined {
5151
*/
5252
const BedrockChatParametersSchema = BaseParametersSchema.extend({
5353

54-
// IP address restrictions
54+
// Frontend WAF toggle (CloudFront scope)
55+
// Set to 'false' if you're unable to deploy resources in us-east-1
56+
enableFrontendWaf: z.boolean().default(true),
57+
58+
// Frontend (CloudFront) IPv6 toggle
59+
enableFrontendIpv6: z.boolean().default(true),
60+
61+
// IP address restrictions - enforced by WAF if enabled
5562
allowedIpV4AddressRanges: z
5663
.array(z.string())
5764
.default(["0.0.0.0/1", "128.0.0.0/1"]),
@@ -202,6 +209,8 @@ export function resolveBedrockChatParameters(
202209
envName,
203210
envPrefix,
204211
bedrockRegion: app.node.tryGetContext("bedrockRegion"),
212+
enableFrontendWaf: app.node.tryGetContext("enableFrontendWaf"),
213+
enableFrontendIpv6: app.node.tryGetContext("enableFrontendIpv6"),
205214
allowedIpV4AddressRanges: app.node.tryGetContext(
206215
"allowedIpV4AddressRanges"
207216
),

cdk/test/cdk.test.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,105 @@ describe("Bedrock Chat Stack Test", () => {
358358
});
359359
});
360360

361+
test("Frontend WAF disabled => no WebACLId on CloudFront", () => {
362+
const app = new cdk.App();
363+
364+
const bedrockRegionResourcesStack = new BedrockRegionResourcesStack(
365+
app,
366+
"BedrockRegionResourcesStackNoWaf",
367+
{
368+
env: { region: "us-east-1" },
369+
crossRegionReferences: true,
370+
}
371+
);
372+
373+
const stack = new BedrockChatStack(app, "NoWafStack", {
374+
env: { region: "us-west-2" },
375+
envName: "test",
376+
envPrefix: "test-",
377+
bedrockRegion: "us-east-1",
378+
crossRegionReferences: true,
379+
// Simulate WAF disabled: no ARN provided from bin
380+
webAclId: "",
381+
identityProviders: [],
382+
userPoolDomainPrefix: "",
383+
publishedApiAllowedIpV4AddressRanges: [""],
384+
publishedApiAllowedIpV6AddressRanges: [""],
385+
allowedIpV4AddressRanges: [""],
386+
allowedIpV6AddressRanges: [""],
387+
allowedSignUpEmailDomains: [],
388+
autoJoinUserGroups: [],
389+
selfSignUpEnabled: true,
390+
enableIpV6: true,
391+
documentBucket: bedrockRegionResourcesStack.documentBucket,
392+
enableRagReplicas: false,
393+
enableBedrockCrossRegionInference: false,
394+
enableLambdaSnapStart: true,
395+
enableBotStore: true,
396+
enableBotStoreReplicas: false,
397+
botStoreLanguage: "en",
398+
tokenValidMinutes: 60,
399+
});
400+
401+
const template = Template.fromStack(stack);
402+
403+
template.hasResourceProperties("AWS::CloudFront::Distribution", {
404+
DistributionConfig: {
405+
WebACLId: Match.absent(),
406+
},
407+
});
408+
});
409+
410+
test("Frontend WAF enabled => WebACLId set on CloudFront", () => {
411+
const app = new cdk.App();
412+
413+
const bedrockRegionResourcesStack = new BedrockRegionResourcesStack(
414+
app,
415+
"BedrockRegionResourcesStackWaf",
416+
{
417+
env: { region: "us-east-1" },
418+
crossRegionReferences: true,
419+
}
420+
);
421+
422+
const wafArn = "arn:aws:wafv2:us-east-1:123456789012:global/webacl/test/uuid";
423+
424+
const stack = new BedrockChatStack(app, "WithWafStack", {
425+
env: { region: "us-west-2" },
426+
envName: "test",
427+
envPrefix: "test-",
428+
bedrockRegion: "us-east-1",
429+
crossRegionReferences: true,
430+
webAclId: wafArn,
431+
identityProviders: [],
432+
userPoolDomainPrefix: "",
433+
publishedApiAllowedIpV4AddressRanges: [""],
434+
publishedApiAllowedIpV6AddressRanges: [""],
435+
allowedIpV4AddressRanges: [""],
436+
allowedIpV6AddressRanges: [""],
437+
allowedSignUpEmailDomains: [],
438+
autoJoinUserGroups: [],
439+
selfSignUpEnabled: true,
440+
enableIpV6: true,
441+
documentBucket: bedrockRegionResourcesStack.documentBucket,
442+
enableRagReplicas: false,
443+
enableBedrockCrossRegionInference: false,
444+
enableLambdaSnapStart: true,
445+
enableBotStore: true,
446+
enableBotStoreReplicas: false,
447+
botStoreLanguage: "en",
448+
tokenValidMinutes: 60,
449+
});
450+
451+
const template = Template.fromStack(stack);
452+
453+
template.hasResourceProperties("AWS::CloudFront::Distribution", {
454+
DistributionConfig: {
455+
WebACLId: wafArn,
456+
},
457+
});
458+
});
459+
361460
test("custom domain configuration", () => {
362461
const app = new cdk.App();
363462

cdk/test/utils/parameter-models.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ describe("resolveBedrockChatParameters", () => {
157157
"0.0.0.0/1",
158158
"128.0.0.0/1",
159159
]); // default value
160+
expect(result.enableFrontendIpv6).toBe(true); // default value
160161
});
161162

162163
test("should correctly parse all parameters when specified", () => {
@@ -166,6 +167,7 @@ describe("resolveBedrockChatParameters", () => {
166167
bedrockRegion: "us-west-2",
167168
allowedIpV4AddressRanges: ["192.168.0.0/16"],
168169
allowedIpV6AddressRanges: ["2001:db8::/32"],
170+
enableFrontendIpv6: false,
169171
identityProviders: [{ service: "google", secretName: "GoogleSecret" }],
170172
userPoolDomainPrefix: "my-app",
171173
allowedSignUpEmailDomains: ["example.com"],
@@ -188,6 +190,7 @@ describe("resolveBedrockChatParameters", () => {
188190
expect(result.bedrockRegion).toBe("us-west-2");
189191
expect(result.allowedIpV4AddressRanges).toEqual(["192.168.0.0/16"]);
190192
expect(result.allowedIpV6AddressRanges).toEqual(["2001:db8::/32"]);
193+
expect(result.enableFrontendIpv6).toBe(false);
191194
expect(result.identityProviders).toEqual([
192195
{ service: "google", secretName: "GoogleSecret" },
193196
]);
@@ -346,6 +349,7 @@ describe("resolveBedrockChatParameters", () => {
346349
"0000:0000:0000:0000:0000:0000:0000:0000/1",
347350
"8000:0000:0000:0000:0000:0000:0000:0000/1",
348351
],
352+
enableFrontendIpv6: false,
349353
identityProviders: [],
350354
userPoolDomainPrefix: "",
351355
allowedSignUpEmailDomains: [],
@@ -377,6 +381,7 @@ describe("resolveBedrockChatParameters", () => {
377381
"0000:0000:0000:0000:0000:0000:0000:0000/1",
378382
"8000:0000:0000:0000:0000:0000:0000:0000/1",
379383
]);
384+
expect(result.enableFrontendIpv6).toBe(false);
380385
expect(result.identityProviders).toEqual([]);
381386
expect(result.userPoolDomainPrefix).toBe("");
382387
expect(result.allowedSignUpEmailDomains).toEqual([]);

frontend/src/hooks/xstates/streaming.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export const streamingStateMachine = setup({
5858
actions: {
5959
reset: assign({
6060
reasoning: '',
61+
text: '',
6162
tools: [],
6263
relatedDocuments: [],
6364
}),

0 commit comments

Comments
 (0)