Skip to content

Commit 467eb2e

Browse files
committed
cmd/tailscale/cli, ipn/ipnlocal: give SSH tips when off/unconfigured
Updates tailscale#3802 Change-Id: I6b9a3175f68a6daa670f912561f2c2ececc07770 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
1 parent 99ed549 commit 467eb2e

File tree

2 files changed

+104
-24
lines changed

2 files changed

+104
-24
lines changed

cmd/tailscale/cli/up.go

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ func updatePrefs(prefs, curPrefs *ipn.Prefs, env upCheckEnv) (simpleUp bool, jus
404404
return simpleUp, justEditMP, nil
405405
}
406406

407-
func runUp(ctx context.Context, args []string) error {
407+
func runUp(ctx context.Context, args []string) (retErr error) {
408408
if len(args) > 0 {
409409
fatalf("too many non-flag arguments: %q", args)
410410
}
@@ -481,6 +481,12 @@ func runUp(ctx context.Context, args []string) error {
481481
}
482482
}
483483

484+
defer func() {
485+
if retErr == nil {
486+
checkSSHUpWarnings(ctx)
487+
}
488+
}()
489+
484490
simpleUp, justEditMP, err := updatePrefs(prefs, curPrefs, env)
485491
if err != nil {
486492
fatalf("%s", err)
@@ -676,6 +682,28 @@ func runUp(ctx context.Context, args []string) error {
676682
}
677683
}
678684

685+
func checkSSHUpWarnings(ctx context.Context) {
686+
if !upArgs.runSSH {
687+
return
688+
}
689+
st, err := localClient.Status(ctx)
690+
if err != nil {
691+
// Ignore. Don't spam more.
692+
return
693+
}
694+
if len(st.Health) == 0 {
695+
return
696+
}
697+
if len(st.Health) == 1 && strings.Contains(st.Health[0], "SSH") {
698+
printf("%s\n", st.Health[0])
699+
return
700+
}
701+
printf("# Health check:\n")
702+
for _, m := range st.Health {
703+
printf(" - %s\n", m)
704+
}
705+
}
706+
679707
func printUpDoneJSON(state ipn.State, errorString string) {
680708
js := &upOutputJSON{BackendState: state.String(), Error: errorString}
681709
data, err := json.MarshalIndent(js, "", " ")

ipn/ipnlocal/local.go

Lines changed: 75 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,9 @@ func (b *LocalBackend) updateStatus(sb *ipnstate.StatusBuilder, extraLocked func
416416
s.Health = append(s.Health, err.Error())
417417
}
418418
}
419+
if m := b.sshOnButUnusableHealthCheckMessageLocked(); m != "" {
420+
s.Health = append(s.Health, m)
421+
}
419422
if b.netMap != nil {
420423
s.CertDomains = append([]string(nil), b.netMap.DNS.CertDomains...)
421424
s.MagicDNSSuffix = b.netMap.MagicDNSSuffix()
@@ -1840,39 +1843,88 @@ func (b *LocalBackend) CheckPrefs(p *ipn.Prefs) error {
18401843
}
18411844

18421845
func (b *LocalBackend) checkPrefsLocked(p *ipn.Prefs) error {
1846+
var errs []error
18431847
if p.Hostname == "badhostname.tailscale." {
18441848
// Keep this one just for testing.
1845-
return errors.New("bad hostname [test]")
1849+
errs = append(errs, errors.New("bad hostname [test]"))
18461850
}
1847-
if p.RunSSH {
1848-
switch runtime.GOOS {
1849-
case "linux":
1850-
if distro.Get() == distro.Synology && !envknob.UseWIPCode() {
1851-
return errors.New("The Tailscale SSH server does not run on Synology.")
1852-
}
1853-
// otherwise okay
1854-
case "darwin":
1855-
// okay only in tailscaled mode for now.
1856-
if version.IsSandboxedMacOS() {
1857-
return errors.New("The Tailscale SSH server does not run in sandboxed Tailscale GUI builds.")
1858-
}
1859-
if !envknob.UseWIPCode() {
1860-
return errors.New("The Tailscale SSH server is disabled on macOS tailscaled by default. To try, set env TAILSCALE_USE_WIP_CODE=1")
1861-
}
1862-
default:
1863-
return errors.New("The Tailscale SSH server is not supported on " + runtime.GOOS)
1851+
if err := b.checkSSHPrefsLocked(p); err != nil {
1852+
errs = append(errs, err)
1853+
}
1854+
return multierr.New(errs...)
1855+
}
1856+
1857+
func (b *LocalBackend) checkSSHPrefsLocked(p *ipn.Prefs) error {
1858+
if !p.RunSSH {
1859+
return nil
1860+
}
1861+
switch runtime.GOOS {
1862+
case "linux":
1863+
if distro.Get() == distro.Synology && !envknob.UseWIPCode() {
1864+
return errors.New("The Tailscale SSH server does not run on Synology.")
1865+
}
1866+
// otherwise okay
1867+
case "darwin":
1868+
// okay only in tailscaled mode for now.
1869+
if version.IsSandboxedMacOS() {
1870+
return errors.New("The Tailscale SSH server does not run in sandboxed Tailscale GUI builds.")
18641871
}
1865-
if !canSSH {
1866-
return errors.New("The Tailscale SSH server has been administratively disabled.")
1872+
if !envknob.UseWIPCode() {
1873+
return errors.New("The Tailscale SSH server is disabled on macOS tailscaled by default. To try, set env TAILSCALE_USE_WIP_CODE=1")
18671874
}
1868-
if b.netMap != nil && b.netMap.SSHPolicy == nil &&
1869-
envknob.SSHPolicyFile() == "" && !envknob.SSHIgnoreTailnetPolicy() {
1870-
return errors.New("Unable to enable local Tailscale SSH server; not enabled/configured on Tailnet.")
1875+
default:
1876+
return errors.New("The Tailscale SSH server is not supported on " + runtime.GOOS)
1877+
}
1878+
if !canSSH {
1879+
return errors.New("The Tailscale SSH server has been administratively disabled.")
1880+
}
1881+
if envknob.SSHIgnoreTailnetPolicy() || envknob.SSHPolicyFile() != "" {
1882+
return nil
1883+
}
1884+
if b.netMap != nil {
1885+
if !hasCapability(b.netMap, tailcfg.CapabilitySSH) {
1886+
if b.isDefaultServerLocked() {
1887+
return errors.New("Unable to enable local Tailscale SSH server; not enabled on Tailnet. See https://tailscale.com/s/ssh")
1888+
}
1889+
return errors.New("Unable to enable local Tailscale SSH server; not enabled on Tailnet.")
18711890
}
18721891
}
18731892
return nil
18741893
}
18751894

1895+
func (b *LocalBackend) sshOnButUnusableHealthCheckMessageLocked() (healthMessage string) {
1896+
if b.prefs == nil || !b.prefs.RunSSH {
1897+
return ""
1898+
}
1899+
if envknob.SSHIgnoreTailnetPolicy() || envknob.SSHPolicyFile() != "" {
1900+
return "development SSH policy in use"
1901+
}
1902+
nm := b.netMap
1903+
if nm == nil {
1904+
return ""
1905+
}
1906+
if nm.SSHPolicy != nil && len(nm.SSHPolicy.Rules) > 0 {
1907+
return ""
1908+
}
1909+
isDefault := b.isDefaultServerLocked()
1910+
isAdmin := hasCapability(nm, tailcfg.CapabilityAdmin)
1911+
1912+
if !isAdmin {
1913+
return "Tailscale SSH enabled, but access controls don't allow anyone to access this device. Ask your admin to update your tailnet's ACLs to allow access."
1914+
}
1915+
if !isDefault {
1916+
return "Tailscale SSH enabled, but access controls don't allow anyone to access this device. Update your tailnet's ACLs to allow access."
1917+
}
1918+
return "Tailscale SSH enabled, but access controls don't allow anyone to access this device. Update your tailnet's ACLs at https://tailscale.com/s/ssh-policy"
1919+
}
1920+
1921+
func (b *LocalBackend) isDefaultServerLocked() bool {
1922+
if b.prefs == nil {
1923+
return true // assume true until set otherwise
1924+
}
1925+
return b.prefs.ControlURLOrDefault() == ipn.DefaultControlURL
1926+
}
1927+
18761928
func (b *LocalBackend) EditPrefs(mp *ipn.MaskedPrefs) (*ipn.Prefs, error) {
18771929
b.mu.Lock()
18781930
p0 := b.prefs.Clone()

0 commit comments

Comments
 (0)