mirror of
https://github.com/tailscale/tailscale.git
synced 2025-01-05 14:57:49 +00:00
cmd/tailscale, etc: make "tailscale up --ssh" fail fast when unavailable
Fail on unsupported platforms (must be Linux or macOS tailscaled with WIP env) or when disabled by admin (with TS_DISABLE_SSH_SERVER=1) Updates #3802 Change-Id: I5ba191ed0d8ba4ddabe9b8fc1c6a0ead8754b286 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
f0e2272e04
commit
8f5e5bff1e
@ -381,6 +381,21 @@ func CheckIPForwarding(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckPrefs validates the provided preferences, without making any changes.
|
||||
//
|
||||
// The CLI uses this before a Start call to fail fast if the preferences won't
|
||||
// work. Currently (2022-04-18) this only checks for SSH server compatibility.
|
||||
// Note that EditPrefs does the same validation as this, so call CheckPrefs before
|
||||
// EditPrefs is not necessary.
|
||||
func CheckPrefs(ctx context.Context, p *ipn.Prefs) error {
|
||||
pj, err := json.Marshal(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = send(ctx, "POST", "/localapi/v0/check-prefs", http.StatusOK, bytes.NewReader(pj))
|
||||
return err
|
||||
}
|
||||
|
||||
func GetPrefs(ctx context.Context) (*ipn.Prefs, error) {
|
||||
body, err := get200(ctx, "/localapi/v0/prefs")
|
||||
if err != nil {
|
||||
|
@ -592,6 +592,10 @@ func runUp(ctx context.Context, args []string) error {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := tailscale.CheckPrefs(ctx, prefs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authKey, err := upArgs.getAuthKey()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1775,11 +1775,49 @@ func (b *LocalBackend) SetCurrentUserID(uid string) {
|
||||
b.mu.Unlock()
|
||||
}
|
||||
|
||||
func (b *LocalBackend) CheckPrefs(p *ipn.Prefs) error {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
return b.checkPrefsLocked(p)
|
||||
}
|
||||
|
||||
func (b *LocalBackend) checkPrefsLocked(p *ipn.Prefs) error {
|
||||
if p.Hostname == "badhostname.tailscale." {
|
||||
// Keep this one just for testing.
|
||||
return errors.New("bad hostname [test]")
|
||||
}
|
||||
if p.RunSSH {
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
// okay
|
||||
case "darwin":
|
||||
// okay only in tailscaled mode for now.
|
||||
if version.IsSandboxedMacOS() {
|
||||
return errors.New("The Tailscale SSH server does not run in sandboxed Tailscale GUI builds.")
|
||||
}
|
||||
if !envknob.UseWIPCode() {
|
||||
return errors.New("The Tailscale SSH server is disabled on macOS tailscaled by default. To try, set env TAILSCALE_USE_WIP_CODE=1")
|
||||
}
|
||||
default:
|
||||
return errors.New("The Tailscale SSH server is not supported on " + runtime.GOOS)
|
||||
}
|
||||
if !canSSH {
|
||||
return errors.New("The Tailscale SSH server has been administratively disabled.")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *LocalBackend) EditPrefs(mp *ipn.MaskedPrefs) (*ipn.Prefs, error) {
|
||||
b.mu.Lock()
|
||||
p0 := b.prefs.Clone()
|
||||
p1 := b.prefs.Clone()
|
||||
p1.ApplyEdits(mp)
|
||||
if err := b.checkPrefsLocked(p1); err != nil {
|
||||
b.mu.Unlock()
|
||||
b.logf("EditPrefs check error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
if p1.RunSSH && !canSSH {
|
||||
b.mu.Unlock()
|
||||
b.logf("EditPrefs requests SSH, but disabled by envknob; returning error")
|
||||
|
@ -111,6 +111,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
h.serveLogout(w, r)
|
||||
case "/localapi/v0/prefs":
|
||||
h.servePrefs(w, r)
|
||||
case "/localapi/v0/check-prefs":
|
||||
h.serveCheckPrefs(w, r)
|
||||
case "/localapi/v0/check-ip-forwarding":
|
||||
h.serveCheckIPForwarding(w, r)
|
||||
case "/localapi/v0/bugreport":
|
||||
@ -376,7 +378,9 @@ func (h *Handler) servePrefs(w http.ResponseWriter, r *http.Request) {
|
||||
var err error
|
||||
prefs, err = h.b.EditPrefs(mp)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 400)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(resJSON{Error: err.Error()})
|
||||
return
|
||||
}
|
||||
case "GET", "HEAD":
|
||||
@ -391,6 +395,33 @@ func (h *Handler) servePrefs(w http.ResponseWriter, r *http.Request) {
|
||||
e.Encode(prefs)
|
||||
}
|
||||
|
||||
type resJSON struct {
|
||||
Error string `json:",omitempty"`
|
||||
}
|
||||
|
||||
func (h *Handler) serveCheckPrefs(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitWrite {
|
||||
http.Error(w, "checkprefs access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if r.Method != "POST" {
|
||||
http.Error(w, "unsupported method", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
p := new(ipn.Prefs)
|
||||
if err := json.NewDecoder(r.Body).Decode(p); err != nil {
|
||||
http.Error(w, "invalid JSON body", 400)
|
||||
return
|
||||
}
|
||||
err := h.b.CheckPrefs(p)
|
||||
var res resJSON
|
||||
if err != nil {
|
||||
res.Error = err.Error()
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(res)
|
||||
}
|
||||
|
||||
func (h *Handler) serveFiles(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitWrite {
|
||||
http.Error(w, "file access denied", http.StatusForbidden)
|
||||
|
Loading…
x
Reference in New Issue
Block a user