cmd/tailscale: make "up", "status" warn if routes and --accept-routes off
Example output: # Health check: # - Some peers are advertising routes but --accept-routes is false Also, move "tailscale status" health checks to the bottom, where they won't be lost in large netmaps. Updates #2053 Updates #6266 Change-Id: I5ae76a0cd69a452ce70063875cd7d974bfeb8f1a Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>pull/5681/merge
parent
66b4a363bd
commit
08e110ebc5
|
@ -16,6 +16,7 @@ import (
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
qt "github.com/frankban/quicktest"
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"tailscale.com/health/healthmsg"
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
"tailscale.com/ipn/ipnstate"
|
"tailscale.com/ipn/ipnstate"
|
||||||
"tailscale.com/tstest"
|
"tailscale.com/tstest"
|
||||||
|
@ -1143,3 +1144,15 @@ func TestCleanUpArgs(t *testing.T) {
|
||||||
c.Assert(got, qt.DeepEquals, tt.want)
|
c.Assert(got, qt.DeepEquals, tt.want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUpWorthWarning(t *testing.T) {
|
||||||
|
if !upWorthyWarning(healthmsg.WarnAcceptRoutesOff) {
|
||||||
|
t.Errorf("WarnAcceptRoutesOff of %q should be worth warning", healthmsg.WarnAcceptRoutesOff)
|
||||||
|
}
|
||||||
|
if !upWorthyWarning(healthmsg.TailscaleSSHOnBut + "some problem") {
|
||||||
|
t.Errorf("want true for SSH problems")
|
||||||
|
}
|
||||||
|
if upWorthyWarning("not in map poll") {
|
||||||
|
t.Errorf("want false for other misc errors")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -128,18 +128,21 @@ func runStatus(ctx context.Context, args []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// print health check information prior to checking LocalBackend state as
|
printHealth := func() {
|
||||||
// it may provide an explanation to the user if we choose to exit early
|
|
||||||
if len(st.Health) > 0 {
|
|
||||||
printf("# Health check:\n")
|
printf("# Health check:\n")
|
||||||
for _, m := range st.Health {
|
for _, m := range st.Health {
|
||||||
printf("# - %s\n", m)
|
printf("# - %s\n", m)
|
||||||
}
|
}
|
||||||
outln()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
description, ok := isRunningOrStarting(st)
|
description, ok := isRunningOrStarting(st)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
// print health check information if we're in a weird state, as it might
|
||||||
|
// provide context about why we're in that weird state.
|
||||||
|
if len(st.Health) > 0 && (st.BackendState == ipn.Starting.String() || st.BackendState == ipn.NoState.String()) {
|
||||||
|
printHealth()
|
||||||
|
outln()
|
||||||
|
}
|
||||||
outln(description)
|
outln(description)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
@ -214,6 +217,10 @@ func runStatus(ctx context.Context, args []string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Stdout.Write(buf.Bytes())
|
Stdout.Write(buf.Bytes())
|
||||||
|
if len(st.Health) > 0 {
|
||||||
|
outln()
|
||||||
|
printHealth()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ import (
|
||||||
shellquote "github.com/kballard/go-shellquote"
|
shellquote "github.com/kballard/go-shellquote"
|
||||||
"github.com/peterbourgon/ff/v3/ffcli"
|
"github.com/peterbourgon/ff/v3/ffcli"
|
||||||
qrcode "github.com/skip2/go-qrcode"
|
qrcode "github.com/skip2/go-qrcode"
|
||||||
|
"tailscale.com/health/healthmsg"
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
"tailscale.com/ipn/ipnstate"
|
"tailscale.com/ipn/ipnstate"
|
||||||
"tailscale.com/net/tsaddr"
|
"tailscale.com/net/tsaddr"
|
||||||
|
@ -495,7 +496,7 @@ func runUp(ctx context.Context, args []string) (retErr error) {
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if retErr == nil {
|
if retErr == nil {
|
||||||
checkSSHUpWarnings(ctx)
|
checkUpWarnings(ctx)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -678,25 +679,39 @@ func runUp(ctx context.Context, args []string) (retErr error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkSSHUpWarnings(ctx context.Context) {
|
// upWorthWarning reports whether the health check message s is worth warning
|
||||||
if !upArgs.runSSH {
|
// about during "tailscale up". Many of the health checks are noisy or confusing
|
||||||
return
|
// or very ephemeral and happen especially briefly at startup.
|
||||||
|
//
|
||||||
|
// TODO(bradfitz): change the server to send typed warnings with metadata about
|
||||||
|
// the health check, rather than just a string.
|
||||||
|
func upWorthyWarning(s string) bool {
|
||||||
|
return strings.Contains(s, healthmsg.TailscaleSSHOnBut) ||
|
||||||
|
strings.Contains(s, healthmsg.WarnAcceptRoutesOff)
|
||||||
}
|
}
|
||||||
st, err := localClient.Status(ctx)
|
|
||||||
|
func checkUpWarnings(ctx context.Context) {
|
||||||
|
st, err := localClient.StatusWithoutPeers(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Ignore. Don't spam more.
|
// Ignore. Don't spam more.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(st.Health) == 0 {
|
var warn []string
|
||||||
|
for _, w := range st.Health {
|
||||||
|
if upWorthyWarning(w) {
|
||||||
|
warn = append(warn, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(warn) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(st.Health) == 1 && strings.Contains(st.Health[0], "SSH") {
|
if len(warn) == 1 {
|
||||||
printf("%s\n", st.Health[0])
|
printf("%s\n", warn[0])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
printf("# Health check:\n")
|
printf("# Health check warnings:\n")
|
||||||
for _, m := range st.Health {
|
for _, m := range warn {
|
||||||
printf(" - %s\n", m)
|
printf("# - %s\n", m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1040,3 +1055,15 @@ func exitNodeIP(p *ipn.Prefs, st *ipnstate.Status) (ip netip.Addr) {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func anyPeerAdvertisingRoutes(st *ipnstate.Status) bool {
|
||||||
|
for _, ps := range st.Peer {
|
||||||
|
if ps.PrimaryRoutes == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ps.PrimaryRoutes.Len() > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -51,6 +51,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||||
tailscale.com/derp/derphttp from tailscale.com/net/netcheck
|
tailscale.com/derp/derphttp from tailscale.com/net/netcheck
|
||||||
tailscale.com/disco from tailscale.com/derp
|
tailscale.com/disco from tailscale.com/derp
|
||||||
tailscale.com/envknob from tailscale.com/cmd/tailscale/cli+
|
tailscale.com/envknob from tailscale.com/cmd/tailscale/cli+
|
||||||
|
tailscale.com/health/healthmsg from tailscale.com/cmd/tailscale/cli
|
||||||
tailscale.com/hostinfo from tailscale.com/net/interfaces+
|
tailscale.com/hostinfo from tailscale.com/net/interfaces+
|
||||||
tailscale.com/ipn from tailscale.com/cmd/tailscale/cli+
|
tailscale.com/ipn from tailscale.com/cmd/tailscale/cli+
|
||||||
tailscale.com/ipn/ipnstate from tailscale.com/cmd/tailscale/cli+
|
tailscale.com/ipn/ipnstate from tailscale.com/cmd/tailscale/cli+
|
||||||
|
|
|
@ -194,6 +194,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||||
tailscale.com/doctor/routetable from tailscale.com/ipn/ipnlocal
|
tailscale.com/doctor/routetable from tailscale.com/ipn/ipnlocal
|
||||||
tailscale.com/envknob from tailscale.com/control/controlclient+
|
tailscale.com/envknob from tailscale.com/control/controlclient+
|
||||||
tailscale.com/health from tailscale.com/control/controlclient+
|
tailscale.com/health from tailscale.com/control/controlclient+
|
||||||
|
tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal
|
||||||
tailscale.com/hostinfo from tailscale.com/control/controlclient+
|
tailscale.com/hostinfo from tailscale.com/control/controlclient+
|
||||||
tailscale.com/ipn from tailscale.com/ipn/ipnlocal+
|
tailscale.com/ipn from tailscale.com/ipn/ipnlocal+
|
||||||
tailscale.com/ipn/ipnlocal from tailscale.com/ssh/tailssh+
|
tailscale.com/ipn/ipnlocal from tailscale.com/ssh/tailssh+
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package healthmsg contains some constants for health messages.
|
||||||
|
//
|
||||||
|
// It's a leaf so both the server and CLI can depend on it without bringing too
|
||||||
|
// much in to the CLI binary.
|
||||||
|
package healthmsg
|
||||||
|
|
||||||
|
const (
|
||||||
|
WarnAcceptRoutesOff = "Some peers are advertising routes but --accept-routes is false"
|
||||||
|
TailscaleSSHOnBut = "Tailscale SSH enabled, but " // + ... something from caller
|
||||||
|
)
|
|
@ -35,6 +35,7 @@ import (
|
||||||
"tailscale.com/doctor/routetable"
|
"tailscale.com/doctor/routetable"
|
||||||
"tailscale.com/envknob"
|
"tailscale.com/envknob"
|
||||||
"tailscale.com/health"
|
"tailscale.com/health"
|
||||||
|
"tailscale.com/health/healthmsg"
|
||||||
"tailscale.com/hostinfo"
|
"tailscale.com/hostinfo"
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
"tailscale.com/ipn/ipnstate"
|
"tailscale.com/ipn/ipnstate"
|
||||||
|
@ -585,7 +586,11 @@ func (b *LocalBackend) updateStatus(sb *ipnstate.StatusBuilder, extraLocked func
|
||||||
s.CurrentTailnet.MagicDNSSuffix = b.netMap.MagicDNSSuffix()
|
s.CurrentTailnet.MagicDNSSuffix = b.netMap.MagicDNSSuffix()
|
||||||
s.CurrentTailnet.MagicDNSEnabled = b.netMap.DNS.Proxied
|
s.CurrentTailnet.MagicDNSEnabled = b.netMap.DNS.Proxied
|
||||||
s.CurrentTailnet.Name = b.netMap.Domain
|
s.CurrentTailnet.Name = b.netMap.Domain
|
||||||
if prefs := b.pm.CurrentPrefs(); prefs.Valid() && !prefs.ExitNodeID().IsZero() {
|
if prefs := b.pm.CurrentPrefs(); prefs.Valid() {
|
||||||
|
if !prefs.RouteAll() && b.netMap.AnyPeersAdvertiseRoutes() {
|
||||||
|
s.Health = append(s.Health, healthmsg.WarnAcceptRoutesOff)
|
||||||
|
}
|
||||||
|
if !prefs.ExitNodeID().IsZero() {
|
||||||
if exitPeer, ok := b.netMap.PeerWithStableID(prefs.ExitNodeID()); ok {
|
if exitPeer, ok := b.netMap.PeerWithStableID(prefs.ExitNodeID()); ok {
|
||||||
var online = false
|
var online = false
|
||||||
if exitPeer.Online != nil {
|
if exitPeer.Online != nil {
|
||||||
|
@ -597,7 +602,7 @@ func (b *LocalBackend) updateStatus(sb *ipnstate.StatusBuilder, extraLocked func
|
||||||
TailscaleIPs: exitPeer.Addresses,
|
TailscaleIPs: exitPeer.Addresses,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -2172,12 +2177,12 @@ func (b *LocalBackend) sshOnButUnusableHealthCheckMessageLocked() (healthMessage
|
||||||
isAdmin := hasCapability(nm, tailcfg.CapabilityAdmin)
|
isAdmin := hasCapability(nm, tailcfg.CapabilityAdmin)
|
||||||
|
|
||||||
if !isAdmin {
|
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."
|
return healthmsg.TailscaleSSHOnBut + "access controls don't allow anyone to access this device. Ask your admin to update your tailnet's ACLs to allow access."
|
||||||
}
|
}
|
||||||
if !isDefault {
|
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 healthmsg.TailscaleSSHOnBut + "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"
|
return healthmsg.TailscaleSSHOnBut + "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 {
|
func (b *LocalBackend) isDefaultServerLocked() bool {
|
||||||
|
|
|
@ -84,6 +84,16 @@ type NetworkMap struct {
|
||||||
UserProfiles map[tailcfg.UserID]tailcfg.UserProfile
|
UserProfiles map[tailcfg.UserID]tailcfg.UserProfile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AnyPeersAdvertiseRoutes reports whether any peer is advertising non-exit node routes.
|
||||||
|
func (nm *NetworkMap) AnyPeersAdvertiseRoutes() bool {
|
||||||
|
for _, p := range nm.Peers {
|
||||||
|
if len(p.PrimaryRoutes) > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// PeerByTailscaleIP returns a peer's Node based on its Tailscale IP.
|
// PeerByTailscaleIP returns a peer's Node based on its Tailscale IP.
|
||||||
//
|
//
|
||||||
// If nm is nil or no peer is found, ok is false.
|
// If nm is nil or no peer is found, ok is false.
|
||||||
|
|
Loading…
Reference in New Issue