- docs/svc-deployment-plan.md: detailed plan for mc-proxy + MCNS on svc as the public edge (executed and live) - .mcp.json: Gitea MCP server config for Claude Code integration - PLATFORM_EVOLUTION.md: mark mc-proxy route persistence as done Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
453 lines
12 KiB
Markdown
453 lines
12 KiB
Markdown
# SVC Public Edge Deployment Plan
|
|
|
|
## Goal
|
|
|
|
Deploy mc-proxy and MCNS on `svc.metacircular.net` to make Metacircular
|
|
platform services publicly accessible and delegate internal DNS zones.
|
|
|
|
## Current State
|
|
|
|
- **svc** (`svc.metacircular.net`): Debian 12, public IP, on tailnet as
|
|
`svc`. Runs MCIAS only (systemd).
|
|
- **rift** (`192.168.88.181` / `100.95.252.120`): NixOS, LAN + tailnet.
|
|
Runs metacrypt, mc-proxy, mcr, mcns, mcp (all containers under mcp user).
|
|
- **DNS**: `metacircular.net` managed at Hurricane Electric. Internal zones
|
|
(`mcp.metacircular.net`, `svc.mcp.metacircular.net`) served by MCNS on
|
|
rift, resolved via systemd-resolved split DNS on LAN clients.
|
|
|
|
## Architecture After Deployment
|
|
|
|
```
|
|
Internet
|
|
│
|
|
▼
|
|
svc.metacircular.net (public IP)
|
|
├── MCIAS (:8443) ─── existing, no change
|
|
├── mc-proxy (:443) ── NEW: L7 TLS termination
|
|
│ └── metacrypt.metacircular.net → rift metacrypt-web (Tailscale)
|
|
└── MCNS (:53) ─────── NEW: authoritative DNS for delegation
|
|
└── mcp.metacircular.net, svc.mcp.metacircular.net zones
|
|
|
|
┌─── Tailscale ───┐
|
|
▼ │
|
|
rift (100.95.252.120) │
|
|
├── mc-proxy (:443/:8443/:9443)
|
|
├── metacrypt-web (:18080) ◄── forwarded from svc mc-proxy
|
|
├── metacrypt-api (:18443)
|
|
├── mcr, mcns, mcp, etc.
|
|
```
|
|
|
|
## Phase 1: Build Binaries
|
|
|
|
Both mc-proxy and mcns need to be built for svc (x86_64 Linux, Debian).
|
|
Since svc doesn't have Go, build on vade and scp.
|
|
|
|
```bash
|
|
# On vade
|
|
cd ~/src/metacircular/mc-proxy
|
|
CGO_ENABLED=0 make mc-proxy
|
|
scp mc-proxy svc:/tmp/
|
|
|
|
cd ~/src/metacircular/mcns
|
|
CGO_ENABLED=0 make mcns
|
|
scp mcns svc:/tmp/
|
|
```
|
|
|
|
## Phase 2: TLS Certificates
|
|
|
|
Need a cert for `metacrypt.metacircular.net` (used by mc-proxy on svc for
|
|
L7 termination). Also need a cert for MCNS management API on svc.
|
|
|
|
**Option A**: Issue from Metacrypt CA via the API.
|
|
**Option B**: Generate self-signed from the wntrmute CA on rift.
|
|
|
|
Certs needed:
|
|
- `metacrypt.metacircular.net` — for mc-proxy L7 route
|
|
- `mcns-svc.svc.mcp.metacircular.net` — for MCNS management API (or self-signed)
|
|
|
|
The wntrmute CA cert (`wntrmute-ca.pem`) must be distributed to external
|
|
users who want to trust the platform's TLS.
|
|
|
|
## Phase 3: Deploy mc-proxy on svc
|
|
|
|
### 3.1 Install
|
|
|
|
```bash
|
|
# On svc (as root)
|
|
useradd --system --no-create-home --shell /usr/sbin/nologin mc-proxy
|
|
mkdir -p /srv/mc-proxy/{certs,backups}
|
|
chown -R mc-proxy:mc-proxy /srv/mc-proxy
|
|
chmod 0700 /srv/mc-proxy
|
|
|
|
install -m 0755 /tmp/mc-proxy /usr/local/bin/mc-proxy
|
|
```
|
|
|
|
### 3.2 Configuration
|
|
|
|
Create `/srv/mc-proxy/mc-proxy.toml`:
|
|
|
|
```toml
|
|
[database]
|
|
path = "/srv/mc-proxy/mc-proxy.db"
|
|
|
|
# Public HTTPS — L7 termination for metacrypt web UI.
|
|
# Backend is metacrypt-web on rift via Tailscale, re-encrypted with TLS.
|
|
# mc-proxy skips backend cert verification (InsecureSkipVerify: trusted
|
|
# internal backend), so SNI mismatch between the public hostname and
|
|
# metacrypt-web's cert is not an issue.
|
|
[[listeners]]
|
|
addr = ":443"
|
|
|
|
[[listeners.routes]]
|
|
hostname = "metacrypt.metacircular.net"
|
|
backend = "100.95.252.120:18080"
|
|
mode = "l7"
|
|
tls_cert = "/srv/mc-proxy/certs/metacrypt.metacircular.net.pem"
|
|
tls_key = "/srv/mc-proxy/certs/metacrypt.metacircular.net.key"
|
|
backend_tls = true
|
|
|
|
# Firewall — internet-facing, aggressive blocking.
|
|
[firewall]
|
|
geoip_db = "/srv/mc-proxy/GeoLite2-Country.mmdb"
|
|
blocked_ips = []
|
|
blocked_cidrs = []
|
|
blocked_countries = []
|
|
rate_limit = 20
|
|
rate_window = "10s"
|
|
|
|
# No gRPC admin API on svc (managed via config file only).
|
|
|
|
[proxy]
|
|
connect_timeout = "5s"
|
|
idle_timeout = "300s"
|
|
shutdown_timeout = "30s"
|
|
|
|
[log]
|
|
level = "info"
|
|
```
|
|
|
|
Notes:
|
|
- `backend_tls = true` — metacrypt is security-sensitive; all hops must
|
|
use TLS. mc-proxy's backend transport uses `InsecureSkipVerify: true`
|
|
for trusted internal backends, so the public hostname / backend cert
|
|
mismatch is not a problem.
|
|
- **Rift prerequisite**: metacrypt-web must serve HTTPS and be exposed
|
|
on rift's Tailscale interface. Currently mapped to `127.0.0.1:18080`.
|
|
Two changes needed:
|
|
1. Configure `web.tls_cert` and `web.tls_key` in metacrypt's config
|
|
(metacrypt-web already supports TLS if these are set).
|
|
2. Update the MCP service definition to expose the port on Tailscale:
|
|
change `127.0.0.1:18080:8080` to `100.95.252.120:18080:8080`.
|
|
3. Redeploy with `mcp deploy metacrypt`.
|
|
|
|
### 3.3 GeoIP Database
|
|
|
|
Download MaxMind GeoLite2-Country database (free, requires account):
|
|
```bash
|
|
# Download and install
|
|
wget -O /srv/mc-proxy/GeoLite2-Country.mmdb <maxmind-url>
|
|
chown mc-proxy:mc-proxy /srv/mc-proxy/GeoLite2-Country.mmdb
|
|
```
|
|
|
|
Populate `blocked_countries` with appropriate list after initial
|
|
deployment. Start empty, add based on access logs.
|
|
|
|
### 3.4 User Agent Blocking
|
|
|
|
UA blocking is L7-only and managed via the gRPC admin API or direct
|
|
database insertion. Common bots to block:
|
|
- Scanners: `zgrab`, `masscan`, `Nuclei`, `httpx`
|
|
- AI scrapers: `GPTBot`, `CCBot`, `ClaudeBot`, `Bytespider`
|
|
|
|
Since we're not exposing the gRPC admin socket on svc, UA policies can
|
|
be pre-seeded in the SQLite database or added later via gRPC.
|
|
|
|
### 3.5 Systemd Unit
|
|
|
|
Copy from mc-proxy deploy, adapting for svc:
|
|
|
|
```ini
|
|
[Unit]
|
|
Description=MC-Proxy TLS Router (svc)
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=mc-proxy
|
|
Group=mc-proxy
|
|
ExecStart=/usr/local/bin/mc-proxy server --config /srv/mc-proxy/mc-proxy.toml
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
|
|
NoNewPrivileges=true
|
|
ProtectSystem=strict
|
|
ProtectHome=true
|
|
PrivateTmp=true
|
|
PrivateDevices=true
|
|
ProtectKernelTunables=true
|
|
ProtectKernelModules=true
|
|
ProtectControlGroups=true
|
|
RestrictSUIDSGID=true
|
|
RestrictNamespaces=true
|
|
LockPersonality=true
|
|
MemoryDenyWriteExecute=true
|
|
RestrictRealtime=true
|
|
ReadWritePaths=/srv/mc-proxy
|
|
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
### 3.6 Enable
|
|
|
|
```bash
|
|
systemctl daemon-reload
|
|
systemctl enable --now mc-proxy
|
|
systemctl enable --now mc-proxy-backup.timer
|
|
```
|
|
|
|
## Phase 4: Deploy MCNS on svc
|
|
|
|
### 4.1 Install
|
|
|
|
```bash
|
|
useradd --system --no-create-home --shell /usr/sbin/nologin mcns
|
|
mkdir -p /srv/mcns/{certs,backups}
|
|
chown -R mcns:mcns /srv/mcns
|
|
chmod 0700 /srv/mcns
|
|
|
|
install -m 0755 /tmp/mcns /usr/local/bin/mcns
|
|
```
|
|
|
|
### 4.2 Configuration
|
|
|
|
Create `/srv/mcns/mcns.toml`:
|
|
|
|
```toml
|
|
[server]
|
|
listen_addr = ":8444"
|
|
grpc_addr = ":9444"
|
|
tls_cert = "/srv/mcns/certs/mcns.pem"
|
|
tls_key = "/srv/mcns/certs/mcns.key"
|
|
|
|
[database]
|
|
path = "/srv/mcns/mcns.db"
|
|
|
|
[dns]
|
|
listen_addr = ":53"
|
|
upstreams = ["1.1.1.1:53", "8.8.8.8:53"]
|
|
|
|
[mcias]
|
|
server_url = "https://127.0.0.1:8443"
|
|
ca_cert = ""
|
|
service_name = "mcns"
|
|
tags = []
|
|
|
|
[log]
|
|
level = "info"
|
|
```
|
|
|
|
Notes:
|
|
- DNS on :53 (public-facing for NS delegation)
|
|
- Management API on :8444/:9444 (different from rift's 8443/9443 to
|
|
avoid confusion; not publicly exposed)
|
|
- MCIAS at localhost since MCIAS runs on svc
|
|
|
|
### 4.3 Seed Data
|
|
|
|
MCNS creates the database and runs migrations on first start. Migration
|
|
v2 seeds the same zones and records as rift's instance:
|
|
- `svc.mcp.metacircular.net` zone (metacrypt, mcr, sgard, mcp-agent)
|
|
- `mcp.metacircular.net` zone (rift, ns)
|
|
|
|
The `ns` record needs updating: for public delegation, `ns` should also
|
|
have svc's public IP so external resolvers can reach it.
|
|
|
|
After first start, add svc's public IP to the ns record via the API:
|
|
```bash
|
|
# Get svc's public IP
|
|
SVC_IP=$(curl -s ifconfig.me)
|
|
|
|
# Add ns record pointing to svc
|
|
curl -k -X POST https://localhost:8444/v1/zones/mcp.metacircular.net/records \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d "{\"name\": \"ns\", \"type\": \"A\", \"value\": \"$SVC_IP\", \"ttl\": 300}"
|
|
```
|
|
|
|
### 4.4 Systemd Unit
|
|
|
|
```ini
|
|
[Unit]
|
|
Description=MCNS Authoritative DNS (svc)
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=mcns
|
|
Group=mcns
|
|
ExecStart=/usr/local/bin/mcns server --config /srv/mcns/mcns.toml
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
|
|
NoNewPrivileges=true
|
|
ProtectSystem=strict
|
|
ProtectHome=true
|
|
PrivateTmp=true
|
|
PrivateDevices=true
|
|
ProtectKernelTunables=true
|
|
ProtectKernelModules=true
|
|
ProtectControlGroups=true
|
|
RestrictSUIDSGID=true
|
|
RestrictNamespaces=true
|
|
LockPersonality=true
|
|
MemoryDenyWriteExecute=true
|
|
RestrictRealtime=true
|
|
ReadWritePaths=/srv/mcns
|
|
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
### 4.5 Enable
|
|
|
|
```bash
|
|
systemctl daemon-reload
|
|
systemctl enable --now mcns
|
|
systemctl enable --now mcns-backup.timer
|
|
```
|
|
|
|
## Phase 5: DNS Changes at Hurricane Electric
|
|
|
|
### 5.1 A Record for metacrypt
|
|
|
|
```
|
|
metacrypt.metacircular.net. A <svc-public-ip>
|
|
```
|
|
|
|
### 5.2 NS Delegation for mcp.metacircular.net
|
|
|
|
```
|
|
mcp.metacircular.net. NS ns.mcp.metacircular.net.
|
|
ns.mcp.metacircular.net. A <svc-public-ip>
|
|
```
|
|
|
|
This tells external resolvers: "for anything under mcp.metacircular.net,
|
|
ask ns.mcp.metacircular.net (which is svc)." MCNS on svc then serves the
|
|
authoritative answers.
|
|
|
|
**Note**: NS delegation requires the glue record (the A record for ns)
|
|
to be in the parent zone at HE. Both records must be added.
|
|
|
|
### 5.3 Verification
|
|
|
|
```bash
|
|
# From an external machine (not on tailnet)
|
|
dig metacrypt.metacircular.net
|
|
dig rift.mcp.metacircular.net
|
|
dig metacrypt.svc.mcp.metacircular.net
|
|
|
|
# Test the web UI
|
|
curl -k https://metacrypt.metacircular.net/
|
|
```
|
|
|
|
## Phase 6: Verification
|
|
|
|
### 6.1 mc-proxy on svc
|
|
|
|
```bash
|
|
# From vade
|
|
curl -k https://metacrypt.metacircular.net/
|
|
# Should show metacrypt web UI (login page)
|
|
|
|
# Check mc-proxy logs
|
|
ssh svc journalctl -u mc-proxy -f
|
|
```
|
|
|
|
### 6.2 MCNS on svc
|
|
|
|
```bash
|
|
# Direct query to svc
|
|
host rift.mcp.metacircular.net <svc-public-ip>
|
|
host metacrypt.svc.mcp.metacircular.net <svc-public-ip>
|
|
|
|
# Forwarding works
|
|
host google.com <svc-public-ip>
|
|
|
|
# Delegation (from external, after HE changes propagate)
|
|
host rift.mcp.metacircular.net 8.8.8.8
|
|
```
|
|
|
|
### 6.3 GeoIP and UA blocking
|
|
|
|
```bash
|
|
# Check firewall is active
|
|
ssh svc journalctl -u mc-proxy | grep -i "blocked\|firewall"
|
|
|
|
# Test UA blocking (after adding policies)
|
|
curl -k -A "zgrab/0.x" https://metacrypt.metacircular.net/
|
|
# Should get 403 Forbidden
|
|
```
|
|
|
|
## Open Questions (Resolved)
|
|
|
|
1. **mc-proxy backend routing**: RESOLVED. mc-proxy uses
|
|
`InsecureSkipVerify: true` for backend TLS — no SNI verification, no
|
|
cert hostname checking. svc mc-proxy connects directly to
|
|
metacrypt-web on rift's Tailscale IP with `backend_tls = true`. No
|
|
SNI mismatch issue.
|
|
|
|
2. **GeoIP country list**: Start empty, add based on access logs after
|
|
deployment. Review periodically.
|
|
|
|
3. **Record sync between MCNS instances**: Manual for now. Both instances
|
|
get the same seed data from migration v2. Future: AXFR/IXFR zone
|
|
transfers from rift (primary) to svc (secondary).
|
|
|
|
4. **MCIAS on svc for MCNS auth**: MCNS config uses
|
|
`server_url = "https://127.0.0.1:8443"`. MCIAS cert is for
|
|
`svc.metacircular.net`. Set `ca_cert` to the wntrmute CA cert and
|
|
check if MCIAS cert includes localhost as a SAN. If not, either add
|
|
localhost SAN or use `svc.metacircular.net` as the server_url.
|
|
|
|
5. **Backup coordination**: Stagger timers. mc-proxy at 02:00 UTC,
|
|
mcns at 02:15 UTC, mcias at 02:30 UTC (check existing mcias timer).
|
|
|
|
## Rollback
|
|
|
|
If anything goes wrong:
|
|
1. `systemctl stop mc-proxy mcns` on svc
|
|
2. Remove DNS records at HE
|
|
3. All traffic returns to existing paths (LAN via rift, MCIAS via svc)
|
|
|
|
No changes are made to rift's configuration. This deployment is purely
|
|
additive on svc.
|
|
|
|
## Security Note: Backend TLS Verification
|
|
|
|
mc-proxy uses `InsecureSkipVerify: true` for backend TLS connections.
|
|
This is acceptable because backend IPs are hardcoded operator-configured
|
|
Tailscale addresses — WireGuard provides cryptographic peer
|
|
authentication, so TLS cert hostname validation is redundant.
|
|
|
|
**Reconsider later**: when services have publicly-facing FQDNs (e.g.,
|
|
`metacrypt.metacircular.net`), issue certs that include both the public
|
|
FQDN and the internal name (`metacrypt.svc.mcp.metacircular.net`) as
|
|
SANs. Then mc-proxy could enable backend cert verification for
|
|
defense-in-depth. This is a low-priority improvement — Tailscale's
|
|
identity guarantee is strong.
|
|
|
|
## Future Considerations
|
|
|
|
- **Let's Encrypt**: mc-proxy has ACME on its roadmap. Once implemented,
|
|
public-facing routes could use trusted certs automatically.
|
|
- **MCIAS migration to rift**: Once mc-proxy on svc handles public
|
|
routing, MCIAS could move to rift. svc mc-proxy would forward auth
|
|
traffic too. Eliminates the separate VPS dependency for MCIAS.
|
|
- **Additional public services**: mcr web UI, MCP status dashboard, etc.
|
|
Each would be an additional L7 route on svc's mc-proxy.
|