ipn: introduce app connector advertisement preference and flags

Introduce a preference structure to store the setting for app connector
advertisement.

Introduce the associated flags:

  tailscale up --advertise-connector{=true,=false}
  tailscale set --advertise-connector{=true,=false}

```
% tailscale set --advertise-connector=false
% tailscale debug prefs | jq .AppConnector.Advertise
false
% tailscale set --advertise-connector=true
% tailscale debug prefs | jq .AppConnector.Advertise
true
% tailscale up --advertise-connector=false
% tailscale debug prefs | jq .AppConnector.Advertise
false
% tailscale up --advertise-connector=true
% tailscale debug prefs | jq .AppConnector.Advertise
true
```

Updates tailscale/corp#15437

Signed-off-by: James Tucker <james@tailscale.com>
This commit is contained in:
James Tucker 2023-10-26 15:55:32 -07:00 committed by James Tucker
parent 09fcbae900
commit ca4c940a4d
7 changed files with 108 additions and 0 deletions

View File

@ -893,6 +893,7 @@ func TestUpdatePrefs(t *testing.T) {
AdvertiseRoutesSet: true, AdvertiseRoutesSet: true,
AdvertiseTagsSet: true, AdvertiseTagsSet: true,
AllowSingleHostsSet: true, AllowSingleHostsSet: true,
AppConnectorSet: true,
ControlURLSet: true, ControlURLSet: true,
CorpDNSSet: true, CorpDNSSet: true,
ExitNodeAllowLANAccessSet: true, ExitNodeAllowLANAccessSet: true,
@ -1131,6 +1132,49 @@ func TestUpdatePrefs(t *testing.T) {
wantJustEditMP: nil, wantJustEditMP: nil,
env: upCheckEnv{backendState: "Running"}, env: upCheckEnv{backendState: "Running"},
}, },
{
name: "advertise_connector",
flags: []string{"--advertise-connector"},
curPrefs: &ipn.Prefs{
ControlURL: ipn.DefaultControlURL,
AllowSingleHosts: true,
CorpDNS: true,
NetfilterMode: preftype.NetfilterOn,
},
wantJustEditMP: &ipn.MaskedPrefs{
AppConnectorSet: true,
WantRunningSet: true,
},
env: upCheckEnv{backendState: "Running"},
checkUpdatePrefsMutations: func(t *testing.T, newPrefs *ipn.Prefs) {
if !newPrefs.AppConnector.Advertise {
t.Errorf("prefs.AppConnector.Advertise not set")
}
},
},
{
name: "no_advertise_connector",
flags: []string{"--advertise-connector=false"},
curPrefs: &ipn.Prefs{
ControlURL: ipn.DefaultControlURL,
AllowSingleHosts: true,
CorpDNS: true,
NetfilterMode: preftype.NetfilterOn,
AppConnector: ipn.AppConnectorPrefs{
Advertise: true,
},
},
wantJustEditMP: &ipn.MaskedPrefs{
AppConnectorSet: true,
WantRunningSet: true,
},
env: upCheckEnv{backendState: "Running"},
checkUpdatePrefsMutations: func(t *testing.T, newPrefs *ipn.Prefs) {
if newPrefs.AppConnector.Advertise {
t.Errorf("prefs.AppConnector.Advertise not unset")
}
},
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View File

@ -45,6 +45,7 @@ type setArgsT struct {
hostname string hostname string
advertiseRoutes string advertiseRoutes string
advertiseDefaultRoute bool advertiseDefaultRoute bool
advertiseConnector bool
opUser string opUser string
acceptedRisks string acceptedRisks string
profileName string profileName string
@ -67,6 +68,7 @@ func newSetFlagSet(goos string, setArgs *setArgsT) *flag.FlagSet {
setf.StringVar(&setArgs.hostname, "hostname", "", "hostname to use instead of the one provided by the OS") setf.StringVar(&setArgs.hostname, "hostname", "", "hostname to use instead of the one provided by the OS")
setf.StringVar(&setArgs.advertiseRoutes, "advertise-routes", "", "routes to advertise to other nodes (comma-separated, e.g. \"10.0.0.0/8,192.168.0.0/24\") or empty string to not advertise routes") setf.StringVar(&setArgs.advertiseRoutes, "advertise-routes", "", "routes to advertise to other nodes (comma-separated, e.g. \"10.0.0.0/8,192.168.0.0/24\") or empty string to not advertise routes")
setf.BoolVar(&setArgs.advertiseDefaultRoute, "advertise-exit-node", false, "offer to be an exit node for internet traffic for the tailnet") setf.BoolVar(&setArgs.advertiseDefaultRoute, "advertise-exit-node", false, "offer to be an exit node for internet traffic for the tailnet")
setf.BoolVar(&setArgs.advertiseConnector, "advertise-connector", false, "offer to be an exit node for internet traffic for the tailnet")
setf.BoolVar(&setArgs.updateCheck, "update-check", true, "notify about available Tailscale updates") setf.BoolVar(&setArgs.updateCheck, "update-check", true, "notify about available Tailscale updates")
setf.BoolVar(&setArgs.updateApply, "auto-update", false, "automatically update to the latest available version") setf.BoolVar(&setArgs.updateApply, "auto-update", false, "automatically update to the latest available version")
setf.BoolVar(&setArgs.postureChecking, "posture-checking", false, "HIDDEN: allow management plane to gather device posture information") setf.BoolVar(&setArgs.postureChecking, "posture-checking", false, "HIDDEN: allow management plane to gather device posture information")
@ -113,6 +115,9 @@ func runSet(ctx context.Context, args []string) (retErr error) {
Check: setArgs.updateCheck, Check: setArgs.updateCheck,
Apply: setArgs.updateApply, Apply: setArgs.updateApply,
}, },
AppConnector: ipn.AppConnectorPrefs{
Advertise: setArgs.advertiseConnector,
},
PostureChecking: setArgs.postureChecking, PostureChecking: setArgs.postureChecking,
}, },
} }

