2020-10-20 14:29:13 +00:00
package tunnel
import (
2022-02-22 15:51:43 +00:00
"encoding/json"
2020-10-20 14:29:13 +00:00
"fmt"
"net/url"
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
2021-03-08 16:46:23 +00:00
"github.com/cloudflare/cloudflared/config"
2020-10-20 14:29:13 +00:00
"github.com/cloudflare/cloudflared/ingress"
2020-11-25 06:55:13 +00:00
"github.com/pkg/errors"
"github.com/urfave/cli/v2"
2020-10-20 14:29:13 +00:00
)
2022-02-22 15:51:43 +00:00
const ingressDataJSONFlagName = "json"
var ingressDataJSON = & cli . StringFlag {
Name : ingressDataJSONFlagName ,
Aliases : [ ] string { "j" } ,
Usage : ` Accepts data in the form of json as an input rather than read from a file ` ,
EnvVars : [ ] string { "TUNNEL_INGRESS_VALIDATE_JSON" } ,
}
2020-10-20 14:29:13 +00:00
func buildIngressSubcommand ( ) * cli . Command {
return & cli . Command {
Name : "ingress" ,
Category : "Tunnel" ,
Usage : "Validate and test cloudflared tunnel's ingress configuration" ,
UsageText : "cloudflared tunnel [--config FILEPATH] ingress COMMAND [arguments...]" ,
Hidden : true ,
Description : ` Cloudflared lets you route traffic from the internet to multiple different addresses on your
origin . Multiple - origin routing is configured by a set of rules . Each rule matches traffic
by its hostname or path , and routes it to an address . These rules are configured under the
' ingress ' key of your config . yaml , for example :
ingress :
- hostname : www . example . com
service : https : //localhost:8000
- hostname : * . example . xyz
path : / [ a - zA - Z ] + . html
service : https : //localhost:8001
- hostname : *
service : https : //localhost:8002
To ensure cloudflared can route all incoming requests , the last rule must be a catch - all
rule that matches all traffic . You can validate these rules with the ' ingress validate '
command , and test which rule matches a particular URL with ' ingress rule < URL > ' .
Multiple - origin routing is incompatible with the -- url flag . ` ,
Subcommands : [ ] * cli . Command { buildValidateIngressCommand ( ) , buildTestURLCommand ( ) } ,
}
}
func buildValidateIngressCommand ( ) * cli . Command {
return & cli . Command {
Name : "validate" ,
2021-05-06 20:10:47 +00:00
Action : cliutil . ConfiguredActionWithWarnings ( validateIngressCommand ) ,
2020-10-20 14:29:13 +00:00
Usage : "Validate the ingress configuration " ,
UsageText : "cloudflared tunnel [--config FILEPATH] ingress validate" ,
Description : "Validates the configuration file, ensuring your ingress rules are OK." ,
2022-02-22 15:51:43 +00:00
Flags : [ ] cli . Flag { ingressDataJSON } ,
2020-10-20 14:29:13 +00:00
}
}
func buildTestURLCommand ( ) * cli . Command {
return & cli . Command {
Name : "rule" ,
2021-03-16 22:36:46 +00:00
Action : cliutil . ConfiguredAction ( testURLCommand ) ,
2020-10-20 14:29:13 +00:00
Usage : "Check which ingress rule matches a given request URL" ,
UsageText : "cloudflared tunnel [--config FILEPATH] ingress rule URL" ,
ArgsUsage : "URL" ,
Description : "Check which ingress rule matches a given request URL. " +
"Ingress rules match a request's hostname and path. Hostname is " +
"optional and is either a full hostname like `www.example.com` or a " +
"hostname with a `*` for its subdomains, e.g. `*.example.com`. Path " +
"is optional and matches a regular expression, like `/[a-zA-Z0-9_]+.html`" ,
}
}
// validateIngressCommand check the syntax of the ingress rules in the cloudflared config file
2021-05-06 20:10:47 +00:00
func validateIngressCommand ( c * cli . Context , warnings string ) error {
2022-02-22 15:51:43 +00:00
conf , err := getConfiguration ( c )
if err != nil {
return err
2020-11-09 17:15:10 +00:00
}
2022-02-22 15:51:43 +00:00
2020-10-30 21:37:40 +00:00
if _ , err := ingress . ParseIngress ( conf ) ; err != nil {
2020-10-20 14:29:13 +00:00
return errors . Wrap ( err , "Validation failed" )
}
if c . IsSet ( "url" ) {
return ingress . ErrURLIncompatibleWithIngress
}
2021-05-06 20:10:47 +00:00
if warnings != "" {
fmt . Println ( "Warning: unused keys detected in your config file. Here is a list of unused keys:" )
fmt . Println ( warnings )
return nil
}
2020-10-20 14:29:13 +00:00
fmt . Println ( "OK" )
return nil
}
2022-02-22 15:51:43 +00:00
func getConfiguration ( c * cli . Context ) ( * config . Configuration , error ) {
var conf * config . Configuration
if c . IsSet ( ingressDataJSONFlagName ) {
ingressJSON := c . String ( ingressDataJSONFlagName )
fmt . Println ( "Validating rules from cmdline flag --json" )
err := json . Unmarshal ( [ ] byte ( ingressJSON ) , & conf )
return conf , err
}
conf = config . GetConfiguration ( )
if conf . Source ( ) == "" {
return nil , errors . New ( "No configuration file was found. Please create one, or use the --config flag to specify its filepath. You can use the help command to learn more about configuration files" )
}
fmt . Println ( "Validating rules from" , conf . Source ( ) )
return conf , nil
}
2020-10-20 14:29:13 +00:00
// testURLCommand checks which ingress rule matches the given URL.
func testURLCommand ( c * cli . Context ) error {
requestArg := c . Args ( ) . First ( )
if requestArg == "" {
return errors . New ( "cloudflared tunnel rule expects a single argument, the URL to test" )
}
requestURL , err := url . Parse ( requestArg )
if err != nil {
return fmt . Errorf ( "%s is not a valid URL" , requestArg )
}
if requestURL . Hostname ( ) == "" && requestURL . Scheme == "" {
return fmt . Errorf ( "%s doesn't have a hostname, consider adding a scheme" , requestArg )
}
conf := config . GetConfiguration ( )
fmt . Println ( "Using rules from" , conf . Source ( ) )
2020-10-30 21:37:40 +00:00
ing , err := ingress . ParseIngress ( conf )
2020-10-20 14:29:13 +00:00
if err != nil {
return errors . Wrap ( err , "Validation failed" )
}
2020-10-15 21:41:03 +00:00
_ , i := ing . FindMatchingRule ( requestURL . Hostname ( ) , requestURL . Path )
2020-10-20 14:29:13 +00:00
fmt . Printf ( "Matched rule #%d\n" , i + 1 )
fmt . Println ( ing . Rules [ i ] . MultiLineString ( ) )
return nil
}