|
|
|
|
@ -6,6 +6,8 @@ package cli |
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
|
"context" |
|
|
|
|
"encoding/base64" |
|
|
|
|
"encoding/json" |
|
|
|
|
"errors" |
|
|
|
|
"flag" |
|
|
|
|
"fmt" |
|
|
|
|
@ -69,6 +71,7 @@ func newUpFlagSet(goos string, upArgs *upArgsT) *flag.FlagSet { |
|
|
|
|
upf := newFlagSet("up") |
|
|
|
|
|
|
|
|
|
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.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"
|
|
|
|
|
hostname string |
|
|
|
|
opUser string |
|
|
|
|
json bool |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (a upArgsT) getAuthKey() (string, error) { |
|
|
|
|
@ -141,6 +145,33 @@ func (a upArgsT) getAuthKey() (string, error) { |
|
|
|
|
|
|
|
|
|
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{}) { |
|
|
|
|
printf("Warning: "+format+"\n", args...) |
|
|
|
|
} |
|
|
|
|
@ -498,10 +529,16 @@ func runUp(ctx context.Context, args []string) error { |
|
|
|
|
startLoginInteractive() |
|
|
|
|
case ipn.NeedsMachineAuth: |
|
|
|
|
printed = true |
|
|
|
|
fmt.Fprintf(Stderr, "\nTo authorize your machine, visit (as admin):\n\n\t%s\n\n", prefs.AdminPageURL()) |
|
|
|
|
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()) |
|
|
|
|
} |
|
|
|
|
case ipn.Running: |
|
|
|
|
// 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.
|
|
|
|
|
fmt.Fprintf(Stderr, "Success.\n") |
|
|
|
|
} |
|
|
|
|
@ -514,15 +551,33 @@ func runUp(ctx context.Context, args []string) error { |
|
|
|
|
} |
|
|
|
|
if url := n.BrowseToURL; url != nil && printAuthURL(*url) { |
|
|
|
|
printed = true |
|
|
|
|
fmt.Fprintf(Stderr, "\nTo authenticate, visit:\n\n\t%s\n\n", *url) |
|
|
|
|
if upArgs.qr { |
|
|
|
|
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("QR code error: %v", err) |
|
|
|
|
log.Printf("upOutputJSON marshalling error: %v", err) |
|
|
|
|
} else { |
|
|
|
|
fmt.Fprintf(Stderr, "%s\n", q.ToString(false)) |
|
|
|
|
fmt.Println(string(data)) |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
fmt.Fprintf(Stderr, "\nTo authenticate, visit:\n\n\t%s\n\n", *url) |
|
|
|
|
if upArgs.qr { |
|
|
|
|
q, err := qrcode.New(*url, qrcode.Medium) |
|
|
|
|
if err != nil { |
|
|
|
|
log.Printf("QR code error: %v", err) |
|
|
|
|
} else { |
|
|
|
|
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 ( |
|
|
|
|
prefsOfFlag = map[string][]string{} // "exit-node" => ExitNodeIP, ExitNodeID
|
|
|
|
|
) |
|
|
|
|
@ -651,7 +716,7 @@ func addPrefFlagMapping(flagName string, prefNames ...string) { |
|
|
|
|
// correspond to an ipn.Pref.
|
|
|
|
|
func preflessFlag(flagName string) bool { |
|
|
|
|
switch flagName { |
|
|
|
|
case "authkey", "force-reauth", "reset", "qr": |
|
|
|
|
case "authkey", "force-reauth", "reset", "qr", "json": |
|
|
|
|
return true |
|
|
|
|
} |
|
|
|
|
return false |
|
|
|
|
|