+3
-2
@@ -1,6 +1,7 @@
|
|||||||
# Secret token — k8s pods must send this in X-Proxy-Token header
|
# Secret token — k8s pods must send this in X-Proxy-Token header (optional)
|
||||||
|
# If empty or unset — token auth is disabled (open mode, protected by IP allowlist only)
|
||||||
# Generate with: openssl rand -hex 32
|
# Generate with: openssl rand -hex 32
|
||||||
PROXY_SECRET=change-me-generate-with-openssl-rand-hex-32
|
PROXY_SECRET=
|
||||||
|
|
||||||
# Allowed source CIDRs (comma-separated), leave empty to allow all (dev mode)
|
# Allowed source CIDRs (comma-separated), leave empty to allow all (dev mode)
|
||||||
# Example: "10.0.0.0/8,172.16.0.0/12"
|
# Example: "10.0.0.0/8,172.16.0.0/12"
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
.env
|
||||||
+5
-4
@@ -4,17 +4,18 @@ FROM nginx:1.27-alpine
|
|||||||
# Remove default config
|
# Remove default config
|
||||||
RUN rm -f /etc/nginx/conf.d/default.conf
|
RUN rm -f /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
# Copy nginx config
|
# Copy nginx configs
|
||||||
COPY nginx/nginx.conf /etc/nginx/nginx.conf
|
COPY nginx/nginx.conf /etc/nginx/nginx.conf
|
||||||
|
COPY nginx/locations.conf /etc/nginx/locations.conf
|
||||||
|
|
||||||
# Copy entrypoint script
|
# Copy entrypoint script
|
||||||
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||||
RUN chmod +x /docker-entrypoint.sh
|
RUN chmod +x /docker-entrypoint.sh
|
||||||
|
|
||||||
# Create conf.d directory for generated configs
|
# Create directories for generated configs and optional SSL certs
|
||||||
RUN mkdir -p /etc/nginx/conf.d
|
RUN mkdir -p /etc/nginx/conf.d /etc/nginx/ssl
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080 8443
|
||||||
|
|
||||||
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
|
||||||
CMD wget -qO- http://localhost:8080/health || exit 1
|
CMD wget -qO- http://localhost:8080/health || exit 1
|
||||||
|
|||||||
@@ -1,38 +1,35 @@
|
|||||||
# API Proxy — HTTPS Reverse Proxy
|
# API Proxy — HTTPS Reverse Proxy
|
||||||
|
|
||||||
Production-ready nginx reverse proxy in Docker for forwarding requests from an internal Kubernetes cluster to external APIs (ElevenLabs, OpenAI, etc.)
|
Production-ready nginx reverse proxy in Docker for forwarding requests from an internal Kubernetes cluster to external APIs (ElevenLabs, OpenAI, Telegram Bot API).
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
- Ubuntu 24.04 VM
|
- Ubuntu 24.04 VM
|
||||||
- Port 8080 open **only** to k8s cluster CIDR (firewall rule)
|
- Port 8080 (HTTP) and optionally 8443 (HTTPS) open **only** to k8s cluster CIDR
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# On a fresh VM (as root):
|
|
||||||
sudo bash scripts/install.sh
|
sudo bash scripts/install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Edit environment variables:
|
vi .env
|
||||||
vi /opt/proxy/proxy-vm/.env
|
|
||||||
|
|
||||||
# Set your k8s cluster CIDR:
|
# Set your secret and cluster CIDR:
|
||||||
|
PROXY_SECRET=<generated-token>
|
||||||
ALLOWED_CIDR=10.0.0.0/8,172.16.0.0/12
|
ALLOWED_CIDR=10.0.0.0/8,172.16.0.0/12
|
||||||
|
|
||||||
# Restart to apply:
|
docker compose up -d --build
|
||||||
cd /opt/proxy/proxy-vm
|
|
||||||
docker compose restart
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Verify
|
### Verify
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Health check (no auth required):
|
# Health check (no auth):
|
||||||
curl http://localhost:8080/health
|
curl http://localhost:8080/health
|
||||||
|
|
||||||
# Test with auth:
|
# Test with auth:
|
||||||
@@ -41,20 +38,46 @@ curl -H "X-Proxy-Token: YOUR_SECRET" http://localhost:8080/elevenlabs/v1/voices
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Available Endpoints
|
||||||
|
|
||||||
|
| Prefix | Upstream |
|
||||||
|
|--------------|-----------------------------|
|
||||||
|
| /elevenlabs/ | https://api.elevenlabs.io |
|
||||||
|
| /openai/ | https://api.openai.com |
|
||||||
|
| /telegram/ | https://api.telegram.org |
|
||||||
|
| /health | local health check (no auth)|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TLS / HTTPS
|
||||||
|
|
||||||
|
To enable HTTPS on port 8443, place your certificate and key in the `ssl/` directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ssl
|
||||||
|
|
||||||
|
# Option A: self-signed cert
|
||||||
|
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
|
||||||
|
-keyout ssl/tls.key \
|
||||||
|
-out ssl/tls.crt \
|
||||||
|
-subj "/CN=api-proxy"
|
||||||
|
|
||||||
|
# Option B: copy existing certs
|
||||||
|
cp /path/to/your/cert.pem ssl/tls.crt
|
||||||
|
cp /path/to/your/key.pem ssl/tls.key
|
||||||
|
|
||||||
|
docker compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
The entrypoint auto-detects `ssl/tls.crt` and `ssl/tls.key`. If present — HTTPS is enabled on port 8443. If absent — only HTTP on 8080.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## How to Add a New Upstream
|
## How to Add a New Upstream
|
||||||
|
|
||||||
Example: adding `api.anthropic.com` → `/anthropic/*`
|
Example: adding `api.anthropic.com` → `/anthropic/*`
|
||||||
|
|
||||||
### 1. Add upstream block in `nginx/nginx.conf`:
|
Add this block to `nginx/locations.conf`:
|
||||||
|
|
||||||
```nginx
|
|
||||||
upstream anthropic_backend {
|
|
||||||
server api.anthropic.com:443;
|
|
||||||
keepalive 32;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Add location block in the `server` section:
|
|
||||||
|
|
||||||
```nginx
|
```nginx
|
||||||
location /anthropic/ {
|
location /anthropic/ {
|
||||||
@@ -65,21 +88,28 @@ location /anthropic/ {
|
|||||||
return 403 '{"error":"invalid_token"}';
|
return 403 '{"error":"invalid_token"}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set $anthropic_upstream https://api.anthropic.com;
|
||||||
rewrite ^/anthropic/(.*) /$1 break;
|
rewrite ^/anthropic/(.*) /$1 break;
|
||||||
|
|
||||||
proxy_pass https://anthropic_backend;
|
proxy_pass $anthropic_upstream;
|
||||||
proxy_ssl_server_name on;
|
proxy_ssl_server_name on;
|
||||||
proxy_ssl_name api.anthropic.com;
|
proxy_ssl_name api.anthropic.com;
|
||||||
|
proxy_ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
|
||||||
proxy_set_header Host api.anthropic.com;
|
proxy_set_header Host api.anthropic.com;
|
||||||
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;
|
|
||||||
proxy_set_header Connection "";
|
proxy_set_header Connection "";
|
||||||
|
|
||||||
|
proxy_set_header X-Forwarded-For "";
|
||||||
|
proxy_set_header X-Real-IP "";
|
||||||
|
proxy_set_header True-Client-IP "";
|
||||||
|
proxy_set_header CF-Connecting-IP "";
|
||||||
|
proxy_set_header X-Client-IP "";
|
||||||
|
proxy_set_header Forwarded "";
|
||||||
|
proxy_set_header Via "";
|
||||||
|
|
||||||
proxy_set_header X-Proxy-Token "";
|
proxy_set_header X-Proxy-Token "";
|
||||||
|
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
proxy_buffering off;
|
proxy_buffering off;
|
||||||
proxy_request_buffering off;
|
proxy_request_buffering off;
|
||||||
proxy_read_timeout 120s;
|
proxy_read_timeout 120s;
|
||||||
@@ -87,23 +117,18 @@ location /anthropic/ {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Rebuild and restart:
|
Then rebuild: `docker compose up -d --build`
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /opt/proxy/proxy-vm
|
|
||||||
docker compose up -d --build
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## K8s Side: How to Call the Proxy
|
## K8s Side: How to Call the Proxy
|
||||||
|
|
||||||
### Environment Variables (in your Deployment/ConfigMap)
|
### Environment Variables
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
env:
|
env:
|
||||||
- name: PROXY_BASE_URL
|
- name: PROXY_BASE_URL
|
||||||
value: "http://10.0.1.50:8080" # proxy VM internal IP
|
value: "http://10.0.1.50:8080" # or https://10.0.1.50:8443
|
||||||
- name: PROXY_SECRET
|
- name: PROXY_SECRET
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
@@ -111,27 +136,36 @@ env:
|
|||||||
key: token
|
key: token
|
||||||
```
|
```
|
||||||
|
|
||||||
### TypeScript Example
|
### TypeScript Examples
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// In tts-worker, replace ELEVENLABS_BASE_URL:
|
// ElevenLabs TTS
|
||||||
// Before: https://api.elevenlabs.io
|
|
||||||
// After: http://<proxy-vm-ip>:8080/elevenlabs
|
|
||||||
|
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${process.env.PROXY_BASE_URL}/elevenlabs/v1/text-to-speech/${voiceId}`,
|
`${process.env.PROXY_BASE_URL}/elevenlabs/v1/text-to-speech/${voiceId}`,
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'xi-api-key': process.env.ELEVENLABS_API_KEY, // passed through to upstream
|
'xi-api-key': process.env.ELEVENLABS_API_KEY,
|
||||||
'X-Proxy-Token': process.env.PROXY_SECRET, // validated by proxy
|
'X-Proxy-Token': process.env.PROXY_SECRET,
|
||||||
},
|
},
|
||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
// Response is binary MP3, identical to direct ElevenLabs call
|
|
||||||
const buffer = Buffer.from(await response.arrayBuffer());
|
const buffer = Buffer.from(await response.arrayBuffer());
|
||||||
|
|
||||||
|
// Telegram Bot API
|
||||||
|
const tgResponse = await fetch(
|
||||||
|
`${process.env.PROXY_BASE_URL}/telegram/bot${process.env.TELEGRAM_BOT_TOKEN}/sendMessage`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Proxy-Token': process.env.PROXY_SECRET,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ chat_id: chatId, text: message }),
|
||||||
|
}
|
||||||
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -140,64 +174,26 @@ const buffer = Buffer.from(await response.arrayBuffer());
|
|||||||
|
|
||||||
- [ ] `PROXY_SECRET` generated via `openssl rand -hex 32`
|
- [ ] `PROXY_SECRET` generated via `openssl rand -hex 32`
|
||||||
- [ ] `ALLOWED_CIDR` restricted to cluster CIDR only
|
- [ ] `ALLOWED_CIDR` restricted to cluster CIDR only
|
||||||
- [ ] Port 8080 closed to the public (only k8s CIDR in firewall/NSG)
|
- [ ] Port 8080/8443 closed to the public (only k8s CIDR in firewall/NSG)
|
||||||
- [ ] VM has no public IP or is behind NAT
|
- [ ] VM has no public IP or is behind NAT
|
||||||
- [ ] Logs rotate (Docker logging driver configured: 50m x 5 files)
|
- [ ] Logs rotate (Docker logging driver configured: 50m x 5 files)
|
||||||
|
- [ ] TLS certs have restricted permissions (`chmod 600 ssl/tls.key`)
|
||||||
---
|
|
||||||
|
|
||||||
## Adding TLS (Self-Signed)
|
|
||||||
|
|
||||||
If you need HTTPS between k8s and the proxy:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Generate self-signed cert:
|
|
||||||
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
|
|
||||||
-keyout /opt/proxy/proxy-vm/nginx/ssl/proxy.key \
|
|
||||||
-out /opt/proxy/proxy-vm/nginx/ssl/proxy.crt \
|
|
||||||
-subj "/CN=api-proxy"
|
|
||||||
```
|
|
||||||
|
|
||||||
Then in `nginx.conf`, change the server block:
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
server {
|
|
||||||
listen 8443 ssl;
|
|
||||||
ssl_certificate /etc/nginx/ssl/proxy.crt;
|
|
||||||
ssl_certificate_key /etc/nginx/ssl/proxy.key;
|
|
||||||
# ... rest of config
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Mount the certs in `docker-compose.yml`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
volumes:
|
|
||||||
- ./nginx/ssl:/etc/nginx/ssl:ro
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Monitoring
|
## Monitoring
|
||||||
|
|
||||||
### Tail logs in JSON format
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# Tail logs
|
||||||
docker compose logs -f proxy | jq .
|
docker compose logs -f proxy | jq .
|
||||||
```
|
|
||||||
|
|
||||||
### Count requests per upstream per minute
|
# Requests per upstream per minute
|
||||||
|
|
||||||
```bash
|
|
||||||
docker compose logs --since=1m proxy --no-log-prefix \
|
docker compose logs --since=1m proxy --no-log-prefix \
|
||||||
| jq -r '.uri' \
|
| jq -r '.uri' \
|
||||||
| cut -d'/' -f2 \
|
| cut -d'/' -f2 \
|
||||||
| sort | uniq -c | sort -rn
|
| sort | uniq -c | sort -rn
|
||||||
```
|
|
||||||
|
|
||||||
### Check container health
|
# Container health
|
||||||
|
|
||||||
```bash
|
|
||||||
docker inspect --format='{{.State.Health.Status}}' api-proxy
|
docker inspect --format='{{.State.Health.Status}}' api-proxy
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -205,8 +201,8 @@ docker inspect --format='{{.State.Health.Status}}' api-proxy
|
|||||||
|
|
||||||
## How It Works
|
## How It Works
|
||||||
|
|
||||||
1. A k8s pod sends an HTTP request to `http://<proxy-vm>:8080/elevenlabs/v1/text-to-speech/...` with the `X-Proxy-Token` header and the original API key (`xi-api-key`).
|
1. A k8s pod sends an HTTP request to `http://<proxy-vm>:8080/elevenlabs/v1/text-to-speech/...` with the `X-Proxy-Token` header and the original API key.
|
||||||
2. Nginx checks the source IP against the configured `ALLOWED_CIDR` geo block and validates the `X-Proxy-Token` against `PROXY_SECRET` — rejecting with 403 if either fails.
|
2. Nginx checks the source IP against `ALLOWED_CIDR` and validates `X-Proxy-Token` — rejecting with 403 if either fails.
|
||||||
3. The `/elevenlabs/` prefix is stripped via rewrite, and the request is forwarded over HTTPS to `api.elevenlabs.io` with SNI enabled and keepalive connections.
|
3. The prefix (`/elevenlabs/`, `/openai/`, `/telegram/`) is stripped via rewrite, and the request is forwarded over HTTPS to the upstream with SNI enabled.
|
||||||
4. The `X-Proxy-Token` header is removed before forwarding; all other headers (including `xi-api-key`) pass through unchanged.
|
4. `X-Proxy-Token` and all IP-leaking headers are removed before forwarding; API keys pass through unchanged.
|
||||||
5. The response streams back unbuffered to the pod — critical for binary audio data from ElevenLabs TTS.
|
5. The response streams back unbuffered to the pod — critical for binary audio data and long-polling (Telegram).
|
||||||
|
|||||||
+5
-3
@@ -1,6 +1,3 @@
|
|||||||
# proxy-vm/docker-compose.yml
|
|
||||||
version: "3.9"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
proxy:
|
proxy:
|
||||||
build: .
|
build: .
|
||||||
@@ -8,7 +5,12 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
|
- "8443:8443"
|
||||||
env_file: .env
|
env_file: .env
|
||||||
|
volumes:
|
||||||
|
# Mount TLS certs (optional — HTTPS disabled if files are absent)
|
||||||
|
# Place tls.crt and tls.key in ./ssl/ directory
|
||||||
|
- ./ssl:/etc/nginx/ssl:ro
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "-qO-", "http://localhost:8080/health"]
|
test: ["CMD", "wget", "-qO-", "http://localhost:8080/health"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
|
|||||||
+37
-6
@@ -10,7 +10,6 @@ mkdir -p "${CONF_DIR}"
|
|||||||
ALLOWLIST_FILE="${CONF_DIR}/allowlist.conf"
|
ALLOWLIST_FILE="${CONF_DIR}/allowlist.conf"
|
||||||
|
|
||||||
if [ -z "${ALLOWED_CIDR}" ]; then
|
if [ -z "${ALLOWED_CIDR}" ]; then
|
||||||
# Dev mode: allow all IPs
|
|
||||||
cat > "${ALLOWLIST_FILE}" <<'GEO'
|
cat > "${ALLOWLIST_FILE}" <<'GEO'
|
||||||
geo $allowed_ip {
|
geo $allowed_ip {
|
||||||
default 1;
|
default 1;
|
||||||
@@ -18,12 +17,11 @@ geo $allowed_ip {
|
|||||||
GEO
|
GEO
|
||||||
echo "[entrypoint] ALLOWED_CIDR is empty — allowing all IPs (dev mode)"
|
echo "[entrypoint] ALLOWED_CIDR is empty — allowing all IPs (dev mode)"
|
||||||
else
|
else
|
||||||
# Build geo block from comma-separated CIDRs
|
|
||||||
{
|
{
|
||||||
echo 'geo $allowed_ip {'
|
echo 'geo $allowed_ip {'
|
||||||
echo ' default 0;'
|
echo ' default 0;'
|
||||||
echo "${ALLOWED_CIDR}" | tr ',' '\n' | while read -r cidr; do
|
echo "${ALLOWED_CIDR}" | tr ',' '\n' | while read -r cidr; do
|
||||||
cidr=$(echo "${cidr}" | xargs) # trim whitespace
|
cidr=$(echo "${cidr}" | xargs)
|
||||||
[ -n "${cidr}" ] && echo " ${cidr} 1;"
|
[ -n "${cidr}" ] && echo " ${cidr} 1;"
|
||||||
done
|
done
|
||||||
echo '}'
|
echo '}'
|
||||||
@@ -35,10 +33,10 @@ fi
|
|||||||
AUTH_FILE="${CONF_DIR}/auth.conf"
|
AUTH_FILE="${CONF_DIR}/auth.conf"
|
||||||
|
|
||||||
if [ -z "${PROXY_SECRET}" ]; then
|
if [ -z "${PROXY_SECRET}" ]; then
|
||||||
echo "[entrypoint] WARNING: PROXY_SECRET is not set — all requests will be rejected!"
|
echo "[entrypoint] PROXY_SECRET is not set — token auth disabled (open mode)"
|
||||||
cat > "${AUTH_FILE}" <<'MAP'
|
cat > "${AUTH_FILE}" <<'MAP'
|
||||||
map $http_x_proxy_token $auth_ok {
|
map $http_x_proxy_token $auth_ok {
|
||||||
default 0;
|
default 1;
|
||||||
}
|
}
|
||||||
MAP
|
MAP
|
||||||
else
|
else
|
||||||
@@ -51,6 +49,39 @@ MAP
|
|||||||
echo "[entrypoint] Token auth configured"
|
echo "[entrypoint] Token auth configured"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- 3. Start nginx ---
|
# --- 3. Copy locations.conf ---
|
||||||
|
cp /etc/nginx/locations.conf "${CONF_DIR}/locations.conf"
|
||||||
|
echo "[entrypoint] Locations config copied"
|
||||||
|
|
||||||
|
# --- 4. Generate HTTPS server block (if certs exist) ---
|
||||||
|
HTTPS_FILE="${CONF_DIR}/https_server.conf"
|
||||||
|
SSL_CERT="/etc/nginx/ssl/tls.crt"
|
||||||
|
SSL_KEY="/etc/nginx/ssl/tls.key"
|
||||||
|
|
||||||
|
if [ -f "${SSL_CERT}" ] && [ -f "${SSL_KEY}" ]; then
|
||||||
|
cat > "${HTTPS_FILE}" <<HTTPS
|
||||||
|
server {
|
||||||
|
listen 8443 ssl;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
ssl_certificate ${SSL_CERT};
|
||||||
|
ssl_certificate_key ${SSL_KEY};
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
ssl_session_cache shared:SSL:10m;
|
||||||
|
ssl_session_timeout 10m;
|
||||||
|
|
||||||
|
include /etc/nginx/conf.d/locations.conf;
|
||||||
|
}
|
||||||
|
HTTPS
|
||||||
|
echo "[entrypoint] HTTPS enabled on port 8443 (cert: ${SSL_CERT})"
|
||||||
|
else
|
||||||
|
# Empty file so nginx include does not fail
|
||||||
|
: > "${HTTPS_FILE}"
|
||||||
|
echo "[entrypoint] No TLS certs found at ${SSL_CERT} — HTTPS disabled"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- 5. Start nginx ---
|
||||||
echo "[entrypoint] Starting nginx..."
|
echo "[entrypoint] Starting nginx..."
|
||||||
exec nginx -g 'daemon off;'
|
exec nginx -g 'daemon off;'
|
||||||
|
|||||||
@@ -0,0 +1,160 @@
|
|||||||
|
# proxy-vm/nginx/locations.conf
|
||||||
|
# Shared location blocks — included by both HTTP and HTTPS server blocks.
|
||||||
|
|
||||||
|
# --- Health check (no auth) ---
|
||||||
|
location = /health {
|
||||||
|
access_log off;
|
||||||
|
default_type application/json;
|
||||||
|
return 200 '{"status":"ok","version":"1.0"}';
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- ElevenLabs ---
|
||||||
|
location /elevenlabs/ {
|
||||||
|
if ($allowed_ip = 0) {
|
||||||
|
return 403 '{"error":"ip_not_allowed"}';
|
||||||
|
}
|
||||||
|
if ($auth_ok = 0) {
|
||||||
|
return 403 '{"error":"invalid_token"}';
|
||||||
|
}
|
||||||
|
|
||||||
|
set $elevenlabs_upstream https://api.elevenlabs.io;
|
||||||
|
rewrite ^/elevenlabs/(.*) /$1 break;
|
||||||
|
|
||||||
|
proxy_pass $elevenlabs_upstream;
|
||||||
|
proxy_ssl_server_name on;
|
||||||
|
proxy_ssl_name api.elevenlabs.io;
|
||||||
|
proxy_ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
|
||||||
|
proxy_set_header Host api.elevenlabs.io;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
|
||||||
|
proxy_set_header X-Forwarded-For "";
|
||||||
|
proxy_set_header X-Real-IP "";
|
||||||
|
proxy_set_header True-Client-IP "";
|
||||||
|
proxy_set_header CF-Connecting-IP "";
|
||||||
|
proxy_set_header X-Client-IP "";
|
||||||
|
proxy_set_header Forwarded "";
|
||||||
|
proxy_set_header Via "";
|
||||||
|
|
||||||
|
proxy_set_header X-Proxy-Token "";
|
||||||
|
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
proxy_read_timeout 120s;
|
||||||
|
proxy_send_timeout 120s;
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- OpenAI ---
|
||||||
|
location /openai/ {
|
||||||
|
if ($allowed_ip = 0) {
|
||||||
|
return 403 '{"error":"ip_not_allowed"}';
|
||||||
|
}
|
||||||
|
if ($auth_ok = 0) {
|
||||||
|
return 403 '{"error":"invalid_token"}';
|
||||||
|
}
|
||||||
|
|
||||||
|
set $openai_upstream https://api.openai.com;
|
||||||
|
rewrite ^/openai/(.*) /$1 break;
|
||||||
|
|
||||||
|
proxy_pass $openai_upstream;
|
||||||
|
proxy_ssl_server_name on;
|
||||||
|
proxy_ssl_name api.openai.com;
|
||||||
|
proxy_ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
|
||||||
|
proxy_set_header Host api.openai.com;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
|
||||||
|
proxy_set_header X-Forwarded-For "";
|
||||||
|
proxy_set_header X-Real-IP "";
|
||||||
|
proxy_set_header True-Client-IP "";
|
||||||
|
proxy_set_header CF-Connecting-IP "";
|
||||||
|
proxy_set_header X-Client-IP "";
|
||||||
|
proxy_set_header Forwarded "";
|
||||||
|
proxy_set_header Via "";
|
||||||
|
|
||||||
|
proxy_set_header X-Proxy-Token "";
|
||||||
|
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
proxy_read_timeout 120s;
|
||||||
|
proxy_send_timeout 120s;
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Telegram Bot API ---
|
||||||
|
location /telegram/ {
|
||||||
|
if ($allowed_ip = 0) {
|
||||||
|
return 403 '{"error":"ip_not_allowed"}';
|
||||||
|
}
|
||||||
|
if ($auth_ok = 0) {
|
||||||
|
return 403 '{"error":"invalid_token"}';
|
||||||
|
}
|
||||||
|
|
||||||
|
set $telegram_upstream https://api.telegram.org;
|
||||||
|
rewrite ^/telegram/(.*) /$1 break;
|
||||||
|
|
||||||
|
proxy_pass $telegram_upstream;
|
||||||
|
proxy_ssl_server_name on;
|
||||||
|
proxy_ssl_name api.telegram.org;
|
||||||
|
proxy_ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
|
||||||
|
proxy_set_header Host api.telegram.org;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
|
||||||
|
proxy_set_header X-Forwarded-For "";
|
||||||
|
proxy_set_header X-Real-IP "";
|
||||||
|
proxy_set_header True-Client-IP "";
|
||||||
|
proxy_set_header CF-Connecting-IP "";
|
||||||
|
proxy_set_header X-Client-IP "";
|
||||||
|
proxy_set_header Forwarded "";
|
||||||
|
proxy_set_header Via "";
|
||||||
|
|
||||||
|
proxy_set_header X-Proxy-Token "";
|
||||||
|
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
proxy_read_timeout 120s;
|
||||||
|
proxy_send_timeout 120s;
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- openrouter Bot API ---
|
||||||
|
location /openrouter/ {
|
||||||
|
if ($allowed_ip = 0) {
|
||||||
|
return 403 '{"error":"ip_not_allowed"}';
|
||||||
|
}
|
||||||
|
|
||||||
|
set $openrouter_upstream https://openrouter.ai/api/v1;
|
||||||
|
rewrite ^/openrouter/(.*) /$1 break;
|
||||||
|
|
||||||
|
proxy_pass $openrouter_upstream;
|
||||||
|
proxy_ssl_server_name on;
|
||||||
|
proxy_ssl_name openrouter.ai;
|
||||||
|
proxy_ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
|
||||||
|
proxy_set_header Host openrouter.ai;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
|
||||||
|
proxy_set_header X-Forwarded-For "";
|
||||||
|
proxy_set_header X-Real-IP "";
|
||||||
|
proxy_set_header True-Client-IP "";
|
||||||
|
proxy_set_header CF-Connecting-IP "";
|
||||||
|
proxy_set_header X-Client-IP "";
|
||||||
|
proxy_set_header Forwarded "";
|
||||||
|
proxy_set_header Via "";
|
||||||
|
|
||||||
|
proxy_set_header X-Proxy-Token "";
|
||||||
|
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
proxy_read_timeout 120s;
|
||||||
|
proxy_send_timeout 120s;
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Catch-all ---
|
||||||
|
location / {
|
||||||
|
default_type application/json;
|
||||||
|
return 404 '{"error":"unknown_upstream","hint":"use /elevenlabs/, /openai/ or /telegram/" or /openrouter/}';
|
||||||
|
}
|
||||||
+5
-101
@@ -45,109 +45,13 @@ http {
|
|||||||
# --- Token auth ---
|
# --- Token auth ---
|
||||||
include /etc/nginx/conf.d/auth.conf;
|
include /etc/nginx/conf.d/auth.conf;
|
||||||
|
|
||||||
|
# --- HTTP server ---
|
||||||
server {
|
server {
|
||||||
listen 8080;
|
listen 8080;
|
||||||
server_name _;
|
server_name _;
|
||||||
|
include /etc/nginx/conf.d/locations.conf;
|
||||||
# --- Health check (no auth) ---
|
|
||||||
location = /health {
|
|
||||||
access_log off;
|
|
||||||
default_type application/json;
|
|
||||||
return 200 '{"status":"ok","version":"1.0"}';
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- ElevenLabs ---
|
|
||||||
location /elevenlabs/ {
|
|
||||||
# Auth checks
|
|
||||||
if ($allowed_ip = 0) {
|
|
||||||
return 403 '{"error":"ip_not_allowed"}';
|
|
||||||
}
|
|
||||||
if ($auth_ok = 0) {
|
|
||||||
return 403 '{"error":"invalid_token"}';
|
|
||||||
}
|
|
||||||
|
|
||||||
# Variable forces runtime DNS resolution (not cached at startup)
|
|
||||||
set $elevenlabs_upstream https://api.elevenlabs.io;
|
|
||||||
|
|
||||||
# Strip /elevenlabs/ prefix and proxy
|
|
||||||
rewrite ^/elevenlabs/(.*) /$1 break;
|
|
||||||
|
|
||||||
proxy_pass $elevenlabs_upstream;
|
|
||||||
proxy_ssl_server_name on;
|
|
||||||
proxy_ssl_name api.elevenlabs.io;
|
|
||||||
proxy_ssl_protocols TLSv1.2 TLSv1.3;
|
|
||||||
|
|
||||||
# Host header must match upstream for Cloudflare
|
|
||||||
proxy_set_header Host api.elevenlabs.io;
|
|
||||||
proxy_set_header Connection "";
|
|
||||||
|
|
||||||
# Scrub all headers that leak the original client IP
|
|
||||||
# Cloudflare reads these to determine "real" client geo
|
|
||||||
proxy_set_header X-Forwarded-For "";
|
|
||||||
proxy_set_header X-Real-IP "";
|
|
||||||
proxy_set_header True-Client-IP "";
|
|
||||||
proxy_set_header CF-Connecting-IP "";
|
|
||||||
proxy_set_header X-Client-IP "";
|
|
||||||
proxy_set_header Forwarded "";
|
|
||||||
proxy_set_header Via "";
|
|
||||||
|
|
||||||
# Remove proxy token before forwarding to upstream
|
|
||||||
proxy_set_header X-Proxy-Token "";
|
|
||||||
|
|
||||||
# HTTP/1.1 for keepalive
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
|
|
||||||
# Streaming / performance
|
|
||||||
proxy_buffering off;
|
|
||||||
proxy_request_buffering off;
|
|
||||||
proxy_read_timeout 120s;
|
|
||||||
proxy_send_timeout 120s;
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- OpenAI ---
|
|
||||||
location /openai/ {
|
|
||||||
if ($allowed_ip = 0) {
|
|
||||||
return 403 '{"error":"ip_not_allowed"}';
|
|
||||||
}
|
|
||||||
if ($auth_ok = 0) {
|
|
||||||
return 403 '{"error":"invalid_token"}';
|
|
||||||
}
|
|
||||||
|
|
||||||
set $openai_upstream https://api.openai.com;
|
|
||||||
|
|
||||||
rewrite ^/openai/(.*) /$1 break;
|
|
||||||
|
|
||||||
proxy_pass $openai_upstream;
|
|
||||||
proxy_ssl_server_name on;
|
|
||||||
proxy_ssl_name api.openai.com;
|
|
||||||
proxy_ssl_protocols TLSv1.2 TLSv1.3;
|
|
||||||
|
|
||||||
proxy_set_header Host api.openai.com;
|
|
||||||
proxy_set_header Connection "";
|
|
||||||
|
|
||||||
# Scrub all headers that leak the original client IP
|
|
||||||
proxy_set_header X-Forwarded-For "";
|
|
||||||
proxy_set_header X-Real-IP "";
|
|
||||||
proxy_set_header True-Client-IP "";
|
|
||||||
proxy_set_header CF-Connecting-IP "";
|
|
||||||
proxy_set_header X-Client-IP "";
|
|
||||||
proxy_set_header Forwarded "";
|
|
||||||
proxy_set_header Via "";
|
|
||||||
|
|
||||||
proxy_set_header X-Proxy-Token "";
|
|
||||||
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
|
|
||||||
proxy_buffering off;
|
|
||||||
proxy_request_buffering off;
|
|
||||||
proxy_read_timeout 120s;
|
|
||||||
proxy_send_timeout 120s;
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Catch-all ---
|
|
||||||
location / {
|
|
||||||
default_type application/json;
|
|
||||||
return 404 '{"error":"unknown_upstream","hint":"use /elevenlabs/ or /openai/"}';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# --- HTTPS server (generated at container start if certs exist) ---
|
||||||
|
include /etc/nginx/conf.d/https_server.conf;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# Ignore actual TLS certificates — never commit to git
|
||||||
|
*.crt
|
||||||
|
*.key
|
||||||
|
*.pem
|
||||||
|
!.gitkeep
|
||||||
|
!.gitignore
|
||||||
Reference in New Issue
Block a user