mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-14 06:57:31 +00:00
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:
@@ -64,6 +64,7 @@ import (
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/util/multierr"
|
||||
"tailscale.com/util/osshare"
|
||||
"tailscale.com/util/syspolicy"
|
||||
"tailscale.com/version"
|
||||
"tailscale.com/version/distro"
|
||||
"tailscale.com/wgengine"
|
||||
@@ -126,6 +127,7 @@ var args struct {
|
||||
debug string
|
||||
port uint16
|
||||
statepath string
|
||||
encryptState bool
|
||||
statedir string
|
||||
socketpath string
|
||||
birdSocketPath string
|
||||
@@ -193,6 +195,7 @@ func main() {
|
||||
flag.StringVar(&args.tunname, "tun", defaultTunName(), `tunnel interface name; use "userspace-networking" (beta) to not use TUN`)
|
||||
flag.Var(flagtype.PortValue(&args.port, defaultPort()), "port", "UDP port to listen on for WireGuard and peer-to-peer traffic; 0 means automatically select")
|
||||
flag.StringVar(&args.statepath, "state", "", "absolute path of state file; use 'kube:<secret-name>' to use Kubernetes secrets or 'arn:aws:ssm:...' to store in AWS SSM; use 'mem:' to not store state and register as an ephemeral node. If empty and --statedir is provided, the default is <statedir>/tailscaled.state. Default: "+paths.DefaultTailscaledStateFile())
|
||||
flag.BoolVar(&args.encryptState, "encrypt-state", defaultEncryptState(), "encrypt the state file on disk; uses TPM on Linux and Windows, on all other platforms this flag is not supported")
|
||||
flag.StringVar(&args.statedir, "statedir", "", "path to directory for storage of config state, TLS certs, temporary incoming Taildrop files, etc. If empty, it's derived from --state when possible.")
|
||||
flag.StringVar(&args.socketpath, "socket", paths.DefaultTailscaledSocket(), "path of the service unix socket")
|
||||
flag.StringVar(&args.birdSocketPath, "bird-socket", "", "path of the bird unix socket")
|
||||
@@ -268,6 +271,28 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
if args.encryptState {
|
||||
if runtime.GOOS != "linux" && runtime.GOOS != "windows" {
|
||||
log.SetFlags(0)
|
||||
log.Fatalf("--encrypt-state is not supported on %s", runtime.GOOS)
|
||||
}
|
||||
// Check if we have TPM support in this build.
|
||||
if !store.HasKnownProviderPrefix(store.TPMPrefix + "/") {
|
||||
log.SetFlags(0)
|
||||
log.Fatal("--encrypt-state is not supported in this build of tailscaled")
|
||||
}
|
||||
// Check if we have TPM access.
|
||||
if !hostinfo.New().TPM.Present() {
|
||||
log.SetFlags(0)
|
||||
log.Fatal("--encrypt-state is not supported on this device or a TPM is not accessible")
|
||||
}
|
||||
// Check for conflicting prefix in --state, like arn: or kube:.
|
||||
if args.statepath != "" && store.HasKnownProviderPrefix(args.statepath) {
|
||||
log.SetFlags(0)
|
||||
log.Fatal("--encrypt-state can only be used with --state set to a local file path")
|
||||
}
|
||||
}
|
||||
|
||||
if args.disableLogs {
|
||||
envknob.SetNoLogsNoSupport()
|
||||
}
|
||||
@@ -315,13 +340,17 @@ func trySynologyMigration(p string) error {
|
||||
}
|
||||
|
||||
func statePathOrDefault() string {
|
||||
var path string
|
||||
if args.statepath != "" {
|
||||
return args.statepath
|
||||
path = args.statepath
|
||||
}
|
||||
if args.statedir != "" {
|
||||
return filepath.Join(args.statedir, "tailscaled.state")
|
||||
if path == "" && args.statedir != "" {
|
||||
path = filepath.Join(args.statedir, "tailscaled.state")
|
||||
}
|
||||
return ""
|
||||
if path != "" && !store.HasKnownProviderPrefix(path) && args.encryptState {
|
||||
path = store.TPMPrefix + path
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// serverOptions is the configuration of the Tailscale node agent.
|
||||
@@ -974,3 +1003,15 @@ func applyIntegrationTestEnvKnob() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func defaultEncryptState() bool {
|
||||
if runtime.GOOS != "windows" && runtime.GOOS != "linux" {
|
||||
// TPM encryption is only configurable on Windows and Linux. Other
|
||||
// platforms either use system APIs and are not configurable
|
||||
// (Android/Apple), or don't support any form of encryption yet
|
||||
// (plan9/FreeBSD/etc).
|
||||
return false
|
||||
}
|
||||
v, _ := syspolicy.GetBoolean(syspolicy.EncryptState, false)
|
||||
return v
|
||||
}
|
||||
|
@@ -10,4 +10,7 @@ export const sessionStateStorage: IPNStateStorage = {
|
||||
getState(id) {
|
||||
return window.sessionStorage[`ipn-state-${id}`] || ""
|
||||
},
|
||||
all() {
|
||||
return JSON.stringify(window.sessionStorage)
|
||||
},
|
||||
}
|
||||
|
1
cmd/tsconnect/src/types/wasm_js.d.ts
vendored
1
cmd/tsconnect/src/types/wasm_js.d.ts
vendored
@@ -44,6 +44,7 @@ declare global {
|
||||
interface IPNStateStorage {
|
||||
setState(id: string, value: string): void
|
||||
getState(id: string): string
|
||||
all(): string
|
||||
}
|
||||
|
||||
type IPNConfig = {
|
||||
|
@@ -15,6 +15,7 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"iter"
|
||||
"log"
|
||||
"math/rand/v2"
|
||||
"net"
|
||||
@@ -579,6 +580,29 @@ func (s *jsStateStore) WriteState(id ipn.StateKey, bs []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *jsStateStore) All() iter.Seq2[ipn.StateKey, []byte] {
|
||||
return func(yield func(ipn.StateKey, []byte) bool) {
|
||||
jsValue := s.jsStateStorage.Call("all")
|
||||
if jsValue.String() == "" {
|
||||
return
|
||||
}
|
||||
buf, err := hex.DecodeString(jsValue.String())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var state map[string][]byte
|
||||
if err := json.Unmarshal(buf, &state); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for k, v := range state {
|
||||
if !yield(ipn.StateKey(k), v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mapSlice[T any, M any](a []T, f func(T) M) []M {
|
||||
n := make([]M, len(a))
|
||||
for i, e := range a {
|
||||
|
Reference in New Issue
Block a user