From c9275bc9354b2473fe2190f11ff6e25d9395ff08 Mon Sep 17 00:00:00 2001 From: Adnan Hajar Date: Wed, 3 Dec 2025 11:35:14 -0500 Subject: [PATCH] Share cookbook for nova proxy (a middleware layer owned by clients so they can hide their api key) --- nova_proxy/README.md | 224 +++++++++++++++++++++++ nova_proxy/template-nova-proxy-lambda.py | 85 +++++++++ 2 files changed, 309 insertions(+) create mode 100644 nova_proxy/README.md create mode 100644 nova_proxy/template-nova-proxy-lambda.py diff --git a/nova_proxy/README.md b/nova_proxy/README.md new file mode 100644 index 0000000..f40cfb0 --- /dev/null +++ b/nova_proxy/README.md @@ -0,0 +1,224 @@ +# πŸš€ Amazon Nova API Proxy with AWS secret manager +*A simple guide for beginners* + +## πŸ“˜ What Is a β€œProxy” in AWS? + +A **proxy** is a small service that sits **between a client** (your app, website, or another backend) **and a downstream service** (Nova API), forwarding requests and returning responses. +Think of it as a middleman: + +``` +Client β†’ Proxy β†’ Nova API +``` + +The proxy itself does not do the main work. Instead, it: + +- receives the request +- optionally validates or logs it +- forwards it to the actual service +- returns the response + +returns the response - this pattern works extremely well in protecting your api key and your secrets! + +--- + +## ⭐ Why Use a Proxy? + +### 1. Hide Secrets +Your proxy stores API keys securely in **AWS Secrets Manager** so clients never see them. + +### 2. Centralize Authentication & Validation +Every request passes through the proxy, allowing you to enforce: + +- authentication +- schema validation +- rate limiting +- safety checks + +### 3. Standardize Requests +Your proxy can rewrite or normalize requests: + +- add default parameters +- enforce model versions +- attach metadata +- convert formats + +### 4. Safer Rollouts +You can upgrade your backend, change routes, update model versions, or add safeguards **without modifying client code**. + +### 5. Monitoring & Observability +With it you can add: + +- logs +- metrics +- alarms and alerts +- error tracking + +Your upstream system receives clean, normalized traffic. + +--- + +## πŸ—οΈ Architecture Overview + +A fully managed AWS proxy looks like this: + +``` +Client + | + v +API Gateway (public HTTPS endpoint) + | + v +AWS Lambda (your proxy code) + | + v +Nova API +``` + +API Gateway = HTTPS entrypoint +Lambda = code execution +Secrets Manager = secure key storage +Backend = your actual ChatCompletion API + +--- + +## 🧰 What a Lambda Proxy Typically Does + +Inside your proxy Lambda, the flow is: + +1. Parse the incoming JSON request +2. Fetch your API key from Secrets Manager +3. Initialize the backend SDK with that key +4. Forward the request to the upstream service +5. Return the upstream response to the caller + +This gives you full control over the request pipeline. + +--- + +## πŸš€ How to Deploy a Proxy to AWS (Simple Steps) + +Below is the simplest possible deployment workflow using the AWS Console. + +--- + +### **1. Create the Secret in Secrets Manager** + +1. Open **AWS Secrets Manager** +2. Click **Store a new secret** +3. Choose **Other type of secret** +4. Store your *plain text* API key: (You can fetch your api key from nova.amazon.com/dev/api) + +--- + +### **2. Create the Lambda Function** + +1. Go to **AWS Lambda β†’ Create function** +2. Choose **Author from scratch** +3. Runtime: **Python 3.10+** +4. Add environment variables to the Lambda configuration: +``` +SECRET_MANAGER_SECRET_NAME=backend/NovaApiKey +``` + +5. Paste your proxy Python code into the editor (the handler should read the request, fetch the secret, call the backend, and respond). + +--- + +### **3. Grant Lambda Permission to Read the Secret** + +Open the **execution role** attached to the Lambda and add this IAM policy: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "secretsmanager:GetSecretValue", + "Resource": "arn:aws:secretsmanager:REGION:ACCOUNT:secret:backend/NovaApiKey" + } + ] +} +``` +⚠️ Replace REGION and ACCOUNT with your values. + +### **4.Create the HTTPS Endpoint (API Gateway)** +This makes your proxy publicly reachable. +1. Open API Gateway +2. Create an HTTP API +3. Add a route: +``` +POST /chat +``` +4. Integrate that route with your Lambda +5. Deploy the API + +You will receive a public URL like: +``` +https://abc123.execute-api.us-east-1.amazonaws.com/chat +``` +This is your new proxy endpoint. + +πŸ§ͺ Example: Calling Your Proxy + +``` +curl -X POST https://abc123.execute-api.us-east-1.amazonaws.com/chat \ + -H "Content-Type: application/json" \ + -d '{ + "model": "nova-2-lite-v1", + "messages": [ + { "role": "user", "content": "Hello from client!" } + ] + }' +``` +Your proxy Lambda will: + +1. Receive this request +2. Fetch the secret +3. Forward it to your ChatCompletion backend +4. Return the result + +### **5. Integrate proxy with your FE code (You are almost there!)** + +Once your Lambda proxy is deployed behind API Gateway, you’ll receive a public HTTPS endpoint like: + +``` +https://abc123.execute-api.us-east-1.amazonaws.com/chat +``` + +Your frontend (React, Vue, Next.js, plain JS, etc.) can call this endpoint directly. +Because the proxy hides the API key, the frontend sends **no secrets**. + +### βœ… Example: Using Fetch in JavaScript + +```js +async function callChatProxy(userMessage) { + const response = await fetch("https://abc123.execute-api.us-east-1.amazonaws.com/chat", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + model: "nova-2-lite-v1", + messages: [ + { role: "user", content: userMessage } + ] + }) + }); + + const data = await response.json(); + return data; +} + +// Usage: +callChatProxy("Hello from the frontend!").then(console.log); +``` + +### Summary +There you have it! if you followed this tutorial you are able to deploy, run and build you own single turn Chat bot while still upholding the highest security standards! + +### What to do next? +1. You can expand the FE code to handle multi turns! +2. You can add a DNS for your proxy endpoint using [Route 53](https://aws.amazon.com/route53/) +3. You can add logging, authentication, error handling to your lambda +4. You can start supporting streaming and a lot more! \ No newline at end of file diff --git a/nova_proxy/template-nova-proxy-lambda.py b/nova_proxy/template-nova-proxy-lambda.py new file mode 100644 index 0000000..dddac13 --- /dev/null +++ b/nova_proxy/template-nova-proxy-lambda.py @@ -0,0 +1,85 @@ +import json +import boto3 +import os +from openai import OpenAI + +### Secret manager + +# This is the name of the secret that will be holding the API key for nova.amazon.com/dev +SECRET_NAME = os.getenv("SECRET_MANAGER_SECRET_NAME", "nova-api-default-secret-holder-name") + +# This is the region that the secret is in +REGION = os.getenv("AWS_REGION", "us-east-1") + +# This is the secret manager client that will be used to pull the api key from secret manager +secrets_client = boto3.client("secretsmanager", region_name=REGION) + +### NOVA API +NOVA_API_DNS = os.getenv("nova_api_dns", "https://api.nova.amazon.com/v1") + +### JSON RESPONSE +# Use this field to decide whether you want the full Json object or just the message content output +JSON_RESPONSE = False + +# This is the function that you can invoke to pull the api key +def get_api_key(): + """Fetch API key from Secrets Manager.""" + resp = secrets_client.get_secret_value(SecretId=SECRET_NAME) + # This setup assumes you have the secret in plain text inside of secret manager + return resp["SecretString"] + +def lambda_handler(event, context): + """ + Expected event: + { + "model": "nova-2-lite-v1", + "messages": [ + {"role": "user", "content": "hello!"} + ] + } + """ + try: + if isinstance(event, str): + body = json.loads(event) + else: + body = event + + + api_key = get_api_key() + + # Initialize OpenAI client for your backend + client = OpenAI( + base_url=NOVA_API_DNS, + api_key=api_key + ) + + # Forward request to backend + completion = client.chat.completions.create( + model=body["model"], + messages=body["messages"] + ) + + if JSON_RESPONSE: + return { + "statusCode": 200, + "body": json.dumps(completion.model_dump()) + } + else: + return completion.choices[0].message.content + + except Exception as e: + return { + "statusCode": 500, + "body": json.dumps({"error": str(e)}) + } + +# Use this if you want to test your lambda locally +if __name__ == "__main__": + example_event = { + "model": "nova-2-lite-v1", + "messages": [ + {"role": "user", "content": "hello!"} + ] + } + result = lambda_handler( example_event, {}) + print(json.dumps(result, indent=2)) \ No newline at end of file