-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add machine-to-machine API authentication example (#549)
## Summary This PR allows developers to authenticate their requests with a server with machine-to-machine authentication. This is useful when the server is accessible to the public but requires a secure API-key authentication to access the HTTP services. ## Checks - [x] `make lint`: I've run `make lint` to lint the changes in this PR. - [x] `make test`: I've made sure the tests (`make test-cpu` or `make test`) are passing. - Additional tests: - [ ] Benchmark tests (when contributing new models) - [ ] GPU/HW tests
- Loading branch information
Showing
10 changed files
with
188 additions
and
43 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
33 changes: 33 additions & 0 deletions
33
examples/tutorials/06-serving-with-m2m-auth/docker-compose.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
version: "3.8" | ||
|
||
services: | ||
nos-http-gateway: | ||
image: autonomi/nos:latest-cpu | ||
command: nos-http-server --host 0.0.0.0 --port 8000 --workers 1 | ||
environment: | ||
- NOS_HOME=/app/.nos | ||
- NOS_LOGGING_LEVEL=INFO | ||
- NOS_GRPC_HOST=nos-grpc-server | ||
- NOS_HTTP_ENV=prod | ||
- NOS_M2M_API_KEYS=sk-test-key-1,sk-test-key-2 | ||
volumes: | ||
- ~/.nosd:/app/.nos | ||
- /dev/shm:/dev/shm | ||
ports: | ||
- 8000:8000 | ||
ipc: host | ||
depends_on: | ||
- nos-grpc-server | ||
|
||
nos-grpc-server: | ||
image: autonomi/nos:latest-cpu | ||
environment: | ||
- NOS_HOME=/app/.nos | ||
- NOS_GRPC_HOST=[::] | ||
- NOS_LOGGING_LEVEL=INFO | ||
volumes: | ||
- ~/.nosd:/app/.nos | ||
- /dev/shm:/dev/shm | ||
ports: | ||
- 50051:50051 | ||
ipc: host |
27 changes: 27 additions & 0 deletions
27
examples/tutorials/06-serving-with-m2m-auth/tests/test_m2m_auth.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import requests | ||
|
||
|
||
if __name__ == "__main__": | ||
BASE_URL = "http://localhost:8000" | ||
|
||
# Test health | ||
response = requests.get(f"{BASE_URL}/v1/health") | ||
response.raise_for_status() | ||
|
||
# Test model info without authentication | ||
response = requests.get(f"{BASE_URL}/v1/models") | ||
assert response.status_code == 401, "Expected 401 Unauthorized" | ||
|
||
# Test model info with invalid authentication | ||
response = requests.get(f"{BASE_URL}/v1/models", headers={"X-Api-Key": "invalid-api-key"}) | ||
assert response.status_code == 403, "Expected 403 Forbidden" | ||
|
||
# Test model info with valid authentication | ||
response = requests.get(f"{BASE_URL}/v1/models", headers={"X-Api-Key": "sk-test-key-1"}) | ||
response.raise_for_status() | ||
assert response.status_code == 200, "Expected 200 OK" | ||
|
||
# Test model inference without authentication | ||
response = requests.get(f"{BASE_URL}/v1/models", headers={"X-Api-Key": "sk-test-key-2"}) | ||
response.raise_for_status() | ||
assert response.status_code == 200, "Expected 200 OK" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
from fastapi import status | ||
from fastapi.responses import JSONResponse | ||
from loguru import logger | ||
from starlette.requests import Request | ||
|
||
|
||
async def default_exception_middleware(request: Request, call_next): | ||
try: | ||
return await call_next(request) | ||
except Exception as exc: | ||
base_error_message = f"Internal server error: [method={request.method}], url={request.url}]" | ||
logger.error(f"Internal server error: [method={request.method}, url={request.url}, exc={exc}]") | ||
return JSONResponse( | ||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | ||
content={"message": base_error_message}, | ||
) | ||
|
||
|
||
async def default_exception_handler(request, error): | ||
"""Default exception handler for all routes.""" | ||
base_error_message = f"Internal server error: [method={request.method}], url={request.url}]" | ||
logger.error(f"Internal server error: [method={request.method}, url={request.url}, error={error}]") | ||
return JSONResponse( | ||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | ||
content={"message": f"{base_error_message}."}, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import os | ||
|
||
from fastapi import Depends, HTTPException, Request, status | ||
from fastapi.security import APIKeyHeader | ||
from loguru import logger | ||
|
||
|
||
valid_m2m_keys = {} | ||
for key in os.getenv("NOS_M2M_API_KEYS", "").split(","): | ||
if len(key) > 0: | ||
logger.debug(f"Adding valid_m2m_keys [key={key}]") | ||
valid_m2m_keys[key] = key | ||
api_key_header = APIKeyHeader(name="X-Api-Key", auto_error=False) | ||
|
||
|
||
async def validate_m2m_key(request: Request, api_key: str = Depends(api_key_header)) -> bool: | ||
logger.debug(f"validate_m2m_key [api_key={api_key}]") | ||
|
||
if not api_key: | ||
raise HTTPException( | ||
status_code=status.HTTP_401_UNAUTHORIZED, | ||
detail="Missing X-Api-Key Key header", | ||
) | ||
|
||
if api_key not in valid_m2m_keys: | ||
raise HTTPException( | ||
status_code=status.HTTP_403_FORBIDDEN, | ||
detail="Invalid Machine-to-Machine Key", | ||
) | ||
|
||
assert isinstance(api_key, str) | ||
return True | ||
|
||
|
||
if valid_m2m_keys: | ||
ValidMachineToMachine = Depends(validate_m2m_key) | ||
else: | ||
ValidMachineToMachine = None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
__version__ = "0.2.1" | ||
__version__ = "0.3.0" |