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 {
if runtime.GOOS == "darwin" && version.IsSandboxedMacOS() && !envknob.UseWIPCode() {
return errors.New("The 'tailscale ssh' subcommand is not available on sandboxed macOS builds.\nUse the regular 'ssh' client instead.")
if runtime.GOOS == "darwin" && version.IsMacAppStore() && !envknob.UseWIPCode() {
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 {
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 != "" {
// 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 {
return port, token, nil
}

View File

@ -48,10 +48,38 @@ func IsSandboxedMacOS() bool {
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]
// IsMacSysExt whether this binary is from the standalone "System
// Extension" (a.k.a. "macsys") version of Tailscale for macOS.
// IsMacSysExt reports whether this binary is the system extension shipped as part of
// the standalone "System Extension" (a.k.a. "macsys") version of Tailscale
// for macOS.
func IsMacSysExt() bool {
if runtime.GOOS != "darwin" {
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]
// 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
// suffix /Contents/MacOS/Tailscale. Check $HOME to filter out running
// 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/") {
return false
}