@ -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 err ors. 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
}