Skip to content

Commit 9358612

Browse files
committed
Some security improvements and fixes
1 parent 6bde101 commit 9358612

File tree

11 files changed

+93
-71
lines changed

11 files changed

+93
-71
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
In this repository, we present a solution that harnesses the power of Generative AI to streamline the user onboarding process for financial services through a digital assistant. Onboarding new customers in the banking is a crucial step in the customer journey, involving a series of activities designed to fulfill Know-Your-Customer (KYC) requirements, conduct necessary verifications, and introduce them to the bank's products or services. Traditionally, user onboarding has been a tedious and heavily manual process. Our solution provides practical guidance on addressing this challenge by leveraging a Generative AI assistant on AWS.
44

5-
Amazon Bedrock is a fully managed service that offers a choice of high-performing foundation models (FMs) from leading AI companies like AI21 Labs, Anthropic, Cohere, Meta, Mistral AI, Stability AI, and Amazon via a single API, along with a broad set of capabilities you need to build generative AI applications with security, privacy, and responsible AI. Using Anthropic Claude 3 Haiku on Amazon Bedrock, we build a digital assistant that automates paperwork, identity verifications, and engages customers through conversational interactions, called Amazon Penny. As a result, customers can be onboarded in a matter of minutes through secure, automated workflows.
5+
Amazon Bedrock is a fully managed service that offers a choice of high-performing foundation models (FMs) from leading AI companies like AI21 Labs, Anthropic, Cohere, Meta, Mistral AI, Stability AI, and Amazon via a single API, along with a broad set of capabilities you need to build generative AI applications with security, privacy, and responsible AI. Using Anthropic Claude 3.5 Sonnet on Amazon Bedrock, we build a digital assistant that automates paperwork, identity verifications, and engages customers through conversational interactions, called Amazon Penny. As a result, customers can be onboarded in a matter of minutes through secure, automated workflows.
66

77
## Architecture
88

@@ -13,9 +13,9 @@ The flow of the application is as follows:
1313

1414
1. Users access the front-end website hosted within the AWS Amplify.
1515

16-
2. The website invokes an endpoint to interact with the digital assistant, Penny, which is containerized and deployed in AWS Fargate.
16+
2. The website invokes an Amazon CloudFront endpoint to interact with the digital assistant, Penny, which is containerized and deployed in AWS Fargate.
1717

18-
3. The digital assistant uses a custom Langchain Agent to answer questions on the bank's products and services and orchestrate the onboarding flow. The Large Language Model (LLM) used by the agent is Anthropic Claude 3 Haiku, provided by Amazon Bedrock.
18+
3. The digital assistant uses a custom Langchain Agent to answer questions on the bank's products and services and orchestrate the onboarding flow. The Large Language Model (LLM) used by the agent is Anthropic Claude 3.5 Sonnet, provided by Amazon Bedrock.
1919

2020
4. If the user asks a general question related to the bank's products or services, the agent will utilize a custom Langchain Tool called the Product Search Tool. This tool uses Amazon Kendra linked with an S3 data source which contains the bank's data.
2121

