Skip to content

Commit 9cea7f9

Browse files
authored
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
1 parent 8177876 commit 9cea7f9

File tree

8 files changed

+46
-21
lines changed

8 files changed

+46
-21
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ If you encounter any issues, please check the [Troubleshooting Guide](./docs/Tro
133133

134134
### SDK/API Usage
135135

136-
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.
136+
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.
137137

138138
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`)...
139139

THIRD_PARTY

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
certifi
2+
3+
SPDX-License-Identifier: MPL-2.0
4+
This Source Code Form is subject to the terms of the Mozilla Public
5+
License, v. 2.0. If a copy of the MPL was not distributed with this
6+
file, You can obtain one at http://mozilla.org/MPL/2.0/.
7+
8+
https://github.com/certifi/python-certifi

deployment/BedrockProxyFargate.template

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -256,8 +256,8 @@ Resources:
256256
Ref: ContainerImageUri
257257
Name: proxy-api
258258
PortMappings:
259-
- ContainerPort: 80
260-
HostPort: 80
259+
- ContainerPort: 8080
260+
HostPort: 8080
261261
Protocol: tcp
262262
Secrets:
263263
- Name: API_KEY
@@ -303,7 +303,7 @@ Resources:
303303
HealthCheckGracePeriodSeconds: 60
304304
LoadBalancers:
305305
- ContainerName: proxy-api
306-
ContainerPort: 80
306+
ContainerPort: 8080
307307
TargetGroupArn:
308308
Ref: ProxyALBListenerTargetsGroup187739FA
309309
NetworkConfiguration:
@@ -340,7 +340,7 @@ Resources:
340340
Type: AWS::EC2::SecurityGroupIngress
341341
Properties:
342342
Description: Load balancer to target
343-
FromPort: 80
343+
FromPort: 8080
344344
GroupId:
345345
Fn::GetAtt:
346346
- ProxyApiServiceSecurityGroup51EBD9B8
@@ -350,7 +350,7 @@ Resources:
350350
Fn::GetAtt:
351351
- ProxyALBSecurityGroup0D6CA3DA
352352
- GroupId
353-
ToPort: 80
353+
ToPort: 8080
354354
DependsOn:
355355
- ProxyTaskRoleDefaultPolicy933321B8
356356
- ProxyTaskRole5DB6A540
@@ -396,13 +396,13 @@ Resources:
396396
Fn::GetAtt:
397397
- ProxyApiServiceSecurityGroup51EBD9B8
398398
- GroupId
399-
FromPort: 80
399+
FromPort: 8080
400400
GroupId:
401401
Fn::GetAtt:
402402
- ProxyALBSecurityGroup0D6CA3DA
403403
- GroupId
404404
IpProtocol: tcp
405-
ToPort: 80
405+
ToPort: 8080
406406
ProxyALBListener933E9515:
407407
Type: AWS::ElasticLoadBalancingV2::Listener
408408
Properties:
@@ -421,7 +421,7 @@ Resources:
421421
HealthCheckIntervalSeconds: 60
422422
HealthCheckPath: /health
423423
HealthCheckTimeoutSeconds: 30
424-
Port: 80
424+
Port: 8080
425425
Protocol: HTTP
426426
TargetGroupAttributes:
427427
- Key: stickiness.enabled

src/Dockerfile_ecs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt
88

99
COPY ./api /app/api
1010

11-
ENV PORT=80
11+
# Create non-root user
12+
RUN groupadd -r appuser && useradd -r -g appuser appuser && \
13+
chown -R appuser:appuser /app
14+
15+
USER appuser
16+
17+
ENV PORT=8080
18+
19+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
20+
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8080/api/v1/models').read()"
1221

1322
CMD ["sh", "-c", "uvicorn api.app:app --host 0.0.0.0 --port ${PORT}"]

src/api/app.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
import os
23

34
import uvicorn
45
from fastapi import FastAPI
@@ -23,9 +24,16 @@
2324
)
2425
app = FastAPI(**config)
2526

27+
allowed_origins = os.environ.get("ALLOWED_ORIGINS", "*")
28+
origins_list = [origin.strip() for origin in allowed_origins.split(",")] if allowed_origins != "*" else ["*"]
29+
30+
# Warn if CORS allows all origins
31+
if origins_list == ["*"]:
32+
logging.warning("CORS is configured to allow all origins (*). Set ALLOWED_ORIGINS environment variable to restrict access.")
33+
2634
app.add_middleware(
2735
CORSMiddleware,
28-
allow_origins=["*"],
36+
allow_origins=origins_list, # nosec - configurable via ALLOWED_ORIGINS env var
2937
allow_credentials=True,
3038
allow_methods=["*"],
3139
allow_headers=["*"],
@@ -61,4 +69,5 @@ async def validation_exception_handler(request, exc):
6169
handler = Mangum(app)
6270

6371
if __name__ == "__main__":
64-
uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True)
72+
# Bind to 0.0.0.0 for container environments, network is handled by network policies and load balancers
73+
uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=False) # nosec B104

src/api/auth.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
from fastapi import Depends, HTTPException, status
88
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
99

10-
from api.setting import DEFAULT_API_KEYS
11-
1210
api_key_param = os.environ.get("API_KEY_PARAM_NAME")
1311
api_key_secret_arn = os.environ.get("API_KEY_SECRET_ARN")
1412
api_key_env = os.environ.get("API_KEY")
@@ -31,8 +29,9 @@
3129
elif api_key_env:
3230
api_key = api_key_env
3331
else:
34-
# For local use only.
35-
api_key = DEFAULT_API_KEYS
32+
raise RuntimeError(
33+
"API Key is not configured. Please set up your API Key."
34+
)
3635

3736
security = HTTPBearer()
3837

src/api/models/bedrock.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,8 @@ def _parse_system_prompts(self, chat_request: ChatRequest) -> list[dict[str, str
310310
if message.role != "system":
311311
# ignore system messages here
312312
continue
313-
assert isinstance(message.content, str)
313+
if not isinstance(message.content, str):
314+
raise TypeError(f"System message content must be a string, got {type(message.content).__name__}")
314315
system_prompts.append({"text": message.content})
315316

316317
return system_prompts
@@ -580,7 +581,8 @@ def _parse_request(self, chat_request: ChatRequest) -> dict:
580581
tool_config["toolChoice"] = {"auto": {}}
581582
else:
582583
# Specific tool to use
583-
assert "function" in chat_request.tool_choice
584+
if "function" not in chat_request.tool_choice:
585+
raise ValueError("tool_choice must contain 'function' key when specifying a specific tool")
584586
tool_config["toolChoice"] = {"tool": {"name": chat_request.tool_choice["function"].get("name", "")}}
585587
args["toolConfig"] = tool_config
586588
# add Additional fields to enable extend thinking
@@ -784,7 +786,7 @@ def _parse_image(self, image_url: str) -> tuple[bytes, str]:
784786
return base64.b64decode(image_data), content_type.group(1)
785787

786788
# Send a request to the image URL
787-
response = requests.get(image_url)
789+
response = requests.get(image_url, timeout=30)
788790
# Check if the request was successful
789791
if response.status_code == 200:
790792
content_type = response.headers.get("Content-Type")

src/api/setting.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import os
22

3-
DEFAULT_API_KEYS = "bedrock"
4-
53
API_ROUTE_PREFIX = os.environ.get("API_ROUTE_PREFIX", "/api/v1")
64

75
TITLE = "Amazon Bedrock Proxy APIs"

0 commit comments

Comments
 (0)