health, ipn/ipnlocal: add healthcheck for state/config

If the user sufficiently breaks their state file such that the local
machine or node keys are zero, the machine ends up in a broken state but
we don't surface this to the user. Add a healthcheck that tracks errors
when these key(s) are zero and surfaces them in 'tailscale status'/etc.

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: Ideffbf9510b09850ebf8daf440fac78109de7812
This commit is contained in:
Andrew Dunham 2023-07-26 12:29:18 -04:00
parent 9edb848505
commit eb23a645a5
2 changed files with 32 additions and 0 deletions

View File

@ -59,6 +59,10 @@ const (
// the system, rather than one particular subsystem.
SysOverall = Subsystem("overall")
// SysConfig is the name of the subsystem that tracks health of the
// Tailscale state file and configuration.
SysConfig = Subsystem("config")
// SysRouter is the name of the wgengine/router subsystem.
SysRouter = Subsystem("router")
@ -203,6 +207,12 @@ func SetTKAHealth(err error) { setErr(SysTKA, err) }
// TKAHealth returns the tailnet key authority error state.
func TKAHealth() error { return get(SysTKA) }
// SetConfigHealth sets the health of the configuration.
func SetConfigHealth(err error) { setErr(SysConfig, err) }
// ConfigHealth returns the configuration error state.
func ConfigHealth() error { return get(SysConfig) }
// SetLocalLogConfigHealth sets the error state of this client's local log configuration.
func SetLocalLogConfigHealth(err error) {
mu.Lock()

View File

@ -3667,6 +3667,27 @@ func (b *LocalBackend) enterStateLockedOnEntry(newState ipn.State) {
addrs = append(addrs, addr.Addr().String())
}
systemd.Status("Connected; %s; %s", activeLogin, strings.Join(addrs, " "))
var configErrs []error
if prefs.Valid() && prefs.Persist().Valid() {
pprefs := prefs.Persist()
if pprefs.PublicNodeKey().IsZero() {
configErrs = append(configErrs, fmt.Errorf("public node key is zero"))
}
if pprefs.PrivateNodeKey().IsZero() {
configErrs = append(configErrs, fmt.Errorf("private node key is zero"))
}
}
b.mu.Lock()
if b.machinePrivKey.IsZero() {
configErrs = append(configErrs, fmt.Errorf("private machine key is zero"))
}
b.mu.Unlock()
if len(configErrs) > 0 {
health.SetConfigHealth(multierr.New(configErrs...))
}
case ipn.NoState:
// Do nothing.
default:
@ -4920,6 +4941,7 @@ func (b *LocalBackend) resetForProfileChangeLockedOnEntry() error {
b.serveConfig = ipn.ServeConfigView{}
b.enterStateLockedOnEntry(ipn.NoState) // Reset state.
health.SetLocalLogConfigHealth(nil)
health.SetConfigHealth(nil)
return b.Start(ipn.Options{})
}