ipn/localapi, cmd/tailscale: add API to get prefs, CLI debug command to show

Updates #1436

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
pull/1670/head
Brad Fitzpatrick 2021-04-07 08:27:35 -07:00
parent 03be116997
commit 50b309c1eb
5 changed files with 57 additions and 1 deletions

View File

@ -18,6 +18,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnstate" "tailscale.com/ipn/ipnstate"
"tailscale.com/paths" "tailscale.com/paths"
"tailscale.com/safesocket" "tailscale.com/safesocket"
@ -199,3 +200,15 @@ func CheckIPForwarding(ctx context.Context) error {
} }
return nil return nil
} }
func GetPrefs(ctx context.Context) (*ipn.Prefs, error) {
body, err := get200(ctx, "/localapi/v0/prefs")
if err != nil {
return nil, err
}
var p ipn.Prefs
if err := json.Unmarshal(body, &p); err != nil {
return nil, fmt.Errorf("invalid JSON from check-ip-forwarding: %w", err)
}
return &p, nil
}

View File

@ -30,6 +30,8 @@ var debugCmd = &ffcli.Command{
fs := flag.NewFlagSet("debug", flag.ExitOnError) fs := flag.NewFlagSet("debug", flag.ExitOnError)
fs.BoolVar(&debugArgs.goroutines, "daemon-goroutines", false, "If true, dump the tailscaled daemon's goroutines") fs.BoolVar(&debugArgs.goroutines, "daemon-goroutines", false, "If true, dump the tailscaled daemon's goroutines")
fs.BoolVar(&debugArgs.ipn, "ipn", false, "If true, subscribe to IPN notifications") fs.BoolVar(&debugArgs.ipn, "ipn", false, "If true, subscribe to IPN notifications")
fs.BoolVar(&debugArgs.prefs, "prefs", false, "If true, dump active prefs")
fs.BoolVar(&debugArgs.pretty, "pretty", false, "If true, pretty-print output (for --prefs)")
fs.BoolVar(&debugArgs.netMap, "netmap", true, "whether to include netmap in --ipn mode") fs.BoolVar(&debugArgs.netMap, "netmap", true, "whether to include netmap in --ipn mode")
fs.BoolVar(&debugArgs.localCreds, "local-creds", false, "print how to connect to local tailscaled") fs.BoolVar(&debugArgs.localCreds, "local-creds", false, "print how to connect to local tailscaled")
fs.StringVar(&debugArgs.file, "file", "", "get, delete:NAME, or NAME") fs.StringVar(&debugArgs.file, "file", "", "get, delete:NAME, or NAME")
@ -43,6 +45,8 @@ var debugArgs struct {
ipn bool ipn bool
netMap bool netMap bool
file string file string
prefs bool
pretty bool
} }
func runDebug(ctx context.Context, args []string) error { func runDebug(ctx context.Context, args []string) error {
@ -62,6 +66,19 @@ func runDebug(ctx context.Context, args []string) error {
fmt.Printf("curl --unix-socket %s http://foo/localapi/v0/status\n", paths.DefaultTailscaledSocket()) fmt.Printf("curl --unix-socket %s http://foo/localapi/v0/status\n", paths.DefaultTailscaledSocket())
return nil return nil
} }
if debugArgs.prefs {
prefs, err := tailscale.GetPrefs(ctx)
if err != nil {
return err
}
if debugArgs.pretty {
fmt.Println(prefs.Pretty())
} else {
j, _ := json.MarshalIndent(prefs, "", "\t")
fmt.Println(string(j))
}
return nil
}
if debugArgs.goroutines { if debugArgs.goroutines {
goroutines, err := tailscale.Goroutines(ctx) goroutines, err := tailscale.Goroutines(ctx)
if err != nil { if err != nil {

View File

@ -19,7 +19,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/derp/derphttp from tailscale.com/net/netcheck tailscale.com/derp/derphttp from tailscale.com/net/netcheck
tailscale.com/derp/derpmap from tailscale.com/cmd/tailscale/cli tailscale.com/derp/derpmap from tailscale.com/cmd/tailscale/cli
tailscale.com/disco from tailscale.com/derp tailscale.com/disco from tailscale.com/derp
tailscale.com/ipn from tailscale.com/cmd/tailscale/cli tailscale.com/ipn from tailscale.com/cmd/tailscale/cli+
tailscale.com/ipn/ipnstate from tailscale.com/cmd/tailscale/cli+ tailscale.com/ipn/ipnstate from tailscale.com/cmd/tailscale/cli+
tailscale.com/metrics from tailscale.com/derp tailscale.com/metrics from tailscale.com/derp
tailscale.com/net/dnscache from tailscale.com/derp/derphttp tailscale.com/net/dnscache from tailscale.com/derp/derphttp

View File

@ -238,6 +238,19 @@ func (b *LocalBackend) Shutdown() {
b.e.Wait() b.e.Wait()
} }
// Prefs returns a copy of b's current prefs, with any private keys removed.
func (b *LocalBackend) Prefs() *ipn.Prefs {
b.mu.Lock()
defer b.mu.Unlock()
p := b.prefs.Clone()
if p != nil && p.Persist != nil {
p.Persist.LegacyFrontendPrivateMachineKey = wgkey.Private{}
p.Persist.PrivateNodeKey = wgkey.Private{}
p.Persist.OldPrivateNodeKey = wgkey.Private{}
}
return p
}
// Status returns the latest status of the backend and its // Status returns the latest status of the backend and its
// sub-components. // sub-components.
func (b *LocalBackend) Status() *ipnstate.Status { func (b *LocalBackend) Status() *ipnstate.Status {

View File

@ -87,6 +87,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.serveGoroutines(w, r) h.serveGoroutines(w, r)
case "/localapi/v0/status": case "/localapi/v0/status":
h.serveStatus(w, r) h.serveStatus(w, r)
case "/localapi/v0/prefs":
h.servePrefs(w, r)
case "/localapi/v0/check-ip-forwarding": case "/localapi/v0/check-ip-forwarding":
h.serveCheckIPForwarding(w, r) h.serveCheckIPForwarding(w, r)
case "/localapi/v0/bugreport": case "/localapi/v0/bugreport":
@ -198,6 +200,17 @@ func (h *Handler) serveStatus(w http.ResponseWriter, r *http.Request) {
e.Encode(st) e.Encode(st)
} }
func (h *Handler) servePrefs(w http.ResponseWriter, r *http.Request) {
if !h.PermitRead {
http.Error(w, "prefs access denied", http.StatusForbidden)
return
}
w.Header().Set("Content-Type", "application/json")
e := json.NewEncoder(w)
e.SetIndent("", "\t")
e.Encode(h.b.Prefs())
}
func (h *Handler) serveFiles(w http.ResponseWriter, r *http.Request) { func (h *Handler) serveFiles(w http.ResponseWriter, r *http.Request) {
if !h.PermitWrite { if !h.PermitWrite {
http.Error(w, "file access denied", http.StatusForbidden) http.Error(w, "file access denied", http.StatusForbidden)