mirror of
https://github.com/tailscale/tailscale.git
synced 2025-10-27 11:41:14 +00:00
cmd/tailscaled: default --encrypt-state to true if TPM is available (#17376)
Whenever running on a platform that has a TPM (and tailscaled can access it), default to encrypting the state. The user can still explicitly set this flag to disable encryption. Updates https://github.com/tailscale/corp/issues/32909 Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
This commit is contained in:
31
cmd/tailscaled/flag.go
Normal file
31
cmd/tailscaled/flag.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
// boolFlag is a flag.Value that tracks whether it was ever set.
|
||||||
|
type boolFlag struct {
|
||||||
|
set bool
|
||||||
|
v bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *boolFlag) String() string {
|
||||||
|
if b == nil || !b.set {
|
||||||
|
return "unset"
|
||||||
|
}
|
||||||
|
return strconv.FormatBool(b.v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *boolFlag) Set(s string) error {
|
||||||
|
v, err := strconv.ParseBool(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.v = v
|
||||||
|
b.set = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *boolFlag) IsBoolFlag() bool { return true }
|
||||||
@@ -120,7 +120,7 @@ var args struct {
|
|||||||
debug string
|
debug string
|
||||||
port uint16
|
port uint16
|
||||||
statepath string
|
statepath string
|
||||||
encryptState bool
|
encryptState boolFlag
|
||||||
statedir string
|
statedir string
|
||||||
socketpath string
|
socketpath string
|
||||||
birdSocketPath string
|
birdSocketPath string
|
||||||
@@ -197,7 +197,7 @@ func main() {
|
|||||||
flag.StringVar(&args.tunname, "tun", defaultTunName(), `tunnel interface name; use "userspace-networking" (beta) to not use TUN`)
|
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.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.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.Var(&args.encryptState, "encrypt-state", `encrypt the state file on disk; when not set encryption will be enabled if supported on this platform; 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.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.socketpath, "socket", paths.DefaultTailscaledSocket(), "path of the service unix socket")
|
||||||
flag.StringVar(&args.birdSocketPath, "bird-socket", "", "path of the bird unix socket")
|
flag.StringVar(&args.birdSocketPath, "bird-socket", "", "path of the bird unix socket")
|
||||||
@@ -275,7 +275,10 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.encryptState {
|
if !args.encryptState.set {
|
||||||
|
args.encryptState.v = defaultEncryptState()
|
||||||
|
}
|
||||||
|
if args.encryptState.v {
|
||||||
if runtime.GOOS != "linux" && runtime.GOOS != "windows" {
|
if runtime.GOOS != "linux" && runtime.GOOS != "windows" {
|
||||||
log.SetFlags(0)
|
log.SetFlags(0)
|
||||||
log.Fatalf("--encrypt-state is not supported on %s", runtime.GOOS)
|
log.Fatalf("--encrypt-state is not supported on %s", runtime.GOOS)
|
||||||
@@ -351,7 +354,7 @@ func statePathOrDefault() string {
|
|||||||
if path == "" && args.statedir != "" {
|
if path == "" && args.statedir != "" {
|
||||||
path = filepath.Join(args.statedir, "tailscaled.state")
|
path = filepath.Join(args.statedir, "tailscaled.state")
|
||||||
}
|
}
|
||||||
if path != "" && !store.HasKnownProviderPrefix(path) && args.encryptState {
|
if path != "" && !store.HasKnownProviderPrefix(path) && args.encryptState.v {
|
||||||
path = store.TPMPrefix + path
|
path = store.TPMPrefix + path
|
||||||
}
|
}
|
||||||
return path
|
return path
|
||||||
@@ -909,6 +912,6 @@ func defaultEncryptState() bool {
|
|||||||
// (plan9/FreeBSD/etc).
|
// (plan9/FreeBSD/etc).
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
v, _ := policyclient.Get().GetBoolean(pkey.EncryptState, false)
|
v, _ := policyclient.Get().GetBoolean(pkey.EncryptState, feature.TPMAvailable())
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,3 +40,15 @@ var HookProxySetSelfProxy Hook[func(...string)]
|
|||||||
// HookProxySetTransportGetProxyConnectHeader is a hook for feature/useproxy to register
|
// HookProxySetTransportGetProxyConnectHeader is a hook for feature/useproxy to register
|
||||||
// [tshttpproxy.SetTransportGetProxyConnectHeader].
|
// [tshttpproxy.SetTransportGetProxyConnectHeader].
|
||||||
var HookProxySetTransportGetProxyConnectHeader Hook[func(*http.Transport)]
|
var HookProxySetTransportGetProxyConnectHeader Hook[func(*http.Transport)]
|
||||||
|
|
||||||
|
// HookTPMAvailable is a hook that reports whether a TPM device is supported
|
||||||
|
// and available.
|
||||||
|
var HookTPMAvailable Hook[func() bool]
|
||||||
|
|
||||||
|
// TPMAvailable reports whether a TPM device is supported and available.
|
||||||
|
func TPMAvailable() bool {
|
||||||
|
if f, ok := HookTPMAvailable.GetOk(); ok {
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ var infoOnce = sync.OnceValue(info)
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
feature.Register("tpm")
|
feature.Register("tpm")
|
||||||
|
feature.HookTPMAvailable.Set(tpmSupported)
|
||||||
hostinfo.RegisterHostinfoNewHook(func(hi *tailcfg.Hostinfo) {
|
hostinfo.RegisterHostinfoNewHook(func(hi *tailcfg.Hostinfo) {
|
||||||
hi.TPM = infoOnce()
|
hi.TPM = infoOnce()
|
||||||
})
|
})
|
||||||
@@ -51,6 +52,15 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tpmSupported() bool {
|
||||||
|
tpm, err := open()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
tpm.Close()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
var verboseTPM = envknob.RegisterBool("TS_DEBUG_TPM")
|
var verboseTPM = envknob.RegisterBool("TS_DEBUG_TPM")
|
||||||
|
|
||||||
func info() *tailcfg.TPMInfo {
|
func info() *tailcfg.TPMInfo {
|
||||||
|
|||||||
@@ -277,15 +277,6 @@ func TestMigrateStateToTPM(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func tpmSupported() bool {
|
|
||||||
tpm, err := open()
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
tpm.Close()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type mockTPMSealProvider struct {
|
type mockTPMSealProvider struct {
|
||||||
path string
|
path string
|
||||||
data map[ipn.StateKey][]byte
|
data map[ipn.StateKey][]byte
|
||||||
|
|||||||
@@ -7559,11 +7559,7 @@ func (b *LocalBackend) stateEncrypted() opt.Bool {
|
|||||||
case version.IsMacAppStore():
|
case version.IsMacAppStore():
|
||||||
return opt.NewBool(true)
|
return opt.NewBool(true)
|
||||||
case version.IsMacSysExt():
|
case version.IsMacSysExt():
|
||||||
// MacSys still stores its state in plaintext on disk in addition to
|
sp, _ := b.polc.GetBoolean(pkey.EncryptState, true)
|
||||||
// the Keychain. A future release will clean up the on-disk state
|
|
||||||
// files.
|
|
||||||
// TODO(#15830): always return true here once MacSys is fully migrated.
|
|
||||||
sp, _ := b.polc.GetBoolean(pkey.EncryptState, false)
|
|
||||||
return opt.NewBool(sp)
|
return opt.NewBool(sp)
|
||||||
default:
|
default:
|
||||||
// Probably self-compiled tailscaled, we don't use the Keychain
|
// Probably self-compiled tailscaled, we don't use the Keychain
|
||||||
|
|||||||
@@ -136,7 +136,9 @@ const (
|
|||||||
FlushDNSOnSessionUnlock Key = "FlushDNSOnSessionUnlock"
|
FlushDNSOnSessionUnlock Key = "FlushDNSOnSessionUnlock"
|
||||||
|
|
||||||
// EncryptState is a boolean setting that specifies whether to encrypt the
|
// EncryptState is a boolean setting that specifies whether to encrypt the
|
||||||
// tailscaled state file with a TPM device.
|
// tailscaled state file.
|
||||||
|
// Windows and Linux use a TPM device, Apple uses the Keychain.
|
||||||
|
// It's a noop on other platforms.
|
||||||
EncryptState Key = "EncryptState"
|
EncryptState Key = "EncryptState"
|
||||||
|
|
||||||
// PostureChecking indicates if posture checking is enabled and the client shall gather
|
// PostureChecking indicates if posture checking is enabled and the client shall gather
|
||||||
|
|||||||
Reference in New Issue
Block a user