mirror of
https://github.com/tailscale/tailscale.git
synced 2024-12-12 11:14:40 +00:00
cmd/tailscale/cli, ipn/ipnlocal: give SSH tips when off/unconfigured
Updates #3802
Change-Id: I6b9a3175f68a6daa670f912561f2c2ececc07770
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 467eb2eca0
)
This commit is contained in:
parent
c1795c6af9
commit
32f26a723b
@ -404,7 +404,7 @@ func updatePrefs(prefs, curPrefs *ipn.Prefs, env upCheckEnv) (simpleUp bool, jus
|
|||||||
return simpleUp, justEditMP, nil
|
return simpleUp, justEditMP, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runUp(ctx context.Context, args []string) error {
|
func runUp(ctx context.Context, args []string) (retErr error) {
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
fatalf("too many non-flag arguments: %q", args)
|
fatalf("too many non-flag arguments: %q", args)
|
||||||
}
|
}
|
||||||
@ -481,6 +481,12 @@ func runUp(ctx context.Context, args []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if retErr == nil {
|
||||||
|
checkSSHUpWarnings(ctx)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
simpleUp, justEditMP, err := updatePrefs(prefs, curPrefs, env)
|
simpleUp, justEditMP, err := updatePrefs(prefs, curPrefs, env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf("%s", err)
|
fatalf("%s", err)
|
||||||
@ -676,6 +682,28 @@ func runUp(ctx context.Context, args []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkSSHUpWarnings(ctx context.Context) {
|
||||||
|
if !upArgs.runSSH {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
st, err := localClient.Status(ctx)
|
||||||
|
if err != nil {
|
||||||
|
// Ignore. Don't spam more.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(st.Health) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(st.Health) == 1 && strings.Contains(st.Health[0], "SSH") {
|
||||||
|
printf("%s\n", st.Health[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
printf("# Health check:\n")
|
||||||
|
for _, m := range st.Health {
|
||||||
|
printf(" - %s\n", m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func printUpDoneJSON(state ipn.State, errorString string) {
|
func printUpDoneJSON(state ipn.State, errorString string) {
|
||||||
js := &upOutputJSON{BackendState: state.String(), Error: errorString}
|
js := &upOutputJSON{BackendState: state.String(), Error: errorString}
|
||||||
data, err := json.MarshalIndent(js, "", " ")
|
data, err := json.MarshalIndent(js, "", " ")
|
||||||
|
@ -416,6 +416,9 @@ func (b *LocalBackend) updateStatus(sb *ipnstate.StatusBuilder, extraLocked func
|
|||||||
s.Health = append(s.Health, err.Error())
|
s.Health = append(s.Health, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if m := b.sshOnButUnusableHealthCheckMessageLocked(); m != "" {
|
||||||
|
s.Health = append(s.Health, m)
|
||||||
|
}
|
||||||
if b.netMap != nil {
|
if b.netMap != nil {
|
||||||
s.CertDomains = append([]string(nil), b.netMap.DNS.CertDomains...)
|
s.CertDomains = append([]string(nil), b.netMap.DNS.CertDomains...)
|
||||||
s.MagicDNSSuffix = b.netMap.MagicDNSSuffix()
|
s.MagicDNSSuffix = b.netMap.MagicDNSSuffix()
|
||||||
@ -1826,39 +1829,88 @@ func (b *LocalBackend) CheckPrefs(p *ipn.Prefs) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *LocalBackend) checkPrefsLocked(p *ipn.Prefs) error {
|
func (b *LocalBackend) checkPrefsLocked(p *ipn.Prefs) error {
|
||||||
|
var errs []error
|
||||||
if p.Hostname == "badhostname.tailscale." {
|
if p.Hostname == "badhostname.tailscale." {
|
||||||
// Keep this one just for testing.
|
// Keep this one just for testing.
|
||||||
return errors.New("bad hostname [test]")
|
errs = append(errs, errors.New("bad hostname [test]"))
|
||||||
}
|
}
|
||||||
if p.RunSSH {
|
if err := b.checkSSHPrefsLocked(p); err != nil {
|
||||||
switch runtime.GOOS {
|
errs = append(errs, err)
|
||||||
case "linux":
|
}
|
||||||
if distro.Get() == distro.Synology && !envknob.UseWIPCode() {
|
return multierr.New(errs...)
|
||||||
return errors.New("The Tailscale SSH server does not run on Synology.")
|
}
|
||||||
}
|
|
||||||
// otherwise okay
|
func (b *LocalBackend) checkSSHPrefsLocked(p *ipn.Prefs) error {
|
||||||
case "darwin":
|
if !p.RunSSH {
|
||||||
// okay only in tailscaled mode for now.
|
return nil
|
||||||
if version.IsSandboxedMacOS() {
|
}
|
||||||
return errors.New("The Tailscale SSH server does not run in sandboxed Tailscale GUI builds.")
|
switch runtime.GOOS {
|
||||||
}
|
case "linux":
|
||||||
if !envknob.UseWIPCode() {
|
if distro.Get() == distro.Synology && !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")
|
return errors.New("The Tailscale SSH server does not run on Synology.")
|
||||||
}
|
|
||||||
default:
|
|
||||||
return errors.New("The Tailscale SSH server is not supported on " + runtime.GOOS)
|
|
||||||
}
|
}
|
||||||
if !canSSH {
|
// otherwise okay
|
||||||
return errors.New("The Tailscale SSH server has been administratively disabled.")
|
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 b.netMap != nil && b.netMap.SSHPolicy == nil &&
|
if !envknob.UseWIPCode() {
|
||||||
envknob.SSHPolicyFile() == "" && !envknob.SSHIgnoreTailnetPolicy() {
|
return errors.New("The Tailscale SSH server is disabled on macOS tailscaled by default. To try, set env TAILSCALE_USE_WIP_CODE=1")
|
||||||
return errors.New("Unable to enable local Tailscale SSH server; not enabled/configured on Tailnet.")
|
}
|
||||||
|
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.")
|
||||||
|
}
|
||||||
|
if envknob.SSHIgnoreTailnetPolicy() || envknob.SSHPolicyFile() != "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if b.netMap != nil {
|
||||||
|
if !hasCapability(b.netMap, tailcfg.CapabilitySSH) {
|
||||||
|
if b.isDefaultServerLocked() {
|
||||||
|
return errors.New("Unable to enable local Tailscale SSH server; not enabled on Tailnet. See https://tailscale.com/s/ssh")
|
||||||
|
}
|
||||||
|
return errors.New("Unable to enable local Tailscale SSH server; not enabled on Tailnet.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *LocalBackend) sshOnButUnusableHealthCheckMessageLocked() (healthMessage string) {
|
||||||
|
if b.prefs == nil || !b.prefs.RunSSH {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if envknob.SSHIgnoreTailnetPolicy() || envknob.SSHPolicyFile() != "" {
|
||||||
|
return "development SSH policy in use"
|
||||||
|
}
|
||||||
|
nm := b.netMap
|
||||||
|
if nm == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if nm.SSHPolicy != nil && len(nm.SSHPolicy.Rules) > 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
isDefault := b.isDefaultServerLocked()
|
||||||
|
isAdmin := hasCapability(nm, tailcfg.CapabilityAdmin)
|
||||||
|
|
||||||
|
if !isAdmin {
|
||||||
|
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."
|
||||||
|
}
|
||||||
|
if !isDefault {
|
||||||
|
return "Tailscale SSH enabled, but access controls don't allow anyone to access this device. Update your tailnet's ACLs to allow access."
|
||||||
|
}
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *LocalBackend) isDefaultServerLocked() bool {
|
||||||
|
if b.prefs == nil {
|
||||||
|
return true // assume true until set otherwise
|
||||||
|
}
|
||||||
|
return b.prefs.ControlURLOrDefault() == ipn.DefaultControlURL
|
||||||
|
}
|
||||||
|
|
||||||
func (b *LocalBackend) EditPrefs(mp *ipn.MaskedPrefs) (*ipn.Prefs, error) {
|
func (b *LocalBackend) EditPrefs(mp *ipn.MaskedPrefs) (*ipn.Prefs, error) {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
p0 := b.prefs.Clone()
|
p0 := b.prefs.Clone()
|
||||||
|
Loading…
Reference in New Issue
Block a user