diff --git a/agent/agent.go b/agent/agent.go index bb0b3e832d..0487eef7ae 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -1251,6 +1251,10 @@ func newConsulConfig(runtimeCfg *config.RuntimeConfig, logger hclog.Logger) (*co cfg.ConfigEntryBootstrap = runtimeCfg.ConfigEntryBootstrap + // Duplicate our own serf config once to make sure that the duplication + // function does not drift. + cfg.SerfLANConfig = consul.CloneSerfLANConfig(cfg.SerfLANConfig) + enterpriseConsulConfig(cfg, runtimeCfg) return cfg, nil } diff --git a/agent/consul/config.go b/agent/consul/config.go index 195e8a4b47..fac025fc1c 100644 --- a/agent/consul/config.go +++ b/agent/consul/config.go @@ -541,6 +541,49 @@ func DefaultConfig() *Config { return conf } +// CloneSerfLANConfig clones an existing serf.Config used on the LAN by +// reconstructing it from defaults and re-applying changes made in the agent +// configs. +// +// This function is tricky to keep from rotting so we enforce that it MUST work +// by cloning our own serf LAN configuration on startup and only using the +// cloned one so any configs we need to change have to be changed here for them +// to work at all. +func CloneSerfLANConfig(base *serf.Config) *serf.Config { + cfg := DefaultConfig().SerfLANConfig + + // from consul.DefaultConfig() + cfg.ReconnectTimeout = base.ReconnectTimeout + cfg.MemberlistConfig.BindPort = base.MemberlistConfig.BindPort + cfg.MemberlistConfig.DeadNodeReclaimTime = base.MemberlistConfig.DeadNodeReclaimTime + + // from agent.newConsulConfig() + cfg.MemberlistConfig.BindAddr = base.MemberlistConfig.BindAddr + cfg.MemberlistConfig.BindPort = base.MemberlistConfig.BindPort + cfg.MemberlistConfig.CIDRsAllowed = base.MemberlistConfig.CIDRsAllowed + cfg.MemberlistConfig.AdvertiseAddr = base.MemberlistConfig.AdvertiseAddr + cfg.MemberlistConfig.AdvertisePort = base.MemberlistConfig.AdvertisePort + cfg.MemberlistConfig.GossipVerifyIncoming = base.MemberlistConfig.GossipVerifyIncoming + cfg.MemberlistConfig.GossipVerifyOutgoing = base.MemberlistConfig.GossipVerifyOutgoing + cfg.MemberlistConfig.GossipInterval = base.MemberlistConfig.GossipInterval + cfg.MemberlistConfig.GossipNodes = base.MemberlistConfig.GossipNodes + cfg.MemberlistConfig.ProbeInterval = base.MemberlistConfig.ProbeInterval + cfg.MemberlistConfig.ProbeTimeout = base.MemberlistConfig.ProbeTimeout + cfg.MemberlistConfig.SuspicionMult = base.MemberlistConfig.SuspicionMult + cfg.MemberlistConfig.RetransmitMult = base.MemberlistConfig.RetransmitMult + + // agent/keyring.go + cfg.MemberlistConfig.Keyring = base.MemberlistConfig.Keyring + + // tests + cfg.KeyringFile = base.KeyringFile + cfg.ReapInterval = base.ReapInterval + cfg.TombstoneTimeout = base.TombstoneTimeout + cfg.MemberlistConfig.SecretKey = base.MemberlistConfig.SecretKey + + return cfg +} + // RPCConfig settings for the RPC server // // TODO: move many settings to this struct. diff --git a/agent/consul/config_test.go b/agent/consul/config_test.go new file mode 100644 index 0000000000..7748895348 --- /dev/null +++ b/agent/consul/config_test.go @@ -0,0 +1,129 @@ +package consul + +import ( + "reflect" + "testing" + "time" + + fuzz "github.com/google/gofuzz" + "github.com/stretchr/testify/require" +) + +func TestCloneSerfLANConfig(t *testing.T) { + config := DefaultConfig().SerfLANConfig + + // NOTE: ALL fields on serf.Config and memberlist.Config MUST BE + // represented either here or in the CloneSerfLANConfig function body. + // Failure to add it to the clone or ignore sections will fail the test. + memberlistIgnoreFieldNames := []string{ + "Alive", + "AwarenessMaxMultiplier", + "Conflict", + "DNSConfigPath", + "Delegate", + "DelegateProtocolMax", + "DelegateProtocolMin", + "DelegateProtocolVersion", + "DisableTcpPings", + "DisableTcpPingsForNode", + "EnableCompression", + "Events", + "GossipToTheDeadTime", + "HandoffQueueDepth", + "IndirectChecks", + "LogOutput", + "Logger", + "Merge", + "Name", + "Ping", + "ProtocolVersion", + "PushPullInterval", + "RequireNodeNames", + "SuspicionMaxTimeoutMult", + "TCPTimeout", + "Transport", + "UDPBufferSize", + } + serfIgnoreFieldNames := []string{ + "BroadcastTimeout", + "CoalescePeriod", + "DisableCoordinates", + "EnableNameConflictResolution", + "EventBuffer", + "EventCh", + "FlapTimeout", + "LeavePropagateDelay", + "LogOutput", + "Logger", + "MaxQueueDepth", + "MemberlistConfig", + "Merge", + "MinQueueDepth", + "NodeName", + "ProtocolVersion", + "QueryBuffer", + "QueryResponseSizeLimit", + "QuerySizeLimit", + "QueryTimeoutMult", + "QueueCheckInterval", + "QueueDepthWarning", + "QuiescentPeriod", + "RecentIntentTimeout", + "ReconnectInterval", + "ReconnectTimeoutOverride", + "RejoinAfterLeave", + "SnapshotPath", + "Tags", + "UserCoalescePeriod", + "UserEventSizeLimit", + "UserQuiescentPeriod", + "ValidateNodeNames", + } + + serfFuzzed := fuzzNonIgnoredFields(config, serfIgnoreFieldNames) + t.Logf("Fuzzing serf.Config fields: %v", serfFuzzed) + + memberlistFuzzed := fuzzNonIgnoredFields(config.MemberlistConfig, memberlistIgnoreFieldNames) + t.Logf("Fuzzing memberlist.Config fields: %v", memberlistFuzzed) + + clone := CloneSerfLANConfig(config) + require.Equal(t, config, clone) +} + +func fuzzNonIgnoredFields(value interface{}, ignoredFields []string) []string { + ignored := make(map[string]struct{}) + for _, field := range ignoredFields { + ignored[field] = struct{}{} + } + + var fuzzed []string + + // Walk the fields of our object to fuzz and selectively only fuzz the + // fields that were not ignored. + fuzzer := fuzz.NewWithSeed(time.Now().UnixNano()) + + v := reflect.ValueOf(value).Elem() + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + if !field.CanInterface() { + continue // skip unexported fields + } + + fieldName := v.Type().Field(i).Name + if _, ok := ignored[fieldName]; ok { + continue + } + + fuzzed = append(fuzzed, fieldName) + + // copy the data somewhere mutable + tmp := reflect.New(field.Type()) + tmp.Elem().Set(field) + // fuzz the copy + fuzzer.Fuzz(tmp.Interface()) + // and set the fuzzed copy back to the original location + field.Set(reflect.Indirect(tmp)) + } + + return fuzzed +}