TUN-5869: Add configuration endpoint in metrics server
This commit is contained in:
parent
6eeaf4be4b
commit
e2a8302bbc
|
@ -73,7 +73,7 @@ func Run(c *cli.Context) error {
|
||||||
log.Fatal().Err(err).Msg("Failed to open the metrics listener")
|
log.Fatal().Err(err).Msg("Failed to open the metrics listener")
|
||||||
}
|
}
|
||||||
|
|
||||||
go metrics.ServeMetrics(metricsListener, nil, nil, "", log)
|
go metrics.ServeMetrics(metricsListener, nil, nil, "", nil, log)
|
||||||
|
|
||||||
listener, err := tunneldns.CreateListener(
|
listener, err := tunneldns.CreateListener(
|
||||||
c.String("address"),
|
c.String("address"),
|
||||||
|
|
|
@ -340,6 +340,11 @@ func StartServer(
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
orchestrator, err := orchestration.NewOrchestrator(ctx, dynamicConfig, tunnelConfig.Tags, tunnelConfig.Log)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
metricsListener, err := listeners.Listen("tcp", c.String("metrics"))
|
metricsListener, err := listeners.Listen("tcp", c.String("metrics"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Error opening metrics server listener")
|
log.Err(err).Msg("Error opening metrics server listener")
|
||||||
|
@ -351,14 +356,9 @@ func StartServer(
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
readinessServer := metrics.NewReadyServer(log)
|
readinessServer := metrics.NewReadyServer(log)
|
||||||
observer.RegisterSink(readinessServer)
|
observer.RegisterSink(readinessServer)
|
||||||
errC <- metrics.ServeMetrics(metricsListener, ctx.Done(), readinessServer, quickTunnelURL, log)
|
errC <- metrics.ServeMetrics(metricsListener, ctx.Done(), readinessServer, quickTunnelURL, orchestrator, log)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
orchestrator, err := orchestration.NewOrchestrator(ctx, dynamicConfig, tunnelConfig.Tags, tunnelConfig.Log)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
reconnectCh := make(chan supervisor.ReconnectSignal, 1)
|
reconnectCh := make(chan supervisor.ReconnectSignal, 1)
|
||||||
if c.IsSet("stdin-control") {
|
if c.IsSet("stdin-control") {
|
||||||
log.Info().Msg("Enabling control through stdin")
|
log.Info().Msg("Enabling control through stdin")
|
||||||
|
|
|
@ -411,7 +411,7 @@ type CustomDuration struct {
|
||||||
time.Duration
|
time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CustomDuration) MarshalJSON() ([]byte, error) {
|
func (s CustomDuration) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(s.Duration.Seconds())
|
return json.Marshal(s.Duration.Seconds())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,14 +11,16 @@ import (
|
||||||
"github.com/cloudflare/cloudflared/tlsconfig"
|
"github.com/cloudflare/cloudflared/tlsconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
var (
|
||||||
defaultConnectTimeout = 30 * time.Second
|
defaultConnectTimeout = config.CustomDuration{Duration: 30 * time.Second}
|
||||||
defaultTLSTimeout = 10 * time.Second
|
defaultTLSTimeout = config.CustomDuration{Duration: 10 * time.Second}
|
||||||
defaultTCPKeepAlive = 30 * time.Second
|
defaultTCPKeepAlive = config.CustomDuration{Duration: 30 * time.Second}
|
||||||
defaultKeepAliveConnections = 100
|
defaultKeepAliveTimeout = config.CustomDuration{Duration: 90 * time.Second}
|
||||||
defaultKeepAliveTimeout = 90 * time.Second
|
)
|
||||||
defaultProxyAddress = "127.0.0.1"
|
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultProxyAddress = "127.0.0.1"
|
||||||
|
defaultKeepAliveConnections = 100
|
||||||
SSHServerFlag = "ssh-server"
|
SSHServerFlag = "ssh-server"
|
||||||
Socks5Flag = "socks5"
|
Socks5Flag = "socks5"
|
||||||
ProxyConnectTimeoutFlag = "proxy-connect-timeout"
|
ProxyConnectTimeoutFlag = "proxy-connect-timeout"
|
||||||
|
@ -68,12 +70,12 @@ func (rc *RemoteConfig) UnmarshalJSON(b []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func originRequestFromSingeRule(c *cli.Context) OriginRequestConfig {
|
func originRequestFromSingeRule(c *cli.Context) OriginRequestConfig {
|
||||||
var connectTimeout time.Duration = defaultConnectTimeout
|
var connectTimeout config.CustomDuration = defaultConnectTimeout
|
||||||
var tlsTimeout time.Duration = defaultTLSTimeout
|
var tlsTimeout config.CustomDuration = defaultTLSTimeout
|
||||||
var tcpKeepAlive time.Duration = defaultTCPKeepAlive
|
var tcpKeepAlive config.CustomDuration = defaultTCPKeepAlive
|
||||||
var noHappyEyeballs bool
|
var noHappyEyeballs bool
|
||||||
var keepAliveConnections int = defaultKeepAliveConnections
|
var keepAliveConnections int = defaultKeepAliveConnections
|
||||||
var keepAliveTimeout time.Duration = defaultKeepAliveTimeout
|
var keepAliveTimeout config.CustomDuration = defaultKeepAliveTimeout
|
||||||
var httpHostHeader string
|
var httpHostHeader string
|
||||||
var originServerName string
|
var originServerName string
|
||||||
var caPool string
|
var caPool string
|
||||||
|
@ -84,13 +86,13 @@ func originRequestFromSingeRule(c *cli.Context) OriginRequestConfig {
|
||||||
var proxyPort uint
|
var proxyPort uint
|
||||||
var proxyType string
|
var proxyType string
|
||||||
if flag := ProxyConnectTimeoutFlag; c.IsSet(flag) {
|
if flag := ProxyConnectTimeoutFlag; c.IsSet(flag) {
|
||||||
connectTimeout = c.Duration(flag)
|
connectTimeout = config.CustomDuration{Duration: c.Duration(flag)}
|
||||||
}
|
}
|
||||||
if flag := ProxyTLSTimeoutFlag; c.IsSet(flag) {
|
if flag := ProxyTLSTimeoutFlag; c.IsSet(flag) {
|
||||||
tlsTimeout = c.Duration(flag)
|
tlsTimeout = config.CustomDuration{Duration: c.Duration(flag)}
|
||||||
}
|
}
|
||||||
if flag := ProxyTCPKeepAliveFlag; c.IsSet(flag) {
|
if flag := ProxyTCPKeepAliveFlag; c.IsSet(flag) {
|
||||||
tcpKeepAlive = c.Duration(flag)
|
tcpKeepAlive = config.CustomDuration{Duration: c.Duration(flag)}
|
||||||
}
|
}
|
||||||
if flag := ProxyNoHappyEyeballsFlag; c.IsSet(flag) {
|
if flag := ProxyNoHappyEyeballsFlag; c.IsSet(flag) {
|
||||||
noHappyEyeballs = c.Bool(flag)
|
noHappyEyeballs = c.Bool(flag)
|
||||||
|
@ -99,7 +101,7 @@ func originRequestFromSingeRule(c *cli.Context) OriginRequestConfig {
|
||||||
keepAliveConnections = c.Int(flag)
|
keepAliveConnections = c.Int(flag)
|
||||||
}
|
}
|
||||||
if flag := ProxyKeepAliveTimeoutFlag; c.IsSet(flag) {
|
if flag := ProxyKeepAliveTimeoutFlag; c.IsSet(flag) {
|
||||||
keepAliveTimeout = c.Duration(flag)
|
keepAliveTimeout = config.CustomDuration{Duration: c.Duration(flag)}
|
||||||
}
|
}
|
||||||
if flag := HTTPHostHeaderFlag; c.IsSet(flag) {
|
if flag := HTTPHostHeaderFlag; c.IsSet(flag) {
|
||||||
httpHostHeader = c.String(flag)
|
httpHostHeader = c.String(flag)
|
||||||
|
@ -158,13 +160,13 @@ func originRequestFromConfig(c config.OriginRequestConfig) OriginRequestConfig {
|
||||||
ProxyAddress: defaultProxyAddress,
|
ProxyAddress: defaultProxyAddress,
|
||||||
}
|
}
|
||||||
if c.ConnectTimeout != nil {
|
if c.ConnectTimeout != nil {
|
||||||
out.ConnectTimeout = c.ConnectTimeout.Duration
|
out.ConnectTimeout = *c.ConnectTimeout
|
||||||
}
|
}
|
||||||
if c.TLSTimeout != nil {
|
if c.TLSTimeout != nil {
|
||||||
out.TLSTimeout = c.TLSTimeout.Duration
|
out.TLSTimeout = *c.TLSTimeout
|
||||||
}
|
}
|
||||||
if c.TCPKeepAlive != nil {
|
if c.TCPKeepAlive != nil {
|
||||||
out.TCPKeepAlive = c.TCPKeepAlive.Duration
|
out.TCPKeepAlive = *c.TCPKeepAlive
|
||||||
}
|
}
|
||||||
if c.NoHappyEyeballs != nil {
|
if c.NoHappyEyeballs != nil {
|
||||||
out.NoHappyEyeballs = *c.NoHappyEyeballs
|
out.NoHappyEyeballs = *c.NoHappyEyeballs
|
||||||
|
@ -173,7 +175,7 @@ func originRequestFromConfig(c config.OriginRequestConfig) OriginRequestConfig {
|
||||||
out.KeepAliveConnections = *c.KeepAliveConnections
|
out.KeepAliveConnections = *c.KeepAliveConnections
|
||||||
}
|
}
|
||||||
if c.KeepAliveTimeout != nil {
|
if c.KeepAliveTimeout != nil {
|
||||||
out.KeepAliveTimeout = c.KeepAliveTimeout.Duration
|
out.KeepAliveTimeout = *c.KeepAliveTimeout
|
||||||
}
|
}
|
||||||
if c.HTTPHostHeader != nil {
|
if c.HTTPHostHeader != nil {
|
||||||
out.HTTPHostHeader = *c.HTTPHostHeader
|
out.HTTPHostHeader = *c.HTTPHostHeader
|
||||||
|
@ -218,52 +220,52 @@ func originRequestFromConfig(c config.OriginRequestConfig) OriginRequestConfig {
|
||||||
// Note: To specify a time.Duration in go-yaml, use e.g. "3s" or "24h".
|
// Note: To specify a time.Duration in go-yaml, use e.g. "3s" or "24h".
|
||||||
type OriginRequestConfig struct {
|
type OriginRequestConfig struct {
|
||||||
// HTTP proxy timeout for establishing a new connection
|
// HTTP proxy timeout for establishing a new connection
|
||||||
ConnectTimeout time.Duration `yaml:"connectTimeout"`
|
ConnectTimeout config.CustomDuration `yaml:"connectTimeout" json:"connectTimeout"`
|
||||||
// HTTP proxy timeout for completing a TLS handshake
|
// HTTP proxy timeout for completing a TLS handshake
|
||||||
TLSTimeout time.Duration `yaml:"tlsTimeout"`
|
TLSTimeout config.CustomDuration `yaml:"tlsTimeout" json:"tlsTimeout"`
|
||||||
// HTTP proxy TCP keepalive duration
|
// HTTP proxy TCP keepalive duration
|
||||||
TCPKeepAlive time.Duration `yaml:"tcpKeepAlive"`
|
TCPKeepAlive config.CustomDuration `yaml:"tcpKeepAlive" json:"tcpKeepAlive"`
|
||||||
// HTTP proxy should disable "happy eyeballs" for IPv4/v6 fallback
|
// HTTP proxy should disable "happy eyeballs" for IPv4/v6 fallback
|
||||||
NoHappyEyeballs bool `yaml:"noHappyEyeballs"`
|
NoHappyEyeballs bool `yaml:"noHappyEyeballs" json:"noHappyEyeballs"`
|
||||||
// HTTP proxy timeout for closing an idle connection
|
// HTTP proxy timeout for closing an idle connection
|
||||||
KeepAliveTimeout time.Duration `yaml:"keepAliveTimeout"`
|
KeepAliveTimeout config.CustomDuration `yaml:"keepAliveTimeout" json:"keepAliveTimeout"`
|
||||||
// HTTP proxy maximum keepalive connection pool size
|
// HTTP proxy maximum keepalive connection pool size
|
||||||
KeepAliveConnections int `yaml:"keepAliveConnections"`
|
KeepAliveConnections int `yaml:"keepAliveConnections" json:"keepAliveConnections"`
|
||||||
// Sets the HTTP Host header for the local webserver.
|
// Sets the HTTP Host header for the local webserver.
|
||||||
HTTPHostHeader string `yaml:"httpHostHeader"`
|
HTTPHostHeader string `yaml:"httpHostHeader" json:"httpHostHeader"`
|
||||||
// Hostname on the origin server certificate.
|
// Hostname on the origin server certificate.
|
||||||
OriginServerName string `yaml:"originServerName"`
|
OriginServerName string `yaml:"originServerName" json:"originServerName"`
|
||||||
// Path to the CA for the certificate of your origin.
|
// Path to the CA for the certificate of your origin.
|
||||||
// This option should be used only if your certificate is not signed by Cloudflare.
|
// This option should be used only if your certificate is not signed by Cloudflare.
|
||||||
CAPool string `yaml:"caPool"`
|
CAPool string `yaml:"caPool" json:"caPool"`
|
||||||
// Disables TLS verification of the certificate presented by your origin.
|
// Disables TLS verification of the certificate presented by your origin.
|
||||||
// Will allow any certificate from the origin to be accepted.
|
// Will allow any certificate from the origin to be accepted.
|
||||||
// Note: The connection from your machine to Cloudflare's Edge is still encrypted.
|
// Note: The connection from your machine to Cloudflare's Edge is still encrypted.
|
||||||
NoTLSVerify bool `yaml:"noTLSVerify"`
|
NoTLSVerify bool `yaml:"noTLSVerify" json:"noTLSVerify"`
|
||||||
// Disables chunked transfer encoding.
|
// Disables chunked transfer encoding.
|
||||||
// Useful if you are running a WSGI server.
|
// Useful if you are running a WSGI server.
|
||||||
DisableChunkedEncoding bool `yaml:"disableChunkedEncoding"`
|
DisableChunkedEncoding bool `yaml:"disableChunkedEncoding" json:"disableChunkedEncoding"`
|
||||||
// Runs as jump host
|
// Runs as jump host
|
||||||
BastionMode bool `yaml:"bastionMode"`
|
BastionMode bool `yaml:"bastionMode" json:"bastionMode"`
|
||||||
// Listen address for the proxy.
|
// Listen address for the proxy.
|
||||||
ProxyAddress string `yaml:"proxyAddress"`
|
ProxyAddress string `yaml:"proxyAddress" json:"proxyAddress"`
|
||||||
// Listen port for the proxy.
|
// Listen port for the proxy.
|
||||||
ProxyPort uint `yaml:"proxyPort"`
|
ProxyPort uint `yaml:"proxyPort" json:"proxyPort"`
|
||||||
// What sort of proxy should be started
|
// What sort of proxy should be started
|
||||||
ProxyType string `yaml:"proxyType"`
|
ProxyType string `yaml:"proxyType" json:"proxyType"`
|
||||||
// IP rules for the proxy service
|
// IP rules for the proxy service
|
||||||
IPRules []ipaccess.Rule `yaml:"ipRules"`
|
IPRules []ipaccess.Rule `yaml:"ipRules" json:"ipRules"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (defaults *OriginRequestConfig) setConnectTimeout(overrides config.OriginRequestConfig) {
|
func (defaults *OriginRequestConfig) setConnectTimeout(overrides config.OriginRequestConfig) {
|
||||||
if val := overrides.ConnectTimeout; val != nil {
|
if val := overrides.ConnectTimeout; val != nil {
|
||||||
defaults.ConnectTimeout = val.Duration
|
defaults.ConnectTimeout = *val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (defaults *OriginRequestConfig) setTLSTimeout(overrides config.OriginRequestConfig) {
|
func (defaults *OriginRequestConfig) setTLSTimeout(overrides config.OriginRequestConfig) {
|
||||||
if val := overrides.TLSTimeout; val != nil {
|
if val := overrides.TLSTimeout; val != nil {
|
||||||
defaults.TLSTimeout = val.Duration
|
defaults.TLSTimeout = *val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,13 +283,13 @@ func (defaults *OriginRequestConfig) setKeepAliveConnections(overrides config.Or
|
||||||
|
|
||||||
func (defaults *OriginRequestConfig) setKeepAliveTimeout(overrides config.OriginRequestConfig) {
|
func (defaults *OriginRequestConfig) setKeepAliveTimeout(overrides config.OriginRequestConfig) {
|
||||||
if val := overrides.KeepAliveTimeout; val != nil {
|
if val := overrides.KeepAliveTimeout; val != nil {
|
||||||
defaults.KeepAliveTimeout = val.Duration
|
defaults.KeepAliveTimeout = *val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (defaults *OriginRequestConfig) setTCPKeepAlive(overrides config.OriginRequestConfig) {
|
func (defaults *OriginRequestConfig) setTCPKeepAlive(overrides config.OriginRequestConfig) {
|
||||||
if val := overrides.TCPKeepAlive; val != nil {
|
if val := overrides.TCPKeepAlive; val != nil {
|
||||||
defaults.TCPKeepAlive = val.Duration
|
defaults.TCPKeepAlive = *val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@ func TestUnmarshalRemoteConfigOverridesGlobal(t *testing.T) {
|
||||||
err := json.Unmarshal(rawConfig, &remoteConfig)
|
err := json.Unmarshal(rawConfig, &remoteConfig)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.True(t, remoteConfig.Ingress.Rules[0].Config.NoTLSVerify)
|
require.True(t, remoteConfig.Ingress.Rules[0].Config.NoTLSVerify)
|
||||||
require.True(t, remoteConfig.Ingress.defaults.NoHappyEyeballs)
|
require.True(t, remoteConfig.Ingress.Defaults.NoHappyEyeballs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOriginRequestConfigOverrides(t *testing.T) {
|
func TestOriginRequestConfigOverrides(t *testing.T) {
|
||||||
|
@ -74,11 +74,11 @@ func TestOriginRequestConfigOverrides(t *testing.T) {
|
||||||
// root-level configuration.
|
// root-level configuration.
|
||||||
actual0 := ing.Rules[0].Config
|
actual0 := ing.Rules[0].Config
|
||||||
expected0 := OriginRequestConfig{
|
expected0 := OriginRequestConfig{
|
||||||
ConnectTimeout: 1 * time.Minute,
|
ConnectTimeout: config.CustomDuration{Duration: 1 * time.Minute},
|
||||||
TLSTimeout: 1 * time.Second,
|
TLSTimeout: config.CustomDuration{Duration: 1 * time.Second},
|
||||||
TCPKeepAlive: 1 * time.Second,
|
TCPKeepAlive: config.CustomDuration{Duration: 1 * time.Second},
|
||||||
NoHappyEyeballs: true,
|
NoHappyEyeballs: true,
|
||||||
KeepAliveTimeout: 1 * time.Second,
|
KeepAliveTimeout: config.CustomDuration{Duration: 1 * time.Second},
|
||||||
KeepAliveConnections: 1,
|
KeepAliveConnections: 1,
|
||||||
HTTPHostHeader: "abc",
|
HTTPHostHeader: "abc",
|
||||||
OriginServerName: "a1",
|
OriginServerName: "a1",
|
||||||
|
@ -99,11 +99,11 @@ func TestOriginRequestConfigOverrides(t *testing.T) {
|
||||||
// Rule 1 overrode all the root-level config.
|
// Rule 1 overrode all the root-level config.
|
||||||
actual1 := ing.Rules[1].Config
|
actual1 := ing.Rules[1].Config
|
||||||
expected1 := OriginRequestConfig{
|
expected1 := OriginRequestConfig{
|
||||||
ConnectTimeout: 2 * time.Minute,
|
ConnectTimeout: config.CustomDuration{Duration: 2 * time.Minute},
|
||||||
TLSTimeout: 2 * time.Second,
|
TLSTimeout: config.CustomDuration{Duration: 2 * time.Second},
|
||||||
TCPKeepAlive: 2 * time.Second,
|
TCPKeepAlive: config.CustomDuration{Duration: 2 * time.Second},
|
||||||
NoHappyEyeballs: false,
|
NoHappyEyeballs: false,
|
||||||
KeepAliveTimeout: 2 * time.Second,
|
KeepAliveTimeout: config.CustomDuration{Duration: 2 * time.Second},
|
||||||
KeepAliveConnections: 2,
|
KeepAliveConnections: 2,
|
||||||
HTTPHostHeader: "def",
|
HTTPHostHeader: "def",
|
||||||
OriginServerName: "b2",
|
OriginServerName: "b2",
|
||||||
|
@ -286,11 +286,11 @@ func TestOriginRequestConfigDefaults(t *testing.T) {
|
||||||
// Rule 1 overrode all defaults.
|
// Rule 1 overrode all defaults.
|
||||||
actual1 := ing.Rules[1].Config
|
actual1 := ing.Rules[1].Config
|
||||||
expected1 := OriginRequestConfig{
|
expected1 := OriginRequestConfig{
|
||||||
ConnectTimeout: 2 * time.Minute,
|
ConnectTimeout: config.CustomDuration{Duration: 2 * time.Minute},
|
||||||
TLSTimeout: 2 * time.Second,
|
TLSTimeout: config.CustomDuration{Duration: 2 * time.Second},
|
||||||
TCPKeepAlive: 2 * time.Second,
|
TCPKeepAlive: config.CustomDuration{Duration: 2 * time.Second},
|
||||||
NoHappyEyeballs: false,
|
NoHappyEyeballs: false,
|
||||||
KeepAliveTimeout: 2 * time.Second,
|
KeepAliveTimeout: config.CustomDuration{Duration: 2 * time.Second},
|
||||||
KeepAliveConnections: 2,
|
KeepAliveConnections: 2,
|
||||||
HTTPHostHeader: "def",
|
HTTPHostHeader: "def",
|
||||||
OriginServerName: "b2",
|
OriginServerName: "b2",
|
||||||
|
|
|
@ -64,8 +64,8 @@ func matchHost(ruleHost, reqHost string) bool {
|
||||||
|
|
||||||
// Ingress maps eyeball requests to origins.
|
// Ingress maps eyeball requests to origins.
|
||||||
type Ingress struct {
|
type Ingress struct {
|
||||||
Rules []Rule
|
Rules []Rule `json:"ingress"`
|
||||||
defaults OriginRequestConfig
|
Defaults OriginRequestConfig `json:"originRequest"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSingleOrigin constructs an Ingress set with only one rule, constructed from
|
// NewSingleOrigin constructs an Ingress set with only one rule, constructed from
|
||||||
|
@ -86,7 +86,7 @@ func NewSingleOrigin(c *cli.Context, allowURLFromArgs bool) (Ingress, error) {
|
||||||
Config: setConfig(defaults, config.OriginRequestConfig{}),
|
Config: setConfig(defaults, config.OriginRequestConfig{}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaults: defaults,
|
Defaults: defaults,
|
||||||
}
|
}
|
||||||
return ing, err
|
return ing, err
|
||||||
}
|
}
|
||||||
|
@ -180,7 +180,7 @@ func validateIngress(ingress []config.UnvalidatedIngressRule, defaults OriginReq
|
||||||
}
|
}
|
||||||
srv := newStatusCode(status)
|
srv := newStatusCode(status)
|
||||||
service = &srv
|
service = &srv
|
||||||
} else if r.Service == "hello_world" || r.Service == "hello-world" || r.Service == "helloworld" {
|
} else if r.Service == HelloWorldService || r.Service == "hello-world" || r.Service == "helloworld" {
|
||||||
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))
|
||||||
|
@ -230,23 +230,24 @@ func validateIngress(ingress []config.UnvalidatedIngressRule, defaults OriginReq
|
||||||
return Ingress{}, err
|
return Ingress{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var pathRegex *regexp.Regexp
|
var pathRegexp *Regexp
|
||||||
if r.Path != "" {
|
if r.Path != "" {
|
||||||
var err error
|
var err error
|
||||||
pathRegex, err = regexp.Compile(r.Path)
|
regex, err := regexp.Compile(r.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Ingress{}, errors.Wrapf(err, "Rule #%d has an invalid regex", i+1)
|
return Ingress{}, errors.Wrapf(err, "Rule #%d has an invalid regex", i+1)
|
||||||
}
|
}
|
||||||
|
pathRegexp = &Regexp{Regexp: regex}
|
||||||
}
|
}
|
||||||
|
|
||||||
rules[i] = Rule{
|
rules[i] = Rule{
|
||||||
Hostname: r.Hostname,
|
Hostname: r.Hostname,
|
||||||
Service: service,
|
Service: service,
|
||||||
Path: pathRegex,
|
Path: pathRegexp,
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Ingress{Rules: rules, defaults: defaults}, nil
|
return Ingress{Rules: rules, Defaults: defaults}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateHostname(r config.UnvalidatedIngressRule, ruleIndex, totalRules int) error {
|
func validateHostname(r config.UnvalidatedIngressRule, ruleIndex, totalRules int) error {
|
||||||
|
|
|
@ -482,12 +482,12 @@ func TestSingleOriginSetsConfig(t *testing.T) {
|
||||||
ingress, err := NewSingleOrigin(cliCtx, allowURLFromArgs)
|
ingress, err := NewSingleOrigin(cliCtx, allowURLFromArgs)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, time.Second, ingress.Rules[0].Config.ConnectTimeout)
|
assert.Equal(t, config.CustomDuration{Duration: time.Second}, ingress.Rules[0].Config.ConnectTimeout)
|
||||||
assert.Equal(t, time.Second, ingress.Rules[0].Config.TLSTimeout)
|
assert.Equal(t, config.CustomDuration{Duration: time.Second}, ingress.Rules[0].Config.TLSTimeout)
|
||||||
assert.Equal(t, time.Second, ingress.Rules[0].Config.TCPKeepAlive)
|
assert.Equal(t, config.CustomDuration{Duration: time.Second}, ingress.Rules[0].Config.TCPKeepAlive)
|
||||||
assert.True(t, ingress.Rules[0].Config.NoHappyEyeballs)
|
assert.True(t, ingress.Rules[0].Config.NoHappyEyeballs)
|
||||||
assert.Equal(t, 10, ingress.Rules[0].Config.KeepAliveConnections)
|
assert.Equal(t, 10, ingress.Rules[0].Config.KeepAliveConnections)
|
||||||
assert.Equal(t, time.Second, ingress.Rules[0].Config.KeepAliveTimeout)
|
assert.Equal(t, config.CustomDuration{Duration: time.Second}, ingress.Rules[0].Config.KeepAliveTimeout)
|
||||||
assert.Equal(t, "example.com:8080", ingress.Rules[0].Config.HTTPHostHeader)
|
assert.Equal(t, "example.com:8080", ingress.Rules[0].Config.HTTPHostHeader)
|
||||||
assert.Equal(t, "example.com", ingress.Rules[0].Config.OriginServerName)
|
assert.Equal(t, "example.com", ingress.Rules[0].Config.OriginServerName)
|
||||||
assert.Equal(t, "/etc/certs/ca.pem", ingress.Rules[0].Config.CAPool)
|
assert.Equal(t, "/etc/certs/ca.pem", ingress.Rules[0].Config.CAPool)
|
||||||
|
@ -508,7 +508,7 @@ func TestFindMatchingRule(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Hostname: "tunnel-b.example.com",
|
Hostname: "tunnel-b.example.com",
|
||||||
Path: mustParsePath(t, "/health"),
|
Path: MustParsePath(t, "/health"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Hostname: "*",
|
Hostname: "*",
|
||||||
|
@ -591,10 +591,10 @@ func TestIsHTTPService(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustParsePath(t *testing.T, path string) *regexp.Regexp {
|
func MustParsePath(t *testing.T, path string) *Regexp {
|
||||||
regexp, err := regexp.Compile(path)
|
regexp, err := regexp.Compile(path)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
return regexp
|
return &Regexp{Regexp: regexp}
|
||||||
}
|
}
|
||||||
|
|
||||||
func MustParseURL(t *testing.T, rawURL string) *url.URL {
|
func MustParseURL(t *testing.T, rawURL string) *url.URL {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package ingress
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
@ -20,7 +21,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
HelloWorldService = "Hello World test origin"
|
HelloWorldService = "hello_world"
|
||||||
|
HttpStatusService = "http_status"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OriginService is something a tunnel can proxy traffic to.
|
// OriginService is something a tunnel can proxy traffic to.
|
||||||
|
@ -31,6 +33,7 @@ type OriginService interface {
|
||||||
// starting the origin service.
|
// starting the origin service.
|
||||||
// Implementor of services managed by cloudflared should terminate the service if shutdownC is closed
|
// Implementor of services managed by cloudflared should terminate the service if shutdownC is closed
|
||||||
start(log *zerolog.Logger, shutdownC <-chan struct{}, cfg OriginRequestConfig) error
|
start(log *zerolog.Logger, shutdownC <-chan struct{}, cfg OriginRequestConfig) error
|
||||||
|
MarshalJSON() ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// unixSocketPath is an OriginService representing a unix socket (which accepts HTTP or HTTPS)
|
// unixSocketPath is an OriginService representing a unix socket (which accepts HTTP or HTTPS)
|
||||||
|
@ -41,7 +44,11 @@ type unixSocketPath struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *unixSocketPath) String() string {
|
func (o *unixSocketPath) String() string {
|
||||||
return "unix socket: " + o.path
|
scheme := ""
|
||||||
|
if o.scheme == "https" {
|
||||||
|
scheme = "+tls"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("unix%s:%s", scheme, o.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *unixSocketPath) start(log *zerolog.Logger, _ <-chan struct{}, cfg OriginRequestConfig) error {
|
func (o *unixSocketPath) start(log *zerolog.Logger, _ <-chan struct{}, cfg OriginRequestConfig) error {
|
||||||
|
@ -53,6 +60,10 @@ func (o *unixSocketPath) start(log *zerolog.Logger, _ <-chan struct{}, cfg Origi
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o unixSocketPath) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(o.String())
|
||||||
|
}
|
||||||
|
|
||||||
type httpService struct {
|
type httpService struct {
|
||||||
url *url.URL
|
url *url.URL
|
||||||
hostHeader string
|
hostHeader string
|
||||||
|
@ -73,6 +84,10 @@ func (o *httpService) String() string {
|
||||||
return o.url.String()
|
return o.url.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o httpService) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(o.String())
|
||||||
|
}
|
||||||
|
|
||||||
// rawTCPService dials TCP to the destination specified by the client
|
// rawTCPService dials TCP to the destination specified by the client
|
||||||
// It's used by warp routing
|
// It's used by warp routing
|
||||||
type rawTCPService struct {
|
type rawTCPService struct {
|
||||||
|
@ -87,6 +102,10 @@ func (o *rawTCPService) start(log *zerolog.Logger, _ <-chan struct{}, cfg Origin
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o rawTCPService) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(o.String())
|
||||||
|
}
|
||||||
|
|
||||||
// tcpOverWSService models TCP origins serving eyeballs connecting over websocket, such as
|
// tcpOverWSService models TCP origins serving eyeballs connecting over websocket, such as
|
||||||
// cloudflared access commands.
|
// cloudflared access commands.
|
||||||
type tcpOverWSService struct {
|
type tcpOverWSService struct {
|
||||||
|
@ -153,6 +172,10 @@ func (o *tcpOverWSService) start(log *zerolog.Logger, _ <-chan struct{}, cfg Ori
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o tcpOverWSService) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(o.String())
|
||||||
|
}
|
||||||
|
|
||||||
func (o *socksProxyOverWSService) start(log *zerolog.Logger, _ <-chan struct{}, cfg OriginRequestConfig) error {
|
func (o *socksProxyOverWSService) start(log *zerolog.Logger, _ <-chan struct{}, cfg OriginRequestConfig) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -161,6 +184,10 @@ func (o *socksProxyOverWSService) String() string {
|
||||||
return ServiceSocksProxy
|
return ServiceSocksProxy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o socksProxyOverWSService) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(o.String())
|
||||||
|
}
|
||||||
|
|
||||||
// HelloWorld is an OriginService for the built-in Hello World server.
|
// HelloWorld is an OriginService for the built-in Hello World server.
|
||||||
// Users only use this for testing and experimenting with cloudflared.
|
// Users only use this for testing and experimenting with cloudflared.
|
||||||
type helloWorld struct {
|
type helloWorld struct {
|
||||||
|
@ -197,6 +224,10 @@ func (o *helloWorld) start(
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o helloWorld) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(o.String())
|
||||||
|
}
|
||||||
|
|
||||||
// statusCode is an OriginService that just responds with a given HTTP status.
|
// statusCode is an OriginService that just responds with a given HTTP status.
|
||||||
// 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 {
|
||||||
|
@ -213,7 +244,7 @@ func newStatusCode(status int) statusCode {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *statusCode) String() string {
|
func (o *statusCode) String() string {
|
||||||
return fmt.Sprintf("HTTP %d", o.resp.StatusCode)
|
return fmt.Sprintf("http_status:%d", o.resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *statusCode) start(
|
func (o *statusCode) start(
|
||||||
|
@ -224,6 +255,10 @@ func (o *statusCode) start(
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o statusCode) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(o.String())
|
||||||
|
}
|
||||||
|
|
||||||
type NopReadCloser struct{}
|
type NopReadCloser struct{}
|
||||||
|
|
||||||
// Read always returns EOF to signal end of input
|
// Read always returns EOF to signal end of input
|
||||||
|
@ -245,8 +280,8 @@ func newHTTPTransport(service OriginService, cfg OriginRequestConfig, log *zerol
|
||||||
Proxy: http.ProxyFromEnvironment,
|
Proxy: http.ProxyFromEnvironment,
|
||||||
MaxIdleConns: cfg.KeepAliveConnections,
|
MaxIdleConns: cfg.KeepAliveConnections,
|
||||||
MaxIdleConnsPerHost: cfg.KeepAliveConnections,
|
MaxIdleConnsPerHost: cfg.KeepAliveConnections,
|
||||||
IdleConnTimeout: cfg.KeepAliveTimeout,
|
IdleConnTimeout: cfg.KeepAliveTimeout.Duration,
|
||||||
TLSHandshakeTimeout: cfg.TLSTimeout,
|
TLSHandshakeTimeout: cfg.TLSTimeout.Duration,
|
||||||
ExpectContinueTimeout: 1 * time.Second,
|
ExpectContinueTimeout: 1 * time.Second,
|
||||||
TLSClientConfig: &tls.Config{RootCAs: originCertPool, InsecureSkipVerify: cfg.NoTLSVerify},
|
TLSClientConfig: &tls.Config{RootCAs: originCertPool, InsecureSkipVerify: cfg.NoTLSVerify},
|
||||||
}
|
}
|
||||||
|
@ -255,8 +290,8 @@ func newHTTPTransport(service OriginService, cfg OriginRequestConfig, log *zerol
|
||||||
}
|
}
|
||||||
|
|
||||||
dialer := &net.Dialer{
|
dialer := &net.Dialer{
|
||||||
Timeout: cfg.ConnectTimeout,
|
Timeout: cfg.ConnectTimeout.Duration,
|
||||||
KeepAlive: cfg.TCPKeepAlive,
|
KeepAlive: cfg.TCPKeepAlive.Duration,
|
||||||
}
|
}
|
||||||
if cfg.NoHappyEyeballs {
|
if cfg.NoHappyEyeballs {
|
||||||
dialer.FallbackDelay = -1 // As of Golang 1.12, a negative delay disables "happy eyeballs"
|
dialer.FallbackDelay = -1 // As of Golang 1.12, a negative delay disables "happy eyeballs"
|
||||||
|
@ -296,3 +331,7 @@ func (mos MockOriginHTTPService) String() string {
|
||||||
func (mos MockOriginHTTPService) start(log *zerolog.Logger, _ <-chan struct{}, cfg OriginRequestConfig) error {
|
func (mos MockOriginHTTPService) start(log *zerolog.Logger, _ <-chan struct{}, cfg OriginRequestConfig) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mos MockOriginHTTPService) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(mos.String())
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package ingress
|
package ingress
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -9,18 +10,18 @@ import (
|
||||||
// service running on the given URL.
|
// service running on the given URL.
|
||||||
type Rule struct {
|
type Rule struct {
|
||||||
// Requests for this hostname will be proxied to this rule's service.
|
// Requests for this hostname will be proxied to this rule's service.
|
||||||
Hostname string
|
Hostname string `json:"hostname"`
|
||||||
|
|
||||||
// Path is an optional regex that can specify path-driven ingress rules.
|
// Path is an optional regex that can specify path-driven ingress rules.
|
||||||
Path *regexp.Regexp
|
Path *Regexp `json:"path"`
|
||||||
|
|
||||||
// A (probably local) address. Requests for a hostname which matches this
|
// A (probably local) address. Requests for a hostname which matches this
|
||||||
// rule's hostname pattern will be proxied to the service running on this
|
// rule's hostname pattern will be proxied to the service running on this
|
||||||
// address.
|
// address.
|
||||||
Service OriginService
|
Service OriginService `json:"service"`
|
||||||
|
|
||||||
// Configure the request cloudflared sends to this specific origin.
|
// Configure the request cloudflared sends to this specific origin.
|
||||||
Config OriginRequestConfig
|
Config OriginRequestConfig `json:"originRequest"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MultiLineString is for outputting rules in a human-friendly way when Cloudflared
|
// MultiLineString is for outputting rules in a human-friendly way when Cloudflared
|
||||||
|
@ -32,9 +33,9 @@ func (r Rule) MultiLineString() string {
|
||||||
out.WriteString(r.Hostname)
|
out.WriteString(r.Hostname)
|
||||||
out.WriteRune('\n')
|
out.WriteRune('\n')
|
||||||
}
|
}
|
||||||
if r.Path != nil {
|
if r.Path != nil && r.Path.Regexp != nil {
|
||||||
out.WriteString("\tpath: ")
|
out.WriteString("\tpath: ")
|
||||||
out.WriteString(r.Path.String())
|
out.WriteString(r.Path.Regexp.String())
|
||||||
out.WriteRune('\n')
|
out.WriteRune('\n')
|
||||||
}
|
}
|
||||||
out.WriteString("\tservice: ")
|
out.WriteString("\tservice: ")
|
||||||
|
@ -45,6 +46,18 @@ func (r Rule) MultiLineString() string {
|
||||||
// Matches checks if the rule matches a given hostname/path combination.
|
// Matches checks if the rule matches a given hostname/path combination.
|
||||||
func (r *Rule) Matches(hostname, path string) bool {
|
func (r *Rule) Matches(hostname, path string) bool {
|
||||||
hostMatch := r.Hostname == "" || r.Hostname == "*" || matchHost(r.Hostname, hostname)
|
hostMatch := r.Hostname == "" || r.Hostname == "*" || matchHost(r.Hostname, hostname)
|
||||||
pathMatch := r.Path == nil || r.Path.MatchString(path)
|
pathMatch := r.Path == nil || r.Path.Regexp == nil || r.Path.Regexp.MatchString(path)
|
||||||
return hostMatch && pathMatch
|
return hostMatch && pathMatch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Regexp adds unmarshalling from json for regexp.Regexp
|
||||||
|
type Regexp struct {
|
||||||
|
*regexp.Regexp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Regexp) MarshalJSON() ([]byte, error) {
|
||||||
|
if r.Regexp == nil {
|
||||||
|
return json.Marshal(nil)
|
||||||
|
}
|
||||||
|
return json.Marshal(r.Regexp.String())
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package ingress
|
package ingress
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -8,12 +9,14 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/cloudflare/cloudflared/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_rule_matches(t *testing.T) {
|
func Test_rule_matches(t *testing.T) {
|
||||||
type fields struct {
|
type fields struct {
|
||||||
Hostname string
|
Hostname string
|
||||||
Path *regexp.Regexp
|
Path *Regexp
|
||||||
Service OriginService
|
Service OriginService
|
||||||
}
|
}
|
||||||
type args struct {
|
type args struct {
|
||||||
|
@ -99,13 +102,35 @@ func Test_rule_matches(t *testing.T) {
|
||||||
name: "Hostname and path",
|
name: "Hostname and path",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
Hostname: "*.example.com",
|
Hostname: "*.example.com",
|
||||||
Path: regexp.MustCompile("/static/.*\\.html"),
|
Path: &Regexp{Regexp: regexp.MustCompile("/static/.*\\.html")},
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
requestURL: MustParseURL(t, "https://www.example.com/static/index.html"),
|
requestURL: MustParseURL(t, "https://www.example.com/static/index.html"),
|
||||||
},
|
},
|
||||||
want: true,
|
want: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Hostname and empty Regex",
|
||||||
|
fields: fields{
|
||||||
|
Hostname: "example.com",
|
||||||
|
Path: &Regexp{},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
requestURL: MustParseURL(t, "https://example.com/"),
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Hostname and nil path",
|
||||||
|
fields: fields{
|
||||||
|
Hostname: "example.com",
|
||||||
|
Path: nil,
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
requestURL: MustParseURL(t, "https://example.com/"),
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -144,3 +169,52 @@ func TestStaticHTTPStatus(t *testing.T) {
|
||||||
sendReq()
|
sendReq()
|
||||||
sendReq()
|
sendReq()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMarshalJSON(t *testing.T) {
|
||||||
|
localhost8000 := MustParseURL(t, "https://localhost:8000")
|
||||||
|
defaultConfig := setConfig(originRequestFromConfig(config.OriginRequestConfig{}), config.OriginRequestConfig{})
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
path *Regexp
|
||||||
|
expected string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Nil",
|
||||||
|
path: nil,
|
||||||
|
expected: `{"hostname":"example.com","path":null,"service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null}}`,
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Nil regex",
|
||||||
|
path: &Regexp{Regexp: nil},
|
||||||
|
expected: `{"hostname":"example.com","path":null,"service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null}}`,
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Empty",
|
||||||
|
path: &Regexp{Regexp: regexp.MustCompile("")},
|
||||||
|
expected: `{"hostname":"example.com","path":"","service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null}}`,
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Basic",
|
||||||
|
path: &Regexp{Regexp: regexp.MustCompile("/echo")},
|
||||||
|
expected: `{"hostname":"example.com","path":"/echo","service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null}}`,
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
r := Rule{
|
||||||
|
Hostname: "example.com",
|
||||||
|
Service: &httpService{url: localhost8000},
|
||||||
|
Path: tt.path,
|
||||||
|
Config: defaultConfig,
|
||||||
|
}
|
||||||
|
bytes, err := json.Marshal(r)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tt.expected, string(bytes))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -22,7 +22,16 @@ const (
|
||||||
startupTime = time.Millisecond * 500
|
startupTime = time.Millisecond * 500
|
||||||
)
|
)
|
||||||
|
|
||||||
func newMetricsHandler(readyServer *ReadyServer, quickTunnelHostname string) *mux.Router {
|
type orchestrator interface {
|
||||||
|
GetConfigJSON() ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMetricsHandler(
|
||||||
|
readyServer *ReadyServer,
|
||||||
|
quickTunnelHostname string,
|
||||||
|
orchestrator orchestrator,
|
||||||
|
log *zerolog.Logger,
|
||||||
|
) *mux.Router {
|
||||||
router := mux.NewRouter()
|
router := mux.NewRouter()
|
||||||
router.PathPrefix("/debug/").Handler(http.DefaultServeMux)
|
router.PathPrefix("/debug/").Handler(http.DefaultServeMux)
|
||||||
|
|
||||||
|
@ -36,6 +45,18 @@ func newMetricsHandler(readyServer *ReadyServer, quickTunnelHostname string) *mu
|
||||||
router.HandleFunc("/quicktunnel", func(w http.ResponseWriter, r *http.Request) {
|
router.HandleFunc("/quicktunnel", func(w http.ResponseWriter, r *http.Request) {
|
||||||
_, _ = fmt.Fprintf(w, `{"hostname":"%s"}`, quickTunnelHostname)
|
_, _ = fmt.Fprintf(w, `{"hostname":"%s"}`, quickTunnelHostname)
|
||||||
})
|
})
|
||||||
|
if orchestrator != nil {
|
||||||
|
router.HandleFunc("/config", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
json, err := orchestrator.GetConfigJSON()
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
_, _ = fmt.Fprintf(w, "ERR: %v", err)
|
||||||
|
log.Err(err).Msg("Failed to serve config")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, _ = w.Write(json)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
@ -45,6 +66,7 @@ func ServeMetrics(
|
||||||
shutdownC <-chan struct{},
|
shutdownC <-chan struct{},
|
||||||
readyServer *ReadyServer,
|
readyServer *ReadyServer,
|
||||||
quickTunnelHostname string,
|
quickTunnelHostname string,
|
||||||
|
orchestrator orchestrator,
|
||||||
log *zerolog.Logger,
|
log *zerolog.Logger,
|
||||||
) (err error) {
|
) (err error) {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
@ -52,7 +74,7 @@ func ServeMetrics(
|
||||||
trace.AuthRequest = func(*http.Request) (bool, bool) { return true, true }
|
trace.AuthRequest = func(*http.Request) (bool, bool) { return true, true }
|
||||||
// TODO: parameterize ReadTimeout and WriteTimeout. The maximum time we can
|
// TODO: parameterize ReadTimeout and WriteTimeout. The maximum time we can
|
||||||
// profile CPU usage depends on WriteTimeout
|
// profile CPU usage depends on WriteTimeout
|
||||||
h := newMetricsHandler(readyServer, quickTunnelHostname)
|
h := newMetricsHandler(readyServer, quickTunnelHostname, orchestrator, log)
|
||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
ReadTimeout: 10 * time.Second,
|
ReadTimeout: 10 * time.Second,
|
||||||
WriteTimeout: 10 * time.Second,
|
WriteTimeout: 10 * time.Second,
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
|
|
||||||
|
"github.com/cloudflare/cloudflared/config"
|
||||||
"github.com/cloudflare/cloudflared/connection"
|
"github.com/cloudflare/cloudflared/connection"
|
||||||
"github.com/cloudflare/cloudflared/ingress"
|
"github.com/cloudflare/cloudflared/ingress"
|
||||||
"github.com/cloudflare/cloudflared/proxy"
|
"github.com/cloudflare/cloudflared/proxy"
|
||||||
|
@ -130,6 +131,32 @@ func (o *Orchestrator) updateIngress(ingressRules ingress.Ingress, warpRoutingEn
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetConfigJSON returns the current version and configuration as JSON
|
||||||
|
func (o *Orchestrator) GetConfigJSON() ([]byte, error) {
|
||||||
|
o.lock.RLock()
|
||||||
|
defer o.lock.RUnlock()
|
||||||
|
var currentConfiguration = struct {
|
||||||
|
Version int32 `json:"version"`
|
||||||
|
Config struct {
|
||||||
|
Ingress []ingress.Rule `json:"ingress"`
|
||||||
|
WarpRouting config.WarpRoutingConfig `json:"warp-routing"`
|
||||||
|
OriginRequest ingress.OriginRequestConfig `json:"originRequest"`
|
||||||
|
} `json:"config"`
|
||||||
|
}{
|
||||||
|
Version: o.currentVersion,
|
||||||
|
Config: struct {
|
||||||
|
Ingress []ingress.Rule `json:"ingress"`
|
||||||
|
WarpRouting config.WarpRoutingConfig `json:"warp-routing"`
|
||||||
|
OriginRequest ingress.OriginRequestConfig `json:"originRequest"`
|
||||||
|
}{
|
||||||
|
Ingress: o.config.Ingress.Rules,
|
||||||
|
WarpRouting: config.WarpRoutingConfig{Enabled: o.config.WarpRoutingEnabled},
|
||||||
|
OriginRequest: o.config.Ingress.Defaults,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return json.Marshal(currentConfiguration)
|
||||||
|
}
|
||||||
|
|
||||||
// GetOriginProxy returns an interface to proxy to origin. It satisfies connection.ConfigManager interface
|
// GetOriginProxy returns an interface to proxy to origin. It satisfies connection.ConfigManager interface
|
||||||
func (o *Orchestrator) GetOriginProxy() (connection.OriginProxy, error) {
|
func (o *Orchestrator) GetOriginProxy() (connection.OriginProxy, error) {
|
||||||
val := o.proxy.Load()
|
val := o.proxy.Load()
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/cloudflare/cloudflared/config"
|
||||||
"github.com/cloudflare/cloudflared/connection"
|
"github.com/cloudflare/cloudflared/connection"
|
||||||
"github.com/cloudflare/cloudflared/ingress"
|
"github.com/cloudflare/cloudflared/ingress"
|
||||||
"github.com/cloudflare/cloudflared/proxy"
|
"github.com/cloudflare/cloudflared/proxy"
|
||||||
|
@ -99,7 +100,7 @@ func TestUpdateConfiguration(t *testing.T) {
|
||||||
require.Equal(t, "http://192.16.19.1:443", configV2.Ingress.Rules[0].Service.String())
|
require.Equal(t, "http://192.16.19.1:443", configV2.Ingress.Rules[0].Service.String())
|
||||||
require.Len(t, configV2.Ingress.Rules, 3)
|
require.Len(t, configV2.Ingress.Rules, 3)
|
||||||
// originRequest of this ingress rule overrides global default
|
// originRequest of this ingress rule overrides global default
|
||||||
require.Equal(t, time.Second*10, configV2.Ingress.Rules[0].Config.ConnectTimeout)
|
require.Equal(t, config.CustomDuration{Duration: time.Second * 10}, configV2.Ingress.Rules[0].Config.ConnectTimeout)
|
||||||
require.Equal(t, true, configV2.Ingress.Rules[0].Config.NoTLSVerify)
|
require.Equal(t, true, configV2.Ingress.Rules[0].Config.NoTLSVerify)
|
||||||
// Inherited from global default
|
// Inherited from global default
|
||||||
require.Equal(t, true, configV2.Ingress.Rules[0].Config.NoHappyEyeballs)
|
require.Equal(t, true, configV2.Ingress.Rules[0].Config.NoHappyEyeballs)
|
||||||
|
@ -108,14 +109,14 @@ func TestUpdateConfiguration(t *testing.T) {
|
||||||
require.True(t, configV2.Ingress.Rules[1].Matches("jira.tunnel.org", "/users"))
|
require.True(t, configV2.Ingress.Rules[1].Matches("jira.tunnel.org", "/users"))
|
||||||
require.Equal(t, "http://172.32.20.6:80", configV2.Ingress.Rules[1].Service.String())
|
require.Equal(t, "http://172.32.20.6:80", configV2.Ingress.Rules[1].Service.String())
|
||||||
// originRequest of this ingress rule overrides global default
|
// originRequest of this ingress rule overrides global default
|
||||||
require.Equal(t, time.Second*30, configV2.Ingress.Rules[1].Config.ConnectTimeout)
|
require.Equal(t, config.CustomDuration{Duration: time.Second * 30}, configV2.Ingress.Rules[1].Config.ConnectTimeout)
|
||||||
require.Equal(t, true, configV2.Ingress.Rules[1].Config.NoTLSVerify)
|
require.Equal(t, true, configV2.Ingress.Rules[1].Config.NoTLSVerify)
|
||||||
// Inherited from global default
|
// Inherited from global default
|
||||||
require.Equal(t, true, configV2.Ingress.Rules[1].Config.NoHappyEyeballs)
|
require.Equal(t, true, configV2.Ingress.Rules[1].Config.NoHappyEyeballs)
|
||||||
// Validate ingress rule 2, it's the catch-all rule
|
// Validate ingress rule 2, it's the catch-all rule
|
||||||
require.True(t, configV2.Ingress.Rules[2].Matches("blogs.tunnel.io", "/2022/02/10"))
|
require.True(t, configV2.Ingress.Rules[2].Matches("blogs.tunnel.io", "/2022/02/10"))
|
||||||
// Inherited from global default
|
// Inherited from global default
|
||||||
require.Equal(t, time.Second*90, configV2.Ingress.Rules[2].Config.ConnectTimeout)
|
require.Equal(t, config.CustomDuration{Duration: time.Second * 90}, configV2.Ingress.Rules[2].Config.ConnectTimeout)
|
||||||
require.Equal(t, false, configV2.Ingress.Rules[2].Config.NoTLSVerify)
|
require.Equal(t, false, configV2.Ingress.Rules[2].Config.NoTLSVerify)
|
||||||
require.Equal(t, true, configV2.Ingress.Rules[2].Config.NoHappyEyeballs)
|
require.Equal(t, true, configV2.Ingress.Rules[2].Config.NoHappyEyeballs)
|
||||||
require.True(t, configV2.WarpRoutingEnabled)
|
require.True(t, configV2.WarpRoutingEnabled)
|
||||||
|
|
Loading…
Reference in New Issue