chore: polish code with little update (#182)
- Run Docker container as non-root user (appuser) to minimize security risks - Add Docker HEALTHCHECK for better container orchestration - Make CORS configurable via ALLOWED_ORIGINS env var with security warning - Replace assertions with proper error handling (TypeError/ValueError) - Add 30s timeout to HTTP requests to prevent hanging connections - Disable auto-reload in production uvicorn settings
This commit is contained in:
@@ -133,7 +133,7 @@ If you encounter any issues, please check the [Troubleshooting Guide](./docs/Tro
|
||||
|
||||
### SDK/API Usage
|
||||
|
||||
All you need is the API Key and the API Base URL. If you didn't set up your own key, then the default API Key (`bedrock`) will be used.
|
||||
All you need is the API Key and the API Base URL. If you didn't set up your own key following Step 1, the application will fail to start with an error message indicating that the API Key is not configured.
|
||||
|
||||
Now, you can try out the proxy APIs. Let's say you want to test Claude 3 Sonnet model (model ID: `anthropic.claude-3-sonnet-20240229-v1:0`)...
|
||||
|
||||
|
||||
8
THIRD_PARTY
Normal file
8
THIRD_PARTY
Normal file
@@ -0,0 +1,8 @@
|
||||
certifi
|
||||
|
||||
SPDX-License-Identifier: MPL-2.0
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
https://github.com/certifi/python-certifi
|
||||
@@ -256,8 +256,8 @@ Resources:
|
||||
Ref: ContainerImageUri
|
||||
Name: proxy-api
|
||||
PortMappings:
|
||||
- ContainerPort: 80
|
||||
HostPort: 80
|
||||
- ContainerPort: 8080
|
||||
HostPort: 8080
|
||||
Protocol: tcp
|
||||
Secrets:
|
||||
- Name: API_KEY
|
||||
@@ -303,7 +303,7 @@ Resources:
|
||||
HealthCheckGracePeriodSeconds: 60
|
||||
LoadBalancers:
|
||||
- ContainerName: proxy-api
|
||||
ContainerPort: 80
|
||||
ContainerPort: 8080
|
||||
TargetGroupArn:
|
||||
Ref: ProxyALBListenerTargetsGroup187739FA
|
||||
NetworkConfiguration:
|
||||
@@ -340,7 +340,7 @@ Resources:
|
||||
Type: AWS::EC2::SecurityGroupIngress
|
||||
Properties:
|
||||
Description: Load balancer to target
|
||||
FromPort: 80
|
||||
FromPort: 8080
|
||||
GroupId:
|
||||
Fn::GetAtt:
|
||||
- ProxyApiServiceSecurityGroup51EBD9B8
|
||||
@@ -350,7 +350,7 @@ Resources:
|
||||
Fn::GetAtt:
|
||||
- ProxyALBSecurityGroup0D6CA3DA
|
||||
- GroupId
|
||||
ToPort: 80
|
||||
ToPort: 8080
|
||||
DependsOn:
|
||||
- ProxyTaskRoleDefaultPolicy933321B8
|
||||
- ProxyTaskRole5DB6A540
|
||||
@@ -396,13 +396,13 @@ Resources:
|
||||
Fn::GetAtt:
|
||||
- ProxyApiServiceSecurityGroup51EBD9B8
|
||||
- GroupId
|
||||
FromPort: 80
|
||||
FromPort: 8080
|
||||
GroupId:
|
||||
Fn::GetAtt:
|
||||
- ProxyALBSecurityGroup0D6CA3DA
|
||||
- GroupId
|
||||
IpProtocol: tcp
|
||||
ToPort: 80
|
||||
ToPort: 8080
|
||||
ProxyALBListener933E9515:
|
||||
Type: AWS::ElasticLoadBalancingV2::Listener
|
||||
Properties:
|
||||
@@ -421,7 +421,7 @@ Resources:
|
||||
HealthCheckIntervalSeconds: 60
|
||||
HealthCheckPath: /health
|
||||
HealthCheckTimeoutSeconds: 30
|
||||
Port: 80
|
||||
Port: 8080
|
||||
Protocol: HTTP
|
||||
TargetGroupAttributes:
|
||||
- Key: stickiness.enabled
|
||||
|
||||
@@ -8,6 +8,15 @@ RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt
|
||||
|
||||
COPY ./api /app/api
|
||||
|
||||
ENV PORT=80
|
||||
# Create non-root user
|
||||
RUN groupadd -r appuser && useradd -r -g appuser appuser && \
|
||||
chown -R appuser:appuser /app
|
||||
|
||||
USER appuser
|
||||
|
||||
ENV PORT=8080
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8080/api/v1/models').read()"
|
||||
|
||||
CMD ["sh", "-c", "uvicorn api.app:app --host 0.0.0.0 --port ${PORT}"]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
import uvicorn
|
||||
from fastapi import FastAPI
|
||||
@@ -23,9 +24,16 @@ logging.basicConfig(
|
||||
)
|
||||
app = FastAPI(**config)
|
||||
|
||||
allowed_origins = os.environ.get("ALLOWED_ORIGINS", "*")
|
||||
origins_list = [origin.strip() for origin in allowed_origins.split(",")] if allowed_origins != "*" else ["*"]
|
||||
|
||||
# Warn if CORS allows all origins
|
||||
if origins_list == ["*"]:
|
||||
logging.warning("CORS is configured to allow all origins (*). Set ALLOWED_ORIGINS environment variable to restrict access.")
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_origins=origins_list, # nosec - configurable via ALLOWED_ORIGINS env var
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
@@ -61,4 +69,5 @@ async def validation_exception_handler(request, exc):
|
||||
handler = Mangum(app)
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True)
|
||||
# Bind to 0.0.0.0 for container environments, network is handled by network policies and load balancers
|
||||
uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=False) # nosec B104
|
||||
|
||||
@@ -7,8 +7,6 @@ from botocore.exceptions import ClientError
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
|
||||
from api.setting import DEFAULT_API_KEYS
|
||||
|
||||
api_key_param = os.environ.get("API_KEY_PARAM_NAME")
|
||||
api_key_secret_arn = os.environ.get("API_KEY_SECRET_ARN")
|
||||
api_key_env = os.environ.get("API_KEY")
|
||||
@@ -31,8 +29,9 @@ elif api_key_secret_arn:
|
||||
elif api_key_env:
|
||||
api_key = api_key_env
|
||||
else:
|
||||
# For local use only.
|
||||
api_key = DEFAULT_API_KEYS
|
||||
raise RuntimeError(
|
||||
"API Key is not configured. Please set up your API Key."
|
||||
)
|
||||
|
||||
security = HTTPBearer()
|
||||
|
||||
|
||||
@@ -310,7 +310,8 @@ class BedrockModel(BaseChatModel):
|
||||
if message.role != "system":
|
||||
# ignore system messages here
|
||||
continue
|
||||
assert isinstance(message.content, str)
|
||||
if not isinstance(message.content, str):
|
||||
raise TypeError(f"System message content must be a string, got {type(message.content).__name__}")
|
||||
system_prompts.append({"text": message.content})
|
||||
|
||||
return system_prompts
|
||||
@@ -580,7 +581,8 @@ class BedrockModel(BaseChatModel):
|
||||
tool_config["toolChoice"] = {"auto": {}}
|
||||
else:
|
||||
# Specific tool to use
|
||||
assert "function" in chat_request.tool_choice
|
||||
if "function" not in chat_request.tool_choice:
|
||||
raise ValueError("tool_choice must contain 'function' key when specifying a specific tool")
|
||||
tool_config["toolChoice"] = {"tool": {"name": chat_request.tool_choice["function"].get("name", "")}}
|
||||
args["toolConfig"] = tool_config
|
||||
# add Additional fields to enable extend thinking
|
||||
@@ -784,7 +786,7 @@ class BedrockModel(BaseChatModel):
|
||||
return base64.b64decode(image_data), content_type.group(1)
|
||||
|
||||
# Send a request to the image URL
|
||||
response = requests.get(image_url)
|
||||
response = requests.get(image_url, timeout=30)
|
||||
# Check if the request was successful
|
||||
if response.status_code == 200:
|
||||
content_type = response.headers.get("Content-Type")
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import os
|
||||
|
||||
DEFAULT_API_KEYS = "bedrock"
|
||||
|
||||
API_ROUTE_PREFIX = os.environ.get("API_ROUTE_PREFIX", "/api/v1")
|
||||
|
||||
TITLE = "Amazon Bedrock Proxy APIs"
|
||||
|
||||
Reference in New Issue
Block a user