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
parent
344abaf3d3
commit
0c4c66948b
|
@ -685,8 +685,8 @@ func (e *serveEnv) runServeFunnel(ctx context.Context, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("getting client status: %w", err)
|
return fmt.Errorf("getting client status: %w", err)
|
||||||
}
|
}
|
||||||
if !slices.Contains(st.Self.Capabilities, tailcfg.NodeAttrFunnel) {
|
if err := checkHasAccess(st.Self.Capabilities); err != nil {
|
||||||
return errors.New("Funnel not available. See https://tailscale.com/s/no-funnel")
|
return err
|
||||||
}
|
}
|
||||||
dnsName := strings.TrimSuffix(st.Self.DNSName, ".")
|
dnsName := strings.TrimSuffix(st.Self.DNSName, ".")
|
||||||
hp := ipn.HostPort(dnsName + ":" + srvPortStr)
|
hp := ipn.HostPort(dnsName + ":" + srvPortStr)
|
||||||
|
@ -709,3 +709,22 @@ func (e *serveEnv) runServeFunnel(ctx context.Context, args []string) error {
|
||||||
}
|
}
|
||||||
return nil
|
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) {
|
func TestServeConfigMutations(t *testing.T) {
|
||||||
// Stateful mutations, starting from an empty config.
|
// Stateful mutations, starting from an empty config.
|
||||||
type step struct {
|
type step struct {
|
||||||
|
|
|
@ -1722,6 +1722,14 @@ const (
|
||||||
CapabilityWakeOnLAN = "https://tailscale.com/cap/wake-on-lan"
|
CapabilityWakeOnLAN = "https://tailscale.com/cap/wake-on-lan"
|
||||||
// CapabilityIngress grants the ability for a peer to send ingress traffic.
|
// CapabilityIngress grants the ability for a peer to send ingress traffic.
|
||||||
CapabilityIngress = "https://tailscale.com/cap/ingress"
|
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 (
|
const (
|
||||||
|
|
Loading…
Reference in New Issue