TUN-7259: Add warning for missing ingress rules
Providing no ingress rules in the configuration file or via the CLI will now provide a warning and return 502 for all incoming HTTP requests.
This commit is contained in:
parent
ede3c8e056
commit
7b8b3f73e7
|
@ -1,3 +1,7 @@
|
||||||
|
## 2023.3.1
|
||||||
|
### Breaking Change
|
||||||
|
- Running a tunnel without ingress rules defined in configuration file nor from the CLI flags will no longer provide a default ingress rule to localhost:8080 and instead will return HTTP response code 502 for all incoming HTTP requests.
|
||||||
|
|
||||||
## 2023.2.2
|
## 2023.2.2
|
||||||
### Notices
|
### Notices
|
||||||
- Legacy tunnels were officially deprecated on December 1, 2022. Starting with this version, cloudflared no longer supports connecting legacy tunnels.
|
- Legacy tunnels were officially deprecated on December 1, 2022. Starting with this version, cloudflared no longer supports connecting legacy tunnels.
|
||||||
|
|
|
@ -227,22 +227,10 @@ func prepareTunnelConfig(
|
||||||
Arch: info.OSArch(),
|
Arch: info.OSArch(),
|
||||||
}
|
}
|
||||||
cfg := config.GetConfiguration()
|
cfg := config.GetConfiguration()
|
||||||
ingressRules, err := ingress.ParseIngress(cfg)
|
ingressRules, err := ingress.ParseIngressFromConfigAndCLI(cfg, c, log)
|
||||||
if err != nil && err != ingress.ErrNoIngressRules {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if c.IsSet("url") || c.IsSet(ingress.HelloWorldFlag) || c.IsSet(config.BastionFlag) {
|
|
||||||
// Ingress rules cannot be provided with --url, --hello-world or --bastion flag
|
|
||||||
if !ingressRules.IsEmpty() {
|
|
||||||
return nil, nil, ingress.ErrURLIncompatibleWithIngress
|
|
||||||
}
|
|
||||||
// Only for quick or adhoc tunnels will we attempt to parse:
|
|
||||||
// --url, --hello-world, --bastion, or --unix-socket flag for a tunnel ingress rule
|
|
||||||
ingressRules, err = ingress.NewSingleOrigin(c, false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protocolSelector, err := connection.NewProtocolSelector(transportProtocol, namedTunnel.Credentials.AccountTag, c.IsSet(TunnelTokenFlag), c.Bool("post-quantum"), edgediscovery.ProtocolPercentage, connection.ResolveTTL, log)
|
protocolSelector, err := connection.NewProtocolSelector(transportProtocol, namedTunnel.Credentials.AccountTag, c.IsSet(TunnelTokenFlag), c.Bool("post-quantum"), edgediscovery.ProtocolPercentage, connection.ResolveTTL, log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -30,6 +30,7 @@ class NamedTunnelBaseConfig(BaseConfig):
|
||||||
tunnel: str = None
|
tunnel: str = None
|
||||||
credentials_file: str = None
|
credentials_file: str = None
|
||||||
ingress: list = None
|
ingress: list = None
|
||||||
|
hostname: str = None
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
if self.tunnel is None:
|
if self.tunnel is None:
|
||||||
|
@ -63,7 +64,7 @@ class NamedTunnelConfig(NamedTunnelBaseConfig):
|
||||||
self.merge_config(additional_config))
|
self.merge_config(additional_config))
|
||||||
|
|
||||||
def get_url(self):
|
def get_url(self):
|
||||||
return "https://" + self.ingress[0]['hostname']
|
return "https://" + self.hostname
|
||||||
|
|
||||||
def base_config(self):
|
def base_config(self):
|
||||||
config = self.full_config.copy()
|
config = self.full_config.copy()
|
||||||
|
|
|
@ -26,7 +26,7 @@ def component_tests_config():
|
||||||
config = yaml.safe_load(stream)
|
config = yaml.safe_load(stream)
|
||||||
LOGGER.info(f"component tests base config {config}")
|
LOGGER.info(f"component tests base config {config}")
|
||||||
|
|
||||||
def _component_tests_config(additional_config={}, cfd_mode=CfdModes.NAMED, run_proxy_dns=True):
|
def _component_tests_config(additional_config={}, cfd_mode=CfdModes.NAMED, run_proxy_dns=True, provide_ingress=True):
|
||||||
if run_proxy_dns:
|
if run_proxy_dns:
|
||||||
# Regression test for TUN-4177, running with proxy-dns should not prevent tunnels from running.
|
# Regression test for TUN-4177, running with proxy-dns should not prevent tunnels from running.
|
||||||
# So we run all tests with it.
|
# So we run all tests with it.
|
||||||
|
@ -36,12 +36,21 @@ def component_tests_config():
|
||||||
additional_config.pop("proxy-dns", None)
|
additional_config.pop("proxy-dns", None)
|
||||||
additional_config.pop("proxy-dns-port", None)
|
additional_config.pop("proxy-dns-port", None)
|
||||||
|
|
||||||
|
# Allows the ingress rules to be omitted from the provided config
|
||||||
|
ingress = []
|
||||||
|
if provide_ingress:
|
||||||
|
ingress = config['ingress']
|
||||||
|
|
||||||
|
# Provide the hostname to allow routing to the tunnel even if the ingress rule isn't defined in the config
|
||||||
|
hostname = config['ingress'][0]['hostname']
|
||||||
|
|
||||||
if cfd_mode is CfdModes.NAMED:
|
if cfd_mode is CfdModes.NAMED:
|
||||||
return NamedTunnelConfig(additional_config=additional_config,
|
return NamedTunnelConfig(additional_config=additional_config,
|
||||||
cloudflared_binary=config['cloudflared_binary'],
|
cloudflared_binary=config['cloudflared_binary'],
|
||||||
tunnel=config['tunnel'],
|
tunnel=config['tunnel'],
|
||||||
credentials_file=config['credentials_file'],
|
credentials_file=config['credentials_file'],
|
||||||
ingress=config['ingress'])
|
ingress=ingress,
|
||||||
|
hostname=hostname)
|
||||||
elif cfd_mode is CfdModes.PROXY_DNS:
|
elif cfd_mode is CfdModes.PROXY_DNS:
|
||||||
return ProxyDnsConfig(cloudflared_binary=config['cloudflared_binary'])
|
return ProxyDnsConfig(cloudflared_binary=config['cloudflared_binary'])
|
||||||
elif cfd_mode is CfdModes.QUICK:
|
elif cfd_mode is CfdModes.QUICK:
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
import requests
|
|
||||||
from conftest import CfdModes
|
from conftest import CfdModes
|
||||||
from constants import METRICS_PORT
|
from constants import METRICS_PORT
|
||||||
from util import LOGGER, start_cloudflared, wait_tunnel_ready, get_quicktunnel_url, send_requests
|
from util import LOGGER, start_cloudflared, wait_tunnel_ready, get_quicktunnel_url, send_requests
|
||||||
|
|
||||||
class TestCLI:
|
class TestQuickTunnels:
|
||||||
def test_quick_tunnel(self, tmp_path, component_tests_config):
|
def test_quick_tunnel(self, tmp_path, component_tests_config):
|
||||||
config = component_tests_config(cfd_mode=CfdModes.QUICK, run_proxy_dns=False)
|
config = component_tests_config(cfd_mode=CfdModes.QUICK, run_proxy_dns=False)
|
||||||
LOGGER.debug(config)
|
LOGGER.debug(config)
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
import requests
|
||||||
|
from conftest import CfdModes
|
||||||
|
from constants import METRICS_PORT, MAX_RETRIES, BACKOFF_SECS
|
||||||
|
from retrying import retry
|
||||||
|
from util import LOGGER, start_cloudflared, wait_tunnel_ready, send_requests
|
||||||
|
|
||||||
|
class TestTunnel:
|
||||||
|
'''Test tunnels with no ingress rules from config.yaml but ingress rules from CLI only'''
|
||||||
|
|
||||||
|
def test_tunnel_hello_world(self, tmp_path, component_tests_config):
|
||||||
|
config = component_tests_config(cfd_mode=CfdModes.NAMED, run_proxy_dns=False, provide_ingress=False)
|
||||||
|
LOGGER.debug(config)
|
||||||
|
with start_cloudflared(tmp_path, config, cfd_args=["run", "--hello-world"], new_process=True):
|
||||||
|
wait_tunnel_ready(tunnel_url=config.get_url(),
|
||||||
|
require_min_connections=4)
|
||||||
|
|
||||||
|
def test_tunnel_url(self, tmp_path, component_tests_config):
|
||||||
|
config = component_tests_config(cfd_mode=CfdModes.NAMED, run_proxy_dns=False, provide_ingress=False)
|
||||||
|
LOGGER.debug(config)
|
||||||
|
with start_cloudflared(tmp_path, config, cfd_args=["run", "--url", f"http://localhost:{METRICS_PORT}/"], new_process=True):
|
||||||
|
wait_tunnel_ready(require_min_connections=4)
|
||||||
|
send_requests(config.get_url()+"/ready", 3, True)
|
||||||
|
|
||||||
|
def test_tunnel_no_ingress(self, tmp_path, component_tests_config):
|
||||||
|
'''
|
||||||
|
Running a tunnel with no ingress rules provided from either config.yaml or CLI will still work but return 502
|
||||||
|
for all incoming requests.
|
||||||
|
'''
|
||||||
|
config = component_tests_config(cfd_mode=CfdModes.NAMED, run_proxy_dns=False, provide_ingress=False)
|
||||||
|
LOGGER.debug(config)
|
||||||
|
with start_cloudflared(tmp_path, config, cfd_args=["run"], new_process=True):
|
||||||
|
wait_tunnel_ready(require_min_connections=4)
|
||||||
|
resp = send_request(config.get_url()+"/")
|
||||||
|
assert resp.status_code == 502, "Expected cloudflared to return 502 for all requests with no ingress defined"
|
||||||
|
resp = send_request(config.get_url()+"/test")
|
||||||
|
assert resp.status_code == 502, "Expected cloudflared to return 502 for all requests with no ingress defined"
|
||||||
|
|
||||||
|
|
||||||
|
@retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000)
|
||||||
|
def send_request(url):
|
||||||
|
with requests.Session() as s:
|
||||||
|
return s.get(url, timeout=BACKOFF_SECS)
|
|
@ -113,7 +113,7 @@ func (rc *RemoteConfig) UnmarshalJSON(b []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func originRequestFromSingeRule(c *cli.Context) OriginRequestConfig {
|
func originRequestFromSingleRule(c *cli.Context) OriginRequestConfig {
|
||||||
var connectTimeout = defaultHTTPConnectTimeout
|
var connectTimeout = defaultHTTPConnectTimeout
|
||||||
var tlsTimeout = defaultTLSTimeout
|
var tlsTimeout = defaultTLSTimeout
|
||||||
var tcpKeepAlive = defaultTCPKeepAlive
|
var tcpKeepAlive = defaultTCPKeepAlive
|
||||||
|
|
|
@ -411,7 +411,7 @@ func TestDefaultConfigFromCLI(t *testing.T) {
|
||||||
KeepAliveTimeout: defaultKeepAliveTimeout,
|
KeepAliveTimeout: defaultKeepAliveTimeout,
|
||||||
ProxyAddress: defaultProxyAddress,
|
ProxyAddress: defaultProxyAddress,
|
||||||
}
|
}
|
||||||
actual := originRequestFromSingeRule(c)
|
actual := originRequestFromSingleRule(c)
|
||||||
require.Equal(t, expected, actual)
|
require.Equal(t, expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrNoIngressRules = errors.New("The config file doesn't contain any ingress rules")
|
ErrNoIngressRules = errors.New("The config file doesn't contain any ingress rules")
|
||||||
|
ErrNoIngressRulesCLI = errors.New("No ingress rules were defined in provided config (if any) nor from the cli, cloudflared will return 502 for all incoming HTTP requests")
|
||||||
errLastRuleNotCatchAll = errors.New("The last ingress rule must match all URLs (i.e. it should not have a hostname or path filter)")
|
errLastRuleNotCatchAll = errors.New("The last ingress rule must match all URLs (i.e. it should not have a hostname or path filter)")
|
||||||
errBadWildcard = errors.New("Hostname patterns can have at most one wildcard character (\"*\") and it can only be used for subdomains, e.g. \"*.example.com\"")
|
errBadWildcard = errors.New("Hostname patterns can have at most one wildcard character (\"*\") and it can only be used for subdomains, e.g. \"*.example.com\"")
|
||||||
errHostnameContainsPort = errors.New("Hostname cannot contain a port")
|
errHostnameContainsPort = errors.New("Hostname cannot contain a port")
|
||||||
|
@ -70,16 +71,52 @@ type Ingress struct {
|
||||||
Defaults OriginRequestConfig `json:"originRequest"`
|
Defaults OriginRequestConfig `json:"originRequest"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSingleOrigin constructs an Ingress set with only one rule, constructed from
|
// ParseIngress parses ingress rules, but does not send HTTP requests to the origins.
|
||||||
// CLI parameters for quick tunnels like --url or --no-chunked-encoding.
|
func ParseIngress(conf *config.Configuration) (Ingress, error) {
|
||||||
func NewSingleOrigin(c *cli.Context, allowURLFromArgs bool) (Ingress, error) {
|
if len(conf.Ingress) == 0 {
|
||||||
|
return Ingress{}, ErrNoIngressRules
|
||||||
|
}
|
||||||
|
return validateIngress(conf.Ingress, originRequestFromConfig(conf.OriginRequest))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseIngressFromConfigAndCLI will parse the configuration rules from config files for ingress
|
||||||
|
// rules and then attempt to parse CLI for ingress rules.
|
||||||
|
// Will always return at least one valid ingress rule. If none are provided by the user, the default
|
||||||
|
// will be to return 502 status code for all incoming requests.
|
||||||
|
func ParseIngressFromConfigAndCLI(conf *config.Configuration, c *cli.Context, log *zerolog.Logger) (Ingress, error) {
|
||||||
|
// Attempt to parse ingress rules from configuration
|
||||||
|
ingressRules, err := ParseIngress(conf)
|
||||||
|
if err == nil && !ingressRules.IsEmpty() {
|
||||||
|
return ingressRules, nil
|
||||||
|
}
|
||||||
|
if err != ErrNoIngressRules {
|
||||||
|
return Ingress{}, err
|
||||||
|
}
|
||||||
|
// Attempt to parse ingress rules from CLI:
|
||||||
|
// --url or --unix-socket flag for a tunnel HTTP ingress
|
||||||
|
// --hello-world for a basic HTTP ingress self-served
|
||||||
|
// --bastion for ssh bastion service
|
||||||
|
ingressRules, err = parseCLIIngress(c, false)
|
||||||
|
if errors.Is(err, ErrNoIngressRulesCLI) {
|
||||||
|
log.Warn().Msgf(ErrNoIngressRulesCLI.Error())
|
||||||
|
return newDefaultOrigin(c, log), nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return Ingress{}, err
|
||||||
|
}
|
||||||
|
return ingressRules, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseCLIIngress constructs an Ingress set with only one rule constructed from
|
||||||
|
// CLI parameters: --url, --hello-world, --bastion, or --unix-socket
|
||||||
|
func parseCLIIngress(c *cli.Context, allowURLFromArgs bool) (Ingress, error) {
|
||||||
service, err := parseSingleOriginService(c, allowURLFromArgs)
|
service, err := parseSingleOriginService(c, allowURLFromArgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Ingress{}, err
|
return Ingress{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct an Ingress with the single rule.
|
// Construct an Ingress with the single rule.
|
||||||
defaults := originRequestFromSingeRule(c)
|
defaults := originRequestFromSingleRule(c)
|
||||||
ing := Ingress{
|
ing := Ingress{
|
||||||
Rules: []Rule{
|
Rules: []Rule{
|
||||||
{
|
{
|
||||||
|
@ -92,6 +129,22 @@ func NewSingleOrigin(c *cli.Context, allowURLFromArgs bool) (Ingress, error) {
|
||||||
return ing, err
|
return ing, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newDefaultOrigin always returns a 502 response code to help indicate that there are no ingress
|
||||||
|
// rules setup, but the tunnel is reachable.
|
||||||
|
func newDefaultOrigin(c *cli.Context, log *zerolog.Logger) Ingress {
|
||||||
|
noRulesService := newDefaultStatusCode(log)
|
||||||
|
defaults := originRequestFromSingleRule(c)
|
||||||
|
ingress := Ingress{
|
||||||
|
Rules: []Rule{
|
||||||
|
{
|
||||||
|
Service: &noRulesService,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Defaults: defaults,
|
||||||
|
}
|
||||||
|
return ingress
|
||||||
|
}
|
||||||
|
|
||||||
// WarpRoutingService starts a tcp stream between the origin and requests from
|
// WarpRoutingService starts a tcp stream between the origin and requests from
|
||||||
// warp clients.
|
// warp clients.
|
||||||
type WarpRoutingService struct {
|
type WarpRoutingService struct {
|
||||||
|
@ -137,8 +190,7 @@ func parseSingleOriginService(c *cli.Context, allowURLFromArgs bool) (OriginServ
|
||||||
}
|
}
|
||||||
return &unixSocketPath{path: path, scheme: "http"}, nil
|
return &unixSocketPath{path: path, scheme: "http"}, nil
|
||||||
}
|
}
|
||||||
u, err := url.Parse("http://localhost:8080")
|
return nil, ErrNoIngressRulesCLI
|
||||||
return &httpService{url: u}, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsEmpty checks if there are any ingress rules.
|
// IsEmpty checks if there are any ingress rules.
|
||||||
|
@ -335,14 +387,6 @@ func (e errRuleShouldNotBeCatchAll) Error() string {
|
||||||
"will never be triggered.", e.index+1, e.hostname)
|
"will never be triggered.", e.index+1, e.hostname)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseIngress parses ingress rules, but does not send HTTP requests to the origins.
|
|
||||||
func ParseIngress(conf *config.Configuration) (Ingress, error) {
|
|
||||||
if len(conf.Ingress) == 0 {
|
|
||||||
return Ingress{}, ErrNoIngressRules
|
|
||||||
}
|
|
||||||
return validateIngress(conf.Ingress, originRequestFromConfig(conf.OriginRequest))
|
|
||||||
}
|
|
||||||
|
|
||||||
func isHTTPService(url *url.URL) bool {
|
func isHTTPService(url *url.URL) bool {
|
||||||
return url.Scheme == "http" || url.Scheme == "https" || url.Scheme == "ws" || url.Scheme == "wss"
|
return url.Scheme == "http" || url.Scheme == "https" || url.Scheme == "ws" || url.Scheme == "wss"
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ ingress:
|
||||||
require.Equal(t, "https", s.scheme)
|
require.Equal(t, "https", s.scheme)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_parseIngress(t *testing.T) {
|
func TestParseIngress(t *testing.T) {
|
||||||
localhost8000 := MustParseURL(t, "https://localhost:8000")
|
localhost8000 := MustParseURL(t, "https://localhost:8000")
|
||||||
localhost8001 := MustParseURL(t, "https://localhost:8001")
|
localhost8001 := MustParseURL(t, "https://localhost:8001")
|
||||||
fourOhFour := newStatusCode(404)
|
fourOhFour := newStatusCode(404)
|
||||||
|
@ -517,7 +517,7 @@ func TestSingleOriginSetsConfig(t *testing.T) {
|
||||||
|
|
||||||
allowURLFromArgs := false
|
allowURLFromArgs := false
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
ingress, err := NewSingleOrigin(cliCtx, allowURLFromArgs)
|
ingress, err := parseCLIIngress(cliCtx, allowURLFromArgs)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, config.CustomDuration{Duration: time.Second}, ingress.Rules[0].Config.ConnectTimeout)
|
assert.Equal(t, config.CustomDuration{Duration: time.Second}, ingress.Rules[0].Config.ConnectTimeout)
|
||||||
|
@ -537,6 +537,119 @@ func TestSingleOriginSetsConfig(t *testing.T) {
|
||||||
assert.Equal(t, socksProxy, ingress.Rules[0].Config.ProxyType)
|
assert.Equal(t, socksProxy, ingress.Rules[0].Config.ProxyType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSingleOriginServices(t *testing.T) {
|
||||||
|
host := "://localhost:8080"
|
||||||
|
httpURL := urlMustParse("http" + host)
|
||||||
|
tcpURL := urlMustParse("tcp" + host)
|
||||||
|
unix := "unix://service"
|
||||||
|
newCli := func(params ...string) *cli.Context {
|
||||||
|
flagSet := flag.NewFlagSet(t.Name(), flag.PanicOnError)
|
||||||
|
flagSet.Bool("hello-world", false, "")
|
||||||
|
flagSet.Bool("bastion", false, "")
|
||||||
|
flagSet.String("url", "", "")
|
||||||
|
flagSet.String("unix-socket", "", "")
|
||||||
|
cliCtx := cli.NewContext(cli.NewApp(), flagSet, nil)
|
||||||
|
for i := 0; i < len(params); i += 2 {
|
||||||
|
cliCtx.Set(params[i], params[i+1])
|
||||||
|
}
|
||||||
|
|
||||||
|
return cliCtx
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
cli *cli.Context
|
||||||
|
expectedService OriginService
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid hello-world",
|
||||||
|
cli: newCli("hello-world", "true"),
|
||||||
|
expectedService: &helloWorld{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid bastion",
|
||||||
|
cli: newCli("bastion", "true"),
|
||||||
|
expectedService: newBastionService(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid http url",
|
||||||
|
cli: newCli("url", httpURL.String()),
|
||||||
|
expectedService: &httpService{url: httpURL},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid tcp url",
|
||||||
|
cli: newCli("url", tcpURL.String()),
|
||||||
|
expectedService: newTCPOverWSService(tcpURL),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid unix-socket",
|
||||||
|
cli: newCli("unix-socket", unix),
|
||||||
|
expectedService: &unixSocketPath{path: unix, scheme: "http"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "No origins defined",
|
||||||
|
cli: newCli(),
|
||||||
|
err: ErrNoIngressRulesCLI,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
ingress, err := parseCLIIngress(test.cli, false)
|
||||||
|
require.Equal(t, err, test.err)
|
||||||
|
if test.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.Equal(t, 1, len(ingress.Rules))
|
||||||
|
rule := ingress.Rules[0]
|
||||||
|
require.Equal(t, test.expectedService, rule.Service)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func urlMustParse(s string) *url.URL {
|
||||||
|
u, err := url.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSingleOriginServices_URL(t *testing.T) {
|
||||||
|
host := "://localhost:8080"
|
||||||
|
newCli := func(param string, value string) *cli.Context {
|
||||||
|
flagSet := flag.NewFlagSet(t.Name(), flag.PanicOnError)
|
||||||
|
flagSet.String("url", "", "")
|
||||||
|
cliCtx := cli.NewContext(cli.NewApp(), flagSet, nil)
|
||||||
|
cliCtx.Set(param, value)
|
||||||
|
return cliCtx
|
||||||
|
}
|
||||||
|
|
||||||
|
httpTests := []string{"http", "https"}
|
||||||
|
for _, test := range httpTests {
|
||||||
|
t.Run(test, func(t *testing.T) {
|
||||||
|
url := urlMustParse(test + host)
|
||||||
|
ingress, err := parseCLIIngress(newCli("url", url.String()), false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(ingress.Rules))
|
||||||
|
rule := ingress.Rules[0]
|
||||||
|
require.Equal(t, &httpService{url: url}, rule.Service)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
tcpTests := []string{"ssh", "rdp", "smb", "tcp"}
|
||||||
|
for _, test := range tcpTests {
|
||||||
|
t.Run(test, func(t *testing.T) {
|
||||||
|
url := urlMustParse(test + host)
|
||||||
|
ingress, err := parseCLIIngress(newCli("url", url.String()), false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(ingress.Rules))
|
||||||
|
rule := ingress.Rules[0]
|
||||||
|
require.Equal(t, newTCPOverWSService(url), rule.Service)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestFindMatchingRule(t *testing.T) {
|
func TestFindMatchingRule(t *testing.T) {
|
||||||
ingress := Ingress{
|
ingress := Ingress{
|
||||||
Rules: []Rule{
|
Rules: []Rule{
|
||||||
|
|
|
@ -44,6 +44,9 @@ func (o *httpService) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *statusCode) RoundTrip(_ *http.Request) (*http.Response, error) {
|
func (o *statusCode) RoundTrip(_ *http.Request) (*http.Response, error) {
|
||||||
|
if o.defaultResp {
|
||||||
|
o.log.Warn().Msgf(ErrNoIngressRulesCLI.Error())
|
||||||
|
}
|
||||||
resp := &http.Response{
|
resp := &http.Response{
|
||||||
StatusCode: o.code,
|
StatusCode: o.code,
|
||||||
Status: fmt.Sprintf("%d %s", o.code, http.StatusText(o.code)),
|
Status: fmt.Sprintf("%d %s", o.code, http.StatusText(o.code)),
|
||||||
|
|
|
@ -247,13 +247,26 @@ func (o helloWorld) MarshalJSON() ([]byte, error) {
|
||||||
// Typical use-case is "user wants the catch-all rule to just respond 404".
|
// Typical use-case is "user wants the catch-all rule to just respond 404".
|
||||||
type statusCode struct {
|
type statusCode struct {
|
||||||
code int
|
code int
|
||||||
|
|
||||||
|
// Set only when the user has not defined any ingress rules
|
||||||
|
defaultResp bool
|
||||||
|
log *zerolog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func newStatusCode(status int) statusCode {
|
func newStatusCode(status int) statusCode {
|
||||||
return statusCode{code: status}
|
return statusCode{code: status}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// default status code (502) that is returned for requests to cloudflared that don't have any ingress rules setup
|
||||||
|
func newDefaultStatusCode(log *zerolog.Logger) statusCode {
|
||||||
|
return statusCode{code: 502, defaultResp: true, log: log}
|
||||||
|
}
|
||||||
|
|
||||||
func (o *statusCode) String() string {
|
func (o *statusCode) String() string {
|
||||||
|
// returning a different service name can help with identifying users via config that don't setup ingress rules
|
||||||
|
if o.defaultResp {
|
||||||
|
return "default_http_status:502"
|
||||||
|
}
|
||||||
return fmt.Sprintf("http_status:%d", o.code)
|
return fmt.Sprintf("http_status:%d", o.code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -149,8 +149,7 @@ func TestProxySingleOrigin(t *testing.T) {
|
||||||
err := cliCtx.Set("hello-world", "true")
|
err := cliCtx.Set("hello-world", "true")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
allowURLFromArgs := false
|
ingressRule, err := ingress.ParseIngressFromConfigAndCLI(&config.Configuration{}, cliCtx, &log)
|
||||||
ingressRule, err := ingress.NewSingleOrigin(cliCtx, allowURLFromArgs)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.NoError(t, ingressRule.StartOrigins(&log, ctx.Done()))
|
require.NoError(t, ingressRule.StartOrigins(&log, ctx.Done()))
|
||||||
|
|
Loading…
Reference in New Issue