@@ -41,7 +41,7 @@ Identify the AWS Account where you would like to deploy this solution and ensure
4141
* Install [NodeJS](https://nodejs.org/en) and [Docker](https://www.docker.com/).
4242

4343
In your chosen AWS Account, complete the following steps:
44-
* Ensure that the [Anthropic Claude 3 Haiku Model is enabled in Amazon Bedrock by adding model access](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html#model-access-add).
44+
* Ensure that the [Anthropic Claude 3.5 Sonnet Model is enabled in Amazon Bedrock by adding model access](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html#model-access-add).
4545
* Ensure that docker is running. This can be done by running the command `sudo docker info`. If Docker is running, information about Docker is displayed.
4646

4747
> Note: Under a set of assumptions made on a monthly basis, running this workload would have an estimated hourly cost of around $1.34. Make sure to check the pricing details for each individual service to understand the costs you may be charged for different usage tiers and resource configurations

api/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
llm/app/__pycache__/
2+
llm/app/penny/__pycache__/
3+
llm/venv/

api/lambdas/verify-face.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,6 @@ def compare_faces(id_file_name, selfie_file_name):
2626
for faceMatch in response['FaceMatches']:
2727
position = faceMatch['Face']['BoundingBox']
2828
similarity = str(faceMatch['Similarity'])
29-
print('The face at ' +
30-
str(position['Left']) + ' ' +
31-
str(position['Top']) +
32-
' matches with ' + similarity + '% confidence')
33-
34-
35-
print(response['FaceMatches'])
3629

3730
return len(response['FaceMatches'])
3831

@@ -47,13 +40,12 @@ def main(event, context):
4740
"Access-Control-Allow-Methods": "POST"
4841
}
4942

50-
print(event)
5143
id_file_name = event["id_file_name"]
5244
selfie_file_name = event["selfie_file_name"]
45+
body = ''
5346

5447
try:
5548
face_matches = compare_faces(id_file_name, selfie_file_name)
56-
print("Compared faces:" + str(face_matches))
5749

5850
if face_matches > 0:
5951
body = 'Face match verified'

api/lambdas/verify-id.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,10 @@ def extract_text(id_file_name):
2929
if "ValueDetection" in str(key):
3030
curr_val = str(val['Text'])
3131
id_field_values[curr_type] = curr_val
32-
print(id_field_values)
3332

34-
print(id_field_values)
3533
return id_field_values
3634

3735
def compare_fields(id_field_values, required_field_values):
38-
3936
for key, val in required_field_values.items():
4037
if not ((key in id_field_values) and (val.lower() == id_field_values[key].lower())):
4138
return "The details you provided for " + key + " do not match your ID"
@@ -52,16 +49,13 @@ def main(event, context):
5249
"Access-Control-Allow-Origin": "*",
5350
"Access-Control-Allow-Methods": "POST"
5451
}
52+
body= ''
5553

56-
print(event)
57-
5854
id_file_name = event["file_name"]
5955
required_field_values = event["required_field_values"]
6056

6157
try:
6258
id_file_values = extract_text(id_file_name)
63-
print("Extracted info:" + str(id_file_values))
64-
6559
body = compare_fields(id_file_values, required_field_values)
6660

6761
except Exception as e:

api/llm/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM public.ecr.aws/docker/library/python:3.9
1+
FROM public.ecr.aws/docker/library/python:3.12
22

33
WORKDIR /code
44

api/llm/app/main.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@
3535
use_tools=True
3636
)
3737
llm = BedrockChat(
38-
model_id='anthropic.claude-3-haiku-20240307-v1:0',
38+
model_id='anthropic.claude-3-5-sonnet-20240620-v1:0',
3939
client=bedrock,
4040
model_kwargs={
41-
"temperature": 0,
41+
"temperature": 0.5,
4242
"top_k": 250,
4343
"top_p": 0.999,
4444
"stop_sequences": ["\\n\\nHuman:"]
@@ -61,7 +61,7 @@ async def question(request: Request) -> Response:
6161
response = agent.step()
6262
return JSONResponse(content={"message": response})
6363

64-
@app.post("/uploadId")
64+
@app.post("/uploadDoc")
6565
async def id(file: UploadFile = File(...)) -> Response:
6666
try:
6767
contents = file.file.read()

api/llm/app/penny/tools.py

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,22 @@
33
import boto3
44
import json
55
from langchain.agents import Tool
6-
from langchain.embeddings.sentence_transformer import SentenceTransformerEmbeddings
76
from langchain_community.chat_models import BedrockChat
87
from langchain_community.retrievers import AmazonKendraRetriever
98
from pydantic import EmailStr, Field
109
from email_validator import validate_email, EmailNotValidError
1110
import requests
1211
import os
1312

14-
#Bedrock client initialization
1513
bedrock = boto3.client(service_name='bedrock-runtime')
16-
#vector db init
17-
modeled = SentenceTransformerEmbeddings(model_name="all-mpnet-base-v2")
18-
1914
API_ENDPOINT = os.environ["apiEndpoint"]
2015

2116
def setup_knowledge_base():
2217
"""
2318
We assume that the product knowledge base is simply a text file.
2419
"""
2520
llm = BedrockChat(
26-
model_id='anthropic.claude-3-haiku-20240307-v1:0',
21+
model_id='anthropic.claude-3-5-sonnet-20240620-v1:0',
2722
client=bedrock,
2823
model_kwargs={
2924
"temperature": 1,
@@ -133,7 +128,7 @@ def _run(self, input=""):
133128
}
134129
r = requests.post(url=url, json=body, timeout=300)
135130

136-
if r.json()["statusCode"] != 200:
131+
if r.status_code != 200:
137132
return "Respond that our onboarding service is currently unavailable and to try again later."
138133

139134
print(r.json())
@@ -142,18 +137,6 @@ def _run(self, input=""):
142137
def _arun(self, query: str):
143138
raise NotImplementedError("This tool does not support async")
144139

145-
146-
class ask_question(BaseTool):
147-
name = "AskUser"
148-
description = "Use this tool to ask something to the user. " \
149-
"It takes the question you want to ask as the input" \
150-
"It will return a question that you can ask"
151-
152-
def _run(self, question):
153-
return "ask user " + question
154-
155-
def _arun(self, query: str):
156-
raise NotImplementedError("This tool does not support async")
157140

158141
class finish_onboarding(BaseTool):
159142
name = "SaveData"
@@ -193,14 +176,8 @@ def get_tools():
193176
# we only use one tool for now, but this is highly extensible!
194177
knowledge_base = setup_knowledge_base()
195178
tools = [
196-
Tool(
197-
name="ProductSearch",
198-
func=knowledge_base.run,
199-
description="useful for when you need to answer any question",
200-
),
201-
#product_search
179+
product_search(),
202180
email_validator(),
203-
ask_question(),
204181
document_verification(),
205182
selfie_verification(),
206183
finish_onboarding()

api/llm/requirements.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
fastapi==0.109.1
22
uvicorn==0.24.0.post1
3-
pydantic==2.5.2
3+
pydantic>=2.7.4,<3.0.0
44
langchain==0.2.16
55
langchain-community==0.2.5
6+
langchain-core>=0.2.40,<0.3.0
67
torch==2.4.1
78
torchvision==0.19.1
89
sentence-transformers==2.2.2
9-
faiss-cpu==1.7.4
10-
email-validator==2.1.0
11-
requests==2.32.0
10+
email-validator==2.1.1
11+
requests==2.31.0
1212
python-multipart==0.0.7
1313
boto3==1.28.65
1414
botocore==1.31.65

infra/lib/infra-stack.ts

Lines changed: 72 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export class PennyInfraStack extends cdk.Stack {
8484

8585
createLambda_verifyId = () => {
8686
this.verifyIdLambda = new lambda.Function(this, 'VerifyId', {
87-
runtime: lambda.Runtime.PYTHON_3_8,
87+
runtime: lambda.Runtime.PYTHON_3_11,
8888
code: lambda.Code.fromAsset('../api/lambdas'),
8989
handler: 'verify-id.main',
9090
timeout: Duration.seconds(10),
@@ -103,7 +103,7 @@ export class PennyInfraStack extends cdk.Stack {
103103

104104
createLambda_verifyFace = () => {
105105
this.verifyFaceLambda = new lambda.Function(this, 'VerifyFace', {
106-
runtime: lambda.Runtime.PYTHON_3_8,
106+
runtime: lambda.Runtime.PYTHON_3_11,
107107
code: lambda.Code.fromAsset('../api/lambdas'),
108108
handler: 'verify-face.main',
109109
timeout: Duration.seconds(10),
@@ -115,15 +115,28 @@ export class PennyInfraStack extends cdk.Stack {
115115
this.verifyFaceLambda.addToRolePolicy(new iam.PolicyStatement(
116116
{
117117
effect: iam.Effect.ALLOW,
118-
actions: ['rekognition:CompareFaces', 's3:Get*', 's3:List*', 's3:Describe*'],
119-
resources: ['*']
118+
actions: ['s3:Get*', 's3:List*', 's3:Describe*'],
119+
resources: [
120+
`arn:aws:s3:::${this.idBucket.bucketName}`,
121+
`arn:aws:s3:::${this.idBucket.bucketName}/*`
122+
]
123+
}),
124+
)
125+
126+
this.verifyFaceLambda.addToRolePolicy(new iam.PolicyStatement(
127+
{
128+
effect: iam.Effect.ALLOW,
129+
actions: ['rekognition:CompareFaces'],
130+
resources: [
131+
`*`,
132+
]
120133
}),
121134
)
122135
}
123136

124137
createLambda_getAccount = () => {
125138
this.getAccountLambda = new lambda.Function(this, 'GetAccount', {
126-
runtime: lambda.Runtime.PYTHON_3_8,
139+
runtime: lambda.Runtime.PYTHON_3_11,
127140
code: lambda.Code.fromAsset('../api/lambdas'),
128141
handler: 'get-account.main',
129142
environment: {
@@ -142,7 +155,7 @@ export class PennyInfraStack extends cdk.Stack {
142155

143156
createLambda_createAccount = () => {
144157
this.createAccountLambda = new lambda.Function(this, 'CreateAccount', {
145-
runtime: lambda.Runtime.PYTHON_3_8,
158+
runtime: lambda.Runtime.PYTHON_3_11,
146159
code: lambda.Code.fromAsset('../api/lambdas'),
147160
handler: 'create-account.main',
148161
environment: {
@@ -162,8 +175,10 @@ export class PennyInfraStack extends cdk.Stack {
162175
this.createAccountLambda.addToRolePolicy(new iam.PolicyStatement(
163176
{
164177
effect: iam.Effect.ALLOW,
165-
actions: ['ses:SendEmail'],
166-
resources: ['*']
178+
actions: ['ses:SendEmail', 'ses:SendRawEmail'],
179+
resources: [
180+
`arn:aws:ses:${this.region}:${this.account}:identity/*`,
181+
],
167182
}),
168183
)
169184
}
@@ -309,11 +324,40 @@ export class PennyInfraStack extends cdk.Stack {
309324
assumedBy: new iam.ServicePrincipal('kendra.amazonaws.com'),
310325
})
311326

327+
role.addToPolicy(new iam.PolicyStatement({
328+
effect: iam.Effect.ALLOW,
329+
actions: [
330+
'cloudwatch:PutMetricData',
331+
],
332+
resources: ['*'],
333+
conditions: {
334+
'StringEquals': {
335+
'cloudwatch:namespace': 'AWS/Kendra'
336+
}
337+
}
338+
}));
339+
340+
role.addToPolicy(new iam.PolicyStatement({
341+
effect: iam.Effect.ALLOW,
342+
actions: [
343+
'logs:DescribeLogGroups',
344+
'logs:CreateLogGroup',
345+
'logs:PutLogEvents'
346+
],
347+
resources: [
348+
`arn:aws:logs:${this.region}:${this.account}:log-group:/aws/kendra/*`,
349+
`arn:aws:logs:${this.region}:${this.account}:log-group:/aws/kendra/*:log-stream:*`
350+
]
351+
}));
352+
312353
role.addToPolicy(new iam.PolicyStatement(
313354
{
314355
effect: iam.Effect.ALLOW,
315-
actions: ['cloudwatch:PutMetricData', 'logs:DescribeLogGroups', 'logs:CreateLogGroup', 'logs:DescribeLogStreams', 'logs:CreateLogStream', 'logs:PutLogEvents', 's3:*'],
316-
resources: ['*']
356+
actions: ['s3:Get*', 's3:List*', 's3:Describe*'],
357+
resources: [
358+
`arn:aws:s3:::${this.catalogBucket.bucketName}`,
359+
`arn:aws:s3:::${this.catalogBucket.bucketName}/*`
360+
]
317361
}),
318362
)
319363

@@ -341,15 +385,15 @@ export class PennyInfraStack extends cdk.Stack {
341385
createLLM = () => {
342386
const vpc = new ec2.Vpc(this, 'LLMVpc', {
343387
subnetConfiguration: [
344-
{ subnetType: ec2.SubnetType.PUBLIC, cidrMask: 24, name: "public-" }
388+
{ subnetType: ec2.SubnetType.PUBLIC, cidrMask: 24, name: "public-" },
389+
{ subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, cidrMask: 24, name: "private-" }
345390
]
346391
})
347392

348393
const sg = new ec2.SecurityGroup(this, 'LLMSG', {
349394
vpc: vpc,
350395
allowAllOutbound: true
351396
})
352-
sg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80));
353397

354398
const cluster = new ecs.Cluster(this, "LLMCluster", {
355399
vpc: vpc
@@ -365,8 +409,20 @@ export class PennyInfraStack extends cdk.Stack {
365409

366410
taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({
367411
effect: iam.Effect.ALLOW,
368-
resources: ['*'],
369-
actions: ['bedrock:InvokeModel', 's3:PutObject', 'kendra:Retrieve']
412+
resources: ['*'],
413+
actions: ['bedrock:InvokeModel']
414+
}));
415+
416+
taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({
417+
effect: iam.Effect.ALLOW,
418+
resources: [`${this.idBucket.bucketArn}/*`],
419+
actions: ['s3:PutObject']
420+
}));
421+
422+
taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({
423+
effect: iam.Effect.ALLOW,
424+
resources: [this.kendraIndex.attrArn],
425+
actions: ['kendra:Retrieve']
370426
}));
371427

372428
const container = taskDefinition.addContainer("LLMContainer", {
@@ -394,9 +450,9 @@ export class PennyInfraStack extends cdk.Stack {
394450
this.llmService = new ecs.FargateService(this, 'LLMFargateService', {
395451
cluster: cluster,
396452
taskDefinition: taskDefinition,
397-
assignPublicIp: true,
453+
assignPublicIp: false,
398454
securityGroups: [sg],
399-
serviceName: 'LLMFargateService'
455+
serviceName: 'LLMFargateService',
400456
})
401457

402458
const lb = new elbv2.ApplicationLoadBalancer(this, 'PennyALB', {

media/architecture.png

8.37 KB
Loading

0 commit comments

Comments
 (0)