mirror of
https://github.com/hanzoai/dns.git
synced 2026-10-30 18:40:55 +00:00
hanzodns becomes the one DNS control plane: authoritative zones served by its
own CoreDNS store AND third-party providers (Cloudflare) via the org's
KMS-sealed token. Both are first-class and coexist per-zone.
- Provider interface (provider.go) mirroring the platform dns-provider.ts:
Type/ListZones/ZoneIDForName/ListRecords/Create/Update/DeleteRecord.
- Cloudflare connector (cloudflare.go) ported from cloudflare.ts — CF API v4
CRUD, token only on the Authorization header, fail-closed, never logged.
- Per-org credential sourcing (kms.go): providerFor reads the sealed token from
KMS by RELAYING the caller's own validated bearer to the KMS REST API
(/v1/kms/orgs/{org}/secrets/integrations/cloudflare/api_token). hanzodns holds
no standing cross-tenant credential — KMS enforces org isolation. org is the
IAM claim; auth-method-agnostic (apikey + oauth seal the same coord).
- Zone.provider dispatch (api.go): authoritative -> local store; cloudflare ->
provider (source of truth) with a local-store mirror so CoreDNS serves it and
listings stay consistent. OIDC wired into the auth chain so org + bearer reach
the provider path.
- Durable authoritative zones (store_snapshot.go): opt-in file snapshot via
HANZO_DNS_STATE_DIR — load on startup, atomic rewrite on every mutation, so a
restart preserves native zones. Unset keeps the in-memory store.
Tests: CF connector, KMS bearer-relay (incl. foreign-org refusal), the full
create/list/delete dispatch for a Cloudflare zone, the native authoritative
path, and snapshot round-trip across a restart. go test -tags metrics green.
128 lines
2.5 KiB
Go
128 lines
2.5 KiB
Go
package hanzodns
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/coredns/caddy"
|
|
"github.com/coredns/coredns/core/dnsserver"
|
|
"github.com/coredns/coredns/plugin"
|
|
clog "github.com/coredns/coredns/plugin/pkg/log"
|
|
"github.com/coredns/coredns/plugin/pkg/reuseport"
|
|
)
|
|
|
|
var log = clog.NewWithPlugin("hanzodns")
|
|
|
|
func init() { plugin.Register("hanzodns", setup) }
|
|
|
|
func setup(c *caddy.Controller) error {
|
|
addr, err := parse(c)
|
|
if err != nil {
|
|
return plugin.Error("hanzodns", err)
|
|
}
|
|
|
|
store := NewStore()
|
|
|
|
// Durable backing for authoritative zones: when HANZO_DNS_STATE_DIR is set,
|
|
// the store loads its last snapshot and persists after every mutation, so a
|
|
// pod restart preserves native zones. Unset keeps the pure in-memory store.
|
|
if err := EnableSnapshot(store, os.Getenv("HANZO_DNS_STATE_DIR")); err != nil {
|
|
return plugin.Error("hanzodns", err)
|
|
}
|
|
|
|
h := &HanzoDNS{
|
|
Store: store,
|
|
Addr: addr,
|
|
}
|
|
|
|
srv := &apiServer{
|
|
addr: addr,
|
|
store: store,
|
|
}
|
|
|
|
c.OnStartup(srv.OnStartup)
|
|
c.OnRestart(srv.OnShutdown)
|
|
c.OnFinalShutdown(srv.OnShutdown)
|
|
|
|
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
|
|
h.Next = next
|
|
return h
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
func parse(c *caddy.Controller) (string, error) {
|
|
addr := ":8443"
|
|
for c.Next() {
|
|
args := c.RemainingArgs()
|
|
switch len(args) {
|
|
case 0:
|
|
case 1:
|
|
addr = args[0]
|
|
if _, _, err := net.SplitHostPort(addr); err != nil {
|
|
return "", err
|
|
}
|
|
default:
|
|
return "", c.ArgErr()
|
|
}
|
|
// No block directives for now.
|
|
for c.NextBlock() {
|
|
return "", c.ArgErr()
|
|
}
|
|
}
|
|
return addr, nil
|
|
}
|
|
|
|
// apiServer manages the lifecycle of the HTTP API server.
|
|
type apiServer struct {
|
|
addr string
|
|
store *Store
|
|
ln net.Listener
|
|
srv *http.Server
|
|
setup bool
|
|
}
|
|
|
|
const shutdownTimeout = 5 * time.Second
|
|
|
|
func (s *apiServer) OnStartup() error {
|
|
ln, err := reuseport.Listen("tcp", s.addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.ln = ln
|
|
s.setup = true
|
|
|
|
mux := http.NewServeMux()
|
|
registerRoutes(mux, s.store)
|
|
|
|
s.srv = &http.Server{
|
|
Handler: mux,
|
|
ReadTimeout: 10 * time.Second,
|
|
WriteTimeout: 10 * time.Second,
|
|
IdleTimeout: 60 * time.Second,
|
|
}
|
|
|
|
log.Infof("Starting Hanzo DNS API on %s", s.addr)
|
|
go func() { s.srv.Serve(s.ln) }()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *apiServer) OnShutdown() error {
|
|
if !s.setup {
|
|
return nil
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
|
|
defer cancel()
|
|
if err := s.srv.Shutdown(ctx); err != nil {
|
|
log.Errorf("Failed to stop Hanzo DNS API server: %s", err)
|
|
}
|
|
s.setup = false
|
|
return nil
|
|
}
|