Managed Route Domain and HTTPS Implementation Plan
Managed Route Domain and HTTPS Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Explicit managed HTTP/WebSocket routes can reach domain-only and HTTPS-only upstream services by independently controlling the dial address, upstream Host header, upstream scheme, and TLS SNI.
Architecture: Store the new options in the existing tunnels.config JSON column to avoid a schema migration. Decode that JSON into the route resolver, propagate the options through the relay request and OPEN_STREAM payload, and let the Agent wrap the target TCP connection in TLS. The public route protocol remains http/websocket; targetScheme describes only the upstream transport.
Tech Stack: Go standard-library crypto/tls, existing relay/protocol packages, Vue 3 + TypeScript + Element Plus, Vitest.
Spec: Confirmed user requirement: “完整支持。动态泛域名路由可以不支持” for explicit managed routes. Dynamic wildcard routes stay IPv4-only and retain plain HTTP upstream behavior.
Global Constraints
- Work only in
/opt/app/workspace/TunnelMeshon the current branch; do not use a worktree. - Preserve all unrelated uncommitted changes.
- Do not commit, push, merge, or create a remote PR without explicit authorization.
- TDD is mandatory: each behavior must first fail a test, then receive the minimal implementation.
- Explicit route defaults:
targetScheme=http; upstream Host defaults totargetHost; HTTPS SNI defaults totargetHost. - TLS verification cannot be disabled. Use system roots, TLS 1.2+, and no
InsecureSkipVerify. - Dynamic routes do not gain domain targets,
hostHeader, HTTPS upstream, or custom SNI. - No schema migration; use
tunnels.config.
Task 1: API Contract and Persistence
Files:
- Modify:
internal/server/api.go - Test:
internal/server/api_test.go
Interfaces:
- Produces first-class JSON request/response fields:
hostHeader,targetScheme,tlsServerName. -
Produces
normalizeTunnelConfig(storage.Tunnel) (storage.Tunnel, string)where the string is a validation error message; an empty string means valid. - Step 1: Write failing API tests
Add tests covering creation, patch, omission-preserving patch, invalid scheme, and tlsServerName with targetScheme=http.
func TestAPIRouteUpstreamDomainAndTLSConfig(t *testing.T) {
// Create with hostHeader=https SNI fields, assert public response and repository config.
// PATCH only targetScheme=http and assert hostHeader is preserved.
// PATCH targetScheme=ftp and tlsServerName+http, assert HTTP 400.
}
- Step 2: Verify RED
Run: go test ./internal/server -run TestAPIRouteUpstreamDomainAndTLSConfig -count=1
Expected failure: request fields are ignored, so response and persisted config do not contain them.
- Step 3: Implement minimal API behavior
Add the fields to tunnel request structs, normalize/trim them, persist them into tunnels.config, and expose them in publicTunnel.
- Step 4: Verify GREEN
Run: go test ./internal/server -run TestAPIRouteUpstreamDomainAndTLSConfig -count=1
Task 2: Resolver and Route Table
Files:
- Modify:
internal/routing/matcher.go - Modify:
internal/server/managed_route_handler.go - Test:
internal/routing/routing_test.go - Test:
internal/server/managed_route_handler_test.go
Interfaces:
- Consumes JSON keys from Task 1.
-
Produces
routing.Route.HostHeader,routing.Route.TargetScheme, androuting.Route.TLSServerName. - Step 1: Write failing tests
Assert active routes decode all three fields, malformed config fails safely, and dynamic routes leave them empty.
- Step 2: Verify RED
Run: go test ./internal/routing ./internal/server -run 'ManagedRoute|RouteResolver' -count=1
- Step 3: Implement route fields and JSON decoding
Decode storage.Tunnel.Config into a small private struct. Invalid JSON is returned as a route-table error, not a panic.
- Step 4: Verify GREEN
Run the same focused tests.
Task 3: HTTP Proxy Host Rewrite and Request Propagation
Files:
- Modify:
internal/server/http_proxy.go - Test:
internal/server/http_proxy_test.go
Interfaces:
- Consumes Task 2 route fields.
-
Produces
relay.StreamRequest{TargetScheme, HostHeader, TLSServerName}. - Step 1: Write failing tests
Use the existing recording opener to assert both ordinary HTTP and WebSocket upgrade requests send Host: service.internal.example.com and propagate scheme/SNI. Also assert empty hostHeader defaults to targetHost.
- Step 2: Verify RED
Run: go test ./internal/server -run 'HTTPProxy.*Upstream|HTTPProxy.*WebSocket' -count=1
- Step 3: Implement minimal propagation
Use route.HostHeader, defaulting to TargetHost, in both request writers and include all options in both OpenStream calls.
- Step 4: Verify GREEN
Run the focused proxy tests.
Task 4: Relay and Protocol Propagation
Files:
- Modify:
internal/relay/service.go - Modify:
internal/relay/transport.go - Modify:
internal/server/agent_relay_transport.go - Test:
internal/protocol/stream_open_test.go - Test:
internal/server/agent_relay_transport_test.go - Test:
internal/relay/transport_test.go
Interfaces:
- Produces optional payload JSON fields:
target_scheme,host_header,tls_server_name. -
Produces equivalent gRPC metadata keys.
- Step 1: Write failing tests
Assert exact JSON omits empty fields; local Agent relay and cross-node gRPC preserve all values.
- Step 2: Verify RED
Run: go test ./internal/protocol ./internal/relay ./internal/server -run 'StreamOpen|AgentRelay|GRPC.*Stream' -count=1
- Step 3: Implement transport propagation
Add fields to StreamRequest, StreamOpenPayload, Agent relay payload construction, and gRPC metadata encode/decode.
- Step 4: Verify GREEN
Run the focused protocol and relay tests.
Task 5: Agent TLS Dialing
Files:
- Modify:
internal/agent/dialer.go - Modify:
internal/agent/session.go - Test:
internal/agent/session_test.go
Interfaces:
- Consumes
protocol.StreamOpenPayload. -
Produces verified TLS wrapping for
target_scheme=https. - Step 1: Write failing tests
Use a local TLS listener to assert SNI, certificate verification, TLS 1.2 minimum, default SNI, and failure for an untrusted certificate.
- Step 2: Verify RED
Run: go test ./internal/agent -run 'Stream.*TLS|Dial.*TLS' -count=1
- Step 3: Implement minimal Agent dialing
Change the internal dial callback to receive the stream payload, dial TCP, optionally wrap with tls.Client, set ServerName to TLSServerName or TargetHost, and perform a context-aware handshake.
- Step 4: Verify GREEN
Run the focused Agent tests.
Task 6: Web Management UI
Files:
- Modify:
web/src/api/client.ts - Modify:
web/src/views/Routes.vue - Modify:
web/src/i18n/messages/zh-CN.ts - Modify:
web/src/i18n/messages/en-US.ts - Test:
web/src/tests/routes.spec.ts
Interfaces:
- Consumes Task 1 API fields.
-
Produces create/update payloads and edit-form prefills for all new fields.
- Step 1: Write failing frontend tests
Assert API payload serialization and that Routes.vue contains scheme select, Host header, TLS server name, and edit prefill.
- Step 2: Verify RED
Run: npm --prefix web test -- --run src/tests/routes.spec.ts
- Step 3: Implement UI and types
Add fields to types and form, show TLS server name for HTTPS, provide Chinese/English labels and help text, and preserve empty values according to PATCH semantics.
- Step 4: Verify GREEN
Run the focused frontend tests.
Task 7: Contract, Documentation, and PR Notes
Files:
- Modify:
docs/api/openapi.yaml - Modify:
docs/user-guide/managed-http-route.md - Create:
docs/pull-requests/2026-09-09-managed-route-domain-https.md
Interfaces:
-
Produces the public API contract and user-facing semantics.
-
Step 1: Update OpenAPI and user guide
Document all new request/response fields, defaults, validation, and the IP-dial/domain-Host/HTTPS-SNI example.
- Step 2: Add PR description
Include problem, solution, compatibility, test plan, rollback, and explicitly state that dynamic routes remain unchanged.
Task 8: Full Verification and Embedded Assets
Files:
-
Regenerate:
internal/server/web_dist/ -
Run:
go test ./... -count=1
go test -race ./...
go vet ./...
git diff --check
npm --prefix web test -- --run
npm --prefix web run build
rsync -a --delete web/dist/ internal/server/web_dist/
./scripts/verify-web-embed.sh
- Re-read the plan and confirm every requirement has an implementation and verification result.