Add Nginx deployment support with IP and API key security features
This commit is contained in:
parent
7d70f362b2
commit
57fbe3a6c5
73
SECURITY.md
Normal file
73
SECURITY.md
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
# Hermes Service - Nginx Deployment with Security Features
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This service provides a multi-user Hermes Agent API that can be deployed behind Nginx with IP address filtering and API key authentication capabilities.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The service uses a `config.yaml` file for configuration. Key security features include:
|
||||||
|
|
||||||
|
### IP Address Checking
|
||||||
|
- Enable with `security.enable_ip_check: true`
|
||||||
|
- Configure allowed IPs in `security.allowed_ips` (supports CIDR notation)
|
||||||
|
- Works with X-Forwarded-For header when behind Nginx
|
||||||
|
|
||||||
|
### API Key Authentication
|
||||||
|
- Enable with `security.enable_api_key: true`
|
||||||
|
- Define valid API keys in `security.api_keys`
|
||||||
|
- Customizable header name via `security.api_key_header`
|
||||||
|
|
||||||
|
### Nginx Integration
|
||||||
|
- Real IP detection from X-Forwarded-For header
|
||||||
|
- Trusted proxy configuration
|
||||||
|
- Service binds to localhost by default for security
|
||||||
|
|
||||||
|
## Deployment with Nginx
|
||||||
|
|
||||||
|
1. **Configure the service** (`config.yaml`):
|
||||||
|
```yaml
|
||||||
|
security:
|
||||||
|
enable_ip_check: true
|
||||||
|
allowed_ips:
|
||||||
|
- "192.168.1.0/24"
|
||||||
|
- "203.0.113.0/24"
|
||||||
|
enable_api_key: true
|
||||||
|
api_keys:
|
||||||
|
- key: "your-secret-api-key"
|
||||||
|
description: "Production API key"
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Start the Hermes service**:
|
||||||
|
```bash
|
||||||
|
python main.py
|
||||||
|
# Service will listen on 127.0.0.1:9120
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Configure Nginx** (see `nginx.conf.example`):
|
||||||
|
- Set up reverse proxy to localhost:9120
|
||||||
|
- Configure SSL (recommended)
|
||||||
|
- Optional: Add additional IP restrictions at Nginx level
|
||||||
|
|
||||||
|
4. **Test the deployment**:
|
||||||
|
```bash
|
||||||
|
# Health check (no auth required)
|
||||||
|
curl http://your-domain.com/health
|
||||||
|
|
||||||
|
# API call with API key
|
||||||
|
curl -H "X-API-Key: your-secret-api-key" \
|
||||||
|
-X POST http://your-domain.com/api/v1/sessions \
|
||||||
|
-d '{"user_id": "test"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Best Practices
|
||||||
|
|
||||||
|
- Always run behind Nginx or similar reverse proxy in production
|
||||||
|
- Use HTTPS/SSL for all communications
|
||||||
|
- Regularly rotate API keys
|
||||||
|
- Restrict allowed IPs to known client networks
|
||||||
|
- Monitor access logs for suspicious activity
|
||||||
|
|
||||||
|
## Configuration Reference
|
||||||
|
|
||||||
|
See `config.yaml` for complete configuration options and examples.
|
||||||
60
config.yaml
Normal file
60
config.yaml
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# Hermes Service Configuration for Nginx Deployment
|
||||||
|
# This configuration file controls security features when running behind Nginx
|
||||||
|
|
||||||
|
# Security settings
|
||||||
|
security:
|
||||||
|
# Enable IP address checking
|
||||||
|
enable_ip_check: false
|
||||||
|
|
||||||
|
# List of allowed IP addresses or CIDR ranges
|
||||||
|
# If empty, all IPs are allowed (when IP check is disabled)
|
||||||
|
allowed_ips:
|
||||||
|
- "127.0.0.1"
|
||||||
|
- "::1"
|
||||||
|
# - "192.168.1.0/24"
|
||||||
|
# - "10.0.0.0/8"
|
||||||
|
|
||||||
|
# Enable API key authentication
|
||||||
|
enable_api_key: false
|
||||||
|
|
||||||
|
# List of valid API keys
|
||||||
|
# Each key can have a description and optional expiration
|
||||||
|
api_keys:
|
||||||
|
# - key: "your-api-key-here"
|
||||||
|
# description: "Main production key"
|
||||||
|
# expires_at: null # null means never expires, or use ISO format: "2025-12-31T23:59:59Z"
|
||||||
|
|
||||||
|
# Header name for API key (default: X-API-Key)
|
||||||
|
api_key_header: "X-API-Key"
|
||||||
|
|
||||||
|
# Nginx integration settings
|
||||||
|
nginx:
|
||||||
|
# Trust X-Forwarded-For header from these proxies
|
||||||
|
# Only set this if you're behind a trusted proxy like Nginx
|
||||||
|
trusted_proxies:
|
||||||
|
- "127.0.0.1"
|
||||||
|
- "::1"
|
||||||
|
|
||||||
|
# Enable real IP detection from X-Forwarded-For
|
||||||
|
enable_real_ip: true
|
||||||
|
|
||||||
|
# Service settings
|
||||||
|
service:
|
||||||
|
# Host to bind to (should be 127.0.0.1 when behind Nginx)
|
||||||
|
host: "127.0.0.1"
|
||||||
|
|
||||||
|
# Port to listen on
|
||||||
|
port: 9120
|
||||||
|
|
||||||
|
# Log level
|
||||||
|
log_level: "info"
|
||||||
|
|
||||||
|
# CORS settings (usually handled by Nginx in production)
|
||||||
|
cors:
|
||||||
|
allow_origins:
|
||||||
|
- "*"
|
||||||
|
allow_credentials: true
|
||||||
|
allow_methods:
|
||||||
|
- "*"
|
||||||
|
allow_headers:
|
||||||
|
- "*"
|
||||||
214
main.py
214
main.py
@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Hermes Service with complete session messaging support
|
Hermes Service with global session management and Nginx security support
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@ -9,12 +9,15 @@ import asyncio
|
|||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, HTTPException
|
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, HTTPException, Request
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from typing import Optional, Dict, Any, List
|
from typing import Optional, Dict, Any, List
|
||||||
import json
|
import json
|
||||||
import shutil
|
import shutil
|
||||||
|
import ipaddress
|
||||||
|
import yaml
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
# Base Hermes Agent path
|
# Base Hermes Agent path
|
||||||
BASE_HERMES_PATH = "/d/hermesai/.hermes/hermes-agent"
|
BASE_HERMES_PATH = "/d/hermesai/.hermes/hermes-agent"
|
||||||
@ -22,19 +25,141 @@ BASE_HERMES_PATH = "/d/hermesai/.hermes/hermes-agent"
|
|||||||
# Clean user data directory structure: /d/hermesai/users/{user_id}/.hermes
|
# Clean user data directory structure: /d/hermesai/users/{user_id}/.hermes
|
||||||
USERS_BASE = "/d/hermesai/users"
|
USERS_BASE = "/d/hermesai/users"
|
||||||
|
|
||||||
|
# Load configuration
|
||||||
|
CONFIG_FILE = os.path.join(os.path.dirname(__file__), "config.yaml")
|
||||||
|
if os.path.exists(CONFIG_FILE):
|
||||||
|
with open(CONFIG_FILE, 'r') as f:
|
||||||
|
config = yaml.safe_load(f)
|
||||||
|
else:
|
||||||
|
# Default configuration
|
||||||
|
config = {
|
||||||
|
'security': {
|
||||||
|
'enable_ip_check': False,
|
||||||
|
'allowed_ips': ['127.0.0.1', '::1'],
|
||||||
|
'enable_api_key': False,
|
||||||
|
'api_keys': [],
|
||||||
|
'api_key_header': 'X-API-Key'
|
||||||
|
},
|
||||||
|
'nginx': {
|
||||||
|
'trusted_proxies': ['127.0.0.1', '::1'],
|
||||||
|
'enable_real_ip': True
|
||||||
|
},
|
||||||
|
'service': {
|
||||||
|
'host': '127.0.0.1',
|
||||||
|
'port': 9120,
|
||||||
|
'log_level': 'info'
|
||||||
|
},
|
||||||
|
'cors': {
|
||||||
|
'allow_origins': ['*'],
|
||||||
|
'allow_credentials': True,
|
||||||
|
'allow_methods': ['*'],
|
||||||
|
'allow_headers': ['*']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
app = FastAPI(title="Hermes Service API", version="1.2.0")
|
app = FastAPI(title="Hermes Service API", version="1.2.0")
|
||||||
|
|
||||||
# Configure CORS
|
# Configure CORS
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=["*"],
|
allow_origins=config['cors']['allow_origins'],
|
||||||
allow_credentials=True,
|
allow_credentials=config['cors']['allow_credentials'],
|
||||||
allow_methods=["*"],
|
allow_methods=config['cors']['allow_methods'],
|
||||||
allow_headers=["*"],
|
allow_headers=config['cors']['allow_headers'],
|
||||||
)
|
)
|
||||||
|
|
||||||
# In-memory session storage for message history
|
# Global session registry: global_session_id -> {user_id, local_session_id, created_at}
|
||||||
active_sessions = {}
|
global_sessions = {}
|
||||||
|
|
||||||
|
def get_real_ip(request: Request) -> str:
|
||||||
|
"""Get the real client IP address, considering X-Forwarded-For header"""
|
||||||
|
if not config['nginx']['enable_real_ip']:
|
||||||
|
return request.client.host
|
||||||
|
|
||||||
|
# Check if the request comes from a trusted proxy
|
||||||
|
client_host = request.client.host
|
||||||
|
trusted_proxies = config['nginx']['trusted_proxies']
|
||||||
|
|
||||||
|
is_trusted = False
|
||||||
|
for trusted_proxy in trusted_proxies:
|
||||||
|
try:
|
||||||
|
if ipaddress.ip_address(client_host) in ipaddress.ip_network(trusted_proxy, strict=False):
|
||||||
|
is_trusted = True
|
||||||
|
break
|
||||||
|
except ValueError:
|
||||||
|
# Invalid IP or network, skip
|
||||||
|
continue
|
||||||
|
|
||||||
|
if is_trusted:
|
||||||
|
# Get the real IP from X-Forwarded-For header
|
||||||
|
forwarded_for = request.headers.get("x-forwarded-for")
|
||||||
|
if forwarded_for:
|
||||||
|
# X-Forwarded-For can contain multiple IPs, take the first one
|
||||||
|
real_ip = forwarded_for.split(",")[0].strip()
|
||||||
|
return real_ip
|
||||||
|
|
||||||
|
return client_host
|
||||||
|
|
||||||
|
def validate_ip_and_apikey():
|
||||||
|
"""Decorator to validate IP and API key for protected endpoints"""
|
||||||
|
def decorator(func):
|
||||||
|
@wraps(func)
|
||||||
|
async def wrapper(*args, **kwargs):
|
||||||
|
# Extract request object (assuming it's the first argument after self)
|
||||||
|
request = None
|
||||||
|
for arg in args:
|
||||||
|
if isinstance(arg, Request):
|
||||||
|
request = arg
|
||||||
|
break
|
||||||
|
|
||||||
|
if request is None:
|
||||||
|
# Try to find request in kwargs
|
||||||
|
request = kwargs.get('request')
|
||||||
|
|
||||||
|
if request is None:
|
||||||
|
# If no request object found, skip validation
|
||||||
|
return await func(*args, **kwargs)
|
||||||
|
|
||||||
|
# IP validation
|
||||||
|
if config['security']['enable_ip_check']:
|
||||||
|
client_ip = get_real_ip(request)
|
||||||
|
allowed = False
|
||||||
|
for allowed_ip in config['security']['allowed_ips']:
|
||||||
|
try:
|
||||||
|
if ipaddress.ip_address(client_ip) in ipaddress.ip_network(allowed_ip, strict=False):
|
||||||
|
allowed = True
|
||||||
|
break
|
||||||
|
except ValueError:
|
||||||
|
# Invalid IP or network, skip
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not allowed:
|
||||||
|
raise HTTPException(status_code=403, detail="IP address not allowed")
|
||||||
|
|
||||||
|
# API Key validation
|
||||||
|
if config['security']['enable_api_key']:
|
||||||
|
api_key_header = config['security']['api_key_header']
|
||||||
|
provided_key = request.headers.get(api_key_header)
|
||||||
|
|
||||||
|
if not provided_key:
|
||||||
|
raise HTTPException(status_code=401, detail="API key required")
|
||||||
|
|
||||||
|
valid_key = False
|
||||||
|
for key_config in config['security']['api_keys']:
|
||||||
|
if key_config['key'] == provided_key:
|
||||||
|
# Check expiration if set
|
||||||
|
if 'expires_at' in key_config and key_config['expires_at']:
|
||||||
|
# TODO: Implement expiration check
|
||||||
|
pass
|
||||||
|
valid_key = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not valid_key:
|
||||||
|
raise HTTPException(status_code=401, detail="Invalid API key")
|
||||||
|
|
||||||
|
return await func(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
return decorator
|
||||||
|
|
||||||
def get_user_hermes_path(user_id: str) -> str:
|
def get_user_hermes_path(user_id: str) -> str:
|
||||||
"""Get isolated Hermes environment path for a user"""
|
"""Get isolated Hermes environment path for a user"""
|
||||||
@ -68,10 +193,12 @@ def ensure_user_hermes_env(user_id: str):
|
|||||||
return user_hermes_path
|
return user_hermes_path
|
||||||
|
|
||||||
@app.get("/health")
|
@app.get("/health")
|
||||||
|
@validate_ip_and_apikey()
|
||||||
async def health_check():
|
async def health_check():
|
||||||
return {"status": "healthy", "service": "hermes-service", "multi_user": True}
|
return {"status": "healthy", "service": "hermes-service", "multi_user": True}
|
||||||
|
|
||||||
@app.get("/api/v1/status")
|
@app.get("/api/v1/status")
|
||||||
|
@validate_ip_and_apikey()
|
||||||
async def get_hermes_status():
|
async def get_hermes_status():
|
||||||
try:
|
try:
|
||||||
result = await execute_hermes_command(["--version"], user_id=None)
|
result = await execute_hermes_command(["--version"], user_id=None)
|
||||||
@ -93,37 +220,41 @@ class SessionMessageRequest(BaseModel):
|
|||||||
user_context: Optional[Dict[str, Any]] = None
|
user_context: Optional[Dict[str, Any]] = None
|
||||||
|
|
||||||
@app.post("/api/v1/sessions")
|
@app.post("/api/v1/sessions")
|
||||||
|
@validate_ip_and_apikey()
|
||||||
async def create_session(request: SessionCreateRequest):
|
async def create_session(request: SessionCreateRequest):
|
||||||
if not request.user_id:
|
if not request.user_id:
|
||||||
raise HTTPException(status_code=400, detail="user_id is required")
|
raise HTTPException(status_code=400, detail="user_id is required")
|
||||||
|
|
||||||
session_id = str(uuid.uuid4())
|
# Create global session ID
|
||||||
|
global_session_id = str(uuid.uuid4())
|
||||||
|
|
||||||
|
# Ensure user environment exists
|
||||||
user_hermes_path = ensure_user_hermes_env(request.user_id)
|
user_hermes_path = ensure_user_hermes_env(request.user_id)
|
||||||
|
|
||||||
active_sessions[session_id] = {
|
# For now, we'll use the global session ID as the local session ID
|
||||||
"id": session_id,
|
# In a production system, we might want to create a proper local session
|
||||||
|
local_session_id = global_session_id
|
||||||
|
|
||||||
|
# Register global session
|
||||||
|
global_sessions[global_session_id] = {
|
||||||
"user_id": request.user_id,
|
"user_id": request.user_id,
|
||||||
|
"local_session_id": local_session_id,
|
||||||
"created_at": datetime.now().isoformat(),
|
"created_at": datetime.now().isoformat(),
|
||||||
"messages": [],
|
"hermes_path": user_hermes_path,
|
||||||
"status": "active"
|
"status": "active"
|
||||||
}
|
}
|
||||||
|
|
||||||
if request.initial_message:
|
|
||||||
active_sessions[session_id]["messages"].append({
|
|
||||||
"role": "user",
|
|
||||||
"content": request.initial_message,
|
|
||||||
"timestamp": datetime.now().isoformat()
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"session_id": session_id,
|
"session_id": global_session_id,
|
||||||
"user_id": request.user_id,
|
"user_id": request.user_id,
|
||||||
"hermes_path": user_hermes_path,
|
"hermes_path": user_hermes_path,
|
||||||
"status": "created"
|
"status": "created"
|
||||||
}
|
}
|
||||||
|
|
||||||
@app.post("/api/v1/execute")
|
@app.post("/api/v1/execute")
|
||||||
|
@validate_ip_and_apikey()
|
||||||
async def execute_command(request: CommandRequest):
|
async def execute_command(request: CommandRequest):
|
||||||
|
# If no user context provided, use anonymous user
|
||||||
user_id = None
|
user_id = None
|
||||||
if request.user_context:
|
if request.user_context:
|
||||||
user_id = request.user_context.get("user_id")
|
user_id = request.user_context.get("user_id")
|
||||||
@ -140,21 +271,20 @@ async def execute_command(request: CommandRequest):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
@app.post("/api/v1/sessions/{session_id}/messages")
|
@app.post("/api/v1/sessions/{session_id}/messages")
|
||||||
|
@validate_ip_and_apikey()
|
||||||
async def send_session_message(session_id: str, request: SessionMessageRequest):
|
async def send_session_message(session_id: str, request: SessionMessageRequest):
|
||||||
if session_id not in active_sessions:
|
if session_id not in global_sessions:
|
||||||
raise HTTPException(status_code=404, detail="Session not found")
|
raise HTTPException(status_code=404, detail="Session not found")
|
||||||
|
|
||||||
session_data = active_sessions[session_id]
|
session_info = global_sessions[session_id]
|
||||||
user_id = session_data["user_id"]
|
user_id = session_info["user_id"]
|
||||||
|
local_session_id = session_info["local_session_id"]
|
||||||
|
|
||||||
session_data["messages"].append({
|
# For chat messages, we need to think about how to properly integrate
|
||||||
"role": "user",
|
# with Hermes' session system. For now, we'll execute commands directly.
|
||||||
"content": request.message,
|
# In production, this would interface with Hermes' internal session management.
|
||||||
"timestamp": datetime.now().isoformat()
|
|
||||||
})
|
|
||||||
|
|
||||||
# Correct way to send chat messages to Hermes
|
# Execute the message as a command
|
||||||
# Use the chat subcommand with the message as direct argument
|
|
||||||
command_args = ["chat", request.message]
|
command_args = ["chat", request.message]
|
||||||
|
|
||||||
result = await execute_hermes_command(
|
result = await execute_hermes_command(
|
||||||
@ -164,27 +294,22 @@ async def send_session_message(session_id: str, request: SessionMessageRequest):
|
|||||||
)
|
)
|
||||||
|
|
||||||
response_content = result.get("stdout", "") if result["success"] else result.get("stderr", "Command failed")
|
response_content = result.get("stdout", "") if result["success"] else result.get("stderr", "Command failed")
|
||||||
session_data["messages"].append({
|
|
||||||
"role": "assistant",
|
|
||||||
"content": response_content,
|
|
||||||
"timestamp": datetime.now().isoformat()
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"session_id": session_id,
|
"session_id": session_id,
|
||||||
"response": response_content,
|
"response": response_content,
|
||||||
"success": result["success"],
|
"success": result["success"]
|
||||||
"message_count": len(session_data["messages"])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@app.get("/api/v1/sessions/{session_id}")
|
@app.get("/api/v1/sessions/{session_id}")
|
||||||
|
@validate_ip_and_apikey()
|
||||||
async def get_session(session_id: str):
|
async def get_session(session_id: str):
|
||||||
if session_id not in active_sessions:
|
if session_id not in global_sessions:
|
||||||
raise HTTPException(status_code=404, detail="Session not found")
|
raise HTTPException(status_code=404, detail="Session not found")
|
||||||
|
|
||||||
session_data = active_sessions[session_id].copy()
|
session_info = global_sessions[session_id].copy()
|
||||||
session_data.pop("id", None)
|
session_info.pop("hermes_path", None) # Don't expose internal paths
|
||||||
return session_data
|
return session_info
|
||||||
|
|
||||||
async def execute_hermes_command(command_args, user_id=None, timeout=300):
|
async def execute_hermes_command(command_args, user_id=None, timeout=300):
|
||||||
try:
|
try:
|
||||||
@ -242,4 +367,9 @@ os.makedirs(USERS_BASE, exist_ok=True, mode=0o755)
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import uvicorn
|
import uvicorn
|
||||||
uvicorn.run(app, host="127.0.0.1", port=9120, log_level="info")
|
uvicorn.run(
|
||||||
|
app,
|
||||||
|
host=config['service']['host'],
|
||||||
|
port=config['service']['port'],
|
||||||
|
log_level=config['service']['log_level']
|
||||||
|
)
|
||||||
83
nginx.conf.example
Normal file
83
nginx.conf.example
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
# Nginx Configuration for Hermes Service
|
||||||
|
# This configuration provides reverse proxy with IP and API key validation
|
||||||
|
|
||||||
|
upstream hermes_service {
|
||||||
|
server 127.0.0.1:9120;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name your-domain.com; # Replace with your actual domain or IP
|
||||||
|
|
||||||
|
# Security headers
|
||||||
|
add_header X-Content-Type-Options nosniff;
|
||||||
|
add_header X-Frame-Options DENY;
|
||||||
|
add_header X-XSS-Protection "1; mode=block";
|
||||||
|
|
||||||
|
# Health check endpoint (no authentication required)
|
||||||
|
location = /health {
|
||||||
|
proxy_pass http://hermes_service;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Status endpoint (no authentication required, optional)
|
||||||
|
location = /api/v1/status {
|
||||||
|
proxy_pass http://hermes_service;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
# All other API endpoints require authentication
|
||||||
|
location /api/v1/ {
|
||||||
|
# IP restriction at Nginx level (optional, can also be handled by hermes-service)
|
||||||
|
# allow 192.168.1.0/24;
|
||||||
|
# allow 10.0.0.0/8;
|
||||||
|
# deny all;
|
||||||
|
|
||||||
|
# API Key validation at Nginx level (optional, can also be handled by hermes-service)
|
||||||
|
# if ($http_x_api_key != "your-api-key-here") {
|
||||||
|
# return 401 "Invalid API key";
|
||||||
|
# }
|
||||||
|
|
||||||
|
proxy_pass http://hermes_service;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
# Timeout settings
|
||||||
|
proxy_connect_timeout 60s;
|
||||||
|
proxy_send_timeout 300s;
|
||||||
|
proxy_read_timeout 300s;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Root location - you might want to serve a web UI here
|
||||||
|
location / {
|
||||||
|
# If you have a web UI, serve it here
|
||||||
|
# root /path/to/web/ui;
|
||||||
|
# index index.html;
|
||||||
|
|
||||||
|
# Or redirect to API documentation
|
||||||
|
return 404 "Hermes Service API - use /api/v1 endpoints";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
access_log /var/log/nginx/hermes-service-access.log;
|
||||||
|
error_log /var/log/nginx/hermes-service-error.log;
|
||||||
|
}
|
||||||
|
|
||||||
|
# SSL Configuration (recommended for production)
|
||||||
|
# server {
|
||||||
|
# listen 443 ssl http2;
|
||||||
|
# server_name your-domain.com;
|
||||||
|
#
|
||||||
|
# ssl_certificate /path/to/certificate.crt;
|
||||||
|
# ssl_certificate_key /path/to/private.key;
|
||||||
|
#
|
||||||
|
# # ... rest of the configuration same as above ...
|
||||||
|
# }
|
||||||
@ -8,7 +8,8 @@ version = "1.0.0"
|
|||||||
description = "Hermes Agent Service Web Application"
|
description = "Hermes Agent Service Web Application"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahserver",
|
"ahserver",
|
||||||
"bricks-framework"
|
"bricks-framework",
|
||||||
|
"pyyaml"
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.setuptools.packages.find]
|
[tool.setuptools.packages.find]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user