Skip to content

add pattern apigw-bedrock-cognito-terraform #2737

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
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
164 changes: 164 additions & 0 deletions apigw-bedrock-cognito-terraform/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# Access Bedrock via API Gateway with Cognito User Management

This pattern creates an AWS CDK Python application to access Bedrock via API Gateway with Cognito user management, domain restriction, API request throttling, and quota limits.

Learn more about this pattern at Serverless Land Patterns: << Add the live URL here >>

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.
* [Install and Configure AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html)
* [Install Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [Install Node and NPM](https://nodejs.org/en/download/)
* [Install Terraform](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli)
* [Install Python 3](https://www.python.org/downloads/)
* [Grant Bedrock Model Access](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html)

## Deployment Instructions

1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository:
```
git clone https://github.com/aws-samples/serverless-patterns
```
2. Change directory to the pattern directory:
```
cd apigw-bedrock-cognito-terraform
```
3. Init terraform:
```
terraform init
```
4. Deploy the AWS resources:
```
terraform apply
```

5. Note the outputs from the CDK deployment process. These contain the resource names and/or ARNs which are used for testing.

## How it works

### Overview

This pattern deploys an Amazon API Gateway REST API with the following routes: `POST /register`, `POST /login`, `GET` and `POST /bedrock`. It includes a Amazon Cognito User Pool and Lambdas to handle requests from the API Gateway. The API Gateway allows CORS for all origins and methods, incorporates an Usage Plan, and has throttle and quota limits for the `/bedrock` endpoint. The `/bedrock` endpoint allows access to Amazon Bedrock Foundation models.

### Components and Configuration

#### API Gateway Routes
- `/register` endpoint: Accepts `email`, `password`, and `fullname` in the body, interacts with a proxy lambda integration to register users to the Cognito User Pool. Returns an API Key which will be associated with the API Gateway's usage plan and stored within the Cognito user's custom `api_key` field . If an organization domain is specified in the `ORGANIZATION_DOMAIN` context variable, a pre signup lambda is provisioned to reject users not belonging to the specified domain.

- `/login` endpoint: Accepts `email` and `password` in the body, interacts with a proxy lambda integration to authenticate the user. Returns an bearer token, containing `IdToken`, `AccessToken`, `RefreshToken` and other metadata. If the user loses their API key, they can decrypt the `IdToken` using [jwt.io](https://jwt.io/) or other libraries to retrieve the API key from the `custom:api_key` field.

- `/bedrock` endpoint: Protected with a Cognito authorizer to ensure only requests with valid `Authorization` and `x-api-key` tokens in headers can access the endpoint, interacts with a proxy lambda integration. A `GET` request lists all foundation models, and a `POST` request takes `modelId` and `inferenceParameters` in the body, to invoke and return response from the foundation model.

#### API Gateway Configuration
- CORS: Enabled for all origins and methods.
- Usage Plan: Configured to manage API access.
- Throttle Limits: Rate limit of 1 request per second with a burst limit of 2 requests.
- Quota Limits: Set to 25 requests per day for the `/bedrock` endpoint.
- These limits can be modified during deployment using context variables (`API_THROTTLE_RATE_LIMIT`, `API_THROTTLE_BURST_LIMIT`, `API_QUOTA_LIMIT`, `API_QUOTA_PERIOD`)
- Logging: Enabled for all Error and Info request logs.

#### Cognito Integration and Configuration
- User Pool: Manages user registration and login.
- Organization Domain Restriction: The organization domain restriction can be adjusted during deployment using the context variable `ORGANIZATION_DOMAIN`. A Pre SignUp Lambda trigger will be added to enforce specific domain restrictions.

#### Lambda Integration
- `bedrock.py`:
- Uses the `boto3` library to make API calls to Amazon Bedrock APIs.
- Utilizes the `jsonpath_ng` library to dynamically map and retrieve responses from foundation models provided by Anthropic, AI21 Labs, Amazon, and Meta.
- `auth.py`:
- Uses the `boto3` library to make API calls to Amazon Cognito and Amazon API Gateway.
- Manages user creation, login, and the creation and association of API keys.
- `pre_signup.py` (if valid):
- Validates user email domain during registration.
- All Lambda Configuration:
- Timeout: Set to 29 second due to maximum integration timeout limit - [Amazon API Gateway Limits](https://docs.aws.amazon.com/apigateway/latest/developerguide/limits.html).
- Logging: All events are logged to Amazon CloudWatch, with a custom redaction function to remove passwords from the `auth.py` Lambda prior to logging.

### Request and Response Examples

- **Register User**
- **Request Body:**
```json
{
"email": "[email protected]",
"password": "securePassword123",
"fullname": "John Doe"
}
```
- **Response Body:**
```json
{
"status": 200,
"message": "User [email protected] created successfully.",
"data": {"API Key": "generatedApiKey"},
"success": true,
}
```

- **Login User**
- **Request Body:**
```json
{
"email": "[email protected]",
"password": "securePassword123"
}
```
- **Response Body:**
```json
{
"status": 200,
"IdToken": "generatedIdToken",
"AccessToken": "generatedAccessToken",
"RefreshToken": "generatedRefreshToken",
"ExpiresIn": 3600,
"TokenType": "Bearer",
"sucess": true
}
```

- **Make Bedrock Request**
- **Request Headers:**
- Authorization: Bearer [IdToken]
- x-api-key: [APIKey]
- **GET /bedrock Response Body:**
```json
{
"status": 200,
"foundationModels": [...],
"message": "Successfully retrieved foundation models list."
}
```
- **POST /bedrock Request Body:**
```json
{
"modelId": "exampleModelId",
"inferenceParameters": {...}
}
```
- **POST /bedrock Response Body:**
```json
{
"status": 200,
"message": "Successfully retrieved response from foundation model.",
"data": "..."
}
```

## Cleanup

1. Delete the stack:
```
terraform destroy
```
2. Delete all API Keys:
Before executing the following command, be aware that it will delete all API keys in the account. Ensure you have the necessary backups or are certain of the consequences.
```
sh utils/delete_all_api_keys.sh
```
----
Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.

SPDX-License-Identifier: MIT-0
157 changes: 157 additions & 0 deletions apigw-bedrock-cognito-terraform/api_gateway.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
resource "aws_api_gateway_rest_api" "restAPI" {
name = "${var.contructID}_rest_api"
endpoint_configuration {
types = ["EDGE"]
}
}

resource "aws_api_gateway_account" "main" {
cloudwatch_role_arn = aws_iam_role.api_gw_logging.arn
}

resource "aws_api_gateway_stage" "stage" {
rest_api_id = aws_api_gateway_rest_api.restAPI.id
stage_name = var.stage_name
deployment_id = aws_api_gateway_deployment.deployment.id

access_log_settings {
destination_arn = "arn:aws:logs:${var.region}:${data.aws_caller_identity.current.account_id}:log-group:/aws/apigateway/${var.stage_name}"
format = "{\"requestId\":\"$context.requestId\",...}"
}

xray_tracing_enabled = true
}

resource "aws_api_gateway_method_settings" "method_settings" {
rest_api_id = aws_api_gateway_rest_api.restAPI.id
stage_name = aws_api_gateway_stage.stage.stage_name

method_path = "*/*"
settings {
metrics_enabled = true
logging_level = "INFO"
throttling_rate_limit = var.rate_limit
throttling_burst_limit = var.burst_limit
}
}

resource "aws_api_gateway_usage_plan" "usage_plan" {
name = "APIUsagePlanID"

api_stages {
api_id = aws_api_gateway_rest_api.restAPI.id
stage = aws_api_gateway_stage.stage.stage_name
}

quota_settings {
limit = var.quota_limit
period = var.quota_period
}
throttle_settings {
burst_limit = var.burst_limit
rate_limit = var.rate_limit
}
}

resource "aws_api_gateway_authorizer" "cognito_authorizer" {
name = "CognitoAuthorizer"
rest_api_id = aws_api_gateway_rest_api.restAPI.id
type = "COGNITO_USER_POOLS"
provider_arns = [aws_cognito_user_pool.UserPool.arn]
identity_source = "method.request.header.Authorization"
}

resource "aws_api_gateway_resource" "register" {
rest_api_id = aws_api_gateway_rest_api.restAPI.id
parent_id = aws_api_gateway_rest_api.restAPI.root_resource_id
path_part = "register"
}

resource "aws_api_gateway_method" "register_post" {
rest_api_id = aws_api_gateway_rest_api.restAPI.id
resource_id = aws_api_gateway_resource.register.id
http_method = "POST"
authorization = "NONE"
}

resource "aws_api_gateway_integration" "register_post_integration" {
rest_api_id = aws_api_gateway_rest_api.restAPI.id
resource_id = aws_api_gateway_resource.register.id
http_method = aws_api_gateway_method.register_post.http_method
integration_http_method = "POST"
type = "AWS_PROXY"
uri = aws_lambda_function.auth_lambda_function.invoke_arn
credentials = aws_iam_role.api_gw_invoke_role.arn
}

resource "aws_api_gateway_resource" "login" {
rest_api_id = aws_api_gateway_rest_api.restAPI.id
parent_id = aws_api_gateway_rest_api.restAPI.root_resource_id
path_part = "login"
}

resource "aws_api_gateway_method" "login_post" {
rest_api_id = aws_api_gateway_rest_api.restAPI.id
resource_id = aws_api_gateway_resource.login.id
http_method = "POST"
authorization = "NONE"
}

resource "aws_api_gateway_integration" "login_post_integration" {
rest_api_id = aws_api_gateway_rest_api.restAPI.id
resource_id = aws_api_gateway_resource.login.id
http_method = aws_api_gateway_method.login_post.http_method
integration_http_method = "POST"
type = "AWS_PROXY"
uri = aws_lambda_function.auth_lambda_function.invoke_arn
credentials = aws_iam_role.api_gw_invoke_role.arn
}

resource "aws_api_gateway_resource" "bedrock" {
rest_api_id = aws_api_gateway_rest_api.restAPI.id
parent_id = aws_api_gateway_rest_api.restAPI.root_resource_id
path_part = "bedrock"
}

resource "aws_api_gateway_method" "bedrock_any" {
rest_api_id = aws_api_gateway_rest_api.restAPI.id
resource_id = aws_api_gateway_resource.bedrock.id
http_method = "ANY"
authorization = "COGNITO_USER_POOLS"
authorizer_id = aws_api_gateway_authorizer.cognito_authorizer.id
api_key_required = true
}

resource "aws_api_gateway_integration" "bedrock_any_integration" {
rest_api_id = aws_api_gateway_rest_api.restAPI.id
resource_id = aws_api_gateway_resource.bedrock.id
http_method = aws_api_gateway_method.bedrock_any.http_method
integration_http_method = "POST"
type = "AWS_PROXY"
uri = aws_lambda_function.bedrock_lambda_function.invoke_arn
credentials = aws_iam_role.api_gw_invoke_role.arn
}

resource "aws_api_gateway_deployment" "deployment" {
rest_api_id = aws_api_gateway_rest_api.restAPI.id

depends_on = [
aws_api_gateway_method.register_post,
aws_api_gateway_integration.register_post_integration,
aws_api_gateway_method.login_post,
aws_api_gateway_integration.login_post_integration,
aws_api_gateway_method.bedrock_any,
aws_api_gateway_integration.bedrock_any_integration
]

triggers = {
redeploy = sha256(jsonencode(aws_api_gateway_rest_api.restAPI))
}
}

resource "aws_ssm_parameter" "usage_play_id" {
name = "/${var.contructID}/api/usage_plan/id"
description = "ID of API gateway usage plan"
type = "String"
value = aws_api_gateway_usage_plan.usage_plan.id
}
67 changes: 67 additions & 0 deletions apigw-bedrock-cognito-terraform/cognito.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Deploys Cognito resources

resource "aws_cognito_user_pool" "UserPool" {
name = "${var.contructID}_user_pool"
mfa_configuration = "OFF"
username_attributes = ["email"]

schema {
name = "fullname"
attribute_data_type = "String"
mutable = true
string_attribute_constraints {
min_length = 1
max_length = 256
}
}

schema {
name = "email"
attribute_data_type = "String"
mutable = true
string_attribute_constraints {
min_length = 1
max_length = 256
}
}

schema {
name = "api_key"
attribute_data_type = "String"
mutable = true
required = false
string_attribute_constraints {
min_length = 1
max_length = 256
}
}
}

resource "aws_cognito_user_pool_client" "client" {
name = "UserPoolClient"
user_pool_id = aws_cognito_user_pool.UserPool.id
explicit_auth_flows = [
"ALLOW_USER_PASSWORD_AUTH",
"ALLOW_REFRESH_TOKEN_AUTH"
]

token_validity_units {
refresh_token = "hours"
}

refresh_token_validity = 1
}

resource "aws_ssm_parameter" "userPoolID" {
name = "/${var.contructID}/user_pool/id"
description = "User pool ID"
type = "String"
value = aws_cognito_user_pool.UserPool.id
}

resource "aws_ssm_parameter" "clientid" {
name = "/${var.contructID}/user_pool/client_id"
description = "User pool client ID"
type = "String"
value = aws_cognito_user_pool_client.client.id
}
1 change: 1 addition & 0 deletions apigw-bedrock-cognito-terraform/data.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
data "aws_caller_identity" "current" {}
Loading