diff options
| author | Brad Fitzpatrick <bradfitz@tailscale.com> | 2021-10-01 09:48:18 -0700 |
|---|---|---|
| committer | Brad Fitzpatrick <bradfitz@tailscale.com> | 2021-10-01 11:04:21 -0700 |
| commit | fd7e7ed5d3c1692a51d908c0e0c9bb7fe4ebfb64 (patch) | |
| tree | 00dc3e532655e54909b5a3af5292ad4f9aeb7dfc | |
| parent | 349015098d161d6e698294e8615f4f3f7455eb39 (diff) | |
| download | tailscale-bradfitz/1.14.tar.xz tailscale-bradfitz/1.14.zip | |
cmd/tailscale: make cert subcommand give hints on access deniedbradfitz/1.14
Lot of people have been hitting this.
Now it says:
$ tailscale cert tsdev.corp.ts.net
Access denied: cert access denied
Use 'sudo tailscale cert' or 'tailscale up --operator=$USER' to not require root.
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit f62e6d83a9cdc2dc62d15411aeb45927e916ead4)
| -rw-r--r-- | client/tailscale/tailscale.go | 25 | ||||
| -rw-r--r-- | cmd/tailscale/cli/cert.go | 4 |
2 files changed, 29 insertions, 0 deletions
diff --git a/client/tailscale/tailscale.go b/client/tailscale/tailscale.go index e5e22020a..16681855f 100644 --- a/client/tailscale/tailscale.go +++ b/client/tailscale/tailscale.go @@ -76,6 +76,20 @@ type errorJSON struct { Error string } +// AccessDeniedError is an error due to permissions. +type AccessDeniedError struct { + err error +} + +func (e *AccessDeniedError) Error() string { return fmt.Sprintf("Access denied: %v", e.err) } +func (e *AccessDeniedError) Unwrap() error { return e.err } + +// IsAccessDeniedError reports whether err is or wraps an AccessDeniedError. +func IsAccessDeniedError(err error) bool { + var ae *AccessDeniedError + return errors.As(err, &ae) +} + // bestError returns either err, or if body contains a valid JSON // object of type errorJSON, its non-empty error body. func bestError(err error, body []byte) error { @@ -86,6 +100,14 @@ func bestError(err error, body []byte) error { return err } +func errorMessageFromBody(body []byte) string { + var j errorJSON + if err := json.Unmarshal(body, &j); err == nil && j.Error != "" { + return j.Error + } + return strings.TrimSpace(string(body)) +} + func send(ctx context.Context, method, path string, wantStatus int, body io.Reader) ([]byte, error) { req, err := http.NewRequestWithContext(ctx, method, "http://local-tailscaled.sock"+path, body) if err != nil { @@ -104,6 +126,9 @@ func send(ctx context.Context, method, path string, wantStatus int, body io.Read return nil, err } if res.StatusCode != wantStatus { + if res.StatusCode == 403 { + return nil, &AccessDeniedError{errors.New(errorMessageFromBody(slurp))} + } err := fmt.Errorf("HTTP %s: %s (expected %v)", res.Status, slurp, wantStatus) return nil, bestError(err, slurp) } diff --git a/cmd/tailscale/cli/cert.go b/cmd/tailscale/cli/cert.go index 3557005e8..284179773 100644 --- a/cmd/tailscale/cli/cert.go +++ b/cmd/tailscale/cli/cert.go @@ -13,6 +13,7 @@ import ( "log" "net/http" "os" + "runtime" "strings" "github.com/peterbourgon/ff/v2/ffcli" @@ -85,6 +86,9 @@ func runCert(ctx context.Context, args []string) error { certArgs.keyFile = domain + ".key" } certPEM, keyPEM, err := tailscale.CertPair(ctx, domain) + if tailscale.IsAccessDeniedError(err) && os.Getuid() != 0 && runtime.GOOS != "windows" { + return fmt.Errorf("%v\n\nUse 'sudo tailscale cert' or 'tailscale up --operator=$USER' to not require root.", err) + } if err != nil { return err } |
