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:
2026-03-17 14:37:21 -07:00
parent e84093b7fb
commit b25e1b0e79
16 changed files with 694 additions and 43 deletions

View File

@@ -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. |