client/local, ipn/localapi, all: add CertDomains and DNSConfig accessors

Add two narrow LocalAPI accessors so callers don't have to subscribe to
the IPN bus and pull a full *netmap.NetworkMap just to read DNS-shaped
fields:

  - GET /localapi/v0/cert-domains returns DNS.CertDomains.
  - GET /localapi/v0/dns-config returns the full tailcfg.DNSConfig.

Migrate in-tree callers off the netmap-on-the-bus pattern:

  - kube/certs.waitForCertDomain still wakes on the IPN bus but now
    queries CertDomains via LocalClient.CertDomains rather than
    reading n.NetMap.DNS.CertDomains. The kube LocalClient interface
    and FakeLocalClient gain a CertDomains method.
  - cmd/tailscale dns status calls LocalClient.DNSConfig directly
    instead of opening a NotifyInitialNetMap watcher.
  - cmd/tailscale configure kubeconfig switches from a netmap watcher
    + serviceDNSRecordFromNetMap to LocalClient.DNSConfig +
    serviceDNSRecordFromDNSConfig.

This is part of a series moving callers away from depending on the
netmap traveling on the IPN bus, so the bus payload can shrink in a
later change.

Updates #12542

Change-Id: Ie10204e141d085fbac183b4cfe497226b670ad6c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2026-04-30 19:34:20 +00:00
committed by Brad Fitzpatrick
parent 822299642b
commit 9f343fdc0c
8 changed files with 105 additions and 59 deletions
+11 -2
View File
@@ -173,6 +173,12 @@ func (cm *CertManager) runCertLoop(ctx context.Context, domain string) {
// waitForCertDomain ensures the requested domain is in the list of allowed
// domains before issuing the cert for the first time.
// It uses the IPN bus only as a wake-up trigger and queries the current cert
// domains explicitly via [LocalClient.CertDomains].
//
// TODO(bradfitz): once Notify.SelfChange lands upstream, switch this to
// watch for SelfChange events instead of NotifyInitialNetMap, and drop the
// netmap dependency on the bus entirely.
func (cm *CertManager) waitForCertDomain(ctx context.Context, domain string) error {
w, err := cm.lc.WatchIPNBus(ctx, ipn.NotifyInitialNetMap)
if err != nil {
@@ -188,8 +194,11 @@ func (cm *CertManager) waitForCertDomain(ctx context.Context, domain string) err
if n.NetMap == nil {
continue
}
if slices.Contains(n.NetMap.DNS.CertDomains, domain) {
domains, err := cm.lc.CertDomains(ctx)
if err != nil {
continue
}
if slices.Contains(domains, domain) {
return nil
}
}
+10 -11
View File
@@ -201,18 +201,12 @@ func TestEnsureCertLoops(t *testing.T) {
notifyChan := make(chan ipn.Notify)
go func() {
// Drive waitForCertDomain by sending notifications
// with empty netmaps as wake-up triggers; the cert
// manager queries CertDomains via the local
// client and not by reading the bus payload.
for {
notifyChan <- ipn.Notify{
NetMap: &netmap.NetworkMap{
DNS: tailcfg.DNSConfig{
CertDomains: []string{
"my-app.tailnetxyz.ts.net",
"my-other-app.tailnetxyz.ts.net",
"my-apiserver.tailnetxyz.ts.net",
},
},
},
}
notifyChan <- ipn.Notify{NetMap: &netmap.NetworkMap{}}
}
}()
cm := &CertManager{
@@ -220,6 +214,11 @@ func TestEnsureCertLoops(t *testing.T) {
FakeIPNBusWatcher: localclient.FakeIPNBusWatcher{
NotifyChan: notifyChan,
},
CertDomainsResult: []string{
"my-app.tailnetxyz.ts.net",
"my-other-app.tailnetxyz.ts.net",
"my-apiserver.tailnetxyz.ts.net",
},
},
logf: log.Printf,
certLoops: make(map[string]context.CancelFunc),