mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 13:05:46 +00:00
cmd/tailscale: add --json for up
Print JSON to stdout containing everything needed for authentication. { "AuthURL": "https://login.tailscale.com/a/0123456789", "QR": "data:image/png;base64,iV...QmCC", "BackendState": "NeedsLogin" } { "BackendState": "Running" } Signed-off-by: Denton Gentry <dgentry@tailscale.com>
This commit is contained in:
parent
d3d503d997
commit
ffb16cdffb
@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -69,6 +71,7 @@ func newUpFlagSet(goos string, upArgs *upArgsT) *flag.FlagSet {
|
|||||||
upf := newFlagSet("up")
|
upf := newFlagSet("up")
|
||||||
|
|
||||||
upf.BoolVar(&upArgs.qr, "qr", false, "show QR code for login URLs")
|
upf.BoolVar(&upArgs.qr, "qr", false, "show QR code for login URLs")
|
||||||
|
upf.BoolVar(&upArgs.json, "json", false, "output in JSON format (WARNING: format subject to change)")
|
||||||
upf.BoolVar(&upArgs.forceReauth, "force-reauth", false, "force reauthentication")
|
upf.BoolVar(&upArgs.forceReauth, "force-reauth", false, "force reauthentication")
|
||||||
upf.BoolVar(&upArgs.reset, "reset", false, "reset unspecified settings to their default values")
|
upf.BoolVar(&upArgs.reset, "reset", false, "reset unspecified settings to their default values")
|
||||||
|
|
||||||
@ -124,6 +127,7 @@ type upArgsT struct {
|
|||||||
authKeyOrFile string // "secret" or "file:/path/to/secret"
|
authKeyOrFile string // "secret" or "file:/path/to/secret"
|
||||||
hostname string
|
hostname string
|
||||||
opUser string
|
opUser string
|
||||||
|
json bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a upArgsT) getAuthKey() (string, error) {
|
func (a upArgsT) getAuthKey() (string, error) {
|
||||||
@ -141,6 +145,33 @@ func (a upArgsT) getAuthKey() (string, error) {
|
|||||||
|
|
||||||
var upArgs upArgsT
|
var upArgs upArgsT
|
||||||
|
|
||||||
|
// Fields output when `tailscale up --json` is used. Two JSON blocks will be output.
|
||||||
|
//
|
||||||
|
// When "tailscale up" is run it first outputs a block with AuthURL and QR populated,
|
||||||
|
// providing the link for where to authenticate this client. BackendState would be
|
||||||
|
// valid but boring, as it will almost certainly be "NeedsLogin". Error would be
|
||||||
|
// populated if something goes badly wrong.
|
||||||
|
//
|
||||||
|
// When the client is authenticated by having someone visit the AuthURL, a second
|
||||||
|
// JSON block will be output. The AuthURL and QR fields will not be present, the
|
||||||
|
// BackendState and Error fields will give the result of the authentication.
|
||||||
|
// Ex:
|
||||||
|
// {
|
||||||
|
// "AuthURL": "https://login.tailscale.com/a/0123456789abcdef",
|
||||||
|
// "QR": "data:image/png;base64,0123...cdef"
|
||||||
|
// "BackendState": "NeedsLogin"
|
||||||
|
// }
|
||||||
|
// {
|
||||||
|
// "BackendState": "Running"
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
type upOutputJSON struct {
|
||||||
|
AuthURL string `json:",omitempty"` // Authentication URL of the form https://login.tailscale.com/a/0123456789
|
||||||
|
QR string `json:",omitempty"` // a DataURL (base64) PNG of a QR code AuthURL
|
||||||
|
BackendState string `json:",omitempty"` // name of state like Running or NeedsMachineAuth
|
||||||
|
Error string `json:",omitempty"` // description of an error
|
||||||
|
}
|
||||||
|
|
||||||
func warnf(format string, args ...interface{}) {
|
func warnf(format string, args ...interface{}) {
|
||||||
printf("Warning: "+format+"\n", args...)
|
printf("Warning: "+format+"\n", args...)
|
||||||
}
|
}
|
||||||
@ -498,10 +529,16 @@ func runUp(ctx context.Context, args []string) error {
|
|||||||
startLoginInteractive()
|
startLoginInteractive()
|
||||||
case ipn.NeedsMachineAuth:
|
case ipn.NeedsMachineAuth:
|
||||||
printed = true
|
printed = true
|
||||||
|
if env.upArgs.json {
|
||||||
|
printUpDoneJSON(ipn.NeedsMachineAuth, "")
|
||||||
|
} else {
|
||||||
fmt.Fprintf(Stderr, "\nTo authorize your machine, visit (as admin):\n\n\t%s\n\n", prefs.AdminPageURL())
|
fmt.Fprintf(Stderr, "\nTo authorize your machine, visit (as admin):\n\n\t%s\n\n", prefs.AdminPageURL())
|
||||||
|
}
|
||||||
case ipn.Running:
|
case ipn.Running:
|
||||||
// Done full authentication process
|
// Done full authentication process
|
||||||
if printed {
|
if env.upArgs.json {
|
||||||
|
printUpDoneJSON(ipn.Running, "")
|
||||||
|
} else if printed {
|
||||||
// Only need to print an update if we printed the "please click" message earlier.
|
// Only need to print an update if we printed the "please click" message earlier.
|
||||||
fmt.Fprintf(Stderr, "Success.\n")
|
fmt.Fprintf(Stderr, "Success.\n")
|
||||||
}
|
}
|
||||||
@ -514,6 +551,24 @@ func runUp(ctx context.Context, args []string) error {
|
|||||||
}
|
}
|
||||||
if url := n.BrowseToURL; url != nil && printAuthURL(*url) {
|
if url := n.BrowseToURL; url != nil && printAuthURL(*url) {
|
||||||
printed = true
|
printed = true
|
||||||
|
if upArgs.json {
|
||||||
|
js := &upOutputJSON{AuthURL: *url, BackendState: st.BackendState}
|
||||||
|
|
||||||
|
q, err := qrcode.New(*url, qrcode.Medium)
|
||||||
|
if err == nil {
|
||||||
|
png, err := q.PNG(128)
|
||||||
|
if err == nil {
|
||||||
|
js.QR = "data:image/png;base64," + base64.StdEncoding.EncodeToString(png)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.MarshalIndent(js, "", "\t")
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("upOutputJSON marshalling error: %v", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println(string(data))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
fmt.Fprintf(Stderr, "\nTo authenticate, visit:\n\n\t%s\n\n", *url)
|
fmt.Fprintf(Stderr, "\nTo authenticate, visit:\n\n\t%s\n\n", *url)
|
||||||
if upArgs.qr {
|
if upArgs.qr {
|
||||||
q, err := qrcode.New(*url, qrcode.Medium)
|
q, err := qrcode.New(*url, qrcode.Medium)
|
||||||
@ -522,7 +577,7 @@ func runUp(ctx context.Context, args []string) error {
|
|||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(Stderr, "%s\n", q.ToString(false))
|
fmt.Fprintf(Stderr, "%s\n", q.ToString(false))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -609,6 +664,16 @@ func runUp(ctx context.Context, args []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func printUpDoneJSON(state ipn.State, errorString string) {
|
||||||
|
js := &upOutputJSON{BackendState: state.String(), Error: errorString}
|
||||||
|
data, err := json.MarshalIndent(js, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("printUpDoneJSON marshalling error: %v", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println(string(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
prefsOfFlag = map[string][]string{} // "exit-node" => ExitNodeIP, ExitNodeID
|
prefsOfFlag = map[string][]string{} // "exit-node" => ExitNodeIP, ExitNodeID
|
||||||
)
|
)
|
||||||
@ -651,7 +716,7 @@ func addPrefFlagMapping(flagName string, prefNames ...string) {
|
|||||||
// correspond to an ipn.Pref.
|
// correspond to an ipn.Pref.
|
||||||
func preflessFlag(flagName string) bool {
|
func preflessFlag(flagName string) bool {
|
||||||
switch flagName {
|
switch flagName {
|
||||||
case "authkey", "force-reauth", "reset", "qr":
|
case "authkey", "force-reauth", "reset", "qr", "json":
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
Loading…
Reference in New Issue
Block a user