ipn/store: automatically migrate between plaintext and encrypted state (#16318)

Add a new `--encrypt-state` flag to `cmd/tailscaled`. Based on that
flag, migrate the existing state file to/from encrypted format if
needed.

Updates #15830

Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
This commit is contained in:
Andrew Lytvynov
2025-06-26 17:09:13 -07:00
committed by GitHub
parent d2c1ed22c3
commit 6feb3c35cb
24 changed files with 546 additions and 26 deletions

View File

@@ -569,11 +569,12 @@ type TestNode struct {
env *TestEnv
tailscaledParser *nodeOutputParser
dir string // temp dir for sock & state
configFile string // or empty for none
sockFile string
stateFile string
upFlagGOOS string // if non-empty, sets TS_DEBUG_UP_FLAG_GOOS for cmd/tailscale CLI
dir string // temp dir for sock & state
configFile string // or empty for none
sockFile string
stateFile string
upFlagGOOS string // if non-empty, sets TS_DEBUG_UP_FLAG_GOOS for cmd/tailscale CLI
encryptState bool
mu sync.Mutex
onLogLine []func([]byte)
@@ -640,7 +641,7 @@ func (n *TestNode) diskPrefs() *ipn.Prefs {
if _, err := os.ReadFile(n.stateFile); err != nil {
t.Fatalf("reading prefs: %v", err)
}
fs, err := store.NewFileStore(nil, n.stateFile)
fs, err := store.New(nil, n.stateFile)
if err != nil {
t.Fatalf("reading prefs, NewFileStore: %v", err)
}
@@ -822,6 +823,9 @@ func (n *TestNode) StartDaemonAsIPNGOOS(ipnGOOS string) *Daemon {
if n.configFile != "" {
cmd.Args = append(cmd.Args, "--config="+n.configFile)
}
if n.encryptState {
cmd.Args = append(cmd.Args, "--encrypt-state")
}
cmd.Env = append(os.Environ(),
"TS_DEBUG_PERMIT_HTTP_C2N=1",
"TS_LOG_TARGET="+n.env.LogCatcherServer.URL,

View File

@@ -21,6 +21,7 @@ import (
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strconv"
"sync/atomic"
"testing"
@@ -32,6 +33,7 @@ import (
"tailscale.com/client/tailscale"
"tailscale.com/clientupdate"
"tailscale.com/cmd/testwrapper/flakytest"
"tailscale.com/hostinfo"
"tailscale.com/ipn"
"tailscale.com/net/tsaddr"
"tailscale.com/net/tstun"
@@ -1470,3 +1472,60 @@ func TestNetstackUDPLoopback(t *testing.T) {
d1.MustCleanShutdown(t)
}
func TestEncryptStateMigration(t *testing.T) {
if !hostinfo.New().TPM.Present() {
t.Skip("TPM not available")
}
if runtime.GOOS != "linux" && runtime.GOOS != "windows" {
t.Skip("--encrypt-state for tailscaled state not supported on this platform")
}
tstest.Shard(t)
tstest.Parallel(t)
env := NewTestEnv(t)
n := NewTestNode(t, env)
runNode := func(t *testing.T, wantStateKeys []string) {
t.Helper()
// Run the node.
d := n.StartDaemon()
n.AwaitResponding()
n.MustUp()
n.AwaitRunning()
// Check the contents of the state file.
buf, err := os.ReadFile(n.stateFile)
if err != nil {
t.Fatalf("reading %q: %v", n.stateFile, err)
}
t.Logf("state file content:\n%s", buf)
var content map[string]any
if err := json.Unmarshal(buf, &content); err != nil {
t.Fatalf("parsing %q: %v", n.stateFile, err)
}
for _, k := range wantStateKeys {
if _, ok := content[k]; !ok {
t.Errorf("state file is missing key %q", k)
}
}
// Stop the node.
d.MustCleanShutdown(t)
}
wantPlaintextStateKeys := []string{"_machinekey", "_current-profile", "_profiles"}
wantEncryptedStateKeys := []string{"key", "nonce", "data"}
t.Run("regular-state", func(t *testing.T) {
n.encryptState = false
runNode(t, wantPlaintextStateKeys)
})
t.Run("migrate-to-encrypted", func(t *testing.T) {
n.encryptState = true
runNode(t, wantEncryptedStateKeys)
})
t.Run("migrate-to-plaintext", func(t *testing.T) {
n.encryptState = false
runNode(t, wantPlaintextStateKeys)
})
}

View File

@@ -51,6 +51,7 @@ import (
_ "tailscale.com/util/eventbus"
_ "tailscale.com/util/multierr"
_ "tailscale.com/util/osshare"
_ "tailscale.com/util/syspolicy"
_ "tailscale.com/version"
_ "tailscale.com/version/distro"
_ "tailscale.com/wgengine"

View File

@@ -51,6 +51,7 @@ import (
_ "tailscale.com/util/eventbus"
_ "tailscale.com/util/multierr"
_ "tailscale.com/util/osshare"
_ "tailscale.com/util/syspolicy"
_ "tailscale.com/version"
_ "tailscale.com/version/distro"
_ "tailscale.com/wgengine"

View File

@@ -51,6 +51,7 @@ import (
_ "tailscale.com/util/eventbus"
_ "tailscale.com/util/multierr"
_ "tailscale.com/util/osshare"
_ "tailscale.com/util/syspolicy"
_ "tailscale.com/version"
_ "tailscale.com/version/distro"
_ "tailscale.com/wgengine"

View File

@@ -51,6 +51,7 @@ import (
_ "tailscale.com/util/eventbus"
_ "tailscale.com/util/multierr"
_ "tailscale.com/util/osshare"
_ "tailscale.com/util/syspolicy"
_ "tailscale.com/version"
_ "tailscale.com/version/distro"
_ "tailscale.com/wgengine"