Files
tunnel/internal/server/dns.go
T
TZGyn ebae420ab1 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.
2026-10-26 00:32:31 +08:00

50 lines
1.6 KiB
Go

package server
import (
"context"
"fmt"
"strings"
)
// DNSProvider manages the TXT records ACME DNS-01 challenges need.
type DNSProvider interface {
// CreateTXT adds a TXT record with the given value at name.
CreateTXT(ctx context.Context, name, value string) error
// DeleteTXT removes every TXT record at name (best effort cleanup).
DeleteTXT(ctx context.Context, name string) error
}
// ChallengeSweeper is implemented by providers that can remove leftover
// challenge records at startup (for example after a process was killed
// mid-issuance).
type ChallengeSweeper interface {
SweepChallenges(ctx context.Context) (int, error)
}
// challengeRecordName is the TXT record name a hostname's DNS-01 challenge
// lives at; the wildcard of a hostname shares the same record.
func challengeRecordName(hostname string) string {
return "_acme-challenge." + hostname
}
// relativeRecordName strips the zone suffix from a record name, as DNS
// provider APIs expect names relative to the zone.
func relativeRecordName(name, zone string) (string, error) {
suffix := "." + zone
if !strings.HasSuffix(name, suffix) {
return "", fmt.Errorf("record %q is not under zone %q", name, zone)
}
return strings.TrimSuffix(name, suffix), nil
}
// dnsZone guesses the registrable zone that hosts the tunnel hostnames:
// the domain minus its first label when it has three or more (tunnel.tzgyn.dev
// is served by the tzgyn.dev zone). Override with DNS_ZONE when wrong.
func DNSZone(domain string) string {
labels := strings.Split(domain, ".")
if len(labels) >= 3 {
return strings.Join(labels[1:], ".")
}
return domain
}