ipn/ipnlocal: don't program system DNS when node key is expired (#13370)

This mimics having Tailscale in the 'Stopped' state by programming an
empty DNS configuration when the current node key is expired.

Updates tailscale/support-escalations#55


Change-Id: I68ff4665761fb621ed57ebf879263c2f4b911610

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
pull/13422/head
Andrew Dunham 3 months ago committed by GitHub
parent 0a2e5afb26
commit 0970615b1b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -50,6 +50,7 @@ func TestDNSConfigForNetmap(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
nm *netmap.NetworkMap nm *netmap.NetworkMap
expired bool
peers []tailcfg.NodeView peers []tailcfg.NodeView
os string // version.OS value; empty means linux os string // version.OS value; empty means linux
cloud cloudenv.Cloud cloud cloudenv.Cloud
@ -327,12 +328,31 @@ func TestDNSConfigForNetmap(t *testing.T) {
Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, Routes: map[dnsname.FQDN][]*dnstype.Resolver{},
}, },
}, },
{
name: "self_expired",
nm: &netmap.NetworkMap{
Name: "myname.net",
SelfNode: (&tailcfg.Node{
Addresses: ipps("100.101.101.101"),
}).View(),
},
expired: true,
peers: nodeViews([]*tailcfg.Node{
{
ID: 1,
Name: "peera.net",
Addresses: ipps("100.102.0.1", "100.102.0.2", "fe75::1001", "fe75::1002"),
},
}),
prefs: &ipn.Prefs{},
want: &dns.Config{},
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
verOS := cmp.Or(tt.os, "linux") verOS := cmp.Or(tt.os, "linux")
var log tstest.MemLogger var log tstest.MemLogger
got := dnsConfigForNetmap(tt.nm, peersMap(tt.peers), tt.prefs.View(), log.Logf, verOS) got := dnsConfigForNetmap(tt.nm, peersMap(tt.peers), tt.prefs.View(), tt.expired, log.Logf, verOS)
if !reflect.DeepEqual(got, tt.want) { if !reflect.DeepEqual(got, tt.want) {
gotj, _ := json.MarshalIndent(got, "", "\t") gotj, _ := json.MarshalIndent(got, "", "\t")
wantj, _ := json.MarshalIndent(tt.want, "", "\t") wantj, _ := json.MarshalIndent(tt.want, "", "\t")

@ -4026,7 +4026,7 @@ func (b *LocalBackend) authReconfig() {
disableSubnetsIfPAC := nm.HasCap(tailcfg.NodeAttrDisableSubnetsIfPAC) disableSubnetsIfPAC := nm.HasCap(tailcfg.NodeAttrDisableSubnetsIfPAC)
userDialUseRoutes := nm.HasCap(tailcfg.NodeAttrUserDialUseRoutes) userDialUseRoutes := nm.HasCap(tailcfg.NodeAttrUserDialUseRoutes)
dohURL, dohURLOK := exitNodeCanProxyDNS(nm, b.peers, prefs.ExitNodeID()) dohURL, dohURLOK := exitNodeCanProxyDNS(nm, b.peers, prefs.ExitNodeID())
dcfg := dnsConfigForNetmap(nm, b.peers, prefs, b.logf, version.OS()) dcfg := dnsConfigForNetmap(nm, b.peers, prefs, b.keyExpired, b.logf, version.OS())
// If the current node is an app connector, ensure the app connector machine is started // If the current node is an app connector, ensure the app connector machine is started
b.reconfigAppConnectorLocked(nm, prefs) b.reconfigAppConnectorLocked(nm, prefs)
b.mu.Unlock() b.mu.Unlock()
@ -4126,10 +4126,23 @@ func shouldUseOneCGNATRoute(logf logger.Logf, controlKnobs *controlknobs.Knobs,
// //
// The versionOS is a Tailscale-style version ("iOS", "macOS") and not // The versionOS is a Tailscale-style version ("iOS", "macOS") and not
// a runtime.GOOS. // a runtime.GOOS.
func dnsConfigForNetmap(nm *netmap.NetworkMap, peers map[tailcfg.NodeID]tailcfg.NodeView, prefs ipn.PrefsView, logf logger.Logf, versionOS string) *dns.Config { func dnsConfigForNetmap(nm *netmap.NetworkMap, peers map[tailcfg.NodeID]tailcfg.NodeView, prefs ipn.PrefsView, selfExpired bool, logf logger.Logf, versionOS string) *dns.Config {
if nm == nil { if nm == nil {
return nil return nil
} }
// If the current node's key is expired, then we don't program any DNS
// configuration into the operating system. This ensures that if the
// DNS configuration specifies a DNS server that is only reachable over
// Tailscale, we don't break connectivity for the user.
//
// TODO(andrew-d): this also stops returning anything from quad-100; we
// could do the same thing as having "CorpDNS: false" and keep that but
// not program the OS?
if selfExpired {
return &dns.Config{}
}
dcfg := &dns.Config{ dcfg := &dns.Config{
Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, Routes: map[dnsname.FQDN][]*dnstype.Resolver{},
Hosts: map[dnsname.FQDN][]netip.Addr{}, Hosts: map[dnsname.FQDN][]netip.Addr{},

@ -1283,7 +1283,7 @@ func TestDNSConfigForNetmapForExitNodeConfigs(t *testing.T) {
} }
prefs := &ipn.Prefs{ExitNodeID: tc.exitNode, CorpDNS: true} prefs := &ipn.Prefs{ExitNodeID: tc.exitNode, CorpDNS: true}
got := dnsConfigForNetmap(nm, peersMap(tc.peers), prefs.View(), t.Logf, "") got := dnsConfigForNetmap(nm, peersMap(tc.peers), prefs.View(), false, t.Logf, "")
if !resolversEqual(t, got.DefaultResolvers, tc.wantDefaultResolvers) { if !resolversEqual(t, got.DefaultResolvers, tc.wantDefaultResolvers) {
t.Errorf("DefaultResolvers: got %#v, want %#v", got.DefaultResolvers, tc.wantDefaultResolvers) t.Errorf("DefaultResolvers: got %#v, want %#v", got.DefaultResolvers, tc.wantDefaultResolvers)
} }

Loading…
Cancel
Save