View File

@ -113,6 +113,7 @@ func newUpFlagSet(goos string, upArgs *upArgsT, cmd string) *flag.FlagSet {
upf.StringVar(&upArgs.advertiseTags, "advertise-tags", "", "comma-separated ACL tags to request; each must start with \"tag:\" (e.g. \"tag:eng,tag:montreal,tag:ssh\")") upf.StringVar(&upArgs.advertiseTags, "advertise-tags", "", "comma-separated ACL tags to request; each must start with \"tag:\" (e.g. \"tag:eng,tag:montreal,tag:ssh\")")
upf.StringVar(&upArgs.hostname, "hostname", "", "hostname to use instead of the one provided by the OS") upf.StringVar(&upArgs.hostname, "hostname", "", "hostname to use instead of the one provided by the OS")
upf.StringVar(&upArgs.advertiseRoutes, "advertise-routes", "", "routes to advertise to other nodes (comma-separated, e.g. \"10.0.0.0/8,192.168.0.0/24\") or empty string to not advertise routes") upf.StringVar(&upArgs.advertiseRoutes, "advertise-routes", "", "routes to advertise to other nodes (comma-separated, e.g. \"10.0.0.0/8,192.168.0.0/24\") or empty string to not advertise routes")
upf.BoolVar(&upArgs.advertiseConnector, "advertise-connector", false, "advertise this node as an app connector")
upf.BoolVar(&upArgs.advertiseDefaultRoute, "advertise-exit-node", false, "offer to be an exit node for internet traffic for the tailnet") upf.BoolVar(&upArgs.advertiseDefaultRoute, "advertise-exit-node", false, "offer to be an exit node for internet traffic for the tailnet")
if safesocket.GOOSUsesPeerCreds(goos) { if safesocket.GOOSUsesPeerCreds(goos) {
@ -165,6 +166,7 @@ type upArgsT struct {
advertiseRoutes string advertiseRoutes string
advertiseDefaultRoute bool advertiseDefaultRoute bool
advertiseTags string advertiseTags string
advertiseConnector bool
snat bool snat bool
netfilterMode string netfilterMode string
authKeyOrFile string // "secret" or "file:/path/to/secret" authKeyOrFile string // "secret" or "file:/path/to/secret"
@ -283,6 +285,7 @@ func prefsFromUpArgs(upArgs upArgsT, warnf logger.Logf, st *ipnstate.Status, goo
prefs.ForceDaemon = upArgs.forceDaemon prefs.ForceDaemon = upArgs.forceDaemon
prefs.OperatorUser = upArgs.opUser prefs.OperatorUser = upArgs.opUser
prefs.ProfileName = upArgs.profileName prefs.ProfileName = upArgs.profileName
prefs.AppConnector.Advertise = upArgs.advertiseConnector
if goos == "linux" { if goos == "linux" {
prefs.NoSNAT = !upArgs.snat prefs.NoSNAT = !upArgs.snat
@ -730,6 +733,7 @@ func init() {
addPrefFlagMapping("nickname", "ProfileName") addPrefFlagMapping("nickname", "ProfileName")
addPrefFlagMapping("update-check", "AutoUpdate") addPrefFlagMapping("update-check", "AutoUpdate")
addPrefFlagMapping("auto-update", "AutoUpdate") addPrefFlagMapping("auto-update", "AutoUpdate")
addPrefFlagMapping("advertise-connector", "AppConnector")
addPrefFlagMapping("posture-checking", "PostureChecking") addPrefFlagMapping("posture-checking", "PostureChecking")
} }
@ -965,6 +969,8 @@ func prefsToFlags(env upCheckEnv, prefs *ipn.Prefs) (flagVal map[string]any) {
set(sb.String()) set(sb.String())
case "advertise-exit-node": case "advertise-exit-node":
set(hasExitNodeRoutes(prefs.AdvertiseRoutes)) set(hasExitNodeRoutes(prefs.AdvertiseRoutes))
case "advertise-connector":
set(prefs.AppConnector.Advertise)
case "snat-subnet-routes": case "snat-subnet-routes":
set(!prefs.NoSNAT) set(!prefs.NoSNAT)
case "netfilter-mode": case "netfilter-mode":

View File

@ -53,6 +53,7 @@ func (src *Prefs) Clone() *Prefs {
OperatorUser string OperatorUser string
ProfileName string ProfileName string
AutoUpdate AutoUpdatePrefs AutoUpdate AutoUpdatePrefs
AppConnector AppConnectorPrefs
PostureChecking bool PostureChecking bool
Persist *persist.Persist Persist *persist.Persist
}{}) }{})

View File

@ -88,6 +88,7 @@ func (v PrefsView) NetfilterMode() preftype.NetfilterMode { return v.ж.Netfilte
func (v PrefsView) OperatorUser() string { return v.ж.OperatorUser } func (v PrefsView) OperatorUser() string { return v.ж.OperatorUser }
func (v PrefsView) ProfileName() string { return v.ж.ProfileName } func (v PrefsView) ProfileName() string { return v.ж.ProfileName }
func (v PrefsView) AutoUpdate() AutoUpdatePrefs { return v.ж.AutoUpdate } func (v PrefsView) AutoUpdate() AutoUpdatePrefs { return v.ж.AutoUpdate }
func (v PrefsView) AppConnector() AppConnectorPrefs { return v.ж.AppConnector }
func (v PrefsView) PostureChecking() bool { return v.ж.PostureChecking } func (v PrefsView) PostureChecking() bool { return v.ж.PostureChecking }
func (v PrefsView) Persist() persist.PersistView { return v.ж.Persist.View() } func (v PrefsView) Persist() persist.PersistView { return v.ж.Persist.View() }
@ -116,6 +117,7 @@ func (v PrefsView) Persist() persist.PersistView { return v.ж.Persist.
OperatorUser string OperatorUser string
ProfileName string ProfileName string
AutoUpdate AutoUpdatePrefs AutoUpdate AutoUpdatePrefs
AppConnector AppConnectorPrefs
PostureChecking bool PostureChecking bool
Persist *persist.Persist Persist *persist.Persist
}{}) }{})

View File

@ -205,6 +205,10 @@ type Prefs struct {
// AutoUpdatePrefs docs for more details. // AutoUpdatePrefs docs for more details.
AutoUpdate AutoUpdatePrefs AutoUpdate AutoUpdatePrefs
// AppConnector sets the app connector preferences for the node agent. See
// AppConnectorPrefs docs for more details.
AppConnector AppConnectorPrefs
// PostureChecking enables the collection of information used for device // PostureChecking enables the collection of information used for device
// posture checks. // posture checks.
PostureChecking bool PostureChecking bool
@ -229,6 +233,13 @@ type AutoUpdatePrefs struct {
Apply bool Apply bool
} }
// AppConnectorPrefs are the app connector settings for the node agent.
type AppConnectorPrefs struct {
// Advertise specifies whether the app connector subsystem is advertising
// this node as a connector.
Advertise bool
}
// MaskedPrefs is a Prefs with an associated bitmask of which fields are set. // MaskedPrefs is a Prefs with an associated bitmask of which fields are set.
type MaskedPrefs struct { type MaskedPrefs struct {
Prefs Prefs
@ -256,6 +267,7 @@ type MaskedPrefs struct {
OperatorUserSet bool `json:",omitempty"` OperatorUserSet bool `json:",omitempty"`
ProfileNameSet bool `json:",omitempty"` ProfileNameSet bool `json:",omitempty"`
AutoUpdateSet bool `json:",omitempty"` AutoUpdateSet bool `json:",omitempty"`
AppConnectorSet bool `json:",omitempty"`
PostureCheckingSet bool `json:",omitempty"` PostureCheckingSet bool `json:",omitempty"`
} }
@ -398,6 +410,7 @@ func (p *Prefs) pretty(goos string) string {
fmt.Fprintf(&sb, "op=%q ", p.OperatorUser) fmt.Fprintf(&sb, "op=%q ", p.OperatorUser)
} }
sb.WriteString(p.AutoUpdate.Pretty()) sb.WriteString(p.AutoUpdate.Pretty())
sb.WriteString(p.AppConnector.Pretty())
if p.Persist != nil { if p.Persist != nil {
sb.WriteString(p.Persist.Pretty()) sb.WriteString(p.Persist.Pretty())
} else { } else {
@ -455,6 +468,7 @@ func (p *Prefs) Equals(p2 *Prefs) bool {
p.Persist.Equals(p2.Persist) && p.Persist.Equals(p2.Persist) &&
p.ProfileName == p2.ProfileName && p.ProfileName == p2.ProfileName &&
p.AutoUpdate == p2.AutoUpdate && p.AutoUpdate == p2.AutoUpdate &&
p.AppConnector == p2.AppConnector &&
p.PostureChecking == p2.PostureChecking p.PostureChecking == p2.PostureChecking
} }
@ -468,6 +482,13 @@ func (au AutoUpdatePrefs) Pretty() string {
return "update=off " return "update=off "
} }
func (ap AppConnectorPrefs) Pretty() string {
if ap.Advertise {
return "appconnector=advertise "
}
return ""
}
func compareIPNets(a, b []netip.Prefix) bool { func compareIPNets(a, b []netip.Prefix) bool {
if len(a) != len(b) { if len(a) != len(b) {
return false return false

View File

@ -58,6 +58,7 @@ func TestPrefsEqual(t *testing.T) {
"OperatorUser", "OperatorUser",
"ProfileName", "ProfileName",
"AutoUpdate", "AutoUpdate",
"AppConnector",
"PostureChecking", "PostureChecking",
"Persist", "Persist",
} }
@ -306,6 +307,16 @@ func TestPrefsEqual(t *testing.T) {
&Prefs{AutoUpdate: AutoUpdatePrefs{Check: true, Apply: false}}, &Prefs{AutoUpdate: AutoUpdatePrefs{Check: true, Apply: false}},
true, true,
}, },
{
&Prefs{AppConnector: AppConnectorPrefs{Advertise: true}},
&Prefs{AppConnector: AppConnectorPrefs{Advertise: true}},
true,
},
{
&Prefs{AppConnector: AppConnectorPrefs{Advertise: true}},
&Prefs{AppConnector: AppConnectorPrefs{Advertise: false}},
false,
},
{ {
&Prefs{PostureChecking: true}, &Prefs{PostureChecking: true},
&Prefs{PostureChecking: true}, &Prefs{PostureChecking: true},
@ -516,6 +527,24 @@ func TestPrefsPretty(t *testing.T) {
"linux", "linux",
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=on Persist=nil}`, `Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=on Persist=nil}`,
}, },
{
Prefs{
AppConnector: AppConnectorPrefs{
Advertise: true,
},
},
"linux",
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=off appconnector=advertise Persist=nil}`,
},
{
Prefs{
AppConnector: AppConnectorPrefs{
Advertise: false,
},
},
"linux",
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=off Persist=nil}`,
},
} }
for i, tt := range tests { for i, tt := range tests {
got := tt.p.pretty(tt.os) got := tt.p.pretty(tt.os)