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:
@@ -0,0 +1,64 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/acme"
|
||||
)
|
||||
|
||||
func TestCooldown(t *testing.T) {
|
||||
now := time.Now()
|
||||
c := newCooldown()
|
||||
c.now = func() time.Time { return now }
|
||||
|
||||
for i := 0; i < 4; i++ {
|
||||
if d := c.failure(); d != 0 {
|
||||
t.Fatalf("failure %d paused for %v, want no pause", i+1, d)
|
||||
}
|
||||
}
|
||||
if d := c.failure(); d != time.Minute {
|
||||
t.Fatalf("fifth failure pause = %v, want 1m", d)
|
||||
}
|
||||
if d := c.remaining(); d != time.Minute {
|
||||
t.Fatalf("remaining = %v, want 1m", d)
|
||||
}
|
||||
if d := c.failure(); d != 2*time.Minute {
|
||||
t.Fatalf("sixth failure pause = %v, want 2m", d)
|
||||
}
|
||||
for i := 0; i < 20; i++ {
|
||||
c.failure()
|
||||
}
|
||||
if d := c.failure(); d != time.Hour {
|
||||
t.Fatalf("pause cap = %v, want 1h", d)
|
||||
}
|
||||
|
||||
now = now.Add(2 * time.Hour)
|
||||
if d := c.remaining(); d != 0 {
|
||||
t.Fatalf("remaining after expiry = %v", d)
|
||||
}
|
||||
c.success()
|
||||
if c.failures != 0 {
|
||||
t.Fatalf("success left failures = %d", c.failures)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCAFailureClassification(t *testing.T) {
|
||||
cases := []struct {
|
||||
err error
|
||||
want bool
|
||||
}{
|
||||
{errors.New("dns: create _acme-challenge: nope"), false},
|
||||
{errors.New("acme: authorization for x is invalid"), false},
|
||||
{errors.New("acme: authorize order: context deadline exceeded"), true},
|
||||
{fmt.Errorf("acme: authorize order: %w", &acme.Error{StatusCode: 503}), true},
|
||||
{fmt.Errorf("acme: authorize order: %w", &acme.Error{StatusCode: 429}), true},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := caFailure(c.err); got != c.want {
|
||||
t.Errorf("caFailure(%v) = %v, want %v", c.err, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user