Files
tunnel/internal/server/dns_porkbun_test.go
T
TZGyn 437a2e447e Issue real certificates via ACME DNS-01
Tunnels now get a per-tunnel certificate from the configured ACME CA
(Let's Encrypt by default) with DNS-01 through Porkbun, keeping the
client's CSR and key. Issuance is asynchronous (issuing/ready/failed);
the account persists in the store, renewals run in a 30-day window with
retries, and a failed renewal keeps the old certificate serving.
Switching from the local CA reissues existing tunnels automatically,
and the admin dashboard shows the issuer and certificate state.

Verified against Pebble and Let's Encrypt staging.
2026-10-25 23:33:52 +08:00

75 lines
2.2 KiB
Go

package server
import (
"context"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestPorkbunDNS(t *testing.T) {
var createPath, createBody, deletePath string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
switch {
case strings.HasPrefix(r.URL.Path, "/dns/create/"):
createPath, createBody = r.URL.Path, string(body)
w.Write([]byte(`{"status":"SUCCESS","id":"42"}`))
case strings.HasPrefix(r.URL.Path, "/dns/deleteByNameType/"):
deletePath = 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()}
ctx := context.Background()
record := "_acme-challenge.abc123.tunnel.tzgyn.dev"
if err := p.CreateTXT(ctx, record, "challenge-value"); err != nil {
t.Fatal(err)
}
if createPath != "/dns/create/tzgyn.dev" {
t.Errorf("create path = %q", createPath)
}
var body map[string]string
if err := json.Unmarshal([]byte(createBody), &body); err != nil {
t.Fatal(err)
}
for key, want := range map[string]string{
"apikey": "pk", "secretapikey": "sk",
"name": "_acme-challenge.abc123.tunnel", "type": "TXT", "content": "challenge-value",
} {
if body[key] != want {
t.Errorf("create body %s = %q, want %q", key, body[key], want)
}
}
if err := p.DeleteTXT(ctx, record); err != nil {
t.Fatal(err)
}
if deletePath != "/dns/deleteByNameType/tzgyn.dev/TXT/_acme-challenge.abc123.tunnel" {
t.Errorf("delete path = %q", deletePath)
}
// A record outside the zone fails before any request.
if err := p.CreateTXT(ctx, "other.example.com", "v"); err == nil {
t.Error("expected an error for a record outside the zone")
}
// API errors surface.
bad := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`{"status":"ERROR","message":"nope"}`))
}))
defer bad.Close()
p2 := &PorkbunDNS{APIKey: "pk", SecretKey: "sk", Zone: "tzgyn.dev", BaseURL: bad.URL, HTTP: bad.Client()}
if err := p2.CreateTXT(ctx, record, "v"); err == nil || !strings.Contains(err.Error(), "nope") {
t.Errorf("expected the API error, got %v", err)
}
}