cmd/tailscale: define CLI tools to manipulate macOS network and system extensions (#14727)

Updates tailscale/corp#25278

Adds definitions for new CLI commands getting added in v1.80. Refactors some pre-existing CLI commands within the `configure` tree to clean up code.

Signed-off-by: Andrea Gottardo <andrea@gottardo.me>
main
Andrea Gottardo 1 year ago committed by GitHub
parent 0fa7b4a236
commit 3dabea0fc2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 7
      cmd/tailscale/cli/cli.go
  2. 10
      cmd/tailscale/cli/configure-kube.go
  3. 13
      cmd/tailscale/cli/configure-kube_omit.go
  4. 7
      cmd/tailscale/cli/configure-synology-cert.go
  5. 19
      cmd/tailscale/cli/configure-synology.go
  6. 29
      cmd/tailscale/cli/configure.go
  7. 11
      cmd/tailscale/cli/configure_apple-all.go
  8. 97
      cmd/tailscale/cli/configure_apple.go

@ -190,7 +190,7 @@ change in the future.
loginCmd, loginCmd,
logoutCmd, logoutCmd,
switchCmd, switchCmd,
configureCmd, configureCmd(),
syspolicyCmd, syspolicyCmd,
netcheckCmd, netcheckCmd,
ipCmd, ipCmd,
@ -216,6 +216,7 @@ change in the future.
driveCmd, driveCmd,
idTokenCmd, idTokenCmd,
advertiseCmd(), advertiseCmd(),
configureHostCmd(),
), ),
FlagSet: rootfs, FlagSet: rootfs,
Exec: func(ctx context.Context, args []string) error { Exec: func(ctx context.Context, args []string) error {
@ -226,10 +227,6 @@ change in the future.
}, },
} }
if runtime.GOOS == "linux" && distro.Get() == distro.Synology {
rootCmd.Subcommands = append(rootCmd.Subcommands, configureHostCmd)
}
walkCommands(rootCmd, func(w cmdWalk) bool { walkCommands(rootCmd, func(w cmdWalk) bool {
if w.UsageFunc == nil { if w.UsageFunc == nil {
w.UsageFunc = usageFunc w.UsageFunc = usageFunc

@ -20,11 +20,8 @@ import (
"tailscale.com/version" "tailscale.com/version"
) )
func init() { func configureKubeconfigCmd() *ffcli.Command {
configureCmd.Subcommands = append(configureCmd.Subcommands, configureKubeconfigCmd) return &ffcli.Command{
}
var configureKubeconfigCmd = &ffcli.Command{
Name: "kubeconfig", Name: "kubeconfig",
ShortHelp: "[ALPHA] Connect to a Kubernetes cluster using a Tailscale Auth Proxy", ShortHelp: "[ALPHA] Connect to a Kubernetes cluster using a Tailscale Auth Proxy",
ShortUsage: "tailscale configure kubeconfig <hostname-or-fqdn>", ShortUsage: "tailscale configure kubeconfig <hostname-or-fqdn>",
@ -41,12 +38,13 @@ See: https://tailscale.com/s/k8s-auth-proxy
})(), })(),
Exec: runConfigureKubeconfig, Exec: runConfigureKubeconfig,
} }
}
// kubeconfigPath returns the path to the kubeconfig file for the current user. // kubeconfigPath returns the path to the kubeconfig file for the current user.
func kubeconfigPath() (string, error) { func kubeconfigPath() (string, error) {
if kubeconfig := os.Getenv("KUBECONFIG"); kubeconfig != "" { if kubeconfig := os.Getenv("KUBECONFIG"); kubeconfig != "" {
if version.IsSandboxedMacOS() { if version.IsSandboxedMacOS() {
return "", errors.New("$KUBECONFIG is incompatible with the App Store version") return "", errors.New("cannot read $KUBECONFIG on GUI builds of the macOS client: this requires the open-source tailscaled distribution")
} }
var out string var out string
for _, out = range filepath.SplitList(kubeconfig) { for _, out = range filepath.SplitList(kubeconfig) {

@ -0,0 +1,13 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build ts_omit_kube
package cli
import "github.com/peterbourgon/ff/v3/ffcli"
func configureKubeconfigCmd() *ffcli.Command {
// omitted from the build when the ts_omit_kube build tag is set
return nil
}

@ -22,7 +22,11 @@ import (
"tailscale.com/version/distro" "tailscale.com/version/distro"
) )
var synologyConfigureCertCmd = &ffcli.Command{ func synologyConfigureCertCmd() *ffcli.Command {
if runtime.GOOS != "linux" || distro.Get() != distro.Synology {
return nil
}
return &ffcli.Command{
Name: "synology-cert", Name: "synology-cert",
Exec: runConfigureSynologyCert, Exec: runConfigureSynologyCert,
ShortHelp: "Configure Synology with a TLS certificate for your tailnet", ShortHelp: "Configure Synology with a TLS certificate for your tailnet",
@ -39,6 +43,7 @@ See: https://tailscale.com/kb/1153/enabling-https
return fs return fs
})(), })(),
} }
}
var synologyConfigureCertArgs struct { var synologyConfigureCertArgs struct {
domain string domain string

@ -21,7 +21,16 @@ import (
// configureHostCmd is the "tailscale configure-host" command which was once // configureHostCmd is the "tailscale configure-host" command which was once
// used to configure Synology devices, but is now a compatibility alias to // used to configure Synology devices, but is now a compatibility alias to
// "tailscale configure synology". // "tailscale configure synology".
var configureHostCmd = &ffcli.Command{ //
// It returns nil if the actual "tailscale configure synology" command is not
// available.
func configureHostCmd() *ffcli.Command {
synologyConfigureCmd := synologyConfigureCmd()
if synologyConfigureCmd == nil {
// No need to offer this compatibility alias if the actual command is not available.
return nil
}
return &ffcli.Command{
Name: "configure-host", Name: "configure-host",
Exec: runConfigureSynology, Exec: runConfigureSynology,
ShortUsage: "tailscale configure-host\n" + synologyConfigureCmd.ShortUsage, ShortUsage: "tailscale configure-host\n" + synologyConfigureCmd.ShortUsage,
@ -32,8 +41,13 @@ var configureHostCmd = &ffcli.Command{
return fs return fs
})(), })(),
} }
}
var synologyConfigureCmd = &ffcli.Command{ func synologyConfigureCmd() *ffcli.Command {
if runtime.GOOS != "linux" || distro.Get() != distro.Synology {
return nil
}
return &ffcli.Command{
Name: "synology", Name: "synology",
Exec: runConfigureSynology, Exec: runConfigureSynology,
ShortUsage: "tailscale configure synology", ShortUsage: "tailscale configure synology",
@ -50,6 +64,7 @@ See: https://tailscale.com/s/synology-outbound
return fs return fs
})(), })(),
} }
}
func runConfigureSynology(ctx context.Context, args []string) error { func runConfigureSynology(ctx context.Context, args []string) error {
if len(args) > 0 { if len(args) > 0 {

@ -5,17 +5,16 @@ package cli
import ( import (
"flag" "flag"
"runtime"
"strings" "strings"
"github.com/peterbourgon/ff/v3/ffcli" "github.com/peterbourgon/ff/v3/ffcli"
"tailscale.com/version/distro"
) )
var configureCmd = &ffcli.Command{ func configureCmd() *ffcli.Command {
return &ffcli.Command{
Name: "configure", Name: "configure",
ShortUsage: "tailscale configure <subcommand>", ShortUsage: "tailscale configure <subcommand>",
ShortHelp: "[ALPHA] Configure the host to enable more Tailscale features", ShortHelp: "Configure the host to enable more Tailscale features",
LongHelp: strings.TrimSpace(` LongHelp: strings.TrimSpace(`
The 'configure' set of commands are intended to provide a way to enable different The 'configure' set of commands are intended to provide a way to enable different
services on the host to use Tailscale in more ways. services on the host to use Tailscale in more ways.
@ -24,13 +23,23 @@ services on the host to use Tailscale in more ways.
fs := newFlagSet("configure") fs := newFlagSet("configure")
return fs return fs
})(), })(),
Subcommands: configureSubcommands(), Subcommands: nonNilCmds(
configureKubeconfigCmd(),
synologyConfigureCmd(),
synologyConfigureCertCmd(),
ccall(maybeSysExtCmd),
ccall(maybeVPNConfigCmd),
),
}
} }
func configureSubcommands() (out []*ffcli.Command) { // ccall calls the function f if it is non-nil, and returns its result.
if runtime.GOOS == "linux" && distro.Get() == distro.Synology { //
out = append(out, synologyConfigureCmd) // It returns the zero value of the type T if f is nil.
out = append(out, synologyConfigureCertCmd) func ccall[T any](f func() T) T {
var zero T
if f == nil {
return zero
} }
return out return f()
} }

@ -0,0 +1,11 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package cli
import "github.com/peterbourgon/ff/v3/ffcli"
var (
maybeSysExtCmd func() *ffcli.Command // non-nil only on macOS, see configure_apple.go
maybeVPNConfigCmd func() *ffcli.Command // non-nil only on macOS, see configure_apple.go
)

@ -0,0 +1,97 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build darwin
package cli
import (
"context"
"errors"
"github.com/peterbourgon/ff/v3/ffcli"
)
func init() {
maybeSysExtCmd = sysExtCmd
maybeVPNConfigCmd = vpnConfigCmd
}
// Functions in this file provide a dummy Exec function that only prints an error message for users of the open-source
// tailscaled distribution. On GUI builds, the Swift code in the macOS client handles these commands by not passing the
// flow of execution to the CLI.
// sysExtCmd returns a command for managing the Tailscale system extension on macOS
// (for the Standalone variant of the client only).
func sysExtCmd() *ffcli.Command {
return &ffcli.Command{
Name: "sysext",
ShortUsage: "tailscale configure sysext [activate|deactivate|status]",
ShortHelp: "Manages the system extension for macOS (Standalone variant)",
LongHelp: "The sysext set of commands provides a way to activate, deactivate, or manage the state of the Tailscale system extension on macOS. " +
"This is only relevant if you are running the Standalone variant of the Tailscale client for macOS. " +
"To access more detailed information about system extensions installed on this Mac, run 'systemextensionsctl list'.",
Subcommands: []*ffcli.Command{
{
Name: "activate",
ShortUsage: "tailscale sysext activate",
ShortHelp: "Register the Tailscale system extension with macOS.",
LongHelp: "This command registers the Tailscale system extension with macOS. To run Tailscale, you'll also need to install the VPN configuration separately (run `tailscale configure vpn-config install`). After running this command, you need to approve the extension in System Settings > Login Items and Extensions > Network Extensions.",
Exec: requiresStandalone,
},
{
Name: "deactivate",
ShortUsage: "tailscale sysext deactivate",
ShortHelp: "Deactivate the Tailscale system extension on macOS",
LongHelp: "This command deactivates the Tailscale system extension on macOS. To completely remove Tailscale, you'll also need to delete the VPN configuration separately (use `tailscale configure vpn-config uninstall`).",
Exec: requiresStandalone,
},
{
Name: "status",
ShortUsage: "tailscale sysext status",
ShortHelp: "Print the enablement status of the Tailscale system extension",
LongHelp: "This command prints the enablement status of the Tailscale system extension. If the extension is not enabled, run `tailscale sysext activate` to enable it.",
Exec: requiresStandalone,
},
},
Exec: requiresStandalone,
}
}
// vpnConfigCmd returns a command for managing the Tailscale VPN configuration on macOS
// (the entry that appears in System Settings > VPN).
func vpnConfigCmd() *ffcli.Command {
return &ffcli.Command{
Name: "mac-vpn",
ShortUsage: "tailscale configure mac-vpn [install|uninstall]",
ShortHelp: "Manage the VPN configuration on macOS (App Store and Standalone variants)",
LongHelp: "The vpn-config set of commands provides a way to add or remove the Tailscale VPN configuration from the macOS settings. This is the entry that appears in System Settings > VPN.",
Subcommands: []*ffcli.Command{
{
Name: "install",
ShortUsage: "tailscale mac-vpn install",
ShortHelp: "Write the Tailscale VPN configuration to the macOS settings",
LongHelp: "This command writes the Tailscale VPN configuration to the macOS settings. This is the entry that appears in System Settings > VPN. If you are running the Standalone variant of the client, you'll also need to install the system extension separately (run `tailscale configure sysext activate`).",
Exec: requiresGUI,
},
{
Name: "uninstall",
ShortUsage: "tailscale mac-vpn uninstall",
ShortHelp: "Delete the Tailscale VPN configuration from the macOS settings",
LongHelp: "This command removes the Tailscale VPN configuration from the macOS settings. This is the entry that appears in System Settings > VPN. If you are running the Standalone variant of the client, you'll also need to deactivate the system extension separately (run `tailscale configure sysext deactivate`).",
Exec: requiresGUI,
},
},
Exec: func(ctx context.Context, args []string) error {
return errors.New("unsupported command: requires a GUI build of the macOS client")
},
}
}
func requiresStandalone(ctx context.Context, args []string) error {
return errors.New("unsupported command: requires the Standalone (.pkg installer) GUI build of the client")
}
func requiresGUI(ctx context.Context, args []string) error {
return errors.New("unsupported command: requires a GUI build of the macOS client")
}
Loading…
Cancel
Save