TUN-1914: Conflate HTTP and Unix OriginConfig, and add TLS config to WebSocketOriginConfig

This commit is contained in:
Chung-Ting Huang 2019-05-30 15:45:46 -05:00
parent 713a2d689e
commit 39d60d1239
4 changed files with 783 additions and 610 deletions

View File

@ -3,6 +3,7 @@ package pogs
import (
"context"
"fmt"
"net/url"
"time"
"github.com/cloudflare/cloudflared/tunnelrpc"
@ -43,7 +44,6 @@ type ReverseProxyConfig struct {
Origin OriginConfig
Retries uint64
ConnectionTimeout time.Duration
ChunkedEncoding bool
CompressionQuality uint64
}
@ -52,7 +52,6 @@ func NewReverseProxyConfig(
originConfig OriginConfig,
retries uint64,
connectionTimeout time.Duration,
chunkedEncoding bool,
compressionQuality uint64,
) (*ReverseProxyConfig, error) {
if originConfig == nil {
@ -63,7 +62,6 @@ func NewReverseProxyConfig(
Origin: originConfig,
Retries: retries,
ConnectionTimeout: connectionTimeout,
ChunkedEncoding: chunkedEncoding,
CompressionQuality: compressionQuality,
}, nil
}
@ -74,7 +72,7 @@ type OriginConfig interface {
}
type HTTPOriginConfig struct {
URL string `capnp:"url"`
URL OriginAddr `capnp:"url"`
TCPKeepAlive time.Duration `capnp:"tcpKeepAlive"`
DialDualStack bool
TLSHandshakeTimeout time.Duration `capnp:"tlsHandshakeTimeout"`
@ -83,18 +81,49 @@ type HTTPOriginConfig struct {
OriginServerName string
MaxIdleConnections uint64
IdleConnectionTimeout time.Duration
ProxyConnectTimeout time.Duration
ExpectContinueTimeout time.Duration
ChunkedEncoding bool
}
func (_ *HTTPOriginConfig) isOriginConfig() {}
type UnixSocketOriginConfig struct {
type OriginAddr interface {
Addr() string
}
type HTTPURL struct {
URL *url.URL
}
func (ha *HTTPURL) Addr() string {
return ha.URL.String()
}
func (ha *HTTPURL) capnpHTTPURL() *CapnpHTTPURL {
return &CapnpHTTPURL{
URL: ha.URL.String(),
}
}
// URL for a HTTP origin, capnp doesn't have native support for URL, so represent it as string
type CapnpHTTPURL struct {
URL string `capnp:"url"`
}
type UnixPath struct {
Path string
}
func (_ *UnixSocketOriginConfig) isOriginConfig() {}
func (up *UnixPath) Addr() string {
return up.Path
}
type WebSocketOriginConfig struct {
URL string `capnp:"url"`
URL string `capnp:"url"`
TLSVerify bool `capnp:"tlsVerify"`
OriginCAPool string
OriginServerName string
}
func (_ *WebSocketOriginConfig) isOriginConfig() {}
@ -239,31 +268,30 @@ func MarshalReverseProxyConfig(s tunnelrpc.ReverseProxyConfig, p *ReverseProxyCo
if err != nil {
return err
}
MarshalHTTPOriginConfig(ss, config)
case *UnixSocketOriginConfig:
ss, err := s.Origin().NewSocket()
if err != nil {
if err := MarshalHTTPOriginConfig(ss, config); err != nil {
return err
}
MarshalUnixSocketOriginConfig(ss, config)
case *WebSocketOriginConfig:
ss, err := s.Origin().NewWebsocket()
if err != nil {
return err
}
MarshalWebSocketOriginConfig(ss, config)
if err := MarshalWebSocketOriginConfig(ss, config); err != nil {
return err
}
case *HelloWorldOriginConfig:
ss, err := s.Origin().NewHelloWorld()
if err != nil {
return err
}
MarshalHelloWorldOriginConfig(ss, config)
if err := MarshalHelloWorldOriginConfig(ss, config); err != nil {
return err
}
default:
return fmt.Errorf("Unknown type for config: %T", config)
}
s.SetRetries(p.Retries)
s.SetConnectionTimeout(p.ConnectionTimeout.Nanoseconds())
s.SetChunkedEncoding(p.ChunkedEncoding)
s.SetCompressionQuality(p.CompressionQuality)
return nil
}
@ -286,16 +314,6 @@ func UnmarshalReverseProxyConfig(s tunnelrpc.ReverseProxyConfig) (*ReverseProxyC
return nil, err
}
p.Origin = config
case tunnelrpc.ReverseProxyConfig_origin_Which_socket:
ss, err := s.Origin().Socket()
if err != nil {
return nil, err
}
config, err := UnmarshalUnixSocketOriginConfig(ss)
if err != nil {
return nil, err
}
p.Origin = config
case tunnelrpc.ReverseProxyConfig_origin_Which_websocket:
ss, err := s.Origin().Websocket()
if err != nil {
@ -319,28 +337,120 @@ func UnmarshalReverseProxyConfig(s tunnelrpc.ReverseProxyConfig) (*ReverseProxyC
}
p.Retries = s.Retries()
p.ConnectionTimeout = time.Duration(s.ConnectionTimeout())
p.ChunkedEncoding = s.ChunkedEncoding()
p.CompressionQuality = s.CompressionQuality()
return p, nil
}
func MarshalHTTPOriginConfig(s tunnelrpc.HTTPOriginConfig, p *HTTPOriginConfig) error {
return pogs.Insert(tunnelrpc.HTTPOriginConfig_TypeID, s.Struct, p)
switch originAddr := p.URL.(type) {
case *HTTPURL:
ss, err := s.OriginAddr().NewHttp()
if err != nil {
return err
}
if err := MarshalHTTPURL(ss, originAddr); err != nil {
return err
}
case *UnixPath:
ss, err := s.OriginAddr().NewUnix()
if err != nil {
return err
}
if err := MarshalUnixPath(ss, originAddr); err != nil {
return err
}
default:
return fmt.Errorf("Unknown type for OriginAddr: %T", originAddr)
}
s.SetTcpKeepAlive(p.TCPKeepAlive.Nanoseconds())
s.SetDialDualStack(p.DialDualStack)
s.SetTlsHandshakeTimeout(p.TLSHandshakeTimeout.Nanoseconds())
s.SetTlsVerify(p.TLSVerify)
s.SetOriginCAPool(p.OriginCAPool)
s.SetOriginServerName(p.OriginServerName)
s.SetMaxIdleConnections(p.MaxIdleConnections)
s.SetIdleConnectionTimeout(p.IdleConnectionTimeout.Nanoseconds())
s.SetProxyConnectionTimeout(p.ProxyConnectTimeout.Nanoseconds())
s.SetExpectContinueTimeout(p.ExpectContinueTimeout.Nanoseconds())
s.SetChunkedEncoding(p.ChunkedEncoding)
return nil
}
func UnmarshalHTTPOriginConfig(s tunnelrpc.HTTPOriginConfig) (*HTTPOriginConfig, error) {
p := new(HTTPOriginConfig)
err := pogs.Extract(p, tunnelrpc.HTTPOriginConfig_TypeID, s.Struct)
return p, err
switch s.OriginAddr().Which() {
case tunnelrpc.HTTPOriginConfig_originAddr_Which_http:
ss, err := s.OriginAddr().Http()
if err != nil {
return nil, err
}
originAddr, err := UnmarshalCapnpHTTPURL(ss)
if err != nil {
return nil, err
}
p.URL = originAddr
case tunnelrpc.HTTPOriginConfig_originAddr_Which_unix:
ss, err := s.OriginAddr().Unix()
if err != nil {
return nil, err
}
originAddr, err := UnmarshalUnixPath(ss)
if err != nil {
return nil, err
}
p.URL = originAddr
default:
return nil, fmt.Errorf("Unknown type for OriginAddr: %T", s.OriginAddr().Which())
}
p.TCPKeepAlive = time.Duration(s.TcpKeepAlive())
p.DialDualStack = s.DialDualStack()
p.TLSHandshakeTimeout = time.Duration(s.TlsHandshakeTimeout())
p.TLSVerify = s.TlsVerify()
originCAPool, err := s.OriginCAPool()
if err != nil {
return nil, err
}
p.OriginCAPool = originCAPool
originServerName, err := s.OriginServerName()
if err != nil {
return nil, err
}
p.OriginServerName = originServerName
p.MaxIdleConnections = s.MaxIdleConnections()
p.IdleConnectionTimeout = time.Duration(s.IdleConnectionTimeout())
p.ProxyConnectTimeout = time.Duration(s.ProxyConnectionTimeout())
p.ExpectContinueTimeout = time.Duration(s.ExpectContinueTimeout())
p.ChunkedEncoding = s.ChunkedEncoding()
return p, nil
}
func MarshalUnixSocketOriginConfig(s tunnelrpc.UnixSocketOriginConfig, p *UnixSocketOriginConfig) error {
return pogs.Insert(tunnelrpc.UnixSocketOriginConfig_TypeID, s.Struct, p)
func MarshalHTTPURL(s tunnelrpc.CapnpHTTPURL, p *HTTPURL) error {
return pogs.Insert(tunnelrpc.CapnpHTTPURL_TypeID, s.Struct, p.capnpHTTPURL())
}
func UnmarshalUnixSocketOriginConfig(s tunnelrpc.UnixSocketOriginConfig) (*UnixSocketOriginConfig, error) {
p := new(UnixSocketOriginConfig)
err := pogs.Extract(p, tunnelrpc.UnixSocketOriginConfig_TypeID, s.Struct)
func UnmarshalCapnpHTTPURL(s tunnelrpc.CapnpHTTPURL) (*HTTPURL, error) {
p := new(CapnpHTTPURL)
err := pogs.Extract(p, tunnelrpc.CapnpHTTPURL_TypeID, s.Struct)
if err != nil {
return nil, err
}
url, err := url.Parse(p.URL)
if err != nil {
return nil, err
}
return &HTTPURL{
URL: url,
}, nil
}
func MarshalUnixPath(s tunnelrpc.UnixPath, p *UnixPath) error {
err := pogs.Insert(tunnelrpc.UnixPath_TypeID, s.Struct, p)
return err
}
func UnmarshalUnixPath(s tunnelrpc.UnixPath) (*UnixPath, error) {
p := new(UnixPath)
err := pogs.Extract(p, tunnelrpc.UnixPath_TypeID, s.Struct)
return p, err
}
@ -365,7 +475,7 @@ func UnmarshalHelloWorldOriginConfig(s tunnelrpc.HelloWorldOriginConfig) (*Hello
}
type ClientService interface {
UseConfiguration(ctx context.Context, config *ClientConfig) (*ClientConfig, error)
UseConfiguration(ctx context.Context, config *ClientConfig) (*UseConfigurationResult, error)
}
type ClientService_PogsClient struct {
@ -383,7 +493,7 @@ func (c *ClientService_PogsClient) UseConfiguration(
) (*UseConfigurationResult, error) {
client := tunnelrpc.ClientService{Client: c.Client}
promise := client.UseConfiguration(ctx, func(p tunnelrpc.ClientService_useConfiguration_Params) error {
clientServiceConfig, err := p.NewClientConfig()
clientServiceConfig, err := p.NewClientServiceConfig()
if err != nil {
return err
}

View File

@ -2,6 +2,7 @@ package pogs
import (
"fmt"
"net/url"
"reflect"
"testing"
"time"
@ -22,13 +23,12 @@ func TestClientConfig(t *testing.T) {
c.ReverseProxyConfigs = []*ReverseProxyConfig{
sampleReverseProxyConfig(),
sampleReverseProxyConfig(func(c *ReverseProxyConfig) {
c.ChunkedEncoding = false
}),
sampleReverseProxyConfig(func(c *ReverseProxyConfig) {
c.Origin = sampleHTTPOriginConfig()
}),
sampleReverseProxyConfig(func(c *ReverseProxyConfig) {
c.Origin = sampleUnixSocketOriginConfig()
c.Origin = sampleHTTPOriginUnixPathConfig()
}),
sampleReverseProxyConfig(func(c *ReverseProxyConfig) {
c.Origin = sampleWebSocketOriginConfig()
@ -120,7 +120,7 @@ func TestReverseProxyConfig(t *testing.T) {
c.Origin = sampleHTTPOriginConfig()
}),
sampleReverseProxyConfig(func(c *ReverseProxyConfig) {
c.Origin = sampleUnixSocketOriginConfig()
c.Origin = sampleHTTPOriginUnixPathConfig()
}),
sampleReverseProxyConfig(func(c *ReverseProxyConfig) {
c.Origin = sampleWebSocketOriginConfig()
@ -166,28 +166,6 @@ func TestHTTPOriginConfig(t *testing.T) {
}
}
func TestUnixSocketOriginConfig(t *testing.T) {
testCases := []*UnixSocketOriginConfig{
sampleUnixSocketOriginConfig(),
}
for i, testCase := range testCases {
_, seg, err := capnp.NewMessage(capnp.SingleSegment(nil))
capnpEntity, err := tunnelrpc.NewUnixSocketOriginConfig(seg)
if !assert.NoError(t, err) {
t.Fatal("Couldn't initialize a new message")
}
err = MarshalUnixSocketOriginConfig(capnpEntity, testCase)
if !assert.NoError(t, err, "testCase index %v failed to marshal", i) {
continue
}
result, err := UnmarshalUnixSocketOriginConfig(capnpEntity)
if !assert.NoError(t, err, "testCase index %v failed to unmarshal", i) {
continue
}
assert.Equal(t, testCase, result, "testCase index %v didn't preserve struct through marshalling and unmarshalling", i)
}
}
func TestWebSocketOriginConfig(t *testing.T) {
testCases := []*WebSocketOriginConfig{
sampleWebSocketOriginConfig(),
@ -249,7 +227,6 @@ func sampleReverseProxyConfig(overrides ...func(*ReverseProxyConfig)) *ReversePr
Origin: &HelloWorldOriginConfig{},
Retries: 18,
ConnectionTimeout: 5 * time.Second,
ChunkedEncoding: true,
CompressionQuality: 4,
}
sample.ensureNoZeroFields()
@ -261,7 +238,12 @@ func sampleReverseProxyConfig(overrides ...func(*ReverseProxyConfig)) *ReversePr
func sampleHTTPOriginConfig(overrides ...func(*HTTPOriginConfig)) *HTTPOriginConfig {
sample := &HTTPOriginConfig{
URL: "https://example.com",
URL: &HTTPURL{
URL: &url.URL{
Scheme: "https",
Host: "example.com",
},
},
TCPKeepAlive: 7 * time.Second,
DialDualStack: true,
TLSHandshakeTimeout: 11 * time.Second,
@ -270,6 +252,9 @@ func sampleHTTPOriginConfig(overrides ...func(*HTTPOriginConfig)) *HTTPOriginCon
OriginServerName: "secure.example.com",
MaxIdleConnections: 19,
IdleConnectionTimeout: 17 * time.Second,
ProxyConnectTimeout: 15 * time.Second,
ExpectContinueTimeout: 21 * time.Second,
ChunkedEncoding: true,
}
sample.ensureNoZeroFields()
for _, f := range overrides {
@ -278,9 +263,22 @@ func sampleHTTPOriginConfig(overrides ...func(*HTTPOriginConfig)) *HTTPOriginCon
return sample
}
func sampleUnixSocketOriginConfig(overrides ...func(*UnixSocketOriginConfig)) *UnixSocketOriginConfig {
sample := &UnixSocketOriginConfig{
Path: "/var/lib/file.sock",
func sampleHTTPOriginUnixPathConfig(overrides ...func(*HTTPOriginConfig)) *HTTPOriginConfig {
sample := &HTTPOriginConfig{
URL: &UnixPath{
Path: "/var/lib/file.sock",
},
TCPKeepAlive: 7 * time.Second,
DialDualStack: true,
TLSHandshakeTimeout: 11 * time.Second,
TLSVerify: true,
OriginCAPool: "/etc/cert.pem",
OriginServerName: "secure.example.com",
MaxIdleConnections: 19,
IdleConnectionTimeout: 17 * time.Second,
ProxyConnectTimeout: 15 * time.Second,
ExpectContinueTimeout: 21 * time.Second,
ChunkedEncoding: true,
}
sample.ensureNoZeroFields()
for _, f := range overrides {
@ -291,7 +289,10 @@ func sampleUnixSocketOriginConfig(overrides ...func(*UnixSocketOriginConfig)) *U
func sampleWebSocketOriginConfig(overrides ...func(*WebSocketOriginConfig)) *WebSocketOriginConfig {
sample := &WebSocketOriginConfig{
URL: "ssh://example.com",
URL: "ssh://example.com",
TLSVerify: true,
OriginCAPool: "/etc/cert.pem",
OriginServerName: "secure.example.com",
}
sample.ensureNoZeroFields()
for _, f := range overrides {
@ -316,10 +317,6 @@ func (c *HTTPOriginConfig) ensureNoZeroFields() {
ensureNoZeroFieldsInSample(reflect.ValueOf(c), []string{})
}
func (c *UnixSocketOriginConfig) ensureNoZeroFields() {
ensureNoZeroFieldsInSample(reflect.ValueOf(c), []string{})
}
func (c *WebSocketOriginConfig) ensureNoZeroFields() {
ensureNoZeroFieldsInSample(reflect.ValueOf(c), []string{})
}

View File

@ -112,63 +112,32 @@ struct ReverseProxyConfig {
tunnelHostname @0 :Text;
origin :union {
http @1 :HTTPOriginConfig;
socket @2 :UnixSocketOriginConfig;
websocket @3 :WebSocketOriginConfig;
helloWorld @4 :HelloWorldOriginConfig;
websocket @2 :WebSocketOriginConfig;
helloWorld @3 :HelloWorldOriginConfig;
}
# Maximum number of retries for connection/protocol errors.
# cloudflared CLI option: `retries`
retries @5 :UInt64;
retries @4 :UInt64;
# maximum time (in ns) for cloudflared to wait to establish a connection
# to the origin. Zero means no timeout.
# cloudflared CLI option: `proxy-connect-timeout`
connectionTimeout @6 :Int64;
# Whether cloudflared should allow chunked transfer encoding to the
# origin. (This should be disabled for WSGI origins, for example.)
# negation of cloudflared CLI option: `no-chunked-encoding`
chunkedEncoding @7 :Bool;
connectionTimeout @5 :Int64;
# (beta) Use cross-stream compression instead of HTTP compression.
# 0=off, 1=low, 2=medium, 3=high.
# For more context see the mapping here: https://github.com/cloudflare/cloudflared/blob/2019.3.2/h2mux/h2_dictionaries.go#L62
# cloudflared CLI option: `compression-quality`
compressionQuality @8 :UInt64;
compressionQuality @6 :UInt64;
}
struct UnixSocketOriginConfig {
# path to the socket file.
# cloudflared will send data to this socket via a Unix socket connection.
# cloudflared CLI option: `unix-socket`
path @0 :Text;
}
#
struct WebSocketOriginConfig {
# URI of the origin service.
# cloudflared will start a websocket server that forwards data to this URI
# cloudflared CLI option: `url`
# cloudflared logic: https://github.com/cloudflare/cloudflared/blob/2019.3.2/cmd/cloudflared/tunnel/cmd.go#L304
url @0 :Text;
}
struct HTTPOriginConfig {
# HTTP(S) URL of the origin service.
# cloudflared CLI option: `url`
url @0 :Text;
# the TCP keep-alive period (in ns) for an active network connection.
# Zero means keep-alives are not enabled.
# cloudflared CLI option: `proxy-tcp-keepalive`
tcpKeepAlive @1 :Int64;
# whether cloudflared should use a "happy eyeballs"-compliant procedure
# to connect to origins that resolve to both IPv4 and IPv6 addresses
# negation of cloudflared CLI option: `proxy-no-happy-eyeballs`
dialDualStack @2 :Bool;
# maximum time (in ns) for cloudflared to wait for a TLS handshake
# with the origin. Zero means no timeout.
# cloudflared CLI option: `proxy-tls-timeout`
tlsHandshakeTimeout @3 :Int64;
# Whether cloudflared should verify TLS connections to the origin.
# negation of cloudflared CLI option: `no-tls-verify`
tlsVerify @4 :Bool;
tlsVerify @1 :Bool;
# originCAPool specifies the root CA that cloudflared should use when
# verifying TLS connections to the origin.
# - if tlsVerify is false, originCAPool will be ignored.
@ -177,18 +146,75 @@ struct HTTPOriginConfig {
# - if tlsVerify is true and originCAPool is non-empty, cloudflared will
# treat it as the filepath to the root CA.
# cloudflared CLI option: `origin-ca-pool`
originCAPool @5 :Text;
originCAPool @2 :Text;
# Hostname to use when verifying TLS connections to the origin.
# cloudflared CLI option: `origin-server-name`
originServerName @6 :Text;
originServerName @3 :Text;
}
struct HTTPOriginConfig {
# HTTP(S) URL of the origin service.
# cloudflared CLI option: `url`
originAddr :union {
http @0 :CapnpHTTPURL;
unix @1 :UnixPath;
}
# the TCP keep-alive period (in ns) for an active network connection.
# Zero means keep-alives are not enabled.
# cloudflared CLI option: `proxy-tcp-keepalive`
tcpKeepAlive @2 :Int64;
# whether cloudflared should use a "happy eyeballs"-compliant procedure
# to connect to origins that resolve to both IPv4 and IPv6 addresses
# negation of cloudflared CLI option: `proxy-no-happy-eyeballs`
dialDualStack @3 :Bool;
# maximum time (in ns) for cloudflared to wait for a TLS handshake
# with the origin. Zero means no timeout.
# cloudflared CLI option: `proxy-tls-timeout`
tlsHandshakeTimeout @4 :Int64;
# Whether cloudflared should verify TLS connections to the origin.
# negation of cloudflared CLI option: `no-tls-verify`
tlsVerify @5 :Bool;
# originCAPool specifies the root CA that cloudflared should use when
# verifying TLS connections to the origin.
# - if tlsVerify is false, originCAPool will be ignored.
# - if tlsVerify is true and originCAPool is empty, the system CA pool
# will be loaded if possible.
# - if tlsVerify is true and originCAPool is non-empty, cloudflared will
# treat it as the filepath to the root CA.
# cloudflared CLI option: `origin-ca-pool`
originCAPool @6 :Text;
# Hostname to use when verifying TLS connections to the origin.
# cloudflared CLI option: `origin-server-name`
originServerName @7 :Text;
# maximum number of idle (keep-alive) connections for cloudflared to
# keep open with the origin. Zero means no limit.
# cloudflared CLI option: `proxy-keepalive-connections`
maxIdleConnections @7 :UInt64;
maxIdleConnections @8 :UInt64;
# maximum time (in ns) for an idle (keep-alive) connection to remain
# idle before closing itself. Zero means no timeout.
# cloudflared CLI option: `proxy-keepalive-timeout`
idleConnectionTimeout @8 :Int64;
idleConnectionTimeout @9 :Int64;
# maximum amount of time a dial will wait for a connect to complete.
proxyConnectionTimeout @10 :Int64;
# The amount of time to wait for origin's first response headers after fully
# writing the request headers if the request has an "Expect: 100-continue" header.
# Zero means no timeout and causes the body to be sent immediately, without
# waiting for the server to approve.
expectContinueTimeout @11 :Int64;
# Whether cloudflared should allow chunked transfer encoding to the
# origin. (This should be disabled for WSGI origins, for example.)
# negation of cloudflared CLI option: `no-chunked-encoding`
chunkedEncoding @12 :Bool;
}
# URL for a HTTP origin, capnp doesn't have native support for URL, so represent it as Text
struct CapnpHTTPURL {
url @0: Text;
}
# Path to a unix socket
struct UnixPath {
path @0: Text;
}
# configuration for cloudflared to provide a DNS over HTTPS proxy server

File diff suppressed because it is too large Load Diff