Add per-IP rate limiting and Unix socket support for gRPC admin API
Rate limiting: per-source-IP connection rate limiter in the firewall layer with configurable limit and sliding window. Blocklisted IPs are rejected before rate limit evaluation to avoid wasting quota. Unix socket: the gRPC admin API can now listen on a Unix domain socket (no TLS required), secured by file permissions (0600), as a simpler alternative for local-only access. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -174,6 +174,47 @@ gRPC admin API.
|
||||
|
||||
---
|
||||
|
||||
## gRPC Admin API
|
||||
|
||||
The admin API is optional (disabled if `[grpc]` is omitted from the config).
|
||||
When enabled, it requires TLS and supports optional mTLS for client
|
||||
authentication. TLS 1.3 is enforced. The API provides runtime management
|
||||
of routes and firewall rules without restarting the proxy.
|
||||
|
||||
### RPCs
|
||||
|
||||
| RPC | Description |
|
||||
|-----|-------------|
|
||||
| `ListRoutes` | List all routes for a given listener |
|
||||
| `AddRoute` | Add a route to a listener (write-through to DB) |
|
||||
| `RemoveRoute` | Remove a route from a listener (write-through to DB) |
|
||||
| `GetFirewallRules` | List all firewall rules |
|
||||
| `AddFirewallRule` | Add a firewall rule (write-through to DB) |
|
||||
| `RemoveFirewallRule` | Remove a firewall rule (write-through to DB) |
|
||||
| `GetStatus` | Return version, uptime, listener status, connection counts |
|
||||
|
||||
### Input Validation
|
||||
|
||||
The admin API validates all inputs before persisting:
|
||||
|
||||
- **Route backends** must be valid `host:port` tuples.
|
||||
- **IP firewall rules** must be valid IP addresses (`netip.ParseAddr`).
|
||||
- **CIDR firewall rules** must be valid prefixes in canonical form.
|
||||
- **Country firewall rules** must be exactly 2 uppercase letters (ISO 3166-1 alpha-2).
|
||||
|
||||
### Security
|
||||
|
||||
The gRPC admin API has no MCIAS integration — mc-proxy is pre-auth
|
||||
infrastructure. Access control relies on:
|
||||
|
||||
1. **Network binding**: bind to `127.0.0.1` (default) to restrict to local access.
|
||||
2. **mTLS**: configure `client_ca` to require client certificates.
|
||||
|
||||
If the admin API is exposed on a non-loopback interface without mTLS,
|
||||
any network client can modify routing and firewall rules.
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
TOML configuration file, loaded at startup. The proxy refuses to start if
|
||||
@@ -210,12 +251,16 @@ addr = ":9443"
|
||||
hostname = "mcias.metacircular.net"
|
||||
backend = "127.0.0.1:28443"
|
||||
|
||||
# gRPC admin API. Optional — omit addr to disable.
|
||||
# gRPC admin API. Optional — omit or leave addr empty to disable.
|
||||
# If enabled, tls_cert and tls_key are required (TLS 1.3 only).
|
||||
# client_ca enables mTLS and is strongly recommended for non-loopback addresses.
|
||||
# ca_cert is used by the `status` CLI command to verify the server certificate.
|
||||
[grpc]
|
||||
addr = "127.0.0.1:9090"
|
||||
tls_cert = "/srv/mc-proxy/certs/cert.pem"
|
||||
tls_key = "/srv/mc-proxy/certs/key.pem"
|
||||
client_ca = "/srv/mc-proxy/certs/ca.pem"
|
||||
ca_cert = "/srv/mc-proxy/certs/ca.pem"
|
||||
|
||||
# Firewall. Global blocklist, evaluated before routing. Default allow.
|
||||
[firewall]
|
||||
@@ -333,6 +378,8 @@ Multi-stage Docker build:
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `mc-proxy.service` | Main proxy service |
|
||||
| `mc-proxy-backup.service` | Oneshot database backup (VACUUM INTO) |
|
||||
| `mc-proxy-backup.timer` | Daily backup timer (02:00 UTC, 5-minute jitter) |
|
||||
|
||||
The proxy binds to privileged ports (443) and should use `AmbientCapabilities=CAP_NET_BIND_SERVICE`
|
||||
in the systemd unit rather than running as root.
|
||||
@@ -354,9 +401,10 @@ On `SIGHUP`:
|
||||
1. Reload the GeoIP database from disk.
|
||||
2. Continue serving with the updated database.
|
||||
|
||||
Configuration changes (routes, listeners, firewall rules) require a full
|
||||
restart. Hot reload of routing rules is deferred to the future SQLite-backed
|
||||
implementation.
|
||||
Routes and firewall rules can be modified at runtime via the gRPC admin API
|
||||
(write-through to SQLite). Listener changes (adding/removing ports) require
|
||||
a full restart. TOML configuration changes (timeouts, log level, GeoIP path)
|
||||
also require a restart.
|
||||
|
||||
---
|
||||
|
||||
@@ -373,7 +421,7 @@ It has no authentication or authorization of its own.
|
||||
| Resource exhaustion (connection flood) | Idle timeout closes stale connections. Per-listener connection limits (future). Rate limiting (future). |
|
||||
| GeoIP evasion via IPv6 | GeoLite2 database includes IPv6 mappings. Both IPv4 and IPv6 source addresses are checked. |
|
||||
| GeoIP evasion via VPN/proxy | Accepted risk. GeoIP blocking is a compliance measure, not a security boundary. Determined adversaries will bypass it. |
|
||||
| Slowloris / slow ClientHello | Timeout on the SNI extraction phase. If a complete ClientHello is not received within a reasonable window (e.g. 10s), the connection is reset. |
|
||||
| Slowloris / slow ClientHello | Hardcoded 10-second timeout on the SNI extraction phase. If a complete ClientHello is not received within this window, the connection is reset. |
|
||||
| Backend unavailability | Connect timeout prevents indefinite hangs. Connection is reset if the backend is unreachable. |
|
||||
| Information leakage | Blocked connections receive only a TCP RST. No version strings, no error messages, no TLS alerts. |
|
||||
|
||||
|
||||
Reference in New Issue
Block a user