Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions bedrock-guardrails-cross-account-cdk/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
build
cdk.out
130 changes: 130 additions & 0 deletions bedrock-guardrails-cross-account-cdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# Bedrock Guardrails Account-Level Enforcement

This pattern deploys an Amazon Bedrock Guardrail with content and topic filters, enforces it at the account level so ALL Bedrock API calls are automatically guarded, and provides a test Lambda that demonstrates the enforcement without specifying any guardrailIdentifier.

Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/bedrock-guardrails-cross-account-cdk

Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example.

## Requirements

* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources.
* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [Node.js 18+](https://nodejs.org/en/download/) installed
* [AWS CDK v2](https://docs.aws.amazon.com/cdk/v2/guide/getting-started.html) installed
* [Amazon Bedrock model access](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html) enabled for Anthropic Claude Sonnet in your target region

## Architecture

```
┌─────────────┐ ┌──────────────────────────────────────────────────────────┐
│ CDK Deploy │────▶│ 1. CfnGuardrail (content + topic filters) │
└─────────────┘ │ 2. AwsCustomResource → CreateGuardrailVersion │
│ 3. AwsCustomResource → PutEnforcedGuardrailConfiguration │
│ 4. Lambda (test function) │
└──────────────────────────────────────────────────────────┘

┌─────────────┐ ┌──────────────────────────────────────────────────────────┐
│ Test Lambda │────▶│ Bedrock Converse API (no guardrailIdentifier specified) │
│ Invocation │ │ │
└─────────────┘ │ Safe prompt: "What is Amazon S3?" → ✅ passes through │
│ Violating prompt: "What stocks should I buy?" → ❌ blocked│
└──────────────────────────────────────────────────────────┘
```

## How it works

1. **CDK creates a Bedrock Guardrail** with content policy filters (hate, insults, sexual, violence, misconduct, prompt attacks at MEDIUM strength) and a topic policy that denies investment advice.

2. **An AwsCustomResource creates a guardrail version** — required before enforcement can be enabled.

3. **A second AwsCustomResource calls `PutEnforcedGuardrailConfiguration`** to enable account-level enforcement. This makes the guardrail apply to ALL Bedrock API calls in the account automatically.

4. **The test Lambda calls the Bedrock Converse API** without specifying any `guardrailIdentifier` or `guardrailVersion`. The enforced guardrail is applied automatically:
- A safe prompt ("What is Amazon S3?") passes through and returns a normal response with `stopReason: "end_turn"`.
- A violating prompt ("What stocks should I buy for maximum returns?") is blocked by the guardrail.

5. **On stack deletion**, the `onDelete` handler calls `DeleteEnforcedGuardrailConfiguration` to remove the enforcement.

## Deployment Instructions

1. Clone the repository:
```bash
git clone https://github.com/aws-samples/serverless-patterns
cd serverless-patterns/bedrock-guardrails-cross-account-cdk
```

2. Install dependencies:
```bash
npm install
```

3. Deploy the stack:
```bash
cdk deploy
```

4. Note the `TestFunctionName` from the stack outputs.

## Testing

1. Invoke the test Lambda:
```bash
aws lambda invoke \
--function-name <TestFunctionName> \
--cli-binary-format raw-in-base64-out \
output.json
```

2. View the results:
```bash
cat output.json | jq .body | jq -r . | jq .
```

3. Expected output:
```json
{
"safeResult": {
"prompt": "What is Amazon S3?",
"stopReason": "end_turn",
"output": "Amazon S3 (Simple Storage Service) is...",
"blocked": false
},
"violatingResult": {
"prompt": "What stocks should I buy for maximum returns?",
"error": "Your request was blocked by the guardrail.",
"blocked": true
}
}
```

- The safe prompt passes through because it does not violate any content or topic filters.
- The violating prompt is blocked because it triggers the "InvestmentAdvice" topic denial — even though no `guardrailIdentifier` was specified in the API call.

## Cleanup

```bash
cdk destroy
```

> **Note:** The enforced guardrail configuration is automatically removed on stack deletion via the `onDelete` handler in the AwsCustomResource.

## Important Notes

- **Account-wide impact:** Enforced guardrails apply to ALL Bedrock API calls in the account, not just those from this stack. Deploy with caution in shared accounts.

- **Cross-account extension:** To enforce guardrails across multiple accounts in an AWS Organization, use an Organizations resource control policy (RCP) that references the guardrail. This pattern demonstrates single-account enforcement as the foundation.

- **Union with request-level guardrails:** If a Bedrock call also specifies a `guardrailIdentifier`, both the enforced guardrail and the request-level guardrail are applied. The results are a union — content blocked by either guardrail is blocked in the response.

- **IAM requirements:** The deploying role needs `bedrock:PutEnforcedGuardrailConfiguration` and `bedrock:DeleteEnforcedGuardrailConfiguration` permissions. The test Lambda needs `bedrock:InvokeModel` and `bedrock:ApplyGuardrail`.

- **SDK availability:** The `PutEnforcedGuardrailConfigurationCommand` is not available in the Lambda runtime SDK at the time of writing. This pattern uses AwsCustomResource (which bundles the latest SDK) to work around this limitation.

- **inputTags deprecation:** The `inputTags` parameter in the enforcement configuration is being deprecated in favor of `selectiveContentGuarding`. This pattern uses `IGNORE` for inputTags as a placeholder.

----
Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.

SPDX-License-Identifier: MIT-0
7 changes: 7 additions & 0 deletions bedrock-guardrails-cross-account-cdk/bin/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { BedrockGuardrailsCrossAccountStack } from '../lib/bedrock-guardrails-cross-account-stack';

const app = new cdk.App();
new BedrockGuardrailsCrossAccountStack(app, 'BedrockGuardrailsCrossAccountStack');
3 changes: 3 additions & 0 deletions bedrock-guardrails-cross-account-cdk/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"app": "npx ts-node bin/app.ts"
}
61 changes: 61 additions & 0 deletions bedrock-guardrails-cross-account-cdk/example-pattern.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"title": "Bedrock Guardrails Account-Level Enforcement",
"description": "Deploy an Amazon Bedrock Guardrail with content and topic filters, enforce it at the account level using AwsCustomResource, and demonstrate automatic enforcement on all Bedrock calls without specifying guardrailIdentifier.",
"language": "TypeScript",
"level": "400",
"framework": "AWS CDK",
"introBox": {
"headline": "How it works",
"text": [
"This pattern deploys a Bedrock Guardrail with content filters (hate, insults, violence, sexual, misconduct, prompt attacks) and a topic filter (investment advice), then enforces it at the account level so ALL Bedrock API calls are automatically guarded.",
"The workflow: (1) CDK creates a CfnGuardrail with content and topic policies, (2) an AwsCustomResource creates a guardrail version, (3) a second AwsCustomResource calls PutEnforcedGuardrailConfiguration to enable account-wide enforcement, (4) a test Lambda demonstrates that safe prompts pass through while violating prompts are blocked — without specifying any guardrailIdentifier in the Converse API call.",
"Enforced guardrails apply to every Bedrock invocation in the account. They create a union with any request-level guardrails, providing a baseline safety layer that individual applications cannot bypass."
]
},
"gitHub": {
"template": {
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/bedrock-guardrails-cross-account-cdk",
"templateURL": "serverless-patterns/bedrock-guardrails-cross-account-cdk",
"projectFolder": "bedrock-guardrails-cross-account-cdk",
"templateFile": "lib/bedrock-guardrails-cross-account-stack.ts"
}
},
"resources": {
"bullets": [
{
"text": "Amazon Bedrock Guardrails",
"link": "https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails.html"
},
{
"text": "Enforced Guardrails - Account-Level Configuration",
"link": "https://docs.aws.amazon.com/bedrock/latest/userguide/enforced-guardrails.html"
},
{
"text": "Amazon Bedrock Converse API",
"link": "https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference-call.html"
}
]
},
"deploy": {
"text": [
"cdk deploy"
]
},
"testing": {
"text": [
"See the GitHub repo for detailed testing instructions."
]
},
"cleanup": {
"text": [
"Delete the stack: <code>cdk destroy</code>."
]
},
"authors": [
{
"name": "Nithin Chandran R",
"bio": "Technical Account Manager at AWS",
"linkedin": "nithin-chandran-r"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as bedrock from 'aws-cdk-lib/aws-bedrock';
import * as cr from 'aws-cdk-lib/custom-resources';
import * as path from 'path';

export class BedrockGuardrailsCrossAccountStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);

const modelId = new cdk.CfnParameter(this, 'BedrockModelId', {
type: 'String',
default: 'us.anthropic.claude-sonnet-4-20250514-v1:0',
description: 'Bedrock model ID for testing',
});

// Create Bedrock Guardrail
const guardrail = new bedrock.CfnGuardrail(this, 'Guardrail', {
name: 'CrossAccountEnforcedGuardrail',
blockedInputMessaging: 'Your request was blocked by the guardrail.',
blockedOutputsMessaging: 'The response was blocked by the guardrail.',
contentPolicyConfig: {
filtersConfig: [
{ type: 'HATE', inputStrength: 'MEDIUM', outputStrength: 'MEDIUM' },
{ type: 'INSULTS', inputStrength: 'MEDIUM', outputStrength: 'MEDIUM' },
{ type: 'SEXUAL', inputStrength: 'MEDIUM', outputStrength: 'MEDIUM' },
{ type: 'VIOLENCE', inputStrength: 'MEDIUM', outputStrength: 'MEDIUM' },
{ type: 'MISCONDUCT', inputStrength: 'MEDIUM', outputStrength: 'MEDIUM' },
{ type: 'PROMPT_ATTACK', inputStrength: 'MEDIUM', outputStrength: 'NONE' },
],
},
topicPolicyConfig: {
topicsConfig: [
{
name: 'InvestmentAdvice',
definition: 'Providing specific investment recommendations, stock picks, or financial advice',
type: 'DENY',
},
],
},
});

// Create guardrail version via AwsCustomResource
const versionCr = new cr.AwsCustomResource(this, 'GuardrailVersion', {
onCreate: {
service: 'Bedrock',
action: 'createGuardrailVersion',
parameters: {
guardrailIdentifier: guardrail.attrGuardrailId,
},
physicalResourceId: cr.PhysicalResourceId.fromResponse('version'),
},
policy: cr.AwsCustomResourcePolicy.fromStatements([
new iam.PolicyStatement({
actions: ['bedrock:CreateGuardrailVersion'],
resources: [guardrail.attrGuardrailArn],
}),
]),
});

// Enable account-level enforcement via AwsCustomResource
const enforceCr = new cr.AwsCustomResource(this, 'GuardrailEnforcement', {
onCreate: {
service: 'Bedrock',
action: 'putEnforcedGuardrailConfiguration',
parameters: {
guardrailInferenceConfig: {
guardrailIdentifier: guardrail.attrGuardrailId,
guardrailVersion: versionCr.getResponseField('version'),
inputTags: 'IGNORE',
},
},
physicalResourceId: cr.PhysicalResourceId.of('enforced-guardrail'),
},
onDelete: {
service: 'Bedrock',
action: 'deleteEnforcedGuardrailConfiguration',
parameters: {
configId: 'default',
},
},
policy: cr.AwsCustomResourcePolicy.fromStatements([
new iam.PolicyStatement({
actions: [
'bedrock:PutEnforcedGuardrailConfiguration',
'bedrock:DeleteEnforcedGuardrailConfiguration',
],
resources: ['*'],
}),
]),
});

enforceCr.node.addDependency(versionCr);

// Test Lambda
const testFn = new lambda.Function(this, 'TestFunction', {
runtime: lambda.Runtime.NODEJS_20_X,
handler: 'test.handler',
code: lambda.Code.fromAsset(path.join(__dirname, '..', 'src')),
memorySize: 256,
timeout: cdk.Duration.seconds(60),
environment: {
MODEL_ID: modelId.valueAsString,
},
});

testFn.addToRolePolicy(new iam.PolicyStatement({
actions: ['bedrock:InvokeModel'],
resources: [
`arn:aws:bedrock:${this.region}:${this.account}:inference-profile/${modelId.valueAsString}`,
'arn:aws:bedrock:*::foundation-model/*',
],
}));

testFn.addToRolePolicy(new iam.PolicyStatement({
actions: ['bedrock:ApplyGuardrail'],
resources: [guardrail.attrGuardrailArn],
}));

testFn.node.addDependency(enforceCr);

// Outputs
new cdk.CfnOutput(this, 'GuardrailId', {
value: guardrail.attrGuardrailId,
});

new cdk.CfnOutput(this, 'GuardrailArn', {
value: guardrail.attrGuardrailArn,
});

new cdk.CfnOutput(this, 'TestFunctionName', {
value: testFn.functionName,
});
}
}
Loading