Skip to content

Commit

Permalink
Added Bedrock/cdk/guardrail/ - cdk for deploying Bedrock Guardrail (#684
Browse files Browse the repository at this point in the history
)
  • Loading branch information
kyhau committed Sep 20, 2024
1 parent 5a6f033 commit 5b8eb5b
Show file tree
Hide file tree
Showing 10 changed files with 553 additions and 10 deletions.
45 changes: 45 additions & 0 deletions .github/workflows/bedrock-build.yml
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
44 changes: 44 additions & 0 deletions Bedrock/cdk/guardrail/Makefile
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__
36 changes: 36 additions & 0 deletions Bedrock/cdk/guardrail/app.py
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()
173 changes: 173 additions & 0 deletions Bedrock/cdk/guardrail/bedrock_guardrail.py
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
61 changes: 61 additions & 0 deletions Bedrock/cdk/guardrail/cdk.json
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
}
}
Loading

0 comments on commit 5b8eb5b

Please sign in to comment.