summaryrefslogtreecommitdiffhomepage
path: root/control/controlclient
diff options
context:
space:
mode:
authorRaj Singh <raj@tailscale.com>2026-03-31 12:50:16 -0500
committerRaj Singh <raj@tailscale.com>2026-03-31 12:51:43 -0500
commitd88ee20a98ca42675604225bb42d40cb9cb3551a (patch)
treef6737d8c0db8e10daa5ea690aa36f7b1fd2a1c3c /control/controlclient
parent70afbac0534e64bac192ce677d4359328ee9890f (diff)
downloadtailscale-rajsingh/persist-nodekey-before-register.tar.xz
tailscale-rajsingh/persist-nodekey-before-register.zip
controlclient: persist node key after login, don't wait for netmaprajsingh/persist-nodekey-before-register
sendStatus only included Persist in the Status when a netmap was available (nm != nil && loggedIn && inMapPoll). This meant the node key was not written to the state store until the first map response arrived. If the process was killed after a successful registration but before the first netmap, the key was lost and a fresh one generated on restart. Decouple persist from the netmap gate so it flows to the state store as soon as login succeeds. Fixes #19149 Signed-off-by: Raj Singh <raj@tailscale.com>
Diffstat (limited to 'control/controlclient')
-rw-r--r--control/controlclient/auto.go5
-rw-r--r--control/controlclient/direct.go16
2 files changed, 3 insertions, 18 deletions
diff --git a/control/controlclient/auto.go b/control/controlclient/auto.go
index b087e1444..d7da21932 100644
--- a/control/controlclient/auto.go
+++ b/control/controlclient/auto.go
@@ -625,9 +625,10 @@ func (c *Auto) sendStatus(who string, err error, url string, nm *netmap.NetworkM
c.logf("[v1] sendStatus: %s: loggedIn=%v inMapPoll=%v", who, loggedIn, inMapPoll)
var p persist.PersistView
- if nm != nil && loggedIn && inMapPoll {
+ if loggedIn {
p = c.direct.GetPersist()
- } else {
+ }
+ if !(nm != nil && loggedIn && inMapPoll) {
// don't send netmap status, as it's misleading when we're
// not logged in.
nm = nil
diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go
index cbbf2bdfe..3b4e6ba9b 100644
--- a/control/controlclient/direct.go
+++ b/control/controlclient/direct.go
@@ -79,7 +79,6 @@ type Direct struct {
autoUpdatePub *eventbus.Publisher[AutoUpdate]
controlTimePub *eventbus.Publisher[ControlTime]
getMachinePrivKey func() (key.MachinePrivate, error)
- persistState func(persist.PersistView) // or nil; called before RegisterRequest to persist new node key
debugFlags []string
pinger Pinger
popBrowser func(url string) // or nil
@@ -177,12 +176,6 @@ type Options struct {
// attempted. It is used to allow the client to clean up any resources or complete any
// tasks that are dependent on a live client.
Shutdown func()
-
- // PersistState, if non-nil, is called with an updated Persist after
- // generating a new node key but before sending the RegisterRequest.
- // This allows the caller to write the key to durable storage so that
- // a crash during registration doesn't lose the key.
- PersistState func(persist.PersistView)
}
// ControlDialPlanner is the interface optionally supplied when creating a
@@ -338,7 +331,6 @@ func NewDirect(opts Options) (*Direct, error) {
dialer: opts.Dialer,
dnsCache: dnsCache,
dialPlan: opts.DialPlan,
- persistState: opts.PersistState,
}
c.discoPubKey = opts.DiscoPublicKey
c.closedCtx, c.closeCtx = context.WithCancel(context.Background())
@@ -665,14 +657,6 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
persist.NetworkLockKey = key.NewNLPrivate()
}
- // Persist the new key before the RegisterRequest so a crash
- // mid-HTTP doesn't lose it.
- if c.persistState != nil && !tryingNewKey.IsZero() && (persist.PrivateNodeKey.IsZero() || tryingNewKey.Public() != persist.PrivateNodeKey.Public()) {
- preHTTPPersist := persist
- preHTTPPersist.PrivateNodeKey = tryingNewKey
- c.persistState(preHTTPPersist.View())
- }
-
nlPub := persist.NetworkLockKey.Public()
if tryingNewKey.IsZero() {