version,cli,safesocket: detect non-sandboxed macOS GUI (#11369)

Updates ENG-2848

We can safely disable the App Sandbox for our macsys GUI, allowing us to use `tailscale ssh` and do a few other things that we've wanted to do for a while. This PR:

- allows Tailscale SSH to be used from the macsys GUI binary when called from a CLI
- tweaks the detection of client variants in prop.go, with new functions `IsMacSys()`, `IsMacSysApp()` and `IsMacAppSandboxEnabled()`

Signed-off-by: Andrea Gottardo <andrea@gottardo.me>
This commit is contained in:
Andrea Gottardo 2024-03-14 14:28:06 -07:00 committed by GitHub
parent ea55f96310
commit 08ebac9acb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 51 additions and 5 deletions

View File

@ -48,8 +48,8 @@
} }
func runSSH(ctx context.Context, args []string) error { func runSSH(ctx context.Context, args []string) error {
if runtime.GOOS == "darwin" && version.IsSandboxedMacOS() && !envknob.UseWIPCode() { if runtime.GOOS == "darwin" && version.IsMacAppStore() && !envknob.UseWIPCode() {
return errors.New("The 'tailscale ssh' subcommand is not available on sandboxed macOS builds.\nUse the regular 'ssh' client instead.") return errors.New("The 'tailscale ssh' subcommand is not available on macOS builds distributed through the App Store or TestFlight.\nInstall the Standalone variant of Tailscale (download it from https://pkgs.tailscale.com), or use the regular 'ssh' client instead.")
} }
if len(args) == 0 { if len(args) == 0 {
return errors.New("usage: ssh [user@]<host>") return errors.New("usage: ssh [user@]<host>")

View File

@ -74,7 +74,7 @@ func localTCPPortAndTokenDarwin() (port int, token string, err error) {
if dir := os.Getenv("TS_MACOS_CLI_SHARED_DIR"); dir != "" { if dir := os.Getenv("TS_MACOS_CLI_SHARED_DIR"); dir != "" {
// First see if we're running as the non-AppStore "macsys" variant. // First see if we're running as the non-AppStore "macsys" variant.
if version.IsMacSysExt() { if version.IsMacSys() {
if port, token, err := localTCPPortAndTokenMacsys(); err == nil { if port, token, err := localTCPPortAndTokenMacsys(); err == nil {
return port, token, nil return port, token, nil
} }

View File

@ -48,10 +48,38 @@ func IsSandboxedMacOS() bool {
return IsMacAppStore() || IsMacSysExt() return IsMacAppStore() || IsMacSysExt()
} }
// IsMacSys reports whether this process is part of the Standalone variant of
// Tailscale for macOS, either the main GUI process (non-sandboxed) or the
// system extension (sandboxed).
func IsMacSys() bool {
return IsMacSysExt() || IsMacSysApp()
}
var isMacSysApp lazy.SyncValue[bool]
// IsMacSysApp reports whether this process is the main, non-sandboxed GUI process
// that ships with the Standalone variant of Tailscale for macOS.
func IsMacSysApp() bool {
if runtime.GOOS != "darwin" {
return false
}
return isMacSysApp.Get(func() bool {
exe, err := os.Executable()
if err != nil {
return false
}
// Check that this is the GUI binary, and it is not sandboxed. The GUI binary
// shipped in the App Store will always have the App Sandbox enabled.
return strings.HasSuffix(exe, "/Contents/MacOS/Tailscale") && !IsMacAppSandboxEnabled()
})
}
var isMacSysExt lazy.SyncValue[bool] var isMacSysExt lazy.SyncValue[bool]
// IsMacSysExt whether this binary is from the standalone "System // IsMacSysExt reports whether this binary is the system extension shipped as part of
// Extension" (a.k.a. "macsys") version of Tailscale for macOS. // the standalone "System Extension" (a.k.a. "macsys") version of Tailscale
// for macOS.
func IsMacSysExt() bool { func IsMacSysExt() bool {
if runtime.GOOS != "darwin" { if runtime.GOOS != "darwin" {
return false return false
@ -68,6 +96,19 @@ func IsMacSysExt() bool {
}) })
} }
var isMacAppSandboxEnabled lazy.SyncValue[bool]
// IsMacAppSandboxEnabled reports whether this process is subject to the App Sandbox
// on macOS.
func IsMacAppSandboxEnabled() bool {
if runtime.GOOS != "darwin" {
return false
}
return isMacAppSandboxEnabled.Get(func() bool {
return os.Getenv("APP_SANDBOX_CONTAINER_ID") != ""
})
}
var isMacAppStore lazy.SyncValue[bool] var isMacAppStore lazy.SyncValue[bool]
// IsMacAppStore whether this binary is from the App Store version of Tailscale // IsMacAppStore whether this binary is from the App Store version of Tailscale
@ -80,6 +121,11 @@ func IsMacAppStore() bool {
// Both macsys and app store versions can run CLI executable with // Both macsys and app store versions can run CLI executable with
// suffix /Contents/MacOS/Tailscale. Check $HOME to filter out running // suffix /Contents/MacOS/Tailscale. Check $HOME to filter out running
// as macsys. // as macsys.
if !IsMacAppSandboxEnabled() {
// If no sandbox found, we're definitely not on an App Store release, as you cannot push
// anything to the App Store that has the App Sandbox disabled.
return false
}
if strings.Contains(os.Getenv("HOME"), "/Containers/io.tailscale.ipn.macsys/") { if strings.Contains(os.Getenv("HOME"), "/Containers/io.tailscale.ipn.macsys/") {
return false return false
} }