summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBrad Fitzpatrick <bradfitz@tailscale.com>2021-10-01 09:48:18 -0700
committerBrad Fitzpatrick <bradfitz@tailscale.com>2021-10-01 11:04:21 -0700
commitfd7e7ed5d3c1692a51d908c0e0c9bb7fe4ebfb64 (patch)
tree00dc3e532655e54909b5a3af5292ad4f9aeb7dfc
parent349015098d161d6e698294e8615f4f3f7455eb39 (diff)
downloadtailscale-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.go25
-rw-r--r--cmd/tailscale/cli/cert.go4
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
}