diff --git a/apigw-bedrock-cognito-terraform/README.md b/apigw-bedrock-cognito-terraform/README.md new file mode 100644 index 000000000..831da1422 --- /dev/null +++ b/apigw-bedrock-cognito-terraform/README.md @@ -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": "user@example.com", + "password": "securePassword123", + "fullname": "John Doe" + } + ``` + - **Response Body:** + ```json + { + "status": 200, + "message": "User user@example.com created successfully.", + "data": {"API Key": "generatedApiKey"}, + "success": true, + } + ``` + +- **Login User** + - **Request Body:** + ```json + { + "email": "user@example.com", + "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 diff --git a/apigw-bedrock-cognito-terraform/api_gateway.tf b/apigw-bedrock-cognito-terraform/api_gateway.tf new file mode 100644 index 000000000..f0f031e8a --- /dev/null +++ b/apigw-bedrock-cognito-terraform/api_gateway.tf @@ -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 +} diff --git a/apigw-bedrock-cognito-terraform/cognito.tf b/apigw-bedrock-cognito-terraform/cognito.tf new file mode 100644 index 000000000..2e5431408 --- /dev/null +++ b/apigw-bedrock-cognito-terraform/cognito.tf @@ -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 +} diff --git a/apigw-bedrock-cognito-terraform/data.tf b/apigw-bedrock-cognito-terraform/data.tf new file mode 100644 index 000000000..d78fce49c --- /dev/null +++ b/apigw-bedrock-cognito-terraform/data.tf @@ -0,0 +1 @@ +data "aws_caller_identity" "current" {} \ No newline at end of file diff --git a/apigw-bedrock-cognito-terraform/iam.tf b/apigw-bedrock-cognito-terraform/iam.tf new file mode 100644 index 000000000..1176d4085 --- /dev/null +++ b/apigw-bedrock-cognito-terraform/iam.tf @@ -0,0 +1,62 @@ +# IAM roles and policies related to API Gateway + +resource "aws_iam_role" "api_gw_invoke_role" { + assume_role_policy = jsonencode({ + Version: "2012-10-17", + Statement: [ + { + Effect: "Allow", + Principal: { Service: "apigateway.amazonaws.com" }, + Action: "sts:AssumeRole" + } + ] + }) +} + +resource "aws_iam_role" "api_gw_logging" { + name = "api-gw-log-role" + + assume_role_policy = <=10.0.0,<11.0.0 diff --git a/apigw-bedrock-cognito-terraform/src/auth/auth.py b/apigw-bedrock-cognito-terraform/src/auth/auth.py new file mode 100644 index 000000000..0cdde8337 --- /dev/null +++ b/apigw-bedrock-cognito-terraform/src/auth/auth.py @@ -0,0 +1,172 @@ +import os +import boto3 +import json +import logging + + +# Configure logging +logger = logging.getLogger() +logger.setLevel(logging.INFO) + +# Initialize AWS clients +cognito_client = boto3.client("cognito-idp") +apigateway_client = boto3.client("apigateway") +ssm = boto3.client("ssm") + + +# Method to get a parameter from AWS Systems Manager Parameter Store +def get_from_parameter_store(key): + parameter = ssm.get_parameter(Name=key) + return parameter["Parameter"]["Value"] + + +# Method to validate the presence of required parameters in a request body +def validate_parameters(body, required_params): + return all(param in body and len(body[param]) > 1 for param in required_params) + + +# Method to generate a standardised API response +def generate_response(status_code, success, message, data=None): + response_data = {"success": success, "message": message} + if data is not None: + response_data["data"] = data + # return {"statusCode": status_code, "body": json.dumps(response_data)} + return { + "statusCode": status_code, + "body": json.dumps(response_data), + "headers": { + "Content-Type": "application/json" + } + } + + +# Method to redact sensitive information in the request body +def redact_password(body): + redacted_body = body.copy() + redacted_body["password"] = "REDACTED" + return redacted_body + + +# Method to log a redacted version of the event +def log_redacted_event(event): + redacted_body = redact_password(json.loads(event["body"])) + redacted_event = event.copy() + redacted_event["body"] = json.dumps(redacted_body) + logger.info("Event: %s", redacted_event) + + +# Method to create an API key and associate it with a usage plan +def create_api_key(user_id): + response = apigateway_client.create_api_key( + name=user_id, + enabled=True, + ) + api_key_id = response["id"] + apigateway_client.create_usage_plan_key( + usagePlanId=USAGE_PLAN_ID, + keyId=api_key_id, + keyType="API_KEY", + ) + logger.info(f"API Key created for user_id: {user_id}.") + return response["value"] + + +# Method to update the custom:api_key field in Cognito user attributes +def update_cognito_user_api_key(email, api_key): + cognito_client.admin_update_user_attributes( + UserPoolId=USER_POOL_ID, + Username=email, + UserAttributes=[ + { + "Name": "custom:api_key", + "Value": api_key, + } + ], + ) + logger.info(f"Updated custom:api_key field for email: {email}.") + + +# Method to create a new Cognito user +def create_cognito_user(email, password, fullname): + response = cognito_client.sign_up( + ClientId=USER_POOL_CLIENT_ID, + Username=email, + Password=password, + UserAttributes=[ + {"Name": "name", "Value": fullname}, + {"Name": "email", "Value": email}, + ], + ) + logger.info(f"Created user: {email}.") + return response + + +# Method to authenticate a Cognito user +def login_cognito_user(email, password): + response = cognito_client.initiate_auth( + AuthFlow="USER_PASSWORD_AUTH", + AuthParameters={ + "USERNAME": email, + "PASSWORD": password, + }, + ClientId=USER_POOL_CLIENT_ID, + ) + logger.info(f"Authenticated: {email}") + return response + + +CONSTRUCT_ID = os.environ["CONSTRUCT_ID"] +# Load parameters from AWS Systems Manager Parameter Store +USER_POOL_CLIENT_ID = get_from_parameter_store(f"/{CONSTRUCT_ID}/user_pool/client_id") +USER_POOL_ID = get_from_parameter_store(f"/{CONSTRUCT_ID}/user_pool/id") +USAGE_PLAN_ID = get_from_parameter_store(f"/{CONSTRUCT_ID}/api/usage_plan/id") + + +def handler(event, context): + try: + # Log a redacted version of the incoming event + log_redacted_event(event) + + # Parse the request body and path from the event + body = json.loads(event["body"]) + path = event["path"] + + # Validate required parameters in the request body + if not validate_parameters(body, ["email", "password"]) or ( + path == "/register" and not validate_parameters(body, ["fullname"]) + ): + logger.error("Missing required parameters: email or password.") + return generate_response(422, False, "Missing required parameters.") + + else: + email = body["email"] + password = body["password"] + fullname = body.get("fullname", "") + + # Handle different paths from the API Gateway + if path == "/login": + # Login path, authenticate the user + response = login_cognito_user(email, password) + return generate_response(200, True, response["AuthenticationResult"]) + + elif path == "/register": + # Registration path, create a new Cognito user and associated API key + response = create_cognito_user(email, password, fullname) + api_key = create_api_key(response["UserSub"]) + update_cognito_user_api_key(email, api_key) + return generate_response( + 200, True, f"User {email} created successfully.", {"API Key": api_key} + ) + + else: + # Handle any other request method + logger.error("Invalid method: %s.", event["httpMethod"]) + return generate_response(400, False, "Invalid method") + + except Exception as err: + logger.exception(f"Failed to {path[1:]}.") + return generate_response( + err.response["ResponseMetadata"]["HTTPStatusCode"], + False, + err.response["Error"]["Message"], + ) diff --git a/apigw-bedrock-cognito-terraform/src/auth/auth.zip b/apigw-bedrock-cognito-terraform/src/auth/auth.zip new file mode 100644 index 000000000..65aaef581 Binary files /dev/null and b/apigw-bedrock-cognito-terraform/src/auth/auth.zip differ diff --git a/apigw-bedrock-cognito-terraform/src/bedrock/bedrock.py b/apigw-bedrock-cognito-terraform/src/bedrock/bedrock.py new file mode 100644 index 000000000..c8460a045 --- /dev/null +++ b/apigw-bedrock-cognito-terraform/src/bedrock/bedrock.py @@ -0,0 +1,118 @@ +import json +import boto3 +import logging +from jsonpath_ng.ext import parse + + +# Configure logging +logger = logging.getLogger() +logger.setLevel(logging.INFO) + +# Initialize AWS clients +bedrock = boto3.client("bedrock") +bedrock_runtime = boto3.client("bedrock-runtime") + +# Mapping of model types to JSONPath expressions for response extraction +MODEL_REPLY_MAPPING = { + "anthropic": "$.completion", + "ai21": "$.completions[0].data.text", + "amazon": "$.results[0].outputText", + "meta": "$.generation", +} + + +# Method to generate a standardised API response +def generate_response(status_code, success, message, data=[]): + return { + "statusCode": status_code, + "body": json.dumps( + { + "success": success, + "message": message, + "data": data, + } + ), + "headers": { + "Content-Type": "application/json" + } + } + + +# Method to list foundation models +def list_foundation_models(): + foundation_models = bedrock.list_foundation_models() + return [ + {"modelName": model["modelName"], "modelId": model["modelId"]} + for model in foundation_models["modelSummaries"] + ] + + +# Method to invoke foundation models +def invoke_foundation_model(model_id, inference_parameters): + # Invoke foundation model + bedrock_runtime_body = json.dumps(inference_parameters) + response = bedrock_runtime.invoke_model(body=bedrock_runtime_body, modelId=model_id) + response_body = json.loads(response["body"].read()) + logger.info("Response: %s", response_body) + + # Get reply from response body from foundation model + if model_id.startswith(tuple(MODEL_REPLY_MAPPING.keys())): + json_path = MODEL_REPLY_MAPPING[model_id.split(".")[0]] + jsonpath_expr = parse(json_path) + answer = jsonpath_expr.find(response_body)[0].value + + else: + answer = response_body + + return generate_response( + 200, True, "Successfully retrieved response from foundation model.", answer + ) + + +def handler(event, context): + try: + # Log incoming event + logger.info("Event: %s", event) + + if event["httpMethod"] == "GET": + # Handle GET request to retrieve foundation models list + foundation_models = list_foundation_models() + logger.info("Foundation Models: %s", foundation_models) + return generate_response( + 200, + True, + "Successfully retrieved foundation models list.", + foundation_models, + ) + + elif event["httpMethod"] == "POST": + # Handle POST request to invoke foundation models with inference parameters + body = json.loads(event["body"]) + model_id = body.get("modelId", None) + inference_parameters = body.get("inferenceParameters", None) + + if not (model_id and inference_parameters): + logger.info( + "Missing required parameters, model_id: %s, inferenceParameters: %s", + model_id, + inference_parameters, + ) + return generate_response(422, False, "Missing required parameters.") + + return invoke_foundation_model(model_id, inference_parameters) + + else: + # Handle any other request method + logger.error("Invalid method: %s.", event["httpMethod"]) + return generate_response(400, False, "Invalid method") + + except botocore.exceptions.ClientError as err: + logger.exception("AWS ClientError occurred.") + return generate_response( + err.response["ResponseMetadata"]["HTTPStatusCode"], + False, + err.response["Error"]["Message"], + ) + except Exception as err: + logger.exception("Unexpected error occurred: %s", err) + return generate_response(500, False, "Internal Server Error") diff --git a/apigw-bedrock-cognito-terraform/src/bedrock/bedrock.zip b/apigw-bedrock-cognito-terraform/src/bedrock/bedrock.zip new file mode 100644 index 000000000..cc6c312a5 Binary files /dev/null and b/apigw-bedrock-cognito-terraform/src/bedrock/bedrock.zip differ diff --git a/apigw-bedrock-cognito-terraform/src/bedrock/requirements.txt b/apigw-bedrock-cognito-terraform/src/bedrock/requirements.txt new file mode 100644 index 000000000..5b0622665 --- /dev/null +++ b/apigw-bedrock-cognito-terraform/src/bedrock/requirements.txt @@ -0,0 +1 @@ +jsonpath-ng \ No newline at end of file diff --git a/apigw-bedrock-cognito-terraform/src/pre_signup/pre_signup.py b/apigw-bedrock-cognito-terraform/src/pre_signup/pre_signup.py new file mode 100644 index 000000000..b68c6349f --- /dev/null +++ b/apigw-bedrock-cognito-terraform/src/pre_signup/pre_signup.py @@ -0,0 +1,32 @@ +import os +import logging + + +# Configure logging +logger = logging.getLogger() +logger.setLevel(logging.INFO) + +# Get organization domain from environment variable +ORGANIZATION_DOMAIN = os.environ.get("ORGANIZATION_DOMAIN") + + +def handler(event, context): + # Log the incoming event + logger.info(event) + + # Extract email domain + email_domain = event["request"]["userAttributes"]["email"].split("@")[1] + + # Check if email domain matches organization + if email_domain == ORGANIZATION_DOMAIN: + # Automatically confirm user registration and verify email + event["response"]["autoConfirmUser"] = True + event["response"]["autoVerifyEmail"] = True + + else: + # Raise an exception if email is not part of the organization domain + raise Exception( + f"Cannot register user as email is not part of domain: {ORGANIZATION_DOMAIN}" + ) + + return event diff --git a/apigw-bedrock-cognito-terraform/utils/delete_all_api_keys.sh b/apigw-bedrock-cognito-terraform/utils/delete_all_api_keys.sh new file mode 100644 index 000000000..43804187e --- /dev/null +++ b/apigw-bedrock-cognito-terraform/utils/delete_all_api_keys.sh @@ -0,0 +1,4 @@ +for api_key_id in $(aws apigateway get-api-keys --query 'items[*].id' --output text); do + echo "Deleting API key $api_key_id" + aws apigateway delete-api-key --api-key $api_key_id +done \ No newline at end of file diff --git a/apigw-bedrock-cognito-terraform/variables.tf b/apigw-bedrock-cognito-terraform/variables.tf new file mode 100644 index 000000000..7e4472631 --- /dev/null +++ b/apigw-bedrock-cognito-terraform/variables.tf @@ -0,0 +1,65 @@ +variable "contructID" { + description = "Construct ID" + type = string + default = "ApigwBedrockCognitoCdkStack" +} + +variable "region" { + description = "The AWS region to deploy the resources" + type = string + default = "us-east-1" +} + +variable "rate_limit" { + description = "Maximum sustained requests per second" + type = number + default = 1 +} + +variable "burst_limit" { + description = "Maximum burst requests per second" + type = number + default = 2 +} + +variable "quota_limit" { + description = "Maximum number of requests allowed in the specified period" + type = number + default = 25 +} + +variable "quota_period" { + description = "Period for the quota (DAY, WEEK, MONTH)" + type = string + default = "MONTH" +} + +variable "stage_name" { + description = "Stage name for the API Gateway deployment" + type = string + default = "prod" +} + +variable "lambda_runtime" { + description = "Runtime for the Lambda function" + type = string + default = "python3.9" +} + +variable "auth_handler" { + description = "The handler for the Lambda function" + type = string + default = "auth.handler" +} + +variable "timeout" { + description = "Timeout for the Lambda function in seconds" + type = number + default = 29 +} + +variable "bedrock_path" { + description = "Path for bedrock zip" + type = string + default = "src/bedrock" +} \ No newline at end of file