cmd/tailscale/cli: Improve messaging when Funnel is unavailable. (#6502)

There are three specific requirements for Funnel to work:
1) They must accept an invite.
2) They must enable HTTPS.
3) The "funnel" node attribute must be appropriately set up in the ACLs.

Signed-off-by: Shayne Sweeney <shayne@tailscale.com>
pull/6505/head
shayne 2 years ago committed by GitHub
parent 344abaf3d3
commit 0c4c66948b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -685,8 +685,8 @@ func (e *serveEnv) runServeFunnel(ctx context.Context, args []string) error {
if err != nil {
return fmt.Errorf("getting client status: %w", err)
}
if !slices.Contains(st.Self.Capabilities, tailcfg.NodeAttrFunnel) {
return errors.New("Funnel not available. See https://tailscale.com/s/no-funnel")
if err := checkHasAccess(st.Self.Capabilities); err != nil {
return err
}
dnsName := strings.TrimSuffix(st.Self.DNSName, ".")
hp := ipn.HostPort(dnsName + ":" + srvPortStr)
@ -709,3 +709,22 @@ func (e *serveEnv) runServeFunnel(ctx context.Context, args []string) error {
}
return nil
}
// checkHasAccess checks three things: 1) an invite was used to join the
// Funnel alpha; 2) HTTPS is enabled; 3) the node has the "funnel" attribute.
// If any of these are false, an error is returned describing the problem.
//
// The nodeAttrs arg should be the node's Self.Capabilities which should contain
// the attribute we're checking for and possibly warning-capabilities for Funnel.
func checkHasAccess(nodeAttrs []string) error {
if slices.Contains(nodeAttrs, tailcfg.CapabilityWarnFunnelNoInvite) {
return errors.New("Funnel not available; an invite is required to join the alpha. See https://tailscale.com/kb/1223/tailscale-funnel/.")
}
if slices.Contains(nodeAttrs, tailcfg.CapabilityWarnFunnelNoHTTPS) {
return errors.New("Funnel not available; HTTPS must be enabled. See https://tailscale.com/kb/1153/enabling-https/.")
}
if !slices.Contains(nodeAttrs, tailcfg.NodeAttrFunnel) {
return errors.New("Funnel not available; \"funnel\" node attribute not set. See https://tailscale.com/kb/1223/tailscale-funnel/.")
}
return nil
}

@ -49,6 +49,30 @@ func TestCleanMountPoint(t *testing.T) {
}
}
func TestCheckHasAccess(t *testing.T) {
tests := []struct {
caps []string
wantErr bool
}{
{[]string{}, true}, // No "funnel" attribute
{[]string{tailcfg.CapabilityWarnFunnelNoInvite}, true},
{[]string{tailcfg.CapabilityWarnFunnelNoHTTPS}, true},
{[]string{tailcfg.NodeAttrFunnel}, false},
}
for _, tt := range tests {
err := checkHasAccess(tt.caps)
switch {
case err != nil && tt.wantErr,
err == nil && !tt.wantErr:
continue
case tt.wantErr:
t.Fatalf("got no error, want error")
case !tt.wantErr:
t.Fatalf("got error %v, want no error", err)
}
}
}
func TestServeConfigMutations(t *testing.T) {
// Stateful mutations, starting from an empty config.
type step struct {

@ -1722,6 +1722,14 @@ const (
CapabilityWakeOnLAN = "https://tailscale.com/cap/wake-on-lan"
// CapabilityIngress grants the ability for a peer to send ingress traffic.
CapabilityIngress = "https://tailscale.com/cap/ingress"
// Funnel warning capabilities used for reporting errors to the user.
// CapabilityWarnFunnelNoInvite indicates an invite has not been accepted for the Funnel alpha.
CapabilityWarnFunnelNoInvite = "https://tailscale.com/cap/warn-funnel-no-invite"
// CapabilityWarnFunnelNoHTTPS indicates HTTPS has not been enabled for the tailnet.
CapabilityWarnFunnelNoHTTPS = "https://tailscale.com/cap/warn-funnel-no-https"
)
const (

Loading…
Cancel
Save