appc,ipn/ipnlocal: add App Connector domain configuration from mapcap

The AppConnector is now configured by the mapcap from the control plane.

Updates tailscale/corp#15437

Signed-off-by: James Tucker <james@tailscale.com>
This commit is contained in:
James Tucker
2023-10-31 14:59:18 -07:00
committed by James Tucker
parent e9de59a315
commit 6ad54fed00
5 changed files with 101 additions and 5 deletions

View File

@@ -67,6 +67,7 @@ import (
"tailscale.com/tka"
"tailscale.com/tsd"
"tailscale.com/tstime"
"tailscale.com/types/appctype"
"tailscale.com/types/dnstype"
"tailscale.com/types/empty"
"tailscale.com/types/key"
@@ -3233,6 +3234,49 @@ func (b *LocalBackend) blockEngineUpdates(block bool) {
b.mu.Unlock()
}
// reconfigAppConnectorLocked updates the app connector state based on the
// current network map and preferences.
// b.mu must be held.
func (b *LocalBackend) reconfigAppConnectorLocked(nm *netmap.NetworkMap, prefs ipn.PrefsView) {
const appConnectorCapName = "tailscale.com/app-connectors"
if !prefs.AppConnector().Advertise {
b.appConnector = nil
return
}
if b.appConnector == nil {
b.appConnector = appc.NewEmbeddedAppConnector(b.logf, b)
}
if nm == nil {
return
}
// TODO(raggi): rework the view infrastructure so the large deep clone is no
// longer required
sn := nm.SelfNode.AsStruct()
attrs, err := tailcfg.UnmarshalNodeCapJSON[appctype.AppConnectorAttr](sn.CapMap, appConnectorCapName)
if err != nil {
b.logf("[unexpected] error parsing app connector mapcap: %v", err)
return
}
var domains []string
for _, attr := range attrs {
// Geometric cost, assumes that the number of advertised tags is small
if !nm.SelfNode.Tags().ContainsFunc(func(tag string) bool {
return slices.Contains(attr.Connectors, tag)
}) {
continue
}
domains = append(domains, attr.Domains...)
}
slices.Sort(domains)
slices.Compact(domains)
b.appConnector.UpdateDomains(domains)
}
// authReconfig pushes a new configuration into wgengine, if engine
// updates are not currently blocked, based on the cached netmap and
// user prefs.
@@ -3246,9 +3290,7 @@ func (b *LocalBackend) authReconfig() {
dohURL, dohURLOK := exitNodeCanProxyDNS(nm, b.peers, prefs.ExitNodeID())
dcfg := dnsConfigForNetmap(nm, b.peers, prefs, b.logf, version.OS())
// If the current node is an app connector, ensure the app connector machine is started
if prefs.AppConnector().Advertise && b.appConnector == nil {
b.appConnector = appc.NewEmbeddedAppConnector(b.logf, b)
}
b.reconfigAppConnectorLocked(nm, prefs)
b.mu.Unlock()
if blocked {

View File

@@ -1187,6 +1187,49 @@ func TestObserveDNSResponse(t *testing.T) {
}
}
func TestReconfigureAppConnector(t *testing.T) {
b := newTestBackend(t)
b.reconfigAppConnectorLocked(b.netMap, b.pm.prefs)
if b.appConnector != nil {
t.Fatal("unexpected app connector")
}
b.EditPrefs(&ipn.MaskedPrefs{
Prefs: ipn.Prefs{
AppConnector: ipn.AppConnectorPrefs{
Advertise: true,
},
},
AppConnectorSet: true,
})
b.reconfigAppConnectorLocked(b.netMap, b.pm.prefs)
if b.appConnector == nil {
t.Fatal("expected app connector")
}
appCfg := `{
"name": "example",
"domains": ["example.com"],
"connectors": ["tag:example"]
}`
b.netMap.SelfNode = (&tailcfg.Node{
Name: "example.ts.net",
Tags: []string{"tag:example"},
CapMap: (tailcfg.NodeCapMap)(map[tailcfg.NodeCapability][]tailcfg.RawMessage{
"tailscale.com/app-connectors": {tailcfg.RawMessage(appCfg)},
}),
}).View()
b.reconfigAppConnectorLocked(b.netMap, b.pm.prefs)
want := []string{"example.com"}
if !slices.Equal(b.appConnector.Domains().AsSlice(), want) {
t.Fatalf("got domains %v, want %v", b.appConnector.Domains(), want)
}
}
func resolversEqual(t *testing.T, a, b []*dnstype.Resolver) bool {
if a == nil && b == nil {
return true