Instrument mc-proxy with prometheus/client_golang. New internal/metrics/ package defines counters, gauges, and histograms for connection totals, active connections, firewall blocks by reason, backend dial latency, bytes transferred, L7 HTTP status codes, and L7 policy blocks. Optional [metrics] config section starts a scrape endpoint. Firewall gains BlockedWithReason() to report block cause. L7 handler wraps ResponseWriter to record status codes per hostname. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3.9 KiB
3.9 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
mc-proxy is a TLS proxy and router for Metacircular Dynamics services. It operates in two per-route modes: L4 passthrough (reads SNI, proxies raw TCP without terminating TLS) and L7 terminating (terminates TLS, reverse proxies HTTP/2 and HTTP/1.1 traffic including gRPC). A global firewall (IP, CIDR, GeoIP, rate limiting) is evaluated before routing. PROXY protocol support enables multi-hop deployments preserving real client IPs. See ARCHITECTURE.md for full design.
Build Commands
make all # vet → lint → test → build
make mc-proxy # build the binary with version injection
make build # compile all packages
make test # run all tests
make vet # go vet
make lint # golangci-lint
make proto # regenerate gRPC code from proto definitions
make devserver # build and run locally with srv/mc-proxy.toml
Run a single test:
go test ./internal/sni -run TestExtract
Architecture
- Module path:
git.wntrmute.dev/kyle/mc-proxy - Go with CGO_ENABLED=0, statically linked, Alpine containers
- Dual mode, per-route — L4 (passthrough) and L7 (TLS-terminating HTTP/2 reverse proxy) coexist on the same listener
- PROXY protocol — listeners accept v1/v2; routes send v2. Enables edge→origin deployments over Tailscale
- gRPC admin API — manages routes and firewall rules at runtime; Unix socket only; optional (disabled if
[grpc]section omitted from config) - No auth on proxy listeners — this is pre-auth infrastructure; services behind it handle their own MCIAS auth
- SQLite database — persists listeners, routes, and firewall rules; pure-Go driver (
modernc.org/sqlite); seeded from TOML on first run, DB is source of truth thereafter - Write-through pattern — gRPC mutations write to DB first, then update in-memory state
- Config: TOML via
go-toml/v2, runtime data in/srv/mc-proxy/ - Testing: stdlib
testingonly,t.TempDir()for isolation - Linting: golangci-lint v2 with
.golangci.yaml
Package Structure
internal/config/— TOML config loading and validationinternal/db/— SQLite database: migrations, CRUD for listeners/routes/firewall rules, seeding, snapshotsinternal/sni/— TLS ClientHello parser; extracts SNI hostname without consuming bytesinternal/firewall/— global blocklist evaluation (IP, CIDR, GeoIP via MaxMind GeoLite2); rate limiting; thread-safe mutations and GeoIP reloadinternal/proxy/— L4 bidirectional TCP relay with half-close propagation and idle timeoutinternal/proxyproto/— PROXY protocol v1/v2 parser and v2 writerinternal/l7/— L7 TLS termination,prefixConn, HTTP/2 reverse proxy with h2c backend transportinternal/server/— orchestrates listeners → PROXY protocol → firewall → SNI → route → L4/L7 dispatch; per-listener state with connection trackinginternal/grpcserver/— gRPC admin API: route/firewall CRUD, status, write-through to DBinternal/metrics/— Prometheus metric definitions and HTTP server; optional[metrics]config sectionproto/mc_proxy/v1/— protobuf definitions;gen/mc_proxy/v1/has generated code
Signals
SIGINT/SIGTERM— graceful shutdown (drain in-flight connections up toshutdown_timeout)SIGHUP— reload GeoIP database without restart
Critical Rules
- L4 routes never terminate TLS and never modify the byte stream.
- L7 routes terminate TLS at the proxy and reverse proxy HTTP/2 (including gRPC) to backends.
- Firewall rules are always evaluated before any routing decision.
- PROXY protocol is only parsed on explicitly enabled listeners.
- SNI matching is exact and case-insensitive.
- Blocked connections get a TCP RST — no error messages, no TLS alerts.
- Database writes must succeed before in-memory state is updated (write-through).