From c086992f4fd6bb1799a0f2c8a2ffaf2a1c0114e9 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 28 May 2026 17:26:43 +0000 Subject: [PATCH] cmd/tailscale/cli: add whoami subcommand Add a "tailscale whoami" subcommand that is equivalent to running "tailscale whois $(tailscale ip -4)" but more ergonomic. It supports the --json flag just like whois, and shares the WhoIsResponse rendering code with whois. Fixes #19907 Signed-off-by: Brad Fitzpatrick Change-Id: I8f33ba7a5608bab7dffa8213303beb5f345936d3 --- cmd/tailscale/cli/cli.go | 1 + cmd/tailscale/cli/whoami.go | 52 +++++++++++++++++++++++++++++++++++++ cmd/tailscale/cli/whois.go | 9 ++++++- 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 cmd/tailscale/cli/whoami.go diff --git a/cmd/tailscale/cli/cli.go b/cmd/tailscale/cli/cli.go index 32e570312..9e4b267ad 100644 --- a/cmd/tailscale/cli/cli.go +++ b/cmd/tailscale/cli/cli.go @@ -299,6 +299,7 @@ func newRootCmd(tb ...testenv.TB) *ffcli.Command { exitNodeCmd(), nilOrCall(maybeUpdateCmd), whoisCmd, + whoamiCmd, debugCmd(), nilOrCall(maybeDriveCmd), idTokenCmd, diff --git a/cmd/tailscale/cli/whoami.go b/cmd/tailscale/cli/whoami.go new file mode 100644 index 000000000..495cb0c3d --- /dev/null +++ b/cmd/tailscale/cli/whoami.go @@ -0,0 +1,52 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +package cli + +import ( + "context" + "errors" + "flag" + "fmt" + "strings" + + "github.com/peterbourgon/ff/v3/ffcli" +) + +var whoamiCmd = &ffcli.Command{ + Name: "whoami", + ShortUsage: "tailscale whoami [--json]", + ShortHelp: "Show the machine and user identity of the current machine", + LongHelp: strings.TrimSpace(` + 'tailscale whoami' shows the machine and user identity of the current machine. + It is equivalent to running 'tailscale whois' against one of the current machine's own Tailscale IP addresses. + `), + Exec: runWhoami, + FlagSet: func() *flag.FlagSet { + fs := newFlagSet("whoami") + fs.BoolVar(&whoamiArgs.json, "json", false, "output in JSON format") + return fs + }(), +} + +var whoamiArgs struct { + json bool // output in JSON format +} + +func runWhoami(ctx context.Context, args []string) error { + if len(args) > 0 { + return errors.New("too many arguments, expected none") + } + st, err := localClient.StatusWithoutPeers(ctx) + if err != nil { + return err + } + if len(st.TailscaleIPs) == 0 { + return fmt.Errorf("no current Tailscale IP address; state: %v", st.BackendState) + } + who, err := localClient.WhoIsProto(ctx, "", st.TailscaleIPs[0].String()) + if err != nil { + return err + } + return printWhoIs(who, whoamiArgs.json) +} diff --git a/cmd/tailscale/cli/whois.go b/cmd/tailscale/cli/whois.go index 7cc8f2889..0b8e407d6 100644 --- a/cmd/tailscale/cli/whois.go +++ b/cmd/tailscale/cli/whois.go @@ -13,6 +13,7 @@ "text/tabwriter" "github.com/peterbourgon/ff/v3/ffcli" + "tailscale.com/client/tailscale/apitype" ) var whoisCmd = &ffcli.Command{ @@ -46,7 +47,13 @@ func runWhoIs(ctx context.Context, args []string) error { if err != nil { return err } - if whoIsArgs.json { + return printWhoIs(who, whoIsArgs.json) +} + +// printWhoIs prints the WhoIsResponse to Stdout, either as JSON (if asJSON is +// true) or in a human-readable form. +func printWhoIs(who *apitype.WhoIsResponse, asJSON bool) error { + if asJSON { ec := json.NewEncoder(Stdout) ec.SetIndent("", " ") ec.Encode(who)