-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added Bedrock/cdk/guardrail/ - cdk for deploying Bedrock Guardrail (#684
- Loading branch information
Showing
10 changed files
with
553 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
name: Bedrock - Build | ||
run-name: Test IaC @ ${{ github.ref_name }} | ||
|
||
on: | ||
push: | ||
paths: | ||
- .github/workflows/bedrock-build.yml | ||
- Bedrock/cdk/** | ||
|
||
concurrency: | ||
cancel-in-progress: true | ||
group: ${{ github.workflow }} | ||
|
||
defaults: | ||
run: | ||
shell: bash | ||
working-directory: Bedrock/cdk/guardrail | ||
|
||
jobs: | ||
bedrock-guardrail: | ||
name: Test Bedrock Guardrail IaC | ||
runs-on: ubuntu-latest | ||
env: | ||
ENV_STAGE: dev | ||
steps: | ||
- uses: actions/checkout@v4 | ||
|
||
- run: make lint-python | ||
|
||
- uses: actions/setup-node@v4 | ||
with: | ||
node-version: 22 | ||
|
||
- name: Set up aws-cdk | ||
run: make install-cdk | ||
|
||
- name: Print deployment environment | ||
run: | | ||
echo "INFO: cdk version: $(cdk --version)" | ||
echo "INFO: node version: $(node --version)" | ||
echo "INFO: npm version: $(npm --version)" | ||
echo "INFO: python3 version: $(python3 --version)" | ||
- name: Run cdk synth | ||
run: make synth-guardrail |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
export AWS_DEFAULT_REGION ?= ap-southeast-2 | ||
export CDK_DEFAULT_REGION ?= ap-southeast-2 | ||
export ENV_STAGE ?= dev | ||
|
||
APP_NAME=$(shell grep -m 1 AppName environment/$(ENV_STAGE).yml | cut -c 10-) | ||
|
||
install-cdk: | ||
npm install -g aws-cdk | ||
python3 -m pip install -U pip | ||
pip3 install -r requirements.txt | ||
|
||
synth-guardrail: | ||
cdk synth $(APP_NAME)-BedrockGuardrail -c env=$(ENV_STAGE) | ||
|
||
diff-guardrail: | ||
cdk diff $(APP_NAME)-BedrockGuardrail -c env=$(ENV_STAGE) | ||
|
||
deploy-guardrail: | ||
cdk deploy $(APP_NAME)-BedrockGuardrail -c env=$(ENV_STAGE) $(APP_NAME) --require-approval never | ||
|
||
destroy-guardrail: | ||
cdk destroy $(APP_NAME)-BedrockGuardrail -f -c env=$(ENV_STAGE) | ||
|
||
test-cdk: | ||
pip3 install -r requirements-dev.txt && \ | ||
python3 -m pytest . | ||
|
||
test-code: | ||
python3 tests/test_guardrail.py | ||
|
||
pre-commit: format-python lint-python lint-yaml | ||
|
||
format-python: | ||
black --line-length=100 **.py */**.py | ||
|
||
lint-python: | ||
pip3 install flake8 | ||
flake8 --ignore E501,F541,W503,W605 **.py */**.py | ||
|
||
lint-yaml: | ||
yamllint -c .github/linters/.yaml-lint.yml -f parsable . | ||
|
||
clean: | ||
rm -rf cdk.out __pycache__ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
#!/usr/bin/env python3 | ||
from os.path import dirname, join, realpath | ||
|
||
import yaml | ||
from aws_cdk import App, Environment, Tags | ||
from bedrock_guardrail import BedrockGuardrailStack | ||
|
||
ENV_DIR = join(dirname(realpath(__file__)), "environment") | ||
|
||
|
||
def main(): | ||
app = App() | ||
|
||
ENV_NAME = app.node.try_get_context("env") or "dev" | ||
|
||
with open(join(ENV_DIR, f"{ENV_NAME}.yml"), "r") as stream: | ||
yaml_data = yaml.safe_load(stream) | ||
config = yaml_data if yaml_data is not None else {} | ||
|
||
app_name = config["AppName"] | ||
|
||
stack = BedrockGuardrailStack( | ||
scope=app, | ||
id=f"{app_name}-BedrockGuardrail", | ||
config=config, | ||
env=Environment(account=config["Account"], region=config["Region"]), | ||
) | ||
|
||
for key, value in config["Tags"].items(): | ||
Tags.of(stack).add(key, value) | ||
|
||
app.synth() | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
from aws_cdk.aws_bedrock import CfnGuardrail, CfnGuardrailVersion | ||
from aws_cdk import CfnOutput, Stack | ||
from constructs import Construct | ||
|
||
|
||
class BedrockGuardrailStack(Stack): | ||
def __init__(self, scope: Construct, id: str, config: dict, **kwargs) -> None: | ||
super().__init__(scope, id, **kwargs) | ||
self.config = config | ||
app_name = self.config["AppName"].lower() | ||
|
||
params = { | ||
"blocked_input_messaging": self.config["BedrockGuardrail"]["blocked_input_messaging"], | ||
"blocked_outputs_messaging": self.config["BedrockGuardrail"][ | ||
"blocked_outputs_messaging" | ||
], | ||
} | ||
|
||
# (1) Content filter policies | ||
content_policy_config = self.create_content_policy_config() | ||
if content_policy_config: | ||
params["content_policy_config"] = content_policy_config | ||
|
||
# (2) Contextual grounding policies | ||
contextual_grounding_policy_config = self.create_contextual_grounding_policy_config() | ||
if contextual_grounding_policy_config: | ||
params["contextual_grounding_policy_config"] = contextual_grounding_policy_config | ||
|
||
# (3) Sensitive information policies | ||
sensitive_information_policy_config = self.create_sensitive_information_policy_config() | ||
if sensitive_information_policy_config: | ||
params["sensitive_information_policy_config"] = sensitive_information_policy_config | ||
|
||
# (4) Topic policies | ||
topic_policy_config = self.create_topic_policy_config() | ||
if topic_policy_config: | ||
params["topic_policy_config"] = topic_policy_config | ||
|
||
# (5) Word filters | ||
word_policy_config = self.create_word_policy_config() | ||
if word_policy_config: | ||
params["word_policy_config"] = word_policy_config | ||
|
||
guardrail = CfnGuardrail( | ||
self, | ||
id=f"{app_name}-guardrail", | ||
name=f"{app_name}-guardrail", | ||
description=f"{app_name} Guardrail", | ||
# kms_key_arn="kmsKeyArn", TODO | ||
**params, | ||
) | ||
|
||
guardrail_version = CfnGuardrailVersion( | ||
self, | ||
id=f"{app_name}-guardrail-version", | ||
description=f"{app_name} Guardrail Version", | ||
guardrail_identifier=guardrail.attr_guardrail_id, | ||
) | ||
CfnOutput(self, "GuardrailIdentifier", value=guardrail.attr_guardrail_id) | ||
CfnOutput(self, "GuardrailVersion", value=guardrail_version.attr_version) | ||
|
||
def create_content_policy_config(self) -> CfnGuardrail.ContentPolicyConfigProperty: | ||
""" | ||
Adjust filter strengths to block input prompts or model responses containing harmful content. | ||
""" | ||
filters_config = [ | ||
CfnGuardrail.ContentFilterConfigProperty( | ||
input_strength=v["input_strength"], | ||
output_strength=v["output_strength"], | ||
type=k, | ||
) | ||
for k, v in self.config["BedrockGuardrail"]["content_policy_config"].items() | ||
] | ||
return CfnGuardrail.ContentPolicyConfigProperty(filters_config=filters_config) | ||
|
||
def create_contextual_grounding_policy_config( | ||
self, | ||
) -> CfnGuardrail.ContextualGroundingPolicyConfigProperty: | ||
""" | ||
Use contextual grounding check to filter hallucinations in responses | ||
""" | ||
filters_config = [ | ||
CfnGuardrail.ContextualGroundingFilterConfigProperty( | ||
threshold=v["threshold"], | ||
type=k, | ||
) | ||
for k, v in self.config["BedrockGuardrail"][ | ||
"contextual_grounding_policy_config" | ||
].items() | ||
] | ||
return CfnGuardrail.ContextualGroundingPolicyConfigProperty(filters_config=filters_config) | ||
|
||
def create_sensitive_information_policy_config( | ||
self, | ||
) -> CfnGuardrail.SensitiveInformationPolicyConfigProperty: | ||
""" | ||
Block or mask sensitive information such as personally identifiable information (PII) | ||
or custom regex in user inputs and model responses. | ||
""" | ||
params = {} | ||
pii_entities_config = [ | ||
CfnGuardrail.PiiEntityConfigProperty(action=v, type=k) | ||
for k, v in self.config["BedrockGuardrail"]["sensitive_information_policy_config"][ | ||
"pii_entities_config" | ||
].items() | ||
] | ||
if pii_entities_config: | ||
params["pii_entities_config"] = pii_entities_config | ||
|
||
regexes_config = [ | ||
CfnGuardrail.RegexConfigProperty( | ||
action=item["action"], | ||
name=item["name"], | ||
pattern=item["pattern"], | ||
description=item.get("description", ""), | ||
) | ||
for item in self.config["BedrockGuardrail"]["sensitive_information_policy_config"][ | ||
"regexes_config" | ||
] | ||
] | ||
if regexes_config: | ||
params["regexes_config"] = regexes_config | ||
|
||
if pii_entities_config or regexes_config: | ||
return CfnGuardrail.SensitiveInformationPolicyConfigProperty(**params) | ||
return None | ||
|
||
def create_topic_policy_config(self) -> CfnGuardrail.TopicPolicyConfigProperty: | ||
""" | ||
Block denied topics to remove harmful content | ||
""" | ||
topics_config = [ | ||
CfnGuardrail.TopicConfigProperty( | ||
definition=item["definition"], | ||
name=item["name"], | ||
type="DENY", | ||
examples=item.get("examples", []), | ||
) | ||
for item in self.config["BedrockGuardrail"]["topic_policy_config"] | ||
] | ||
if topics_config: | ||
return CfnGuardrail.TopicPolicyConfigProperty(topics_config=topics_config) | ||
return None | ||
|
||
def create_word_policy_config(self) -> CfnGuardrail.WordPolicyConfigProperty: | ||
""" | ||
Configure filters to block undesirable words, phrases, and profanity. Such words can include offensive terms, competitor names etc. | ||
""" | ||
params = {} | ||
|
||
managed_word_lists_config = ( | ||
[CfnGuardrail.ManagedWordsConfigProperty(type="PROFANITY")] | ||
if self.config["BedrockGuardrail"]["word_policy_config"].get( | ||
"managed_word_lists_config" | ||
) | ||
== "PROFANITY" | ||
else [] | ||
) | ||
if managed_word_lists_config: | ||
params["managed_word_lists_config"] = managed_word_lists_config | ||
|
||
words_config = [ | ||
CfnGuardrail.WordConfigProperty(text=item) | ||
for item in self.config["BedrockGuardrail"]["word_policy_config"].get( | ||
"words_config", [] | ||
) | ||
] | ||
if words_config: | ||
params["words_config"] = words_config | ||
|
||
if managed_word_lists_config or words_config: | ||
return CfnGuardrail.WordPolicyConfigProperty(**params) | ||
return None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
{ | ||
"app": "python3 app.py", | ||
"watch": { | ||
"include": ["**"], | ||
"exclude": [ | ||
"README.md", | ||
"cdk*.json", | ||
"requirements*.txt", | ||
"**/__init__.py", | ||
"**/__pycache__", | ||
"tests" | ||
] | ||
}, | ||
"context": { | ||
"@aws-cdk/aws-lambda:recognizeLayerVersion": true, | ||
"@aws-cdk/core:checkSecretUsage": true, | ||
"@aws-cdk/core:target-partitions": ["aws", "aws-cn"], | ||
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, | ||
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, | ||
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, | ||
"@aws-cdk/aws-iam:minimizePolicies": true, | ||
"@aws-cdk/core:validateSnapshotRemovalPolicy": true, | ||
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, | ||
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, | ||
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, | ||
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true, | ||
"@aws-cdk/core:enablePartitionLiterals": true, | ||
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, | ||
"@aws-cdk/aws-iam:standardizedServicePrincipals": true, | ||
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, | ||
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, | ||
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, | ||
"@aws-cdk/aws-route53-patters:useCertificate": true, | ||
"@aws-cdk/customresources:installLatestAwsSdkDefault": false, | ||
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, | ||
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, | ||
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, | ||
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, | ||
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, | ||
"@aws-cdk/aws-redshift:columnId": true, | ||
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, | ||
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, | ||
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, | ||
"@aws-cdk/aws-kms:aliasNameRef": true, | ||
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, | ||
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true, | ||
"@aws-cdk/aws-efs:denyAnonymousAccess": true, | ||
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, | ||
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, | ||
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, | ||
"@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, | ||
"@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, | ||
"@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, | ||
"@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true, | ||
"@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true, | ||
"@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true, | ||
"@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true, | ||
"@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true, | ||
"@aws-cdk/aws-eks:nodegroupNameAttribute": true | ||
} | ||
} |
Oops, something went wrong.