├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── app.py ├── cdk.json ├── cdk ├── __init__.py └── cdk_stack.py ├── docker_app ├── Dockerfile ├── app.py ├── config_file.py ├── docker-compose.yml ├── requirements.txt └── utils │ ├── __init__.py │ ├── auth.py │ └── llm.py ├── img └── archi_streamlit_cdk.png ├── requirements-dev.txt ├── requirements.txt ├── source.bat └── tests ├── __init__.py └── unit ├── __init__.py └── test_cdk_stack.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | package-lock.json 3 | __pycache__ 4 | .pytest_cache 5 | .venv 6 | *.egg-info 7 | 8 | # CDK asset staging directory 9 | .cdk.staging 10 | cdk.out 11 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 4 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 5 | opensource-codeofconduct@amazon.com with any additional questions or comments. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | ## Reporting Bugs/Feature Requests 10 | 11 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 12 | 13 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 14 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 15 | 16 | * A reproducible test case or series of steps 17 | * The version of our code being used 18 | * Any modifications you've made relevant to the bug 19 | * Anything unusual about your environment or deployment 20 | 21 | ## Contributing via Pull Requests 22 | 23 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 24 | 25 | 1. You are working against the latest source on the *main* branch. 26 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 27 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 28 | 29 | To send us a pull request, please: 30 | 31 | 1. Fork the repository. 32 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 33 | 3. Ensure local tests pass. 34 | 4. Commit to your fork using clear commit messages. 35 | 5. Send us a pull request, answering any default questions in the pull request interface. 36 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 37 | 38 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 39 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 40 | 41 | ## Finding contributions to work on 42 | 43 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 44 | 45 | ## Code of Conduct 46 | 47 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 48 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 49 | opensource-codeofconduct@amazon.com with any additional questions or comments. 50 | 51 | ## Security issue notifications 52 | 53 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 54 | 55 | ## Licensing 56 | 57 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 4 | software and associated documentation files (the "Software"), to deal in the Software 5 | without restriction, including without limitation the rights to use, copy, modify, 6 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 7 | permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 10 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 11 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 12 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 13 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 14 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # deploy-streamlit-app 2 | 3 | This app can be used as a starting point to easily create and deploy a GenAI demo, with web interface and user authentication. It is written in python only, with cdk template to deploy on AWS. 4 | 5 | It deploys a basic Streamlit app, and contains the following components: 6 | 7 | * The Streamlit app in ECS/Fargate, behind an ALB and CloudFront 8 | * A Cognito user pool in which you can manage users 9 | 10 | By default, the Streamlit app has the following features: 11 | 12 | * Authentication through Cognito 13 | * Connection to Bedrock 14 | 15 | ## Architecture diagram 16 | 17 | ![Architecture diagram](img/archi_streamlit_cdk.png) 18 | 19 | ## Usage 20 | 21 | In the docker_app folder, you will find the streamlit app. You can run it locally or with docker. 22 | 23 | Note: for the docker version to run, you will need to give appropriate permissions to the container for bedrock access. This is not implemented yet. 24 | 25 | In the main folder, you will find a cdk template to deploy the app on ECS / ALB. 26 | 27 | Prerequisites: 28 | 29 | * python >= 3.8 30 | * docker 31 | * use a Chrome browser for development 32 | * `anthropic.claude-v2` model activated in Amazon Bedrock in your AWS account 33 | * the environment used to create this demo was an AWS Cloud9 m5.large instance with Amazon Linux 2023, but it should also work with other configurations. It has also been tested on a mac laptop with colima as container runtime. 34 | * You also need to install the AWS Command Line Interface (CLI), the AWS Cloud Development KIT (CDK), and to configure the AWS CLI on your development environment (not required if you use Cloud9, as it is already configured by default). One way to configure the AWS CLI is to get your access key through the AWS console, and use the `aws configure` command in your terminal to setup your credentials. 35 | 36 | To deploy: 37 | 38 | 1. Edit `docker_app/config_file.py`, choose a `STACK_NAME` and a `CUSTOM_HEADER_VALUE`. 39 | 40 | 2. Install dependencies 41 | 42 | ``` 43 | python -m venv .venv 44 | source .venv/bin/activate 45 | pip install -r requirements.txt 46 | ``` 47 | 48 | 3. Deploy the cdk template 49 | 50 | ``` 51 | cdk bootstrap 52 | cdk deploy 53 | ``` 54 | 55 | The deployment takes 5 to 10 minutes. 56 | 57 | Make a note of the output, in which you will find the CloudFront distribution URL 58 | and the Cognito user pool id. 59 | 60 | 4. Create a user in the Cognito UserPool that has been created. You can perform this action from your AWS Console. 61 | 5. From your browser, connect to the CloudFront distribution url. 62 | 6. Log in to the Streamlit app with the user you have created in Cognito. 63 | 64 | ## Testing and developing in Cloud9 65 | 66 | After deployment of the cdk template containing the Cognito user pool required for authentication, you can test the Streamlit app directly from Cloud9. 67 | You can either use docker, but this would require setting up a role with appropriate permissions, or run the Streamlit app directly in your terminal after having installed the required python dependencies. 68 | 69 | To run the Streamlit app directly: 70 | 71 | 1. If you have activated a virtual env for deploying the cdk template, deactivate it: 72 | 73 | ``` 74 | deactivate 75 | ``` 76 | 77 | 2. cd into the streamlit-docker directory, create a new virtual env, and install dependencies: 78 | 79 | ``` 80 | cd docker_app 81 | python -m venv .venv 82 | source .venv/bin/activate 83 | pip install -r requirements.txt 84 | ``` 85 | 86 | 3. Launch the streamlit server 87 | 88 | ``` 89 | streamlit run app.py --server.port 8080 90 | ``` 91 | 92 | 4. Click on the Preview/Preview running application button in Cloud9, and click on the button to Pop out the browser in a new window, as the Cloud9 embedded browser does not keep session cookies, which prevents the authentication mechanism to work properly. 93 | If the new window does not display the app, you may need to configure your browser to accept cross-site tracking cookies. 94 | 95 | 5. You can now modify the streamlit app to build your own demo! 96 | 97 | ## Some limitations 98 | 99 | * The connection between CloudFront and the ALB is in HTTP, not SSL encrypted. 100 | This means traffic between CloudFront and the ALB is unencrypted. 101 | It is **strongly recommended** to configure HTTPS by bringing your own domain name and SSL/TLS certificate to the ALB. 102 | * The provided code is intended as a demo and starting point, not production ready. 103 | The Python app relies on third party libraries like Streamlit and streamlit-cognito-auth. 104 | As the developer, it is your responsibility to properly vet, maintain, and test all third party dependencies. 105 | The authentication and authorization mechanisms in particular should be thoroughly evaluated. 106 | More generally, you should perform security reviews and testing before incorporating this demo code in a production application or with sensitive data. 107 | * In this demo, Amazon Cognito is in a simple configuration. 108 | Note that Amazon Cognito user pools can be configured to enforce strong password policies, 109 | enable multi-factor authentication, 110 | and set the AdvancedSecurityMode to ENFORCED to enable the system to detect and act upon malicious sign-in attempts. 111 | * AWS provides various services, not implemented in this demo, that can improve the security of this application. 112 | Network security services like network ACLs and AWS WAF can control access to resources. 113 | You could also use AWS Shield for DDoS protection and Amazon GuardDuty for threats detection. 114 | Amazon Inspector performs security assessments. 115 | There are many more AWS services and best practices that can enhance security - 116 | refer to the AWS Shared Responsibility Model and security best practices guidance for additional recommendations. 117 | The developer is responsible for properly implementing and configuring these services to meet their specific security requirements. 118 | * Regular rotation of secrets is recommended, not implemented in this demo. 119 | 120 | ## Acknowledgments 121 | 122 | This code is inspired from: 123 | 124 | * https://github.com/tzaffi/streamlit-cdk-fargate.git 125 | * https://github.com/aws-samples/build-scale-generative-ai-applications-with-amazon-bedrock-workshop/ 126 | 127 | ## Security 128 | 129 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 130 | 131 | ## License 132 | 133 | This application is licensed under the MIT-0 License. See the LICENSE file. -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | 4 | import aws_cdk as cdk 5 | 6 | from cdk.cdk_stack import CdkStack 7 | from docker_app.config_file import Config 8 | 9 | 10 | app = cdk.App() 11 | CdkStack(app, Config.STACK_NAME, 12 | # If you don't specify 'env', this stack will be environment-agnostic. 13 | # Account/Region-dependent features and context lookups will not work, 14 | # but a single synthesized template can be deployed anywhere. 15 | 16 | # Uncomment the next line to specialize this stack for the AWS Account 17 | # and Region that are implied by the current CLI configuration. 18 | 19 | #env=cdk.Environment(account=os.getenv('CDK_DEFAULT_ACCOUNT'), region=os.getenv('CDK_DEFAULT_REGION')), 20 | 21 | # Uncomment the next line if you know exactly what Account and Region you 22 | # want to deploy the stack to. */ 23 | 24 | #env=cdk.Environment(account='123456789012', region='us-east-1'), 25 | 26 | # For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html 27 | 28 | env=cdk.Environment(region=Config.DEPLOYMENT_REGION) 29 | ) 30 | 31 | app.synth() 32 | -------------------------------------------------------------------------------- /cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "python3 app.py", 3 | "watch": { 4 | "include": [ 5 | "**" 6 | ], 7 | "exclude": [ 8 | "README.md", 9 | "cdk*.json", 10 | "requirements*.txt", 11 | "source.bat", 12 | "**/__init__.py", 13 | "**/__pycache__", 14 | "tests" 15 | ] 16 | }, 17 | "context": { 18 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true, 19 | "@aws-cdk/core:checkSecretUsage": true, 20 | "@aws-cdk/core:target-partitions": [ 21 | "aws", 22 | "aws-cn" 23 | ], 24 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 25 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 26 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, 27 | "@aws-cdk/aws-iam:minimizePolicies": true, 28 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true, 29 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, 30 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, 31 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, 32 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, 33 | "@aws-cdk/core:enablePartitionLiterals": true, 34 | "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, 35 | "@aws-cdk/aws-iam:standardizedServicePrincipals": true, 36 | "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, 37 | "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, 38 | "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, 39 | "@aws-cdk/aws-route53-patters:useCertificate": true, 40 | "@aws-cdk/customresources:installLatestAwsSdkDefault": false, 41 | "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, 42 | "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, 43 | "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, 44 | "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, 45 | "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, 46 | "@aws-cdk/aws-redshift:columnId": true, 47 | "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, 48 | "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, 49 | "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, 50 | "@aws-cdk/aws-kms:aliasNameRef": true, 51 | "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, 52 | "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, 53 | "@aws-cdk/aws-efs:denyAnonymousAccess": true, 54 | "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, 55 | "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, 56 | "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, 57 | "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, 58 | "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, 59 | "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, 60 | "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /cdk/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/deploy-streamlit-app/08a3df8c12b759165b2f981e193134257be8967c/cdk/__init__.py -------------------------------------------------------------------------------- /cdk/cdk_stack.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import ( 2 | # Duration, 3 | Stack, 4 | aws_ec2 as ec2, 5 | aws_ecs as ecs, 6 | aws_iam as iam, 7 | aws_cognito as cognito, 8 | aws_secretsmanager as secretsmanager, 9 | aws_cloudfront as cloudfront, 10 | aws_cloudfront_origins as origins, 11 | aws_elasticloadbalancingv2 as elbv2, 12 | SecretValue, 13 | CfnOutput, 14 | ) 15 | from constructs import Construct 16 | from docker_app.config_file import Config 17 | 18 | CUSTOM_HEADER_NAME = "X-Custom-Header" 19 | 20 | class CdkStack(Stack): 21 | 22 | def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: 23 | super().__init__(scope, construct_id, **kwargs) 24 | 25 | # Define prefix that will be used in some resource names 26 | prefix = Config.STACK_NAME 27 | 28 | # Create Cognito user pool 29 | user_pool = cognito.UserPool(self, f"{prefix}UserPool") 30 | 31 | # Create Cognito client 32 | user_pool_client = cognito.UserPoolClient(self, f"{prefix}UserPoolClient", 33 | user_pool=user_pool, 34 | generate_secret=True 35 | ) 36 | 37 | # Store Cognito parameters in a Secrets Manager secret 38 | secret = secretsmanager.Secret(self, f"{prefix}ParamCognitoSecret", 39 | secret_object_value={ 40 | "pool_id": SecretValue.unsafe_plain_text(user_pool.user_pool_id), 41 | "app_client_id": SecretValue.unsafe_plain_text(user_pool_client.user_pool_client_id), 42 | "app_client_secret": user_pool_client.user_pool_client_secret 43 | }, 44 | # This secret name should be identical 45 | # to the one defined in the Streamlit 46 | # container 47 | secret_name=Config.SECRETS_MANAGER_ID 48 | ) 49 | 50 | 51 | # VPC for ALB and ECS cluster 52 | vpc = ec2.Vpc( 53 | self, 54 | f"{prefix}AppVpc", 55 | ip_addresses=ec2.IpAddresses.cidr("10.0.0.0/16"), 56 | max_azs=2, 57 | vpc_name=f"{prefix}-stl-vpc", 58 | nat_gateways=1, 59 | ) 60 | 61 | ecs_security_group = ec2.SecurityGroup( 62 | self, 63 | f"{prefix}SecurityGroupECS", 64 | vpc=vpc, 65 | security_group_name=f"{prefix}-stl-ecs-sg", 66 | ) 67 | 68 | alb_security_group = ec2.SecurityGroup( 69 | self, 70 | f"{prefix}SecurityGroupALB", 71 | vpc=vpc, 72 | security_group_name=f"{prefix}-stl-alb-sg", 73 | ) 74 | 75 | ecs_security_group.add_ingress_rule( 76 | peer=alb_security_group, 77 | connection=ec2.Port.tcp(8501), 78 | description="ALB traffic", 79 | ) 80 | 81 | # ECS cluster and service definition 82 | cluster = ecs.Cluster( 83 | self, 84 | f"{prefix}Cluster", 85 | enable_fargate_capacity_providers=True, 86 | vpc=vpc) 87 | 88 | # ALB to connect to ECS 89 | alb = elbv2.ApplicationLoadBalancer( 90 | self, 91 | f"{prefix}Alb", 92 | vpc=vpc, 93 | internet_facing=True, 94 | load_balancer_name=f"{prefix}-stl", 95 | security_group=alb_security_group, 96 | vpc_subnets=ec2.SubnetSelection(subnet_type=ec2.SubnetType.PUBLIC), 97 | ) 98 | 99 | fargate_task_definition = ecs.FargateTaskDefinition( 100 | self, 101 | f"{prefix}WebappTaskDef", 102 | memory_limit_mib=512, 103 | cpu=256, 104 | ) 105 | 106 | # Build Dockerfile from local folder and push to ECR 107 | image = ecs.ContainerImage.from_asset('docker_app') 108 | 109 | fargate_task_definition.add_container( 110 | f"{prefix}WebContainer", 111 | # Use an image from DockerHub 112 | image=image, 113 | port_mappings=[ 114 | ecs.PortMapping( 115 | container_port=8501, 116 | protocol=ecs.Protocol.TCP)], 117 | logging=ecs.LogDrivers.aws_logs(stream_prefix="WebContainerLogs"), 118 | ) 119 | 120 | service = ecs.FargateService( 121 | self, 122 | f"{prefix}ECSService", 123 | cluster=cluster, 124 | task_definition=fargate_task_definition, 125 | service_name=f"{prefix}-stl-front", 126 | security_groups=[ecs_security_group], 127 | vpc_subnets=ec2.SubnetSelection( 128 | subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS), 129 | ) 130 | 131 | # Grant access to Bedrock 132 | bedrock_policy = iam.Policy(self, f"{prefix}BedrockPolicy", 133 | statements=[ 134 | iam.PolicyStatement( 135 | actions=["bedrock:InvokeModel"], 136 | resources=["*"] 137 | ) 138 | ] 139 | ) 140 | task_role = fargate_task_definition.task_role 141 | task_role.attach_inline_policy(bedrock_policy) 142 | 143 | # Grant access to read the secret in Secrets Manager 144 | secret.grant_read(task_role) 145 | 146 | # Add ALB as CloudFront Origin 147 | origin = origins.LoadBalancerV2Origin( 148 | alb, 149 | custom_headers={CUSTOM_HEADER_NAME: Config.CUSTOM_HEADER_VALUE}, 150 | origin_shield_enabled=False, 151 | protocol_policy=cloudfront.OriginProtocolPolicy.HTTP_ONLY, 152 | ) 153 | 154 | cloudfront_distribution = cloudfront.Distribution( 155 | self, 156 | f"{prefix}CfDist", 157 | default_behavior=cloudfront.BehaviorOptions( 158 | origin=origin, 159 | viewer_protocol_policy=cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, 160 | allowed_methods=cloudfront.AllowedMethods.ALLOW_ALL, 161 | cache_policy=cloudfront.CachePolicy.CACHING_DISABLED, 162 | origin_request_policy=cloudfront.OriginRequestPolicy.ALL_VIEWER, 163 | ), 164 | ) 165 | 166 | # ALB Listener 167 | http_listener = alb.add_listener( 168 | f"{prefix}HttpListener", 169 | port=80, 170 | open=True, 171 | ) 172 | 173 | http_listener.add_targets( 174 | f"{prefix}TargetGroup", 175 | target_group_name=f"{prefix}-tg", 176 | port=8501, 177 | priority=1, 178 | conditions=[ 179 | elbv2.ListenerCondition.http_header( 180 | CUSTOM_HEADER_NAME, 181 | [Config.CUSTOM_HEADER_VALUE])], 182 | protocol=elbv2.ApplicationProtocol.HTTP, 183 | targets=[service], 184 | ) 185 | # add a default action to the listener that will deny all requests that 186 | # do not have the custom header 187 | http_listener.add_action( 188 | "default-action", 189 | action=elbv2.ListenerAction.fixed_response( 190 | status_code=403, 191 | content_type="text/plain", 192 | message_body="Access denied", 193 | ), 194 | ) 195 | 196 | # Output CloudFront URL 197 | CfnOutput(self, "CloudFrontDistributionURL", 198 | value=cloudfront_distribution.domain_name) 199 | # Output Cognito pool id 200 | CfnOutput(self, "CognitoPoolId", 201 | value=user_pool.user_pool_id) 202 | -------------------------------------------------------------------------------- /docker_app/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=linux/amd64 python:3.12 2 | EXPOSE 8501 3 | WORKDIR /app 4 | COPY requirements.txt ./requirements.txt 5 | RUN pip3 install --upgrade pip && pip3 install -r requirements.txt 6 | COPY . . 7 | 8 | # Command overriden by docker-compose 9 | CMD streamlit run app.py 10 | -------------------------------------------------------------------------------- /docker_app/app.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | import json 3 | import boto3 4 | from utils.auth import Auth 5 | from utils.llm import Llm 6 | from config_file import Config 7 | 8 | # ID of Secrets Manager containing cognito parameters 9 | secrets_manager_id = Config.SECRETS_MANAGER_ID 10 | 11 | # ID of the AWS region in which Secrets Manager is deployed 12 | region = Config.DEPLOYMENT_REGION 13 | 14 | # Initialise CognitoAuthenticator 15 | authenticator = Auth.get_authenticator(secrets_manager_id, region) 16 | 17 | # Authenticate user, and stop here if not logged in 18 | is_logged_in = authenticator.login() 19 | if not is_logged_in: 20 | st.stop() 21 | 22 | 23 | def logout(): 24 | authenticator.logout() 25 | 26 | 27 | with st.sidebar: 28 | st.text(f"Welcome,\n{authenticator.get_username()}") 29 | st.button("Logout", "logout_btn", on_click=logout) 30 | 31 | # Add title on the page 32 | st.title("Generative AI Application") 33 | 34 | # Ask user for input text 35 | input_sent = st.text_input("Input Sentence", "Say Hello World! in Spanish, French and Japanese.") 36 | 37 | # Create the large language model object 38 | llm = Llm(Config.BEDROCK_REGION) 39 | 40 | # When there is an input text to process 41 | if input_sent: 42 | # Invoke the Bedrock foundation model 43 | response = llm.invoke(input_sent) 44 | 45 | # Transform response to json 46 | json_response = json.loads(response.get("body").read()) 47 | 48 | # Format response and print it in the console 49 | pretty_json_output = json.dumps(json_response, indent=2) 50 | print("API response: ", pretty_json_output) 51 | 52 | # Write response on Streamlit web interface 53 | st.write("**Foundation model output** \n\n", json_response['completion']) 54 | -------------------------------------------------------------------------------- /docker_app/config_file.py: -------------------------------------------------------------------------------- 1 | class Config: 2 | # Stack name 3 | # Change this value if you want to create a new instance of the stack 4 | STACK_NAME = "Streamlit" 5 | 6 | # Put your own custom value here to prevent ALB to accept requests from 7 | # other clients that CloudFront. You can choose any random string. 8 | CUSTOM_HEADER_VALUE = "My_random_value_58dsv15e4s31" 9 | 10 | # ID of Secrets Manager containing cognito parameters 11 | # When you delete a secret, you cannot create another one immediately 12 | # with the same name. Change this value if you destroy your stack and need 13 | # to recreate it with the same STACK_NAME. 14 | SECRETS_MANAGER_ID = f"{STACK_NAME}ParamCognitoSecret12345" 15 | 16 | # AWS region in which you want to deploy the cdk stack 17 | DEPLOYMENT_REGION = "us-east-1" 18 | 19 | # If Bedrock is not activated in us-east-1 in your account, set this value 20 | # accordingly 21 | BEDROCK_REGION = "us-east-1" 22 | -------------------------------------------------------------------------------- /docker_app/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | 5 | streamlit: 6 | image: streamlit/sl:1 7 | build: 8 | context: . 9 | ports: 10 | - "8080:8501" 11 | entrypoint: streamlit run app.py 12 | 13 | 14 | -------------------------------------------------------------------------------- /docker_app/requirements.txt: -------------------------------------------------------------------------------- 1 | streamlit==1.29.0 2 | boto3==1.33.11 3 | streamlit-cognito-auth==1.2.0 -------------------------------------------------------------------------------- /docker_app/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/deploy-streamlit-app/08a3df8c12b759165b2f981e193134257be8967c/docker_app/utils/__init__.py -------------------------------------------------------------------------------- /docker_app/utils/auth.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import json 3 | from streamlit_cognito_auth import CognitoAuthenticator 4 | 5 | class Auth: 6 | 7 | @staticmethod 8 | def get_authenticator(secret_id, region): 9 | """ 10 | Get Cognito parameters from Secrets Manager and 11 | returns a CognitoAuthenticator object. 12 | """ 13 | # Get Cognito parameters from Secrets Manager 14 | secretsmanager_client = boto3.client( 15 | "secretsmanager", 16 | region_name=region 17 | ) 18 | response = secretsmanager_client.get_secret_value( 19 | SecretId=secret_id, 20 | ) 21 | secret_string = json.loads(response['SecretString']) 22 | pool_id = secret_string['pool_id'] 23 | app_client_id = secret_string['app_client_id'] 24 | app_client_secret = secret_string['app_client_secret'] 25 | 26 | # Initialise CognitoAuthenticator 27 | authenticator = CognitoAuthenticator( 28 | pool_id=pool_id, 29 | app_client_id=app_client_id, 30 | app_client_secret=app_client_secret, 31 | ) 32 | 33 | return authenticator 34 | -------------------------------------------------------------------------------- /docker_app/utils/llm.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import json 3 | 4 | 5 | class Llm: 6 | 7 | def __init__(self, bedrock_region): 8 | # Create Bedrock client 9 | bedrock_client = boto3.client( 10 | 'bedrock-runtime', 11 | region_name=bedrock_region, 12 | ) 13 | self.bedrock_client = bedrock_client 14 | 15 | def invoke(self, input_text): 16 | """ 17 | Make a call to the foundation model through Bedrock 18 | """ 19 | 20 | # Prepare a Bedrock API call to invoke a foundation model 21 | prompt = f"""\n\nHuman: {input_text} 22 | \n\nAssistant:""" 23 | 24 | model_id = "anthropic.claude-v2" 25 | body = { 26 | "prompt": prompt, 27 | "max_tokens_to_sample": 4096, 28 | "temperature": 0., 29 | } 30 | body = json.dumps(body) 31 | accept = 'application/json' 32 | contentType = 'application/json' 33 | 34 | # Make the API call to Bedrock 35 | response = self.bedrock_client.invoke_model( 36 | body=body, modelId=model_id, accept=accept, contentType=contentType 37 | ) 38 | 39 | return response 40 | -------------------------------------------------------------------------------- /img/archi_streamlit_cdk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/deploy-streamlit-app/08a3df8c12b759165b2f981e193134257be8967c/img/archi_streamlit_cdk.png -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | pytest==6.2.5 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aws-cdk-lib==2.160.0 -------------------------------------------------------------------------------- /source.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem The sole purpose of this script is to make the command 4 | rem 5 | rem source .venv/bin/activate 6 | rem 7 | rem (which activates a Python virtualenv on Linux or Mac OS X) work on Windows. 8 | rem On Windows, this command just runs this batch file (the argument is ignored). 9 | rem 10 | rem Now we don't need to document a Windows command for activating a virtualenv. 11 | 12 | echo Executing .venv\Scripts\activate.bat for you 13 | .venv\Scripts\activate.bat 14 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/deploy-streamlit-app/08a3df8c12b759165b2f981e193134257be8967c/tests/__init__.py -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/deploy-streamlit-app/08a3df8c12b759165b2f981e193134257be8967c/tests/unit/__init__.py -------------------------------------------------------------------------------- /tests/unit/test_cdk_stack.py: -------------------------------------------------------------------------------- 1 | import aws_cdk as core 2 | import aws_cdk.assertions as assertions 3 | 4 | from cdk.cdk_stack import CdkStack 5 | 6 | # example tests. To run these tests, uncomment this file along with the example 7 | # resource in cdk/cdk_stack.py 8 | def test_sqs_queue_created(): 9 | app = core.App() 10 | stack = CdkStack(app, "cdk") 11 | template = assertions.Template.from_stack(stack) 12 | 13 | # template.has_resource_properties("AWS::SQS::Queue", { 14 | # "VisibilityTimeout": 300 15 | # }) 16 | --------------------------------------------------------------------------------