Overhaul the auto-config translation

This fixes some issues around spurious warnings about using enterprise configuration in OSS.
This commit is contained in:
Matt Keeler 2020-06-26 14:53:07 -04:00
parent 6ef032fefe
commit 1fcb9ef03f
No known key found for this signature in database
GPG Key ID: 04DBAE1857E0081B
2 changed files with 119 additions and 47 deletions

View File

@ -2,7 +2,6 @@ package autoconf
import ( import (
pbconfig "github.com/hashicorp/consul/agent/agentpb/config" pbconfig "github.com/hashicorp/consul/agent/agentpb/config"
"github.com/hashicorp/consul/agent/config"
) )
// translateAgentConfig is meant to take in a agent/agentpb/config.Config type // translateAgentConfig is meant to take in a agent/agentpb/config.Config type
@ -13,81 +12,149 @@ import (
// //
// Why is this function not in the agent/agentpb/config package? The answer, that // Why is this function not in the agent/agentpb/config package? The answer, that
// package cannot import the agent/config package without running into import cycles. // package cannot import the agent/config package without running into import cycles.
func translateConfig(c *pbconfig.Config) *config.Config { //
out := config.Config{ // If this function is meant to output an agent/config.Config then why does it output
Datacenter: &c.Datacenter, // a map[string]interface{}? The answer is that our config and command line option
PrimaryDatacenter: &c.PrimaryDatacenter, // parsing is messed up and it would require major changes to fix (we probably should
NodeName: &c.NodeName, // do them but not for the auto-config feature). To understand this we need to work
SegmentName: &c.SegmentName, // backwards. What we want to be able to do is persist the config settings from an
// auto-config response configuration to disk. We then want that configuration
// to be able to be parsed with the normal configuration parser/builder. It sort of was
// working with returning a filled out agent/config.Config but the problem was that
// the struct has a lot of non-pointer struct members. Thus, JSON serializtion caused
// these to always be emitted even if they contained no non-empty fields. The
// configuration would then seem to parse okay, but in OSS we would get warnings for
// setting a bunch of enterprise fields like "audit" at the top level. In an attempt
// to quiet those warnings, I had converted all the existing non-pointer struct fields
// to pointers. Then there were issues with the builder code expecting concrete values.
// I could add nil checks **EVERYWHERE** in builder.go or take a different approach.
// I then made a function utilizing github.com/mitchellh/reflectwalk to un-nil all the
// struct pointers after parsing to prevent any nil pointer dereferences. At first
// glance this seemed like it was going to work but then I saw that nearly all of the
// tests in runtime_test.go were failing. The first issues was that we were not merging
// pointers to struct fields properly. It was simply taking the new pointer if non-nil
// and defaulting to the original. So I updated that code, to properly merge pointers
// to structs. That fixed a bunch of tests but then there was another issue with
// the runtime tests where it was emitting warnings for using consul enterprise only
// configuration. After spending some time tracking this down it turns out that it
// was coming from our CLI option parsing. Our CLI option parsing works by filling
// in a agent/config.Config struct. Along the way when converting to pointers to
// structs I had to add a call to that function to un-nil various pointers to prevent
// the CLI from segfaulting. However this un-nil operation was causing the various
// enterprise only keys to be materialized. Thus we were back to where we were before
// the conversion to pointers to structs and mostly stuck.
//
// Therefore, this function will create a map[string]interface{} that should be
// compatible with the agent/config.Config struct but where we can more tightly
// control which fields are output. Its not a nice solution. It has a non-trivial
// maintenance burden. In the long run we should unify the protobuf Config and
// the normal agent/config.Config so that we can just serialize the protobuf version
// without any translation. For now, this hack is necessary :(
func translateConfig(c *pbconfig.Config) map[string]interface{} {
out := map[string]interface{}{
"datacenter": c.Datacenter,
"primary_datacenter": c.PrimaryDatacenter,
"node_name": c.NodeName,
}
// only output the SegmentName in the configuration if its non-empty
// this will avoid a warning later when parsing the persisted configuration
if c.SegmentName != "" {
out["segment"] = c.SegmentName
} }
// Translate Auto Encrypt settings // Translate Auto Encrypt settings
if a := c.AutoEncrypt; a != nil { if a := c.AutoEncrypt; a != nil {
out.AutoEncrypt = config.AutoEncrypt{ autoEncryptConfig := map[string]interface{}{
TLS: &a.TLS, "tls": a.TLS,
DNSSAN: a.DNSSAN, "allow_tls": a.AllowTLS,
IPSAN: a.IPSAN,
AllowTLS: &a.AllowTLS,
} }
if len(a.DNSSAN) > 0 {
autoEncryptConfig["dns_san"] = a.DNSSAN
}
if len(a.IPSAN) > 0 {
autoEncryptConfig["ip_san"] = a.IPSAN
}
out["auto_encrypt"] = autoEncryptConfig
} }
// Translate all the ACL settings // Translate all the ACL settings
if a := c.ACL; a != nil { if a := c.ACL; a != nil {
out.ACL = config.ACL{ aclConfig := map[string]interface{}{
Enabled: &a.Enabled, "enabled": a.Enabled,
PolicyTTL: &a.PolicyTTL, "policy_ttl": a.PolicyTTL,
RoleTTL: &a.RoleTTL, "role_ttl": a.RoleTTL,
TokenTTL: &a.TokenTTL, "token_ttl": a.TokenTTL,
DownPolicy: &a.DownPolicy, "down_policy": a.DownPolicy,
DefaultPolicy: &a.DefaultPolicy, "default_policy": a.DefaultPolicy,
EnableKeyListPolicy: &a.EnableKeyListPolicy, "enable_key_list_policy": a.EnableKeyListPolicy,
DisabledTTL: &a.DisabledTTL, "disabled_ttl": a.DisabledTTL,
EnableTokenPersistence: &a.EnableTokenPersistence, "enable_token_persistence": a.EnableTokenPersistence,
MSPDisableBootstrap: &a.MSPDisableBootstrap,
} }
if t := c.ACL.Tokens; t != nil { if t := c.ACL.Tokens; t != nil {
var tokens []config.ServiceProviderToken var mspTokens []map[string]string
// create the slice of msp tokens if any // create the slice of msp tokens if any
for _, mspToken := range t.ManagedServiceProvider { for _, mspToken := range t.ManagedServiceProvider {
tokens = append(tokens, config.ServiceProviderToken{ mspTokens = append(mspTokens, map[string]string{
AccessorID: &mspToken.AccessorID, "accessor_id": mspToken.AccessorID,
SecretID: &mspToken.SecretID, "secret_id": mspToken.SecretID,
}) })
} }
out.ACL.Tokens = config.Tokens{ tokenConfig := make(map[string]interface{})
Master: &t.Master,
Replication: &t.Replication, if t.Master != "" {
AgentMaster: &t.AgentMaster, tokenConfig["master"] = t.Master
Default: &t.Default,
Agent: &t.Agent,
ManagedServiceProvider: tokens,
} }
if t.Replication != "" {
tokenConfig["replication"] = t.Replication
}
if t.AgentMaster != "" {
tokenConfig["agent_master"] = t.AgentMaster
}
if t.Default != "" {
tokenConfig["default"] = t.Default
}
if t.Agent != "" {
tokenConfig["agent"] = t.Agent
}
if len(mspTokens) > 0 {
tokenConfig["managed_service_provider"] = mspTokens
}
aclConfig["tokens"] = tokenConfig
} }
out["acl"] = aclConfig
} }
// Translate the Gossip settings // Translate the Gossip settings
if g := c.Gossip; g != nil { if g := c.Gossip; g != nil {
out.RetryJoinLAN = g.RetryJoinLAN out["retry_join"] = g.RetryJoinLAN
// Translate the Gossip Encryption settings // Translate the Gossip Encryption settings
if e := c.Gossip.Encryption; e != nil { if e := c.Gossip.Encryption; e != nil {
out.EncryptKey = &e.Key out["encrypt"] = e.Key
out.EncryptVerifyIncoming = &e.VerifyIncoming out["encrypt_verify_incoming"] = e.VerifyIncoming
out.EncryptVerifyOutgoing = &e.VerifyOutgoing out["encrypt_verify_outgoing"] = e.VerifyOutgoing
} }
} }
// Translate the Generic TLS settings // Translate the Generic TLS settings
if t := c.TLS; t != nil { if t := c.TLS; t != nil {
out.VerifyOutgoing = &t.VerifyOutgoing out["verify_outgoing"] = t.VerifyOutgoing
out.VerifyServerHostname = &t.VerifyServerHostname out["verify_server_hostname"] = t.VerifyServerHostname
out.TLSMinVersion = &t.MinVersion if t.MinVersion != "" {
out.TLSCipherSuites = &t.CipherSuites out["tls_min_version"] = t.MinVersion
out.TLSPreferServerCipherSuites = &t.PreferServerCipherSuites }
if t.CipherSuites != "" {
out["tls_cipher_suites"] = t.CipherSuites
}
out["tls_prefer_server_cipher_suites"] = t.PreferServerCipherSuites
} }
return &out return out
} }

View File

@ -1,6 +1,7 @@
package autoconf package autoconf
import ( import (
"encoding/json"
"testing" "testing"
pbconfig "github.com/hashicorp/consul/agent/agentpb/config" pbconfig "github.com/hashicorp/consul/agent/agentpb/config"
@ -94,7 +95,6 @@ func TestConfig_translateConfig(t *testing.T) {
EnableKeyListPolicy: boolPointer(true), EnableKeyListPolicy: boolPointer(true),
DisabledTTL: stringPointer("4s"), DisabledTTL: stringPointer("4s"),
EnableTokenPersistence: boolPointer(true), EnableTokenPersistence: boolPointer(true),
MSPDisableBootstrap: boolPointer(false),
Tokens: config.Tokens{ Tokens: config.Tokens{
Master: stringPointer("99e7e490-6baf-43fc-9010-78b6aa9a6813"), Master: stringPointer("99e7e490-6baf-43fc-9010-78b6aa9a6813"),
Replication: stringPointer("51308d40-465c-4ac6-a636-7c0747edec89"), Replication: stringPointer("51308d40-465c-4ac6-a636-7c0747edec89"),
@ -117,6 +117,11 @@ func TestConfig_translateConfig(t *testing.T) {
}, },
} }
actual := translateConfig(&original) translated := translateConfig(&original)
require.Equal(t, expected, actual) data, err := json.Marshal(translated)
require.NoError(t, err)
actual, _, err := config.Parse(string(data), "json")
require.NoError(t, err)
require.Equal(t, expected, &actual)
} }