TUN-5702: Allow to deserialize config from JSON

This commit is contained in:
cthuang 2022-01-28 14:37:17 +00:00
parent d07d24e5a2
commit b1edf5b96d
7 changed files with 649 additions and 267 deletions

View File

@ -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 {

View File

@ -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)
}

View File

@ -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
} }

422
ingress/config_test.go Normal file
View File

@ -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
}

View File

@ -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 {

View File

@ -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, "")

View File

@ -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)
}