TUN-7245: Add bastion flag to origin service check

This commit is contained in:
Devin Carr 2023-03-06 15:19:10 -08:00
parent bf3136debb
commit 93f8f6b55c
9 changed files with 75 additions and 50 deletions

View File

@ -199,7 +199,7 @@ func TunnelCommand(c *cli.Context) error {
// Run a quick tunnel // Run a quick tunnel
// A unauthenticated named tunnel hosted on <random>.<quick-tunnels-service>.com // A unauthenticated named tunnel hosted on <random>.<quick-tunnels-service>.com
// We don't support running proxy-dns and a quick tunnel at the same time as the same process // We don't support running proxy-dns and a quick tunnel at the same time as the same process
shouldRunQuickTunnel := c.IsSet("url") || c.IsSet("hello-world") shouldRunQuickTunnel := c.IsSet("url") || c.IsSet(ingress.HelloWorldFlag)
if !c.IsSet("proxy-dns") && c.String("quick-service") != "" && shouldRunQuickTunnel { if !c.IsSet("proxy-dns") && c.String("quick-service") != "" && shouldRunQuickTunnel {
return RunQuickTunnel(sc) return RunQuickTunnel(sc)
} }
@ -215,6 +215,9 @@ func TunnelCommand(c *cli.Context) error {
} }
if c.IsSet("proxy-dns") { if c.IsSet("proxy-dns") {
if shouldRunQuickTunnel {
return fmt.Errorf("running a quick tunnel with `proxy-dns` is not supported")
}
// NamedTunnelProperties are nil since proxy dns server does not need it. // NamedTunnelProperties are nil since proxy dns server does not need it.
// This is supported for legacy reasons: dns proxy server is not a tunnel and ideally should // This is supported for legacy reasons: dns proxy server is not a tunnel and ideally should
// not run as part of cloudflared tunnel. // not run as part of cloudflared tunnel.
@ -786,7 +789,7 @@ func configureProxyFlags(shouldHide bool) []cli.Flag {
Hidden: shouldHide, Hidden: shouldHide,
}), }),
altsrc.NewBoolFlag(&cli.BoolFlag{ altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "hello-world", Name: ingress.HelloWorldFlag,
Value: false, Value: false,
Usage: "Run Hello World Server", Usage: "Run Hello World Server",
EnvVars: []string{"TUNNEL_HELLO_WORLD"}, EnvVars: []string{"TUNNEL_HELLO_WORLD"},

View File

@ -124,7 +124,7 @@ func isSecretEnvVar(key string) bool {
func dnsProxyStandAlone(c *cli.Context, namedTunnel *connection.NamedTunnelProperties) bool { func dnsProxyStandAlone(c *cli.Context, namedTunnel *connection.NamedTunnelProperties) bool {
return c.IsSet("proxy-dns") && return c.IsSet("proxy-dns") &&
!(c.IsSet("name") || // adhoc-named tunnel !(c.IsSet("name") || // adhoc-named tunnel
c.IsSet("hello-world") || // quick or named tunnel c.IsSet(ingress.HelloWorldFlag) || // quick or named tunnel
namedTunnel != nil) // named tunnel namedTunnel != nil) // named tunnel
} }
@ -231,17 +231,16 @@ func prepareTunnelConfig(
if err != nil && err != ingress.ErrNoIngressRules { if err != nil && err != ingress.ErrNoIngressRules {
return nil, nil, err return nil, nil, err
} }
if c.IsSet("url") { if c.IsSet("url") || c.IsSet(ingress.HelloWorldFlag) || c.IsSet(config.BastionFlag) {
// Ingress rules cannot be provided with --url flag // Ingress rules cannot be provided with --url, --hello-world or --bastion flag
if !ingressRules.IsEmpty() { if !ingressRules.IsEmpty() {
return nil, nil, ingress.ErrURLIncompatibleWithIngress return nil, nil, ingress.ErrURLIncompatibleWithIngress
} else { }
// Only for quick or adhoc tunnels will we attempt to parse: // Only for quick or adhoc tunnels will we attempt to parse:
// --url, --hello-world, or --unix-socket flag for a tunnel ingress rule // --url, --hello-world, --bastion, or --unix-socket flag for a tunnel ingress rule
ingressRules, err = ingress.NewSingleOrigin(c, false) ingressRules, err = ingress.NewSingleOrigin(c, false)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
}
} }
} }

View File

@ -41,8 +41,10 @@ class NamedTunnelBaseConfig(BaseConfig):
def merge_config(self, additional): def merge_config(self, additional):
config = super(NamedTunnelBaseConfig, self).merge_config(additional) config = super(NamedTunnelBaseConfig, self).merge_config(additional)
config['tunnel'] = self.tunnel if 'tunnel' not in config:
config['credentials-file'] = self.credentials_file config['tunnel'] = self.tunnel
if 'credentials-file' not in config:
config['credentials-file'] = self.credentials_file
# In some cases we want to override default ingress, such as in config tests # In some cases we want to override default ingress, such as in config tests
if 'ingress' not in config: if 'ingress' not in config:
config['ingress'] = self.ingress config['ingress'] = self.ingress
@ -84,28 +86,9 @@ class NamedTunnelConfig(NamedTunnelBaseConfig):
def get_credentials_json(self): def get_credentials_json(self):
with open(self.credentials_file) as json_file: with open(self.credentials_file) as json_file:
return json.load(json_file) return json.load(json_file)
@dataclass(frozen=True) @dataclass(frozen=True)
class ClassicTunnelBaseConfig(BaseConfig): class QuickTunnelConfig(BaseConfig):
hostname: str = None
origincert: str = None
def __post_init__(self):
if self.hostname is None:
raise TypeError("Field tunnel is not set")
if self.origincert is None:
raise TypeError("Field credentials_file is not set")
def merge_config(self, additional):
config = super(ClassicTunnelBaseConfig, self).merge_config(additional)
config['hostname'] = self.hostname
config['origincert'] = self.origincert
return config
@dataclass(frozen=True)
class ClassicTunnelConfig(ClassicTunnelBaseConfig):
full_config: dict = None full_config: dict = None
additional_config: InitVar[dict] = {} additional_config: InitVar[dict] = {}
@ -115,10 +98,6 @@ class ClassicTunnelConfig(ClassicTunnelBaseConfig):
object.__setattr__(self, 'full_config', object.__setattr__(self, 'full_config',
self.merge_config(additional_config)) self.merge_config(additional_config))
def get_url(self):
return "https://" + self.hostname
@dataclass(frozen=True) @dataclass(frozen=True)
class ProxyDnsConfig(BaseConfig): class ProxyDnsConfig(BaseConfig):
full_config = { full_config = {

View File

@ -5,14 +5,14 @@ from time import sleep
import pytest import pytest
import yaml import yaml
from config import NamedTunnelConfig, ClassicTunnelConfig, ProxyDnsConfig from config import NamedTunnelConfig, ProxyDnsConfig, QuickTunnelConfig
from constants import BACKOFF_SECS, PROXY_DNS_PORT from constants import BACKOFF_SECS, PROXY_DNS_PORT
from util import LOGGER from util import LOGGER
class CfdModes(Enum): class CfdModes(Enum):
NAMED = auto() NAMED = auto()
CLASSIC = auto() QUICK = auto()
PROXY_DNS = auto() PROXY_DNS = auto()
@ -42,12 +42,10 @@ def component_tests_config():
tunnel=config['tunnel'], tunnel=config['tunnel'],
credentials_file=config['credentials_file'], credentials_file=config['credentials_file'],
ingress=config['ingress']) ingress=config['ingress'])
elif cfd_mode is CfdModes.CLASSIC:
return ClassicTunnelConfig(
additional_config=additional_config, cloudflared_binary=config['cloudflared_binary'],
hostname=config['classic_hostname'], origincert=config['origincert'])
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:
return QuickTunnelConfig(additional_config=additional_config, cloudflared_binary=config['cloudflared_binary'])
else: else:
raise Exception(f"Unknown cloudflared mode {cfd_mode}") raise Exception(f"Unknown cloudflared mode {cfd_mode}")

View File

@ -7,4 +7,4 @@ PROXY_DNS_PORT = 9053
def protocols(): def protocols():
return ["h2mux", "http2", "quic"] return ["http2", "quic"]

View File

@ -0,0 +1,34 @@
#!/usr/bin/env python
import requests
from conftest import CfdModes
from constants import METRICS_PORT
from util import LOGGER, start_cloudflared, wait_tunnel_ready, get_quicktunnel_url, send_requests
class TestCLI:
def test_quick_tunnel(self, tmp_path, component_tests_config):
config = component_tests_config(cfd_mode=CfdModes.QUICK, run_proxy_dns=False)
LOGGER.debug(config)
with start_cloudflared(tmp_path, config, cfd_args=["--hello-world"], new_process=True):
wait_tunnel_ready(require_min_connections=4)
url = get_quicktunnel_url()
send_requests(url, 3, True)
def test_quick_tunnel_url(self, tmp_path, component_tests_config):
config = component_tests_config(cfd_mode=CfdModes.QUICK, run_proxy_dns=False)
LOGGER.debug(config)
with start_cloudflared(tmp_path, config, cfd_args=["--url", f"http://localhost:{METRICS_PORT}/"], new_process=True):
wait_tunnel_ready()
url = get_quicktunnel_url()
send_requests(url+"/ready", 3, True)
def test_quick_tunnel_proxy_dns_url(self, tmp_path, component_tests_config):
config = component_tests_config(cfd_mode=CfdModes.QUICK, run_proxy_dns=True)
LOGGER.debug(config)
failed_start = start_cloudflared(tmp_path, config, cfd_args=["--url", f"http://localhost:{METRICS_PORT}/"], expect_success=False)
assert failed_start.returncode == 1, "Expected cloudflared to fail to run with `proxy-dns` and `hello-world`"
def test_quick_tunnel_proxy_dns_hello_world(self, tmp_path, component_tests_config):
config = component_tests_config(cfd_mode=CfdModes.QUICK, run_proxy_dns=True)
LOGGER.debug(config)
failed_start = start_cloudflared(tmp_path, config, cfd_args=["--hello-world"], expect_success=False)
assert failed_start.returncode == 1, "Expected cloudflared to fail to run with `proxy-dns` and `url`"

View File

@ -35,7 +35,7 @@ def write_config(directory, config):
def start_cloudflared(directory, config, cfd_args=["run"], cfd_pre_args=["tunnel"], new_process=False, def start_cloudflared(directory, config, cfd_args=["run"], cfd_pre_args=["tunnel"], new_process=False,
allow_input=False, capture_output=True, root=False, skip_config_flag=False): allow_input=False, capture_output=True, root=False, skip_config_flag=False, expect_success=True):
config_path = None config_path = None
if not skip_config_flag: if not skip_config_flag:
@ -46,7 +46,7 @@ def start_cloudflared(directory, config, cfd_args=["run"], cfd_pre_args=["tunnel
if new_process: if new_process:
return run_cloudflared_background(cmd, allow_input, capture_output) return run_cloudflared_background(cmd, allow_input, capture_output)
# By setting check=True, it will raise an exception if the process exits with non-zero exit code # By setting check=True, it will raise an exception if the process exits with non-zero exit code
return subprocess.run(cmd, check=True, capture_output=capture_output) return subprocess.run(cmd, check=expect_success, capture_output=capture_output)
def cloudflared_cmd(config, config_path, cfd_args, cfd_pre_args, root): def cloudflared_cmd(config, config_path, cfd_args, cfd_pre_args, root):
@ -77,7 +77,18 @@ def run_cloudflared_background(cmd, allow_input, capture_output):
cfd.terminate() cfd.terminate()
if capture_output: if capture_output:
LOGGER.info(f"cloudflared log: {cfd.stderr.read()}") LOGGER.info(f"cloudflared log: {cfd.stderr.read()}")
def get_quicktunnel_url():
quicktunnel_url = f'http://localhost:{METRICS_PORT}/quicktunnel'
with requests.Session() as s:
resp = send_request(s, quicktunnel_url, True)
hostname = resp.json()["hostname"]
assert hostname, \
f"Quicktunnel endpoint returned {hostname} but we expected a url"
return f"https://{hostname}"
def wait_tunnel_ready(tunnel_url=None, require_min_connections=1, cfd_logs=None): def wait_tunnel_ready(tunnel_url=None, require_min_connections=1, cfd_logs=None):
try: try:

View File

@ -112,7 +112,7 @@ func NewWarpRoutingService(config WarpRoutingConfig) *WarpRoutingService {
// Get a single origin service from the CLI/config. // Get a single origin service from the CLI/config.
func parseSingleOriginService(c *cli.Context, allowURLFromArgs bool) (OriginService, error) { func parseSingleOriginService(c *cli.Context, allowURLFromArgs bool) (OriginService, error) {
if c.IsSet("hello-world") { if c.IsSet(HelloWorldFlag) {
return new(helloWorld), nil return new(helloWorld), nil
} }
if c.IsSet(config.BastionFlag) { if c.IsSet(config.BastionFlag) {
@ -206,7 +206,7 @@ func validateIngress(ingress []config.UnvalidatedIngressRule, defaults OriginReq
} }
srv := newStatusCode(statusCode) srv := newStatusCode(statusCode)
service = &srv service = &srv
} else if r.Service == HelloWorldService || r.Service == "hello-world" || r.Service == "helloworld" { } else if r.Service == HelloWorldFlag || r.Service == HelloWorldService {
service = new(helloWorld) service = new(helloWorld)
} else if r.Service == ServiceSocksProxy { } else if r.Service == ServiceSocksProxy {
rules := make([]ipaccess.Rule, len(r.OriginRequest.IPRules)) rules := make([]ipaccess.Rule, len(r.OriginRequest.IPRules))

View File

@ -23,6 +23,7 @@ import (
const ( const (
HelloWorldService = "hello_world" HelloWorldService = "hello_world"
HelloWorldFlag = "hello-world"
HttpStatusService = "http_status" HttpStatusService = "http_status"
) )