TUN-5702: Allow to deserialize config from JSON
This commit is contained in:
		
							parent
							
								
									d07d24e5a2
								
							
						
					
					
						commit
						b1edf5b96d
					
				|  | @ -175,60 +175,62 @@ func ValidateUrl(c *cli.Context, allowURLFromArgs bool) (*url.URL, error) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type UnvalidatedIngressRule struct { | type UnvalidatedIngressRule struct { | ||||||
| 	Hostname      string | 	Hostname      string              `json:"hostname"` | ||||||
| 	Path          string | 	Path          string              `json:"path"` | ||||||
| 	Service       string | 	Service       string              `json:"service"` | ||||||
| 	OriginRequest OriginRequestConfig `yaml:"originRequest"` | 	OriginRequest OriginRequestConfig `yaml:"originRequest" json:"originRequest"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // OriginRequestConfig is a set of optional fields that users may set to
 | // OriginRequestConfig is a set of optional fields that users may set to
 | ||||||
| // customize how cloudflared sends requests to origin services. It is used to set
 | // customize how cloudflared sends requests to origin services. It is used to set
 | ||||||
| // up general config that apply to all rules, and also, specific per-rule
 | // up general config that apply to all rules, and also, specific per-rule
 | ||||||
| // config.
 | // config.
 | ||||||
| // 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".
 | ||||||
|  | // - To specify a time.Duration in json, use int64 of the nanoseconds
 | ||||||
| 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 *time.Duration `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 *time.Duration `yaml:"tlsTimeout" json:"tlsTimeout"` | ||||||
| 	// HTTP proxy TCP keepalive duration
 | 	// HTTP proxy TCP keepalive duration
 | ||||||
| 	TCPKeepAlive *time.Duration `yaml:"tcpKeepAlive"` | 	TCPKeepAlive *time.Duration `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 maximum keepalive connection pool size
 | 	// HTTP proxy maximum keepalive connection pool size
 | ||||||
| 	KeepAliveConnections *int `yaml:"keepAliveConnections"` | 	KeepAliveConnections *int `yaml:"keepAliveConnections" json:"keepAliveConnections"` | ||||||
| 	// HTTP proxy timeout for closing an idle connection
 | 	// HTTP proxy timeout for closing an idle connection
 | ||||||
| 	KeepAliveTimeout *time.Duration `yaml:"keepAliveTimeout"` | 	KeepAliveTimeout *time.Duration `yaml:"keepAliveTimeout" json:"keepAliveTimeout"` | ||||||
| 	// 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"` | ||||||
| 	// Valid options are 'socks' or empty.
 | 	// Valid options are 'socks' or empty.
 | ||||||
| 	ProxyType *string `yaml:"proxyType"` | 	ProxyType *string `yaml:"proxyType" json:"proxyType"` | ||||||
| 	// IP rules for the proxy service
 | 	// IP rules for the proxy service
 | ||||||
| 	IPRules []IngressIPRule `yaml:"ipRules"` | 	IPRules []IngressIPRule `yaml:"ipRules" json:"ipRules"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type IngressIPRule struct { | type IngressIPRule struct { | ||||||
| 	Prefix *string `yaml:"prefix"` | 	Prefix *string `yaml:"prefix" json:"prefix"` | ||||||
| 	Ports  []int   `yaml:"ports"` | 	Ports  []int   `yaml:"ports" json:"ports"` | ||||||
| 	Allow  bool    `yaml:"allow"` | 	Allow  bool    `yaml:"allow" json:"allow"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type Configuration struct { | type Configuration struct { | ||||||
|  | @ -240,7 +242,7 @@ type Configuration struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type WarpRoutingConfig struct { | type WarpRoutingConfig struct { | ||||||
| 	Enabled bool `yaml:"enabled"` | 	Enabled bool `yaml:"enabled" json:"enabled"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type configFileSettings struct { | type configFileSettings struct { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| package config | package config | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"encoding/json" | ||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
|  | @ -26,6 +27,18 @@ func TestConfigFileSettings(t *testing.T) { | ||||||
| 	) | 	) | ||||||
| 	rawYAML := ` | 	rawYAML := ` | ||||||
| tunnel: config-file-test | tunnel: config-file-test | ||||||
|  | originRequest: | ||||||
|  |   ipRules: | ||||||
|  |   - prefix: "10.0.0.0/8" | ||||||
|  |     ports: | ||||||
|  |     - 80 | ||||||
|  |     - 8080 | ||||||
|  |     allow: false | ||||||
|  |   - prefix: "fc00::/7" | ||||||
|  |     ports: | ||||||
|  |     - 443 | ||||||
|  |     - 4443 | ||||||
|  |     allow: true | ||||||
| ingress: | ingress: | ||||||
|  - hostname: tunnel1.example.com |  - hostname: tunnel1.example.com | ||||||
|    path: /id |    path: /id | ||||||
|  | @ -53,6 +66,21 @@ counters: | ||||||
| 	assert.Equal(t, firstIngress, config.Ingress[0]) | 	assert.Equal(t, firstIngress, config.Ingress[0]) | ||||||
| 	assert.Equal(t, secondIngress, config.Ingress[1]) | 	assert.Equal(t, secondIngress, config.Ingress[1]) | ||||||
| 	assert.Equal(t, warpRouting, config.WarpRouting) | 	assert.Equal(t, warpRouting, config.WarpRouting) | ||||||
|  | 	privateV4 := "10.0.0.0/8" | ||||||
|  | 	privateV6 := "fc00::/7" | ||||||
|  | 	ipRules := []IngressIPRule{ | ||||||
|  | 		{ | ||||||
|  | 			Prefix: &privateV4, | ||||||
|  | 			Ports:  []int{80, 8080}, | ||||||
|  | 			Allow:  false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Prefix: &privateV6, | ||||||
|  | 			Ports:  []int{443, 4443}, | ||||||
|  | 			Allow:  true, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	assert.Equal(t, ipRules, config.OriginRequest.IPRules) | ||||||
| 
 | 
 | ||||||
| 	retries, err := config.Int("retries") | 	retries, err := config.Int("retries") | ||||||
| 	assert.NoError(t, err) | 	assert.NoError(t, err) | ||||||
|  | @ -81,3 +109,71 @@ counters: | ||||||
| 	assert.Equal(t, 456, counters[1]) | 	assert.Equal(t, 456, counters[1]) | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func TestUnmarshalOriginRequestConfig(t *testing.T) { | ||||||
|  | 	raw := []byte(` | ||||||
|  | { | ||||||
|  | 	"connectTimeout": 10000000000, | ||||||
|  | 	"tlsTimeout": 30000000000, | ||||||
|  | 	"tcpKeepAlive": 30000000000, | ||||||
|  | 	"noHappyEyeballs": true, | ||||||
|  | 	"keepAliveTimeout": 60000000000, | ||||||
|  | 	"keepAliveConnections": 10, | ||||||
|  | 	"httpHostHeader": "app.tunnel.com", | ||||||
|  | 	"originServerName": "app.tunnel.com", | ||||||
|  | 	"caPool": "/etc/capool", | ||||||
|  | 	"noTLSVerify": true, | ||||||
|  | 	"disableChunkedEncoding": true, | ||||||
|  | 	"bastionMode": true, | ||||||
|  | 	"proxyAddress": "127.0.0.3", | ||||||
|  | 	"proxyPort": 9000, | ||||||
|  | 	"proxyType": "socks", | ||||||
|  | 	"ipRules": [ | ||||||
|  | 		{ | ||||||
|  | 			"prefix": "10.0.0.0/8", | ||||||
|  | 			"ports": [80, 8080], | ||||||
|  | 			"allow": false | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"prefix": "fc00::/7", | ||||||
|  | 			"ports": [443, 4443], | ||||||
|  | 			"allow": true | ||||||
|  | 		} | ||||||
|  | 	] | ||||||
|  | } | ||||||
|  | `) | ||||||
|  | 	var config OriginRequestConfig | ||||||
|  | 	assert.NoError(t, json.Unmarshal(raw, &config)) | ||||||
|  | 	assert.Equal(t, time.Second*10, *config.ConnectTimeout) | ||||||
|  | 	assert.Equal(t, time.Second*30, *config.TLSTimeout) | ||||||
|  | 	assert.Equal(t, time.Second*30, *config.TCPKeepAlive) | ||||||
|  | 	assert.Equal(t, true, *config.NoHappyEyeballs) | ||||||
|  | 	assert.Equal(t, time.Second*60, *config.KeepAliveTimeout) | ||||||
|  | 	assert.Equal(t, 10, *config.KeepAliveConnections) | ||||||
|  | 	assert.Equal(t, "app.tunnel.com", *config.HTTPHostHeader) | ||||||
|  | 	assert.Equal(t, "app.tunnel.com", *config.OriginServerName) | ||||||
|  | 	assert.Equal(t, "/etc/capool", *config.CAPool) | ||||||
|  | 	assert.Equal(t, true, *config.NoTLSVerify) | ||||||
|  | 	assert.Equal(t, true, *config.DisableChunkedEncoding) | ||||||
|  | 	assert.Equal(t, true, *config.BastionMode) | ||||||
|  | 	assert.Equal(t, "127.0.0.3", *config.ProxyAddress) | ||||||
|  | 	assert.Equal(t, true, *config.NoTLSVerify) | ||||||
|  | 	assert.Equal(t, uint(9000), *config.ProxyPort) | ||||||
|  | 	assert.Equal(t, "socks", *config.ProxyType) | ||||||
|  | 
 | ||||||
|  | 	privateV4 := "10.0.0.0/8" | ||||||
|  | 	privateV6 := "fc00::/7" | ||||||
|  | 	ipRules := []IngressIPRule{ | ||||||
|  | 		{ | ||||||
|  | 			Prefix: &privateV4, | ||||||
|  | 			Ports:  []int{80, 8080}, | ||||||
|  | 			Allow:  false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Prefix: &privateV6, | ||||||
|  | 			Ports:  []int{443, 4443}, | ||||||
|  | 			Allow:  true, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	assert.Equal(t, ipRules, config.IPRules) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| package ingress | package ingress | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"encoding/json" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
|  | @ -38,6 +39,34 @@ const ( | ||||||
| 	socksProxy = "socks" | 	socksProxy = "socks" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | // RemoteConfig models ingress settings that can be managed remotely, for example through the dashboard.
 | ||||||
|  | type RemoteConfig struct { | ||||||
|  | 	Ingress     Ingress | ||||||
|  | 	WarpRouting config.WarpRoutingConfig | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type remoteConfigJSON struct { | ||||||
|  | 	GlobalOriginRequest config.OriginRequestConfig      `json:"originRequest"` | ||||||
|  | 	IngressRules        []config.UnvalidatedIngressRule `json:"ingress"` | ||||||
|  | 	WarpRouting         config.WarpRoutingConfig        `json:"warp-routing"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (rc *RemoteConfig) UnmarshalJSON(b []byte) error { | ||||||
|  | 	var rawConfig remoteConfigJSON | ||||||
|  | 	if err := json.Unmarshal(b, &rawConfig); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	ingress, err := validateIngress(rawConfig.IngressRules, originRequestFromConfig(rawConfig.GlobalOriginRequest)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	rc.Ingress = ingress | ||||||
|  | 	rc.WarpRouting = rawConfig.WarpRouting | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func originRequestFromSingeRule(c *cli.Context) OriginRequestConfig { | func originRequestFromSingeRule(c *cli.Context) OriginRequestConfig { | ||||||
| 	var connectTimeout time.Duration = defaultConnectTimeout | 	var connectTimeout time.Duration = defaultConnectTimeout | ||||||
| 	var tlsTimeout time.Duration = defaultTLSTimeout | 	var tlsTimeout time.Duration = defaultTLSTimeout | ||||||
|  | @ -119,7 +148,7 @@ func originRequestFromSingeRule(c *cli.Context) OriginRequestConfig { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func originRequestFromYAML(y config.OriginRequestConfig) OriginRequestConfig { | func originRequestFromConfig(c config.OriginRequestConfig) OriginRequestConfig { | ||||||
| 	out := OriginRequestConfig{ | 	out := OriginRequestConfig{ | ||||||
| 		ConnectTimeout:       defaultConnectTimeout, | 		ConnectTimeout:       defaultConnectTimeout, | ||||||
| 		TLSTimeout:           defaultTLSTimeout, | 		TLSTimeout:           defaultTLSTimeout, | ||||||
|  | @ -128,50 +157,58 @@ func originRequestFromYAML(y config.OriginRequestConfig) OriginRequestConfig { | ||||||
| 		KeepAliveTimeout:     defaultKeepAliveTimeout, | 		KeepAliveTimeout:     defaultKeepAliveTimeout, | ||||||
| 		ProxyAddress:         defaultProxyAddress, | 		ProxyAddress:         defaultProxyAddress, | ||||||
| 	} | 	} | ||||||
| 	if y.ConnectTimeout != nil { | 	if c.ConnectTimeout != nil { | ||||||
| 		out.ConnectTimeout = *y.ConnectTimeout | 		out.ConnectTimeout = *c.ConnectTimeout | ||||||
| 	} | 	} | ||||||
| 	if y.TLSTimeout != nil { | 	if c.TLSTimeout != nil { | ||||||
| 		out.TLSTimeout = *y.TLSTimeout | 		out.TLSTimeout = *c.TLSTimeout | ||||||
| 	} | 	} | ||||||
| 	if y.TCPKeepAlive != nil { | 	if c.TCPKeepAlive != nil { | ||||||
| 		out.TCPKeepAlive = *y.TCPKeepAlive | 		out.TCPKeepAlive = *c.TCPKeepAlive | ||||||
| 	} | 	} | ||||||
| 	if y.NoHappyEyeballs != nil { | 	if c.NoHappyEyeballs != nil { | ||||||
| 		out.NoHappyEyeballs = *y.NoHappyEyeballs | 		out.NoHappyEyeballs = *c.NoHappyEyeballs | ||||||
| 	} | 	} | ||||||
| 	if y.KeepAliveConnections != nil { | 	if c.KeepAliveConnections != nil { | ||||||
| 		out.KeepAliveConnections = *y.KeepAliveConnections | 		out.KeepAliveConnections = *c.KeepAliveConnections | ||||||
| 	} | 	} | ||||||
| 	if y.KeepAliveTimeout != nil { | 	if c.KeepAliveTimeout != nil { | ||||||
| 		out.KeepAliveTimeout = *y.KeepAliveTimeout | 		out.KeepAliveTimeout = *c.KeepAliveTimeout | ||||||
| 	} | 	} | ||||||
| 	if y.HTTPHostHeader != nil { | 	if c.HTTPHostHeader != nil { | ||||||
| 		out.HTTPHostHeader = *y.HTTPHostHeader | 		out.HTTPHostHeader = *c.HTTPHostHeader | ||||||
| 	} | 	} | ||||||
| 	if y.OriginServerName != nil { | 	if c.OriginServerName != nil { | ||||||
| 		out.OriginServerName = *y.OriginServerName | 		out.OriginServerName = *c.OriginServerName | ||||||
| 	} | 	} | ||||||
| 	if y.CAPool != nil { | 	if c.CAPool != nil { | ||||||
| 		out.CAPool = *y.CAPool | 		out.CAPool = *c.CAPool | ||||||
| 	} | 	} | ||||||
| 	if y.NoTLSVerify != nil { | 	if c.NoTLSVerify != nil { | ||||||
| 		out.NoTLSVerify = *y.NoTLSVerify | 		out.NoTLSVerify = *c.NoTLSVerify | ||||||
| 	} | 	} | ||||||
| 	if y.DisableChunkedEncoding != nil { | 	if c.DisableChunkedEncoding != nil { | ||||||
| 		out.DisableChunkedEncoding = *y.DisableChunkedEncoding | 		out.DisableChunkedEncoding = *c.DisableChunkedEncoding | ||||||
| 	} | 	} | ||||||
| 	if y.BastionMode != nil { | 	if c.BastionMode != nil { | ||||||
| 		out.BastionMode = *y.BastionMode | 		out.BastionMode = *c.BastionMode | ||||||
| 	} | 	} | ||||||
| 	if y.ProxyAddress != nil { | 	if c.ProxyAddress != nil { | ||||||
| 		out.ProxyAddress = *y.ProxyAddress | 		out.ProxyAddress = *c.ProxyAddress | ||||||
| 	} | 	} | ||||||
| 	if y.ProxyPort != nil { | 	if c.ProxyPort != nil { | ||||||
| 		out.ProxyPort = *y.ProxyPort | 		out.ProxyPort = *c.ProxyPort | ||||||
| 	} | 	} | ||||||
| 	if y.ProxyType != nil { | 	if c.ProxyType != nil { | ||||||
| 		out.ProxyType = *y.ProxyType | 		out.ProxyType = *c.ProxyType | ||||||
|  | 	} | ||||||
|  | 	if len(c.IPRules) > 0 { | ||||||
|  | 		for _, r := range c.IPRules { | ||||||
|  | 			rule, err := ipaccess.NewRuleByCIDR(r.Prefix, r.Ports, r.Allow) | ||||||
|  | 			if err == nil { | ||||||
|  | 				out.IPRules = append(out.IPRules, rule) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 	return out | 	return out | ||||||
| } | } | ||||||
|  | @ -188,10 +225,10 @@ type OriginRequestConfig struct { | ||||||
| 	TCPKeepAlive time.Duration `yaml:"tcpKeepAlive"` | 	TCPKeepAlive time.Duration `yaml:"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"` | ||||||
| 	// HTTP proxy maximum keepalive connection pool size
 |  | ||||||
| 	KeepAliveConnections int `yaml:"keepAliveConnections"` |  | ||||||
| 	// HTTP proxy timeout for closing an idle connection
 | 	// HTTP proxy timeout for closing an idle connection
 | ||||||
| 	KeepAliveTimeout time.Duration `yaml:"keepAliveTimeout"` | 	KeepAliveTimeout time.Duration `yaml:"keepAliveTimeout"` | ||||||
|  | 	// HTTP proxy maximum keepalive connection pool size
 | ||||||
|  | 	KeepAliveConnections int `yaml:"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"` | ||||||
| 	// Hostname on the origin server certificate.
 | 	// Hostname on the origin server certificate.
 | ||||||
|  | @ -308,6 +345,19 @@ func (defaults *OriginRequestConfig) setProxyType(overrides config.OriginRequest | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (defaults *OriginRequestConfig) setIPRules(overrides config.OriginRequestConfig) { | ||||||
|  | 	if val := overrides.IPRules; len(val) > 0 { | ||||||
|  | 		ipAccessRule := make([]ipaccess.Rule, len(overrides.IPRules)) | ||||||
|  | 		for i, r := range overrides.IPRules { | ||||||
|  | 			rule, err := ipaccess.NewRuleByCIDR(r.Prefix, r.Ports, r.Allow) | ||||||
|  | 			if err == nil { | ||||||
|  | 				ipAccessRule[i] = rule | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		defaults.IPRules = ipAccessRule | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // SetConfig gets config for the requests that cloudflared sends to origins.
 | // SetConfig gets config for the requests that cloudflared sends to origins.
 | ||||||
| // Each field has a setter method which sets a value for the field by trying to find:
 | // Each field has a setter method which sets a value for the field by trying to find:
 | ||||||
| //   1. The user config for this rule
 | //   1. The user config for this rule
 | ||||||
|  | @ -332,5 +382,6 @@ func setConfig(defaults OriginRequestConfig, overrides config.OriginRequestConfi | ||||||
| 	cfg.setProxyPort(overrides) | 	cfg.setProxyPort(overrides) | ||||||
| 	cfg.setProxyAddress(overrides) | 	cfg.setProxyAddress(overrides) | ||||||
| 	cfg.setProxyType(overrides) | 	cfg.setProxyType(overrides) | ||||||
|  | 	cfg.setIPRules(overrides) | ||||||
| 	return cfg | 	return cfg | ||||||
| } | } | ||||||
|  | @ -0,0 +1,422 @@ | ||||||
|  | package ingress | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"flag" | ||||||
|  | 	"testing" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/stretchr/testify/require" | ||||||
|  | 	"github.com/urfave/cli/v2" | ||||||
|  | 	yaml "gopkg.in/yaml.v2" | ||||||
|  | 
 | ||||||
|  | 	"github.com/cloudflare/cloudflared/config" | ||||||
|  | 	"github.com/cloudflare/cloudflared/ipaccess" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Ensure that the nullable config from `config` package and the
 | ||||||
|  | // non-nullable config from `ingress` package have the same number of
 | ||||||
|  | // fields.
 | ||||||
|  | // This test ensures that programmers didn't add a new field to
 | ||||||
|  | // one struct and forget to add it to the other ;)
 | ||||||
|  | func TestCorrespondingFields(t *testing.T) { | ||||||
|  | 	require.Equal( | ||||||
|  | 		t, | ||||||
|  | 		CountFields(t, config.OriginRequestConfig{}), | ||||||
|  | 		CountFields(t, OriginRequestConfig{}), | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func CountFields(t *testing.T, val interface{}) int { | ||||||
|  | 	b, err := yaml.Marshal(val) | ||||||
|  | 	require.NoError(t, err) | ||||||
|  | 	m := make(map[string]interface{}, 0) | ||||||
|  | 	err = yaml.Unmarshal(b, &m) | ||||||
|  | 	require.NoError(t, err) | ||||||
|  | 	return len(m) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestUnmarshalRemoteConfigOverridesGlobal(t *testing.T) { | ||||||
|  | 	rawConfig := []byte(` | ||||||
|  | { | ||||||
|  |     "originRequest": { | ||||||
|  |         "connectTimeout": 90, | ||||||
|  | 		"noHappyEyeballs": true | ||||||
|  |     }, | ||||||
|  |     "ingress": [ | ||||||
|  |         { | ||||||
|  |             "hostname": "jira.cfops.com", | ||||||
|  |             "service": "http://192.16.19.1:80", | ||||||
|  |             "originRequest": { | ||||||
|  |                 "noTLSVerify": true, | ||||||
|  |                 "connectTimeout": 10 | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "service": "http_status:404" | ||||||
|  |         } | ||||||
|  |     ], | ||||||
|  |     "warp-routing": { | ||||||
|  |         "enabled": true | ||||||
|  |     } | ||||||
|  | }	 | ||||||
|  | `) | ||||||
|  | 	var remoteConfig RemoteConfig | ||||||
|  | 	err := json.Unmarshal(rawConfig, &remoteConfig) | ||||||
|  | 	require.NoError(t, err) | ||||||
|  | 	require.True(t, remoteConfig.Ingress.Rules[0].Config.NoTLSVerify) | ||||||
|  | 	require.True(t, remoteConfig.Ingress.defaults.NoHappyEyeballs) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestOriginRequestConfigOverrides(t *testing.T) { | ||||||
|  | 	validate := func(ing Ingress) { | ||||||
|  | 		// Rule 0 didn't override anything, so it inherits the user-specified
 | ||||||
|  | 		// root-level configuration.
 | ||||||
|  | 		actual0 := ing.Rules[0].Config | ||||||
|  | 		expected0 := OriginRequestConfig{ | ||||||
|  | 			ConnectTimeout:         1 * time.Minute, | ||||||
|  | 			TLSTimeout:             1 * time.Second, | ||||||
|  | 			TCPKeepAlive:           1 * time.Second, | ||||||
|  | 			NoHappyEyeballs:        true, | ||||||
|  | 			KeepAliveTimeout:       1 * time.Second, | ||||||
|  | 			KeepAliveConnections:   1, | ||||||
|  | 			HTTPHostHeader:         "abc", | ||||||
|  | 			OriginServerName:       "a1", | ||||||
|  | 			CAPool:                 "/tmp/path0", | ||||||
|  | 			NoTLSVerify:            true, | ||||||
|  | 			DisableChunkedEncoding: true, | ||||||
|  | 			BastionMode:            true, | ||||||
|  | 			ProxyAddress:           "127.1.2.3", | ||||||
|  | 			ProxyPort:              uint(100), | ||||||
|  | 			ProxyType:              "socks5", | ||||||
|  | 			IPRules: []ipaccess.Rule{ | ||||||
|  | 				newIPRule(t, "10.0.0.0/8", []int{80, 8080}, false), | ||||||
|  | 				newIPRule(t, "fc00::/7", []int{443, 4443}, true), | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		require.Equal(t, expected0, actual0) | ||||||
|  | 
 | ||||||
|  | 		// Rule 1 overrode all the root-level config.
 | ||||||
|  | 		actual1 := ing.Rules[1].Config | ||||||
|  | 		expected1 := OriginRequestConfig{ | ||||||
|  | 			ConnectTimeout:         2 * time.Minute, | ||||||
|  | 			TLSTimeout:             2 * time.Second, | ||||||
|  | 			TCPKeepAlive:           2 * time.Second, | ||||||
|  | 			NoHappyEyeballs:        false, | ||||||
|  | 			KeepAliveTimeout:       2 * time.Second, | ||||||
|  | 			KeepAliveConnections:   2, | ||||||
|  | 			HTTPHostHeader:         "def", | ||||||
|  | 			OriginServerName:       "b2", | ||||||
|  | 			CAPool:                 "/tmp/path1", | ||||||
|  | 			NoTLSVerify:            false, | ||||||
|  | 			DisableChunkedEncoding: false, | ||||||
|  | 			BastionMode:            false, | ||||||
|  | 			ProxyAddress:           "interface", | ||||||
|  | 			ProxyPort:              uint(200), | ||||||
|  | 			ProxyType:              "", | ||||||
|  | 			IPRules: []ipaccess.Rule{ | ||||||
|  | 				newIPRule(t, "10.0.0.0/16", []int{3000, 3030}, false), | ||||||
|  | 				newIPRule(t, "192.16.0.0/24", []int{5000, 5050}, true), | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		require.Equal(t, expected1, actual1) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	rulesYAML := ` | ||||||
|  | originRequest: | ||||||
|  |   connectTimeout: 1m | ||||||
|  |   tlsTimeout: 1s | ||||||
|  |   noHappyEyeballs: true | ||||||
|  |   tcpKeepAlive: 1s | ||||||
|  |   keepAliveConnections: 1 | ||||||
|  |   keepAliveTimeout: 1s | ||||||
|  |   httpHostHeader: abc | ||||||
|  |   originServerName: a1 | ||||||
|  |   caPool: /tmp/path0 | ||||||
|  |   noTLSVerify: true | ||||||
|  |   disableChunkedEncoding: true | ||||||
|  |   bastionMode: True | ||||||
|  |   proxyAddress: 127.1.2.3 | ||||||
|  |   proxyPort: 100 | ||||||
|  |   proxyType: socks5 | ||||||
|  |   ipRules: | ||||||
|  |   - prefix: "10.0.0.0/8" | ||||||
|  |     ports: | ||||||
|  |     - 80 | ||||||
|  |     - 8080 | ||||||
|  |     allow: false | ||||||
|  |   - prefix: "fc00::/7" | ||||||
|  |     ports: | ||||||
|  |     - 443 | ||||||
|  |     - 4443 | ||||||
|  |     allow: true | ||||||
|  | ingress: | ||||||
|  | - hostname: tun.example.com | ||||||
|  |   service: https://localhost:8000
 | ||||||
|  | - hostname: "*" | ||||||
|  |   service: https://localhost:8001
 | ||||||
|  |   originRequest: | ||||||
|  |     connectTimeout: 2m | ||||||
|  |     tlsTimeout: 2s | ||||||
|  |     noHappyEyeballs: false | ||||||
|  |     tcpKeepAlive: 2s | ||||||
|  |     keepAliveConnections: 2 | ||||||
|  |     keepAliveTimeout: 2s | ||||||
|  |     httpHostHeader: def | ||||||
|  |     originServerName: b2 | ||||||
|  |     caPool: /tmp/path1 | ||||||
|  |     noTLSVerify: false | ||||||
|  |     disableChunkedEncoding: false | ||||||
|  |     bastionMode: false | ||||||
|  |     proxyAddress: interface | ||||||
|  |     proxyPort: 200 | ||||||
|  |     proxyType: "" | ||||||
|  |     ipRules: | ||||||
|  |     - prefix: "10.0.0.0/16" | ||||||
|  |       ports: | ||||||
|  |       - 3000 | ||||||
|  |       - 3030 | ||||||
|  |       allow: false | ||||||
|  |     - prefix: "192.16.0.0/24" | ||||||
|  |       ports: | ||||||
|  |       - 5000 | ||||||
|  |       - 5050 | ||||||
|  |       allow: true | ||||||
|  | ` | ||||||
|  | 
 | ||||||
|  | 	ing, err := ParseIngress(MustReadIngress(rulesYAML)) | ||||||
|  | 	require.NoError(t, err) | ||||||
|  | 	validate(ing) | ||||||
|  | 
 | ||||||
|  | 	rawConfig := []byte(` | ||||||
|  | { | ||||||
|  |     "originRequest": { | ||||||
|  |         "connectTimeout": 60000000000, | ||||||
|  | 		"tlsTimeout": 1000000000, | ||||||
|  | 		"noHappyEyeballs": true, | ||||||
|  | 		"tcpKeepAlive": 1000000000, | ||||||
|  | 		"keepAliveConnections": 1, | ||||||
|  | 		"keepAliveTimeout": 1000000000, | ||||||
|  | 		"httpHostHeader": "abc", | ||||||
|  | 		"originServerName": "a1", | ||||||
|  | 		"caPool": "/tmp/path0", | ||||||
|  | 		"noTLSVerify": true, | ||||||
|  | 		"disableChunkedEncoding": true, | ||||||
|  | 		"bastionMode": true, | ||||||
|  | 		"proxyAddress": "127.1.2.3", | ||||||
|  | 		"proxyPort": 100, | ||||||
|  | 		"proxyType": "socks5", | ||||||
|  | 		"ipRules": [ | ||||||
|  | 			{ | ||||||
|  | 				"prefix": "10.0.0.0/8", | ||||||
|  | 				"ports": [80, 8080], | ||||||
|  | 				"allow": false | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				"prefix": "fc00::/7", | ||||||
|  | 				"ports": [443, 4443], | ||||||
|  | 				"allow": true | ||||||
|  | 			} | ||||||
|  | 		] | ||||||
|  |     }, | ||||||
|  |     "ingress": [ | ||||||
|  |         { | ||||||
|  |             "hostname": "tun.example.com", | ||||||
|  |             "service": "https://localhost:8000" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  | 			"hostname": "*", | ||||||
|  |             "service": "https://localhost:8001", | ||||||
|  | 			"originRequest": { | ||||||
|  | 				"connectTimeout": 120000000000, | ||||||
|  | 				"tlsTimeout": 2000000000, | ||||||
|  | 				"noHappyEyeballs": false, | ||||||
|  | 				"tcpKeepAlive": 2000000000, | ||||||
|  | 				"keepAliveConnections": 2, | ||||||
|  | 				"keepAliveTimeout": 2000000000, | ||||||
|  | 				"httpHostHeader": "def", | ||||||
|  | 				"originServerName": "b2", | ||||||
|  | 				"caPool": "/tmp/path1", | ||||||
|  | 				"noTLSVerify": false, | ||||||
|  | 				"disableChunkedEncoding": false, | ||||||
|  | 				"bastionMode": false, | ||||||
|  | 				"proxyAddress": "interface", | ||||||
|  | 				"proxyPort": 200, | ||||||
|  | 				"proxyType": "", | ||||||
|  | 				"ipRules": [ | ||||||
|  | 					{ | ||||||
|  | 						"prefix": "10.0.0.0/16", | ||||||
|  | 						"ports": [3000, 3030], | ||||||
|  | 						"allow": false | ||||||
|  | 					}, | ||||||
|  | 					{ | ||||||
|  | 						"prefix": "192.16.0.0/24", | ||||||
|  | 						"ports": [5000, 5050], | ||||||
|  | 						"allow": true | ||||||
|  | 					} | ||||||
|  | 				] | ||||||
|  |     		} | ||||||
|  |         } | ||||||
|  |     ], | ||||||
|  |     "warp-routing": { | ||||||
|  |         "enabled": true | ||||||
|  |     } | ||||||
|  | }	 | ||||||
|  | `) | ||||||
|  | 	var remoteConfig RemoteConfig | ||||||
|  | 	err = json.Unmarshal(rawConfig, &remoteConfig) | ||||||
|  | 	require.NoError(t, err) | ||||||
|  | 	validate(remoteConfig.Ingress) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestOriginRequestConfigDefaults(t *testing.T) { | ||||||
|  | 	validate := func(ing Ingress) { | ||||||
|  | 		// Rule 0 didn't override anything, so it inherits the cloudflared defaults
 | ||||||
|  | 		actual0 := ing.Rules[0].Config | ||||||
|  | 		expected0 := OriginRequestConfig{ | ||||||
|  | 			ConnectTimeout:       defaultConnectTimeout, | ||||||
|  | 			TLSTimeout:           defaultTLSTimeout, | ||||||
|  | 			TCPKeepAlive:         defaultTCPKeepAlive, | ||||||
|  | 			KeepAliveConnections: defaultKeepAliveConnections, | ||||||
|  | 			KeepAliveTimeout:     defaultKeepAliveTimeout, | ||||||
|  | 			ProxyAddress:         defaultProxyAddress, | ||||||
|  | 		} | ||||||
|  | 		require.Equal(t, expected0, actual0) | ||||||
|  | 
 | ||||||
|  | 		// Rule 1 overrode all defaults.
 | ||||||
|  | 		actual1 := ing.Rules[1].Config | ||||||
|  | 		expected1 := OriginRequestConfig{ | ||||||
|  | 			ConnectTimeout:         2 * time.Minute, | ||||||
|  | 			TLSTimeout:             2 * time.Second, | ||||||
|  | 			TCPKeepAlive:           2 * time.Second, | ||||||
|  | 			NoHappyEyeballs:        false, | ||||||
|  | 			KeepAliveTimeout:       2 * time.Second, | ||||||
|  | 			KeepAliveConnections:   2, | ||||||
|  | 			HTTPHostHeader:         "def", | ||||||
|  | 			OriginServerName:       "b2", | ||||||
|  | 			CAPool:                 "/tmp/path1", | ||||||
|  | 			NoTLSVerify:            false, | ||||||
|  | 			DisableChunkedEncoding: false, | ||||||
|  | 			BastionMode:            false, | ||||||
|  | 			ProxyAddress:           "interface", | ||||||
|  | 			ProxyPort:              uint(200), | ||||||
|  | 			ProxyType:              "", | ||||||
|  | 			IPRules: []ipaccess.Rule{ | ||||||
|  | 				newIPRule(t, "10.0.0.0/16", []int{3000, 3030}, false), | ||||||
|  | 				newIPRule(t, "192.16.0.0/24", []int{5000, 5050}, true), | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		require.Equal(t, expected1, actual1) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	rulesYAML := ` | ||||||
|  | ingress: | ||||||
|  | - hostname: tun.example.com | ||||||
|  |   service: https://localhost:8000
 | ||||||
|  | - hostname: "*" | ||||||
|  |   service: https://localhost:8001
 | ||||||
|  |   originRequest: | ||||||
|  |     connectTimeout: 2m | ||||||
|  |     tlsTimeout: 2s | ||||||
|  |     noHappyEyeballs: false | ||||||
|  |     tcpKeepAlive: 2s | ||||||
|  |     keepAliveConnections: 2 | ||||||
|  |     keepAliveTimeout: 2s | ||||||
|  |     httpHostHeader: def | ||||||
|  |     originServerName: b2 | ||||||
|  |     caPool: /tmp/path1 | ||||||
|  |     noTLSVerify: false | ||||||
|  |     disableChunkedEncoding: false | ||||||
|  |     bastionMode: false | ||||||
|  |     proxyAddress: interface | ||||||
|  |     proxyPort: 200 | ||||||
|  |     proxyType: "" | ||||||
|  |     ipRules: | ||||||
|  |     - prefix: "10.0.0.0/16" | ||||||
|  |       ports: | ||||||
|  |       - 3000 | ||||||
|  |       - 3030 | ||||||
|  |       allow: false | ||||||
|  |     - prefix: "192.16.0.0/24" | ||||||
|  |       ports: | ||||||
|  |       - 5000 | ||||||
|  |       - 5050 | ||||||
|  |       allow: true | ||||||
|  | ` | ||||||
|  | 	ing, err := ParseIngress(MustReadIngress(rulesYAML)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  | 	validate(ing) | ||||||
|  | 
 | ||||||
|  | 	rawConfig := []byte(` | ||||||
|  | { | ||||||
|  | 	"ingress": [ | ||||||
|  |         { | ||||||
|  |             "hostname": "tun.example.com", | ||||||
|  |             "service": "https://localhost:8000" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  | 			"hostname": "*", | ||||||
|  |             "service": "https://localhost:8001", | ||||||
|  | 			"originRequest": { | ||||||
|  | 				"connectTimeout": 120000000000, | ||||||
|  | 				"tlsTimeout": 2000000000, | ||||||
|  | 				"noHappyEyeballs": false, | ||||||
|  | 				"tcpKeepAlive": 2000000000, | ||||||
|  | 				"keepAliveConnections": 2, | ||||||
|  | 				"keepAliveTimeout": 2000000000, | ||||||
|  | 				"httpHostHeader": "def", | ||||||
|  | 				"originServerName": "b2", | ||||||
|  | 				"caPool": "/tmp/path1", | ||||||
|  | 				"noTLSVerify": false, | ||||||
|  | 				"disableChunkedEncoding": false, | ||||||
|  | 				"bastionMode": false, | ||||||
|  | 				"proxyAddress": "interface", | ||||||
|  | 				"proxyPort": 200, | ||||||
|  | 				"proxyType": "", | ||||||
|  | 				"ipRules": [ | ||||||
|  | 					{ | ||||||
|  | 						"prefix": "10.0.0.0/16", | ||||||
|  | 						"ports": [3000, 3030], | ||||||
|  | 						"allow": false | ||||||
|  | 					}, | ||||||
|  | 					{ | ||||||
|  | 						"prefix": "192.16.0.0/24", | ||||||
|  | 						"ports": [5000, 5050], | ||||||
|  | 						"allow": true | ||||||
|  | 					} | ||||||
|  | 				] | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	] | ||||||
|  | } | ||||||
|  | `) | ||||||
|  | 
 | ||||||
|  | 	var remoteConfig RemoteConfig | ||||||
|  | 	err = json.Unmarshal(rawConfig, &remoteConfig) | ||||||
|  | 	require.NoError(t, err) | ||||||
|  | 	validate(remoteConfig.Ingress) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestDefaultConfigFromCLI(t *testing.T) { | ||||||
|  | 	set := flag.NewFlagSet("contrive", 0) | ||||||
|  | 	c := cli.NewContext(nil, set, nil) | ||||||
|  | 
 | ||||||
|  | 	expected := OriginRequestConfig{ | ||||||
|  | 		ConnectTimeout:       defaultConnectTimeout, | ||||||
|  | 		TLSTimeout:           defaultTLSTimeout, | ||||||
|  | 		TCPKeepAlive:         defaultTCPKeepAlive, | ||||||
|  | 		KeepAliveConnections: defaultKeepAliveConnections, | ||||||
|  | 		KeepAliveTimeout:     defaultKeepAliveTimeout, | ||||||
|  | 		ProxyAddress:         defaultProxyAddress, | ||||||
|  | 	} | ||||||
|  | 	actual := originRequestFromSingeRule(c) | ||||||
|  | 	require.Equal(t, expected, actual) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newIPRule(t *testing.T, prefix string, ports []int, allow bool) ipaccess.Rule { | ||||||
|  | 	rule, err := ipaccess.NewRuleByCIDR(&prefix, ports, allow) | ||||||
|  | 	require.NoError(t, err) | ||||||
|  | 	return rule | ||||||
|  | } | ||||||
|  | @ -163,7 +163,7 @@ func (ing Ingress) CatchAll() *Rule { | ||||||
| 	return &ing.Rules[len(ing.Rules)-1] | 	return &ing.Rules[len(ing.Rules)-1] | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func validate(ingress []config.UnvalidatedIngressRule, defaults OriginRequestConfig) (Ingress, error) { | func validateIngress(ingress []config.UnvalidatedIngressRule, defaults OriginRequestConfig) (Ingress, error) { | ||||||
| 	rules := make([]Rule, len(ingress)) | 	rules := make([]Rule, len(ingress)) | ||||||
| 	for i, r := range ingress { | 	for i, r := range ingress { | ||||||
| 		cfg := setConfig(defaults, r.OriginRequest) | 		cfg := setConfig(defaults, r.OriginRequest) | ||||||
|  | @ -290,7 +290,7 @@ func ParseIngress(conf *config.Configuration) (Ingress, error) { | ||||||
| 	if len(conf.Ingress) == 0 { | 	if len(conf.Ingress) == 0 { | ||||||
| 		return Ingress{}, ErrNoIngressRules | 		return Ingress{}, ErrNoIngressRules | ||||||
| 	} | 	} | ||||||
| 	return validate(conf.Ingress, originRequestFromYAML(conf.OriginRequest)) | 	return validateIngress(conf.Ingress, originRequestFromConfig(conf.OriginRequest)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func isHTTPService(url *url.URL) bool { | func isHTTPService(url *url.URL) bool { | ||||||
|  |  | ||||||
|  | @ -34,7 +34,7 @@ func Test_parseIngress(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) | ||||||
| 	defaultConfig := setConfig(originRequestFromYAML(config.OriginRequestConfig{}), config.OriginRequestConfig{}) | 	defaultConfig := setConfig(originRequestFromConfig(config.OriginRequestConfig{}), config.OriginRequestConfig{}) | ||||||
| 	require.Equal(t, defaultKeepAliveConnections, defaultConfig.KeepAliveConnections) | 	require.Equal(t, defaultKeepAliveConnections, defaultConfig.KeepAliveConnections) | ||||||
| 	tr := true | 	tr := true | ||||||
| 	type args struct { | 	type args struct { | ||||||
|  | @ -324,7 +324,17 @@ ingress: | ||||||
| 				{ | 				{ | ||||||
| 					Hostname: "socks.foo.com", | 					Hostname: "socks.foo.com", | ||||||
| 					Service:  newSocksProxyOverWSService(accessPolicy()), | 					Service:  newSocksProxyOverWSService(accessPolicy()), | ||||||
| 					Config:   defaultConfig, | 					Config: setConfig(originRequestFromConfig(config.OriginRequestConfig{}), config.OriginRequestConfig{IPRules: []config.IngressIPRule{ | ||||||
|  | 						{ | ||||||
|  | 							Prefix: ipRulePrefix("1.1.1.0/24"), | ||||||
|  | 							Ports:  []int{80, 443}, | ||||||
|  | 							Allow:  true, | ||||||
|  | 						}, | ||||||
|  | 						{ | ||||||
|  | 							Prefix: ipRulePrefix("0.0.0.0/0"), | ||||||
|  | 							Allow:  false, | ||||||
|  | 						}, | ||||||
|  | 					}}), | ||||||
| 				}, | 				}, | ||||||
| 				{ | 				{ | ||||||
| 					Service: &fourOhFour, | 					Service: &fourOhFour, | ||||||
|  | @ -345,7 +355,7 @@ ingress: | ||||||
| 				{ | 				{ | ||||||
| 					Hostname: "bastion.foo.com", | 					Hostname: "bastion.foo.com", | ||||||
| 					Service:  newBastionService(), | 					Service:  newBastionService(), | ||||||
| 					Config:   setConfig(originRequestFromYAML(config.OriginRequestConfig{}), config.OriginRequestConfig{BastionMode: &tr}), | 					Config:   setConfig(originRequestFromConfig(config.OriginRequestConfig{}), config.OriginRequestConfig{BastionMode: &tr}), | ||||||
| 				}, | 				}, | ||||||
| 				{ | 				{ | ||||||
| 					Service: &fourOhFour, | 					Service: &fourOhFour, | ||||||
|  | @ -365,7 +375,7 @@ ingress: | ||||||
| 				{ | 				{ | ||||||
| 					Hostname: "bastion.foo.com", | 					Hostname: "bastion.foo.com", | ||||||
| 					Service:  newBastionService(), | 					Service:  newBastionService(), | ||||||
| 					Config:   setConfig(originRequestFromYAML(config.OriginRequestConfig{}), config.OriginRequestConfig{BastionMode: &tr}), | 					Config:   setConfig(originRequestFromConfig(config.OriginRequestConfig{}), config.OriginRequestConfig{BastionMode: &tr}), | ||||||
| 				}, | 				}, | ||||||
| 				{ | 				{ | ||||||
| 					Service: &fourOhFour, | 					Service: &fourOhFour, | ||||||
|  | @ -397,6 +407,10 @@ ingress: | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func ipRulePrefix(s string) *string { | ||||||
|  | 	return &s | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func TestSingleOriginSetsConfig(t *testing.T) { | func TestSingleOriginSetsConfig(t *testing.T) { | ||||||
| 	flagSet := flag.NewFlagSet(t.Name(), flag.PanicOnError) | 	flagSet := flag.NewFlagSet(t.Name(), flag.PanicOnError) | ||||||
| 	flagSet.Bool("hello-world", true, "") | 	flagSet.Bool("hello-world", true, "") | ||||||
|  |  | ||||||
|  | @ -1,203 +0,0 @@ | ||||||
| package ingress |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"flag" |  | ||||||
| 	"testing" |  | ||||||
| 	"time" |  | ||||||
| 
 |  | ||||||
| 	"github.com/stretchr/testify/require" |  | ||||||
| 	"github.com/urfave/cli/v2" |  | ||||||
| 	yaml "gopkg.in/yaml.v2" |  | ||||||
| 
 |  | ||||||
| 	"github.com/cloudflare/cloudflared/config" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Ensure that the nullable config from `config` package and the
 |  | ||||||
| // non-nullable config from `ingress` package have the same number of
 |  | ||||||
| // fields.
 |  | ||||||
| // This test ensures that programmers didn't add a new field to
 |  | ||||||
| // one struct and forget to add it to the other ;)
 |  | ||||||
| func TestCorrespondingFields(t *testing.T) { |  | ||||||
| 	require.Equal( |  | ||||||
| 		t, |  | ||||||
| 		CountFields(t, config.OriginRequestConfig{}), |  | ||||||
| 		CountFields(t, OriginRequestConfig{}), |  | ||||||
| 	) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func CountFields(t *testing.T, val interface{}) int { |  | ||||||
| 	b, err := yaml.Marshal(val) |  | ||||||
| 	require.NoError(t, err) |  | ||||||
| 	m := make(map[string]interface{}, 0) |  | ||||||
| 	err = yaml.Unmarshal(b, &m) |  | ||||||
| 	require.NoError(t, err) |  | ||||||
| 	return len(m) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestOriginRequestConfigOverrides(t *testing.T) { |  | ||||||
| 	rulesYAML := ` |  | ||||||
| originRequest: |  | ||||||
|   connectTimeout: 1m |  | ||||||
|   tlsTimeout: 1s |  | ||||||
|   noHappyEyeballs: true |  | ||||||
|   tcpKeepAlive: 1s |  | ||||||
|   keepAliveConnections: 1 |  | ||||||
|   keepAliveTimeout: 1s |  | ||||||
|   httpHostHeader: abc |  | ||||||
|   originServerName: a1 |  | ||||||
|   caPool: /tmp/path0 |  | ||||||
|   noTLSVerify: true |  | ||||||
|   disableChunkedEncoding: true |  | ||||||
|   bastionMode: True |  | ||||||
|   proxyAddress: 127.1.2.3 |  | ||||||
|   proxyPort: 100 |  | ||||||
|   proxyType: socks5 |  | ||||||
| ingress: |  | ||||||
| - hostname: tun.example.com |  | ||||||
|   service: https://localhost:8000
 |  | ||||||
| - hostname: "*" |  | ||||||
|   service: https://localhost:8001
 |  | ||||||
|   originRequest: |  | ||||||
|     connectTimeout: 2m |  | ||||||
|     tlsTimeout: 2s |  | ||||||
|     noHappyEyeballs: false |  | ||||||
|     tcpKeepAlive: 2s |  | ||||||
|     keepAliveConnections: 2 |  | ||||||
|     keepAliveTimeout: 2s |  | ||||||
|     httpHostHeader: def |  | ||||||
|     originServerName: b2 |  | ||||||
|     caPool: /tmp/path1 |  | ||||||
|     noTLSVerify: false |  | ||||||
|     disableChunkedEncoding: false |  | ||||||
|     bastionMode: false |  | ||||||
|     proxyAddress: interface |  | ||||||
|     proxyPort: 200 |  | ||||||
|     proxyType: "" |  | ||||||
| ` |  | ||||||
| 	ing, err := ParseIngress(MustReadIngress(rulesYAML)) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Error(err) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Rule 0 didn't override anything, so it inherits the user-specified
 |  | ||||||
| 	// root-level configuration.
 |  | ||||||
| 	actual0 := ing.Rules[0].Config |  | ||||||
| 	expected0 := OriginRequestConfig{ |  | ||||||
| 		ConnectTimeout:         1 * time.Minute, |  | ||||||
| 		TLSTimeout:             1 * time.Second, |  | ||||||
| 		NoHappyEyeballs:        true, |  | ||||||
| 		TCPKeepAlive:           1 * time.Second, |  | ||||||
| 		KeepAliveConnections:   1, |  | ||||||
| 		KeepAliveTimeout:       1 * time.Second, |  | ||||||
| 		HTTPHostHeader:         "abc", |  | ||||||
| 		OriginServerName:       "a1", |  | ||||||
| 		CAPool:                 "/tmp/path0", |  | ||||||
| 		NoTLSVerify:            true, |  | ||||||
| 		DisableChunkedEncoding: true, |  | ||||||
| 		BastionMode:            true, |  | ||||||
| 		ProxyAddress:           "127.1.2.3", |  | ||||||
| 		ProxyPort:              uint(100), |  | ||||||
| 		ProxyType:              "socks5", |  | ||||||
| 	} |  | ||||||
| 	require.Equal(t, expected0, actual0) |  | ||||||
| 
 |  | ||||||
| 	// Rule 1 overrode all the root-level config.
 |  | ||||||
| 	actual1 := ing.Rules[1].Config |  | ||||||
| 	expected1 := OriginRequestConfig{ |  | ||||||
| 		ConnectTimeout:         2 * time.Minute, |  | ||||||
| 		TLSTimeout:             2 * time.Second, |  | ||||||
| 		NoHappyEyeballs:        false, |  | ||||||
| 		TCPKeepAlive:           2 * time.Second, |  | ||||||
| 		KeepAliveConnections:   2, |  | ||||||
| 		KeepAliveTimeout:       2 * time.Second, |  | ||||||
| 		HTTPHostHeader:         "def", |  | ||||||
| 		OriginServerName:       "b2", |  | ||||||
| 		CAPool:                 "/tmp/path1", |  | ||||||
| 		NoTLSVerify:            false, |  | ||||||
| 		DisableChunkedEncoding: false, |  | ||||||
| 		BastionMode:            false, |  | ||||||
| 		ProxyAddress:           "interface", |  | ||||||
| 		ProxyPort:              uint(200), |  | ||||||
| 		ProxyType:              "", |  | ||||||
| 	} |  | ||||||
| 	require.Equal(t, expected1, actual1) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestOriginRequestConfigDefaults(t *testing.T) { |  | ||||||
| 	rulesYAML := ` |  | ||||||
| ingress: |  | ||||||
| - hostname: tun.example.com |  | ||||||
|   service: https://localhost:8000
 |  | ||||||
| - hostname: "*" |  | ||||||
|   service: https://localhost:8001
 |  | ||||||
|   originRequest: |  | ||||||
|     connectTimeout: 2m |  | ||||||
|     tlsTimeout: 2s |  | ||||||
|     noHappyEyeballs: false |  | ||||||
|     tcpKeepAlive: 2s |  | ||||||
|     keepAliveConnections: 2 |  | ||||||
|     keepAliveTimeout: 2s |  | ||||||
|     httpHostHeader: def |  | ||||||
|     originServerName: b2 |  | ||||||
|     caPool: /tmp/path1 |  | ||||||
|     noTLSVerify: false |  | ||||||
|     disableChunkedEncoding: false |  | ||||||
|     bastionMode: false |  | ||||||
|     proxyAddress: interface |  | ||||||
|     proxyPort: 200 |  | ||||||
|     proxyType: "" |  | ||||||
| ` |  | ||||||
| 	ing, err := ParseIngress(MustReadIngress(rulesYAML)) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Error(err) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Rule 0 didn't override anything, so it inherits the cloudflared defaults
 |  | ||||||
| 	actual0 := ing.Rules[0].Config |  | ||||||
| 	expected0 := OriginRequestConfig{ |  | ||||||
| 		ConnectTimeout:       defaultConnectTimeout, |  | ||||||
| 		TLSTimeout:           defaultTLSTimeout, |  | ||||||
| 		TCPKeepAlive:         defaultTCPKeepAlive, |  | ||||||
| 		KeepAliveConnections: defaultKeepAliveConnections, |  | ||||||
| 		KeepAliveTimeout:     defaultKeepAliveTimeout, |  | ||||||
| 		ProxyAddress:         defaultProxyAddress, |  | ||||||
| 	} |  | ||||||
| 	require.Equal(t, expected0, actual0) |  | ||||||
| 
 |  | ||||||
| 	// Rule 1 overrode all defaults.
 |  | ||||||
| 	actual1 := ing.Rules[1].Config |  | ||||||
| 	expected1 := OriginRequestConfig{ |  | ||||||
| 		ConnectTimeout:         2 * time.Minute, |  | ||||||
| 		TLSTimeout:             2 * time.Second, |  | ||||||
| 		NoHappyEyeballs:        false, |  | ||||||
| 		TCPKeepAlive:           2 * time.Second, |  | ||||||
| 		KeepAliveConnections:   2, |  | ||||||
| 		KeepAliveTimeout:       2 * time.Second, |  | ||||||
| 		HTTPHostHeader:         "def", |  | ||||||
| 		OriginServerName:       "b2", |  | ||||||
| 		CAPool:                 "/tmp/path1", |  | ||||||
| 		NoTLSVerify:            false, |  | ||||||
| 		DisableChunkedEncoding: false, |  | ||||||
| 		BastionMode:            false, |  | ||||||
| 		ProxyAddress:           "interface", |  | ||||||
| 		ProxyPort:              uint(200), |  | ||||||
| 		ProxyType:              "", |  | ||||||
| 	} |  | ||||||
| 	require.Equal(t, expected1, actual1) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestDefaultConfigFromCLI(t *testing.T) { |  | ||||||
| 	set := flag.NewFlagSet("contrive", 0) |  | ||||||
| 	c := cli.NewContext(nil, set, nil) |  | ||||||
| 
 |  | ||||||
| 	expected := OriginRequestConfig{ |  | ||||||
| 		ConnectTimeout:       defaultConnectTimeout, |  | ||||||
| 		TLSTimeout:           defaultTLSTimeout, |  | ||||||
| 		TCPKeepAlive:         defaultTCPKeepAlive, |  | ||||||
| 		KeepAliveConnections: defaultKeepAliveConnections, |  | ||||||
| 		KeepAliveTimeout:     defaultKeepAliveTimeout, |  | ||||||
| 		ProxyAddress:         defaultProxyAddress, |  | ||||||
| 	} |  | ||||||
| 	actual := originRequestFromSingeRule(c) |  | ||||||
| 	require.Equal(t, expected, actual) |  | ||||||
| } |  | ||||||
		Loading…
	
		Reference in New Issue