209 lines
5.1 KiB
Markdown
209 lines
5.1 KiB
Markdown
# 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, Telegram Bot API).
|
|
|
|
## Quick Start
|
|
|
|
### Prerequisites
|
|
- Ubuntu 24.04 VM
|
|
- Port 8080 (HTTP) and optionally 8443 (HTTPS) open **only** to k8s cluster CIDR
|
|
|
|
### Installation
|
|
|
|
```bash
|
|
sudo bash scripts/install.sh
|
|
```
|
|
|
|
### Configuration
|
|
|
|
```bash
|
|
vi .env
|
|
|
|
# Set your secret and cluster CIDR:
|
|
PROXY_SECRET=<generated-token>
|
|
ALLOWED_CIDR=10.0.0.0/8,172.16.0.0/12
|
|
|
|
docker compose up -d --build
|
|
```
|
|
|
|
### Verify
|
|
|
|
```bash
|
|
# Health check (no auth):
|
|
curl http://localhost:8080/health
|
|
|
|
# Test with auth:
|
|
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
|
|
|
|
Example: adding `api.anthropic.com` → `/anthropic/*`
|
|
|
|
Add this block to `nginx/locations.conf`:
|
|
|
|
```nginx
|
|
location /anthropic/ {
|
|
if ($allowed_ip = 0) {
|
|
return 403 '{"error":"ip_not_allowed"}';
|
|
}
|
|
if ($auth_ok = 0) {
|
|
return 403 '{"error":"invalid_token"}';
|
|
}
|
|
|
|
set $anthropic_upstream https://api.anthropic.com;
|
|
rewrite ^/anthropic/(.*) /$1 break;
|
|
|
|
proxy_pass $anthropic_upstream;
|
|
proxy_ssl_server_name on;
|
|
proxy_ssl_name api.anthropic.com;
|
|
proxy_ssl_protocols TLSv1.2 TLSv1.3;
|
|
|
|
proxy_set_header Host api.anthropic.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;
|
|
}
|
|
```
|
|
|
|
Then rebuild: `docker compose up -d --build`
|
|
|
|
---
|
|
|
|
## K8s Side: How to Call the Proxy
|
|
|
|
### Environment Variables
|
|
|
|
```yaml
|
|
env:
|
|
- name: PROXY_BASE_URL
|
|
value: "http://10.0.1.50:8080" # or https://10.0.1.50:8443
|
|
- name: PROXY_SECRET
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: api-proxy
|
|
key: token
|
|
```
|
|
|
|
### TypeScript Examples
|
|
|
|
```typescript
|
|
// ElevenLabs TTS
|
|
const response = await fetch(
|
|
`${process.env.PROXY_BASE_URL}/elevenlabs/v1/text-to-speech/${voiceId}`,
|
|
{
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'xi-api-key': process.env.ELEVENLABS_API_KEY,
|
|
'X-Proxy-Token': process.env.PROXY_SECRET,
|
|
},
|
|
body: JSON.stringify(payload),
|
|
}
|
|
);
|
|
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 }),
|
|
}
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## Security Checklist
|
|
|
|
- [ ] `PROXY_SECRET` generated via `openssl rand -hex 32`
|
|
- [ ] `ALLOWED_CIDR` restricted to cluster CIDR only
|
|
- [ ] Port 8080/8443 closed to the public (only k8s CIDR in firewall/NSG)
|
|
- [ ] VM has no public IP or is behind NAT
|
|
- [ ] Logs rotate (Docker logging driver configured: 50m x 5 files)
|
|
- [ ] TLS certs have restricted permissions (`chmod 600 ssl/tls.key`)
|
|
|
|
---
|
|
|
|
## Monitoring
|
|
|
|
```bash
|
|
# Tail logs
|
|
docker compose logs -f proxy | jq .
|
|
|
|
# Requests per upstream per minute
|
|
docker compose logs --since=1m proxy --no-log-prefix \
|
|
| jq -r '.uri' \
|
|
| cut -d'/' -f2 \
|
|
| sort | uniq -c | sort -rn
|
|
|
|
# Container health
|
|
docker inspect --format='{{.State.Health.Status}}' api-proxy
|
|
```
|
|
|
|
---
|
|
|
|
## 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.
|
|
2. Nginx checks the source IP against `ALLOWED_CIDR` and validates `X-Proxy-Token` — rejecting with 403 if either fails.
|
|
3. The prefix (`/elevenlabs/`, `/openai/`, `/telegram/`) is stripped via rewrite, and the request is forwarded over HTTPS to the upstream with SNI enabled.
|
|
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 and long-polling (Telegram).
|