Sub2API v1.0 - AI API 网关(二开初始版本,基于上游 Wei-Shaw/sub2api)
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
# Edge and HTTP Ingress Security
|
||||
|
||||
Sub2API supports long-lived SSE and WebSocket requests. Protect the request
|
||||
ingress without imposing a response `WriteTimeout`: a write deadline would
|
||||
terminate healthy long generations and streams.
|
||||
|
||||
## Application defaults
|
||||
|
||||
- `server.max_header_bytes: 65536` limits HTTP/1 request headers to 64 KiB;
|
||||
Go maps it to the corresponding HTTP/2 header-list limit.
|
||||
- `server.read_header_timeout: 10` bounds slow-header attacks. It does not
|
||||
limit request processing or response streaming.
|
||||
- `server.max_request_body_size: 268435456` is the absolute 256 MiB safety net.
|
||||
- `gateway.max_body_size: 268435456` remains available to multimodal, Gemini,
|
||||
image, video, and batch-image endpoints.
|
||||
- `gateway.text_max_body_size: 33554432` limits the known pure-text
|
||||
`/embeddings` and `/alpha/search` endpoints to 32 MiB.
|
||||
- H2C defaults to 50 concurrent streams per connection, a 2 MiB connection
|
||||
upload window, and a 512 KiB stream upload window.
|
||||
- Invalid credential abuse is limited in process by trusted client IP (IPv6
|
||||
`/64`): 120 failures per 60 seconds followed by a 60-second block. This is a
|
||||
per-instance safety net; multi-instance enforcement still belongs at the
|
||||
load balancer, CDN, or WAF.
|
||||
|
||||
Do not add a single application-wide request semaphore: an SSE request may
|
||||
legitimately occupy it for many minutes. Apply connection and unauthenticated
|
||||
request controls at the edge; authenticated user/API-key concurrency remains
|
||||
the application's responsibility.
|
||||
|
||||
## Trusted client IPs
|
||||
|
||||
`security.trust_forwarded_ip_for_api_key_acl` is enabled by default for upgrade
|
||||
compatibility. While enabled, raw forwarding headers take over client-IP
|
||||
resolution for logs and security-sensitive paths. Custom headers from
|
||||
`security.forwarded_client_ip_headers` are checked in configured order before
|
||||
the built-in `CF-Connecting-IP`, `X-Real-IP`, and `X-Forwarded-For` fallback.
|
||||
Header names are case-insensitive, normalized when loaded, de-duplicated, and
|
||||
limited to 16 unique valid HTTP field names. Header values must contain IP
|
||||
literals; comma-separated values are supported, invalid entries are skipped,
|
||||
and public addresses are preferred over private fallback addresses.
|
||||
|
||||
The list can be supplied in YAML or with the comma-separated environment
|
||||
variable `SECURITY_FORWARDED_CLIENT_IP_HEADERS`; an explicitly empty environment
|
||||
value clears YAML values. It is also editable from the admin security settings
|
||||
and updates at runtime without a restart. A request snapshots the switch and
|
||||
header list together, so one request cannot mix old and new settings. Custom
|
||||
headers are ignored completely when the switch is disabled. In that mode Gin's
|
||||
`server.trusted_proxies` chain is authoritative: configure only the exact
|
||||
CIDR/IP addresses that connect directly to Sub2API. An explicit empty list
|
||||
trusts no forwarded client IPs.
|
||||
|
||||
On the first upgrade to this mode, a legacy `false` value is changed to `true`
|
||||
only when `server.trusted_proxies` was not explicitly configured; explicit
|
||||
proxy policies remain in secure mode. New installations persist the configured
|
||||
custom header list during database initialization. Existing installations
|
||||
backfill a missing database value from the YAML configuration. A hidden
|
||||
migration marker prevents later administrator changes from being overwritten.
|
||||
If settings cannot be read or the persisted custom-header list is malformed,
|
||||
the process fails closed to trusted-proxy mode with no custom headers. If a
|
||||
migration write fails, the computed mode remains active for the current process
|
||||
and startup records a warning.
|
||||
|
||||
Compatibility takeover accepts forwarded headers without validating the direct
|
||||
peer, including any configured custom header. Protect the origin from direct
|
||||
access while it is enabled. A CDN deployment must firewall the origin so only
|
||||
the CDN or load balancer can reach it, and that proxy must overwrite every
|
||||
trusted client-IP header rather than append an untrusted client value.
|
||||
|
||||
Example for a proxy on the same host:
|
||||
|
||||
```yaml
|
||||
server:
|
||||
trusted_proxies:
|
||||
- 127.0.0.1/32
|
||||
- ::1/128
|
||||
```
|
||||
|
||||
## Nginx baseline
|
||||
|
||||
Define shared zones in the `http` block. Tune rates to measured legitimate
|
||||
traffic; the values below are conservative starting points, not universal
|
||||
capacity targets.
|
||||
|
||||
```nginx
|
||||
limit_conn_zone $binary_remote_addr zone=sub2api_conn:20m;
|
||||
limit_req_zone $binary_remote_addr zone=sub2api_auth:20m rate=5r/s;
|
||||
limit_req_zone $binary_remote_addr zone=sub2api_api:40m rate=30r/s;
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name api.example.com;
|
||||
|
||||
client_header_timeout 10s;
|
||||
client_max_body_size 256m;
|
||||
large_client_header_buffers 4 16k;
|
||||
limit_conn sub2api_conn 40;
|
||||
|
||||
location ~ ^/(auth|api/auth)/ {
|
||||
limit_req zone=sub2api_auth burst=10 nodelay;
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
}
|
||||
|
||||
location ~ ^/(v1/)?(embeddings|alpha/search)$ {
|
||||
client_max_body_size 32m;
|
||||
limit_req zone=sub2api_api burst=60 nodelay;
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
}
|
||||
|
||||
location / {
|
||||
limit_req zone=sub2api_api burst=60 nodelay;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_buffering off;
|
||||
proxy_request_buffering off;
|
||||
proxy_read_timeout 1800s;
|
||||
proxy_send_timeout 1800s;
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If Nginx gzip is enabled in the `http` block, keep `text/event-stream` out of
|
||||
`gzip_types` and do not use `gzip_types *` for Sub2API. The
|
||||
`proxy_buffering off` setting above prevents proxy buffering, but it does not
|
||||
disable the gzip response filter. Use an explicit list for ordinary responses:
|
||||
|
||||
```nginx
|
||||
gzip on;
|
||||
gzip_types text/plain text/css application/json application/javascript application/xml image/svg+xml;
|
||||
```
|
||||
|
||||
If a shared global configuration cannot exclude SSE by content type, set
|
||||
`gzip off;` in the locations serving streaming API routes. This leaves gzip
|
||||
available for the web UI and static assets.
|
||||
|
||||
Do not use an incoming `$http_x_forwarded_for` value unless Nginx real-IP
|
||||
processing is restricted to explicit trusted proxy CIDRs.
|
||||
|
||||
## Caddy and CDN
|
||||
|
||||
The bundled `deploy/Caddyfile` sets a 64 KiB header limit, a 10-second header
|
||||
timeout, a 256 MiB absolute body limit, and overwrites forwarded addresses from
|
||||
the TCP peer. It is therefore a direct-to-Caddy baseline. Do not use its
|
||||
`{remote_host}` forwarding lines unchanged behind a CDN: all clients would be
|
||||
attributed to a CDN egress address, collapsing rejection aggregation and the
|
||||
invalid-auth limiter onto unrelated users.
|
||||
|
||||
The bundled Caddy configuration leaves `flush_interval` unset so Caddy can
|
||||
automatically flush `text/event-stream` responses while still propagating
|
||||
client cancellation upstream. Do not set it globally: positive values can add
|
||||
streaming latency, while Caddy 2.6.2's special `-1` mode also causes
|
||||
reverse-proxied requests to continue after clients disconnect. The
|
||||
configuration uses an explicit response content-type list for compression. Do
|
||||
not replace that list with `text/*` or the shorthand `encode gzip zstd`: both
|
||||
match `text/event-stream` and can buffer SSE until the response ends. Keep
|
||||
streaming responses uncompressed while retaining compression for the web UI,
|
||||
JSON, and static assets.
|
||||
|
||||
For a CDN deployment, first firewall the origin so only current CDN egress
|
||||
CIDRs can connect. Then configure those exact ranges as Caddy trusted proxies
|
||||
and derive upstream headers from Caddy's parsed `{client_ip}`. For example:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
servers {
|
||||
trusted_proxies static 192.0.2.0/24 2001:db8:1234::/48
|
||||
trusted_proxies_strict
|
||||
client_ip_headers CF-Connecting-IP X-Forwarded-For
|
||||
}
|
||||
}
|
||||
|
||||
api.example.com {
|
||||
reverse_proxy 127.0.0.1:8080 {
|
||||
header_up X-Real-IP {client_ip}
|
||||
header_up X-Forwarded-For {client_ip}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Replace the documentation ranges with the CDN's published, automatically
|
||||
maintained egress ranges. `CF-Connecting-IP` is safe here only because direct
|
||||
origin access is blocked and Caddy trusts only those TCP peers. Configure
|
||||
Sub2API `server.trusted_proxies` with the Caddy address/private subnet so the
|
||||
application accepts only Caddy's rewritten headers.
|
||||
|
||||
Caddy core does not provide a general request-rate limiter; use a trusted
|
||||
CDN/WAF, a supported rate-limit module, or host firewall controls.
|
||||
|
||||
At a CDN/WAF, configure connection limits, header/body limits, bot challenges,
|
||||
and per-IP/ASN rates before traffic reaches the origin. Allow origin ingress
|
||||
only from CDN egress CIDRs or a private load balancer. Keep the application port
|
||||
off the public Internet.
|
||||
|
||||
## DDoS boundary
|
||||
|
||||
Application checks reduce amplification after a connection reaches Go. They
|
||||
cannot absorb volumetric attacks, TLS floods, bandwidth saturation, or a large
|
||||
distributed source set. Those require upstream network capacity, CDN/WAF
|
||||
filtering, provider firewall rules, and origin isolation. Avoid high-cardinality
|
||||
metrics or per-request database security logs during rejection storms.
|
||||
Reference in New Issue
Block a user