TUN-1952: Group ClientConfig fields by the component that uses the config, and return the part of the config that failed to be applied

This commit is contained in:
Chung-Ting Huang
2019-06-12 10:07:24 -05:00
parent 25a04e0c69
commit ca619a97bc
5 changed files with 1270 additions and 341 deletions

View File

@@ -10,6 +10,7 @@ import (
"net/url"
"time"
"github.com/cloudflare/cloudflared/h2mux"
"github.com/cloudflare/cloudflared/originservice"
"github.com/cloudflare/cloudflared/tlsconfig"
"github.com/cloudflare/cloudflared/tunnelrpc"
@@ -17,37 +18,82 @@ import (
capnp "zombiezen.com/go/capnproto2"
"zombiezen.com/go/capnproto2/pogs"
"zombiezen.com/go/capnproto2/rpc"
"zombiezen.com/go/capnproto2/server"
)
///
/// Structs
///
// ClientConfig is a collection of FallibleConfig that determines how cloudflared should function
type ClientConfig struct {
Version uint64
Version Version
SupervisorConfig *SupervisorConfig
EdgeConnectionConfig *EdgeConnectionConfig
DoHProxyConfigs []*DoHProxyConfig
ReverseProxyConfigs []*ReverseProxyConfig
}
// Version type models the version of a ClientConfig
type Version uint64
func InitVersion() Version {
return Version(0)
}
func (v Version) IsNewerOrEqual(comparedVersion Version) bool {
return v >= comparedVersion
}
func (v Version) String() string {
return fmt.Sprintf("Version: %d", v)
}
// FallibleConfig is an interface implemented by configs that cloudflared might not be able to apply
type FallibleConfig interface {
FailReason(err error) string
}
// SupervisorConfig specifies config of components managed by Supervisor other than ConnectionManager
type SupervisorConfig struct {
AutoUpdateFrequency time.Duration
MetricsUpdateFrequency time.Duration
HeartbeatInterval time.Duration
MaxFailedHeartbeats uint64
GracePeriod time.Duration
DoHProxyConfigs []*DoHProxyConfig
ReverseProxyConfigs []*ReverseProxyConfig
NumHAConnections uint8
}
type UseConfigurationResult struct {
Success bool
ErrorMessage string
// FailReason impelents FallibleConfig interface for SupervisorConfig
func (sc *SupervisorConfig) FailReason(err error) string {
return fmt.Sprintf("Cannot apply SupervisorConfig, err: %v", err)
}
// EdgeConnectionConfig specifies what parameters and how may connections should ConnectionManager establish with edge
type EdgeConnectionConfig struct {
NumHAConnections uint8
HeartbeatInterval time.Duration
Timeout time.Duration
MaxFailedHeartbeats uint64
}
// FailReason impelents FallibleConfig interface for EdgeConnectionConfig
func (cmc *EdgeConnectionConfig) FailReason(err error) string {
return fmt.Sprintf("Cannot apply EdgeConnectionConfig, err: %v", err)
}
// DoHProxyConfig is configuration for DNS over HTTPS service
type DoHProxyConfig struct {
ListenHost string
ListenPort uint16
Upstreams []string
}
// FailReason impelents FallibleConfig interface for DoHProxyConfig
func (dpc *DoHProxyConfig) FailReason(err error) string {
return fmt.Sprintf("Cannot apply DoHProxyConfig, err: %v", err)
}
// ReverseProxyConfig how and for what hostnames can this cloudflared proxy
type ReverseProxyConfig struct {
TunnelHostname string
TunnelHostname h2mux.TunnelHostname
Origin OriginConfig
Retries uint64
ConnectionTimeout time.Duration
@@ -65,7 +111,7 @@ func NewReverseProxyConfig(
return nil, fmt.Errorf("NewReverseProxyConfig: originConfig was null")
}
return &ReverseProxyConfig{
TunnelHostname: tunnelHostname,
TunnelHostname: h2mux.TunnelHostname(tunnelHostname),
Origin: originConfig,
Retries: retries,
ConnectionTimeout: connectionTimeout,
@@ -73,6 +119,11 @@ func NewReverseProxyConfig(
}, nil
}
// FailReason impelents FallibleConfig interface for ReverseProxyConfig
func (rpc *ReverseProxyConfig) FailReason(err error) string {
return fmt.Sprintf("Cannot apply ReverseProxyConfig, err: %v", err)
}
//go-sumtype:decl OriginConfig
type OriginConfig interface {
// Service returns a OriginService used to proxy to the origin
@@ -221,18 +272,45 @@ func (_ *HelloWorldOriginConfig) isOriginConfig() {}
*/
func MarshalClientConfig(s tunnelrpc.ClientConfig, p *ClientConfig) error {
s.SetVersion(p.Version)
s.SetAutoUpdateFrequency(p.AutoUpdateFrequency.Nanoseconds())
s.SetMetricsUpdateFrequency(p.MetricsUpdateFrequency.Nanoseconds())
s.SetHeartbeatInterval(p.HeartbeatInterval.Nanoseconds())
s.SetMaxFailedHeartbeats(p.MaxFailedHeartbeats)
s.SetGracePeriod(p.GracePeriod.Nanoseconds())
s.SetNumHAConnections(p.NumHAConnections)
err := marshalDoHProxyConfigs(s, p.DoHProxyConfigs)
s.SetVersion(uint64(p.Version))
supervisorConfig, err := s.NewSupervisorConfig()
if err != nil {
return err
return errors.Wrap(err, "failed to get SupervisorConfig")
}
return marshalReverseProxyConfigs(s, p.ReverseProxyConfigs)
if err = MarshalSupervisorConfig(supervisorConfig, p.SupervisorConfig); err != nil {
return errors.Wrap(err, "MarshalSupervisorConfig error")
}
edgeConnectionConfig, err := s.NewEdgeConnectionConfig()
if err != nil {
return errors.Wrap(err, "failed to get EdgeConnectionConfig")
}
if err := MarshalEdgeConnectionConfig(edgeConnectionConfig, p.EdgeConnectionConfig); err != nil {
return errors.Wrap(err, "MarshalEdgeConnectionConfig error")
}
if err := marshalDoHProxyConfigs(s, p.DoHProxyConfigs); err != nil {
return errors.Wrap(err, "marshalDoHProxyConfigs error")
}
if err := marshalReverseProxyConfigs(s, p.ReverseProxyConfigs); err != nil {
return errors.Wrap(err, "marshalReverseProxyConfigs error")
}
return nil
}
func MarshalSupervisorConfig(s tunnelrpc.SupervisorConfig, p *SupervisorConfig) error {
if err := pogs.Insert(tunnelrpc.SupervisorConfig_TypeID, s.Struct, p); err != nil {
return errors.Wrap(err, "failed to insert SupervisorConfig")
}
return nil
}
func MarshalEdgeConnectionConfig(s tunnelrpc.EdgeConnectionConfig, p *EdgeConnectionConfig) error {
if err := pogs.Insert(tunnelrpc.EdgeConnectionConfig_TypeID, s.Struct, p); err != nil {
return errors.Wrap(err, "failed to insert EdgeConnectionConfig")
}
return nil
}
func marshalDoHProxyConfigs(s tunnelrpc.ClientConfig, dohProxyConfigs []*DoHProxyConfig) error {
@@ -265,23 +343,48 @@ func marshalReverseProxyConfigs(s tunnelrpc.ClientConfig, reverseProxyConfigs []
func UnmarshalClientConfig(s tunnelrpc.ClientConfig) (*ClientConfig, error) {
p := new(ClientConfig)
p.Version = s.Version()
p.AutoUpdateFrequency = time.Duration(s.AutoUpdateFrequency())
p.MetricsUpdateFrequency = time.Duration(s.MetricsUpdateFrequency())
p.HeartbeatInterval = time.Duration(s.HeartbeatInterval())
p.MaxFailedHeartbeats = s.MaxFailedHeartbeats()
p.GracePeriod = time.Duration(s.GracePeriod())
p.NumHAConnections = s.NumHAConnections()
dohProxyConfigs, err := unmarshalDoHProxyConfigs(s)
p.Version = Version(s.Version())
supervisorConfig, err := s.SupervisorConfig()
if err != nil {
return nil, err
return nil, errors.Wrap(err, "failed to get SupervisorConfig")
}
p.DoHProxyConfigs = dohProxyConfigs
reverseProxyConfigs, err := unmarshalReverseProxyConfigs(s)
p.SupervisorConfig, err = UnmarshalSupervisorConfig(supervisorConfig)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "UnmarshalSupervisorConfig error")
}
p.ReverseProxyConfigs = reverseProxyConfigs
edgeConnectionConfig, err := s.EdgeConnectionConfig()
if err != nil {
return nil, errors.Wrap(err, "failed to get ConnectionManagerConfig")
}
p.EdgeConnectionConfig, err = UnmarshalEdgeConnectionConfig(edgeConnectionConfig)
if err != nil {
return nil, errors.Wrap(err, "UnmarshalConnectionManagerConfig error")
}
p.DoHProxyConfigs, err = unmarshalDoHProxyConfigs(s)
if err != nil {
return nil, errors.Wrap(err, "unmarshalDoHProxyConfigs error")
}
p.ReverseProxyConfigs, err = unmarshalReverseProxyConfigs(s)
if err != nil {
return nil, errors.Wrap(err, "unmarshalReverseProxyConfigs error")
}
return p, nil
}
func UnmarshalSupervisorConfig(s tunnelrpc.SupervisorConfig) (*SupervisorConfig, error) {
p := new(SupervisorConfig)
err := pogs.Extract(p, tunnelrpc.SupervisorConfig_TypeID, s.Struct)
return p, err
}
func UnmarshalEdgeConnectionConfig(s tunnelrpc.EdgeConnectionConfig) (*EdgeConnectionConfig, error) {
p := new(EdgeConnectionConfig)
err := pogs.Extract(p, tunnelrpc.EdgeConnectionConfig_TypeID, s.Struct)
return p, err
}
@@ -320,13 +423,38 @@ func unmarshalReverseProxyConfigs(s tunnelrpc.ClientConfig) ([]*ReverseProxyConf
}
func MarshalUseConfigurationResult(s tunnelrpc.UseConfigurationResult, p *UseConfigurationResult) error {
return pogs.Insert(tunnelrpc.UseConfigurationResult_TypeID, s.Struct, p)
capnpList, err := s.NewFailedConfigs(int32(len(p.FailedConfigs)))
if err != nil {
return errors.Wrap(err, "Cannot create new FailedConfigs")
}
for i, unmarshalledFailedConfig := range p.FailedConfigs {
err := MarshalFailedConfig(capnpList.At(i), unmarshalledFailedConfig)
if err != nil {
return errors.Wrapf(err, "Cannot MarshalFailedConfig at index %d", i)
}
}
s.SetSuccess(p.Success)
return nil
}
func UnmarshalUseConfigurationResult(s tunnelrpc.UseConfigurationResult) (*UseConfigurationResult, error) {
p := new(UseConfigurationResult)
err := pogs.Extract(p, tunnelrpc.UseConfigurationResult_TypeID, s.Struct)
return p, err
var failedConfigs []*FailedConfig
marshalledFailedConfigs, err := s.FailedConfigs()
if err != nil {
return nil, errors.Wrap(err, "Cannot get FailedConfigs")
}
for i := 0; i < marshalledFailedConfigs.Len(); i++ {
ss := marshalledFailedConfigs.At(i)
failedConfig, err := UnmarshalFailedConfig(ss)
if err != nil {
return nil, errors.Wrapf(err, "Cannot UnmarshalFailedConfig at index %d", i)
}
failedConfigs = append(failedConfigs, failedConfig)
}
p.FailedConfigs = failedConfigs
p.Success = s.Success()
return p, nil
}
func MarshalDoHProxyConfig(s tunnelrpc.DoHProxyConfig, p *DoHProxyConfig) error {
@@ -340,7 +468,7 @@ func UnmarshalDoHProxyConfig(s tunnelrpc.DoHProxyConfig) (*DoHProxyConfig, error
}
func MarshalReverseProxyConfig(s tunnelrpc.ReverseProxyConfig, p *ReverseProxyConfig) error {
s.SetTunnelHostname(p.TunnelHostname)
s.SetTunnelHostname(p.TunnelHostname.String())
switch config := p.Origin.(type) {
case *HTTPOriginConfig:
ss, err := s.Origin().NewHttp()
@@ -381,7 +509,7 @@ func UnmarshalReverseProxyConfig(s tunnelrpc.ReverseProxyConfig) (*ReverseProxyC
if err != nil {
return nil, err
}
p.TunnelHostname = tunnelHostname
p.TunnelHostname = h2mux.TunnelHostname(tunnelHostname)
switch s.Origin().Which() {
case tunnelrpc.ReverseProxyConfig_origin_Which_http:
ss, err := s.Origin().Http()
@@ -584,3 +712,141 @@ func (c *ClientService_PogsClient) UseConfiguration(
}
return UnmarshalUseConfigurationResult(retval)
}
func ClientService_ServerToClient(s ClientService) tunnelrpc.ClientService {
return tunnelrpc.ClientService_ServerToClient(ClientService_PogsImpl{s})
}
type ClientService_PogsImpl struct {
impl ClientService
}
func (i ClientService_PogsImpl) UseConfiguration(p tunnelrpc.ClientService_useConfiguration) error {
config, err := p.Params.ClientServiceConfig()
if err != nil {
return errors.Wrap(err, "Cannot get CloudflaredConfig parameter")
}
pogsConfig, err := UnmarshalClientConfig(config)
if err != nil {
return errors.Wrap(err, "Cannot unmarshal tunnelrpc.CloudflaredConfig to *CloudflaredConfig")
}
server.Ack(p.Options)
userConfigResult, err := i.impl.UseConfiguration(p.Ctx, pogsConfig)
if err != nil {
return err
}
result, err := p.Results.NewResult()
if err != nil {
return err
}
return MarshalUseConfigurationResult(result, userConfigResult)
}
type UseConfigurationResult struct {
Success bool
FailedConfigs []*FailedConfig
}
type FailedConfig struct {
Config FallibleConfig
Reason string
}
func MarshalFailedConfig(s tunnelrpc.FailedConfig, p *FailedConfig) error {
switch config := p.Config.(type) {
case *SupervisorConfig:
ss, err := s.Config().NewSupervisor()
if err != nil {
return err
}
err = MarshalSupervisorConfig(ss, config)
if err != nil {
return err
}
case *EdgeConnectionConfig:
ss, err := s.Config().EdgeConnection()
if err != nil {
return err
}
err = MarshalEdgeConnectionConfig(ss, config)
if err != nil {
return err
}
case *DoHProxyConfig:
ss, err := s.Config().NewDoh()
if err != nil {
return err
}
err = MarshalDoHProxyConfig(ss, config)
if err != nil {
return err
}
case *ReverseProxyConfig:
ss, err := s.Config().NewReverseProxy()
if err != nil {
return err
}
err = MarshalReverseProxyConfig(ss, config)
if err != nil {
return err
}
default:
return fmt.Errorf("Unknown type for Config: %T", config)
}
s.SetReason(p.Reason)
return nil
}
func UnmarshalFailedConfig(s tunnelrpc.FailedConfig) (*FailedConfig, error) {
p := new(FailedConfig)
switch s.Config().Which() {
case tunnelrpc.FailedConfig_config_Which_supervisor:
ss, err := s.Config().Supervisor()
if err != nil {
return nil, errors.Wrap(err, "Cannot get SupervisorConfig from Config")
}
config, err := UnmarshalSupervisorConfig(ss)
if err != nil {
return nil, errors.Wrap(err, "Cannot UnmarshalSupervisorConfig")
}
p.Config = config
case tunnelrpc.FailedConfig_config_Which_edgeConnection:
ss, err := s.Config().EdgeConnection()
if err != nil {
return nil, errors.Wrap(err, "Cannot get ConnectionManager from Config")
}
config, err := UnmarshalEdgeConnectionConfig(ss)
if err != nil {
return nil, errors.Wrap(err, "Cannot UnmarshalConnectionManagerConfig")
}
p.Config = config
case tunnelrpc.FailedConfig_config_Which_doh:
ss, err := s.Config().Doh()
if err != nil {
return nil, errors.Wrap(err, "Cannot get Doh from Config")
}
config, err := UnmarshalDoHProxyConfig(ss)
if err != nil {
return nil, errors.Wrap(err, "Cannot UnmarshalDoHProxyConfig")
}
p.Config = config
case tunnelrpc.FailedConfig_config_Which_reverseProxy:
ss, err := s.Config().ReverseProxy()
if err != nil {
return nil, errors.Wrap(err, "Cannot get ReverseProxy from Config")
}
config, err := UnmarshalReverseProxyConfig(ss)
if err != nil {
return nil, errors.Wrap(err, "Cannot UnmarshalReverseProxyConfig")
}
p.Config = config
default:
return nil, fmt.Errorf("Unknown type for FailedConfig: %v", s.Config().Which())
}
reason, err := s.Reason()
if err != nil {
return nil, errors.Wrap(err, "Cannot get Reason")
}
p.Reason = reason
return p, nil
}

View File

@@ -13,6 +13,14 @@ import (
capnp "zombiezen.com/go/capnproto2"
)
func TestVersion(t *testing.T) {
firstVersion := InitVersion()
secondVersion := Version(1)
assert.False(t, firstVersion.IsNewerOrEqual(secondVersion))
assert.True(t, secondVersion.IsNewerOrEqual(firstVersion))
assert.True(t, secondVersion.IsNewerOrEqual(secondVersion))
}
func TestClientConfig(t *testing.T) {
addDoHProxyConfigs := func(c *ClientConfig) {
c.DoHProxyConfigs = []*DoHProxyConfig{
@@ -66,8 +74,17 @@ func TestUseConfigurationResult(t *testing.T) {
Success: true,
},
&UseConfigurationResult{
Success: false,
ErrorMessage: "the quick brown fox jumped over the lazy dogs",
Success: false,
FailedConfigs: []*FailedConfig{
{
Config: sampleReverseProxyConfig(),
Reason: "Invalid certificate",
},
{
Config: sampleDoHProxyConfig(),
Reason: "Cannot listen on port 53",
},
},
},
}
for i, testCase := range testCases {
@@ -193,13 +210,18 @@ func TestWebSocketOriginConfig(t *testing.T) {
func sampleClientConfig(overrides ...func(*ClientConfig)) *ClientConfig {
sample := &ClientConfig{
Version: uint64(1337),
AutoUpdateFrequency: 21 * time.Hour,
MetricsUpdateFrequency: 11 * time.Minute,
HeartbeatInterval: 5 * time.Second,
MaxFailedHeartbeats: 9001,
GracePeriod: 31 * time.Second,
NumHAConnections: 49,
Version: Version(1337),
SupervisorConfig: &SupervisorConfig{
AutoUpdateFrequency: 21 * time.Hour,
MetricsUpdateFrequency: 11 * time.Minute,
GracePeriod: 31 * time.Second,
},
EdgeConnectionConfig: &EdgeConnectionConfig{
NumHAConnections: 49,
Timeout: 9 * time.Second,
HeartbeatInterval: 5 * time.Second,
MaxFailedHeartbeats: 9001,
},
}
sample.ensureNoZeroFields()
for _, f := range overrides {