Add the native Go client and finish M3 hardening

The new cmd/tunnel client keeps the API URL and tunnel identity in
~/.config/tunnel/config.json (0600), provisions the key/CSR and
certificate, terminates TLS, proxies to local routes, reloads routes
without a restart, and runs as a background service. `tunnel import`
takes over an existing official CLI profile, preserving its tunnel ID,
token, key and certificate.

Server hardening: per-IP and per-tunnel rate limits for creates and
binds, a CA cool-down after repeated ACME failures, a startup sweep for
stale _acme-challenge records, admin metrics, a 60-second half-close
timeout, and separate prioritized control / ordered data bridge queues
(with `end` ordered after the final bytes). The dashboard shows the new
metrics.
This commit is contained in:
2026-10-26 00:32:31 +08:00
parent cba7d2366b
commit ebae420ab1
27 changed files with 3745 additions and 56 deletions
+43
View File
@@ -72,3 +72,46 @@ func TestPorkbunDNS(t *testing.T) {
t.Errorf("expected the API error, got %v", err)
}
}
func TestPorkbunSweepChallenges(t *testing.T) {
var deletes []string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
case r.URL.Path == "/dns/retrieve/tzgyn.dev":
w.Write([]byte(`{"status":"SUCCESS","records":[
{"name":"_acme-challenge.a.tunnel.tzgyn.dev","type":"TXT","content":"one"},
{"name":"_acme-challenge.a.tunnel.tzgyn.dev","type":"TXT","content":"two"},
{"name":"_acme-challenge.b.tunnel.tzgyn.dev","type":"TXT","content":"three"},
{"name":"www.tzgyn.dev","type":"TXT","content":"keep"},
{"name":"_acme-challenge.other.example.com","type":"TXT","content":"outside"}
]}`))
case strings.HasPrefix(r.URL.Path, "/dns/deleteByNameType/"):
deletes = append(deletes, r.URL.Path)
w.Write([]byte(`{"status":"SUCCESS"}`))
default:
http.NotFound(w, r)
}
}))
defer srv.Close()
p := &PorkbunDNS{APIKey: "pk", SecretKey: "sk", Zone: "tzgyn.dev", BaseURL: srv.URL, HTTP: srv.Client()}
count, err := p.SweepChallenges(context.Background())
if err != nil {
t.Fatal(err)
}
if count != 2 {
t.Errorf("count = %d, want 2", count)
}
want := []string{
"/dns/deleteByNameType/tzgyn.dev/TXT/_acme-challenge.a.tunnel",
"/dns/deleteByNameType/tzgyn.dev/TXT/_acme-challenge.b.tunnel",
}
if len(deletes) != len(want) {
t.Fatalf("deletes = %v, want %v", deletes, want)
}
for i := range want {
if deletes[i] != want[i] {
t.Errorf("delete[%d] = %q, want %q", i, deletes[i], want[i])
}
}
}