diff --git a/agent/connect/ca/provider.go b/agent/connect/ca/provider.go index d557d289c5..0fdd3e41bd 100644 --- a/agent/connect/ca/provider.go +++ b/agent/connect/ca/provider.go @@ -1,4 +1,4 @@ -package connect +package ca import ( "crypto/x509" diff --git a/agent/connect/ca/provider_consul.go b/agent/connect/ca/provider_consul.go index 20641a16cb..afe99db790 100644 --- a/agent/connect/ca/provider_consul.go +++ b/agent/connect/ca/provider_consul.go @@ -1,4 +1,4 @@ -package connect +package ca import ( "bytes" @@ -15,7 +15,6 @@ import ( "github.com/hashicorp/consul/agent/connect" "github.com/hashicorp/consul/agent/consul/state" "github.com/hashicorp/consul/agent/structs" - "github.com/mitchellh/mapstructure" ) type ConsulProvider struct { @@ -111,19 +110,6 @@ func NewConsulProvider(rawConfig map[string]interface{}, delegate ConsulProvider return provider, nil } -func ParseConsulCAConfig(raw map[string]interface{}) (*structs.ConsulCAProviderConfig, error) { - var config structs.ConsulCAProviderConfig - if err := mapstructure.WeakDecode(raw, &config); err != nil { - return nil, fmt.Errorf("error decoding config: %s", err) - } - - if config.PrivateKey == "" && config.RootCert != "" { - return nil, fmt.Errorf("must provide a private key when providing a root cert") - } - - return &config, nil -} - // Return the active root CA and generate a new one if needed func (c *ConsulProvider) ActiveRoot() (string, error) { state := c.delegate.State() diff --git a/agent/connect/ca/provider_consul_config.go b/agent/connect/ca/provider_consul_config.go new file mode 100644 index 0000000000..e0112b3e20 --- /dev/null +++ b/agent/connect/ca/provider_consul_config.go @@ -0,0 +1,77 @@ +package ca + +import ( + "fmt" + "reflect" + "time" + + "github.com/hashicorp/consul/agent/structs" + "github.com/mitchellh/mapstructure" +) + +func ParseConsulCAConfig(raw map[string]interface{}) (*structs.ConsulCAProviderConfig, error) { + var config structs.ConsulCAProviderConfig + decodeConf := &mapstructure.DecoderConfig{ + DecodeHook: ParseDurationFunc(), + ErrorUnused: true, + Result: &config, + WeaklyTypedInput: true, + } + + decoder, err := mapstructure.NewDecoder(decodeConf) + if err != nil { + return nil, err + } + + if err := decoder.Decode(raw); err != nil { + return nil, fmt.Errorf("error decoding config: %s", err) + } + + if config.PrivateKey == "" && config.RootCert != "" { + return nil, fmt.Errorf("must provide a private key when providing a root cert") + } + + return &config, nil +} + +// ParseDurationFunc is a mapstructure hook for decoding a string or +// []uint8 into a time.Duration value. +func ParseDurationFunc() mapstructure.DecodeHookFunc { + uint8ToString := func(bs []uint8) string { + b := make([]byte, len(bs)) + for i, v := range bs { + b[i] = byte(v) + } + return string(b) + } + + return func( + f reflect.Type, + t reflect.Type, + data interface{}) (interface{}, error) { + var v time.Duration + if t != reflect.TypeOf(v) { + return data, nil + } + + switch { + case f.Kind() == reflect.String: + if dur, err := time.ParseDuration(data.(string)); err != nil { + return nil, err + } else { + v = dur + } + return v, nil + case f == reflect.SliceOf(reflect.TypeOf(uint8(0))): + s := uint8ToString(data.([]uint8)) + if dur, err := time.ParseDuration(s); err != nil { + return nil, err + } else { + v = dur + } + return v, nil + default: + return data, nil + } + } +} diff --git a/agent/connect/ca/provider_consul_test.go b/agent/connect/ca/provider_consul_test.go index 9f8cc04b4a..c3b375fc21 100644 --- a/agent/connect/ca/provider_consul_test.go +++ b/agent/connect/ca/provider_consul_test.go @@ -1,4 +1,4 @@ -package connect +package ca import ( "fmt" diff --git a/agent/connect_ca_endpoint.go b/agent/connect_ca_endpoint.go index 979005df13..f7f83b13a7 100644 --- a/agent/connect_ca_endpoint.go +++ b/agent/connect_ca_endpoint.go @@ -47,6 +47,7 @@ func (s *HTTPServer) ConnectCAConfigurationGet(resp http.ResponseWriter, req *ht var reply structs.CAConfiguration err := s.agent.RPC("ConnectCA.ConfigurationGet", &args, &reply) + fixupConfig(&reply) return reply, err } @@ -67,3 +68,25 @@ func (s *HTTPServer) ConnectCAConfigurationSet(resp http.ResponseWriter, req *ht err := s.agent.RPC("ConnectCA.ConfigurationSet", &args, &reply) return nil, err } + +// A hack to fix up the config types inside of the map[string]interface{} +// so that they get formatted correctly during json.Marshal. Without this, +// duration values given as text like "24h" end up getting output back +// to the user in base64-encoded form. +func fixupConfig(conf *structs.CAConfiguration) { + if conf.Provider == structs.ConsulCAProvider { + if v, ok := conf.Config["RotationPeriod"]; ok { + if raw, ok := v.([]uint8); ok { + conf.Config["RotationPeriod"] = uint8ToString(raw) + } + } + } +} + +func uint8ToString(bs []uint8) string { + b := make([]byte, len(bs)) + for i, v := range bs { + b[i] = byte(v) + } + return string(b) +}