From 57f0f427335d6d7effbfcf1b8c826a870b075f98 Mon Sep 17 00:00:00 2001 From: Dan Upton Date: Fri, 18 Mar 2022 10:46:58 +0000 Subject: [PATCH] =?UTF-8?q?Support=20per-listener=20TLS=20configuration=20?= =?UTF-8?q?=E2=9A=99=EF=B8=8F=20(#12504)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces the capability to configure TLS differently for Consul's listeners/ports (i.e. HTTPS, gRPC, and the internal multiplexed RPC port) which is useful in scenarios where you may want the HTTPS or gRPC interfaces to present a certificate signed by a well-known/public CA, rather than the certificate used for internal communication which must have a SAN in the form `server..consul`. --- .changelog/12504.txt | 7 + agent/agent.go | 6 +- agent/auto-config/auto_config_test.go | 2 +- agent/auto-config/config_translate.go | 11 +- agent/auto-config/config_translate_test.go | 40 +- agent/config/builder.go | 227 +- agent/config/config.go | 36 +- agent/config/default.go | 7 +- agent/config/deprecated.go | 133 +- agent/config/deprecated_test.go | 46 +- agent/config/runtime.go | 138 +- agent/config/runtime_test.go | 592 ++++-- .../TestRuntimeConfig_Sanitize.golden | 52 +- agent/config/testdata/full-config.hcl | 42 + agent/config/testdata/full-config.json | 41 + agent/consul/auto_config_endpoint.go | 13 +- agent/consul/auto_config_endpoint_test.go | 105 +- agent/consul/auto_encrypt_endpoint_test.go | 19 +- agent/consul/client_test.go | 15 +- agent/consul/rpc_test.go | 65 +- agent/consul/server.go | 3 +- agent/consul/server_serf.go | 2 +- agent/consul/server_test.go | 81 +- agent/consul/status_endpoint_test.go | 3 +- agent/consul/subscribe_backend_test.go | 20 +- agent/grpc/client_test.go | 48 +- agent/grpc/server_test.go | 3 +- agent/pool/pool.go | 3 +- agent/setup.go | 2 +- agent/xds/server.go | 8 +- command/agent/agent.go | 2 +- proto/pbconfig/config.pb.go | 139 +- proto/pbconfig/config.proto | 4 +- tlsutil/config.go | 488 +++-- tlsutil/config_test.go | 1846 +++++++++-------- website/content/docs/agent/index.mdx | 42 +- website/content/docs/agent/options.mdx | 290 ++- .../content/docs/connect/configuration.mdx | 2 +- .../docs/ecs/manual/secure-configuration.mdx | 20 +- website/content/docs/k8s/helm.mdx | 10 +- .../servers-outside-kubernetes.mdx | 2 +- .../multi-cluster/vms-and-kubernetes.mdx | 29 +- website/content/docs/security/encryption.mdx | 22 +- .../docs/security/security-models/core.mdx | 86 +- 44 files changed, 2886 insertions(+), 1866 deletions(-) create mode 100644 .changelog/12504.txt diff --git a/.changelog/12504.txt b/.changelog/12504.txt new file mode 100644 index 000000000..55fcbfa95 --- /dev/null +++ b/.changelog/12504.txt @@ -0,0 +1,7 @@ +```release-note:feature +tls: it is now possible to configure TLS differently for each of Consul's listeners (i.e. HTTPS, gRPC and the internal multiplexed RPC listener) using the `tls` stanza +``` + +```release-note:deprecation +config: setting `cert_file`, `key_file`, `ca_file`, `ca_path`, `tls_min_version`, `tls_cipher_suites`, `verify_incoming`, `verify_incoming_rpc`, `verify_incoming_https`, `verify_outgoing` and `verify_server_hostname` at the top-level is now deprecated, use the `tls` stanza instead +``` diff --git a/agent/agent.go b/agent/agent.go index 40151727d..6e74f5062 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -489,7 +489,7 @@ func (a *Agent) Start(ctx context.Context) error { c.NodeID = a.config.NodeID a.config = c - if err := a.tlsConfigurator.Update(a.config.ToTLSUtilConfig()); err != nil { + if err := a.tlsConfigurator.Update(a.config.TLS); err != nil { return fmt.Errorf("Failed to load TLS configurations after applying auto-config settings: %w", err) } @@ -1218,7 +1218,7 @@ func newConsulConfig(runtimeCfg *config.RuntimeConfig, logger hclog.Logger) (*co } cfg.Build = fmt.Sprintf("%s%s:%s", runtimeCfg.Version, runtimeCfg.VersionPrerelease, revision) - cfg.TLSConfig = runtimeCfg.ToTLSUtilConfig() + cfg.TLSConfig = runtimeCfg.TLS cfg.DefaultQueryTime = runtimeCfg.DefaultQueryTime cfg.MaxQueryTime = runtimeCfg.MaxQueryTime @@ -3748,7 +3748,7 @@ func (a *Agent) reloadConfigInternal(newCfg *config.RuntimeConfig) error { // the checks and service registrations. a.tokens.Load(newCfg.ACLTokens, a.logger) - if err := a.tlsConfigurator.Update(newCfg.ToTLSUtilConfig()); err != nil { + if err := a.tlsConfigurator.Update(newCfg.TLS); err != nil { return fmt.Errorf("Failed reloading tls configuration: %s", err) } diff --git a/agent/auto-config/auto_config_test.go b/agent/auto-config/auto_config_test.go index 7f49df7c1..c810ed926 100644 --- a/agent/auto-config/auto_config_test.go +++ b/agent/auto-config/auto_config_test.go @@ -822,7 +822,7 @@ func startedAutoConfig(t *testing.T, autoEncrypt bool) testAutoConfig { if !autoEncrypt { // auto-encrypt doesn't modify the config but rather sets the value // in the TLS configurator - require.True(t, cfg.VerifyServerHostname) + require.True(t, cfg.TLS.InternalRPC.VerifyServerHostname) } ctx, cancel := context.WithCancel(context.Background()) diff --git a/agent/auto-config/config_translate.go b/agent/auto-config/config_translate.go index bc75783fd..4b7f7e663 100644 --- a/agent/auto-config/config_translate.go +++ b/agent/auto-config/config_translate.go @@ -81,11 +81,12 @@ func translateConfig(c *pbconfig.Config) config.Config { } if t := c.TLS; t != nil { - result.VerifyOutgoing = &t.VerifyOutgoing - result.VerifyServerHostname = &t.VerifyServerHostname - result.TLSMinVersion = stringPtrOrNil(t.MinVersion) - result.TLSCipherSuites = stringPtrOrNil(t.CipherSuites) - result.TLSPreferServerCipherSuites = &t.PreferServerCipherSuites + result.TLS.Defaults = config.TLSProtocolConfig{ + VerifyOutgoing: &t.VerifyOutgoing, + TLSMinVersion: stringPtrOrNil(t.MinVersion), + TLSCipherSuites: stringPtrOrNil(t.CipherSuites), + } + result.TLS.InternalRPC.VerifyServerHostname = &t.VerifyServerHostname } return result diff --git a/agent/auto-config/config_translate_test.go b/agent/auto-config/config_translate_test.go index 28f62dc14..c60b84a9c 100644 --- a/agent/auto-config/config_translate_test.go +++ b/agent/auto-config/config_translate_test.go @@ -88,28 +88,32 @@ func TestTranslateConfig(t *testing.T) { }, }, TLS: &pbconfig.TLS{ - VerifyOutgoing: true, - VerifyServerHostname: true, - CipherSuites: "stuff", - MinVersion: "tls13", - PreferServerCipherSuites: true, + VerifyOutgoing: true, + VerifyServerHostname: true, + CipherSuites: "stuff", + MinVersion: "tls13", }, } expected := config.Config{ - Datacenter: stringPointer("abc"), - PrimaryDatacenter: stringPointer("def"), - NodeName: stringPointer("ghi"), - SegmentName: stringPointer("jkl"), - RetryJoinLAN: []string{"10.0.0.1"}, - EncryptKey: stringPointer("blarg"), - EncryptVerifyIncoming: boolPointer(true), - EncryptVerifyOutgoing: boolPointer(true), - VerifyOutgoing: boolPointer(true), - VerifyServerHostname: boolPointer(true), - TLSCipherSuites: stringPointer("stuff"), - TLSMinVersion: stringPointer("tls13"), - TLSPreferServerCipherSuites: boolPointer(true), + Datacenter: stringPointer("abc"), + PrimaryDatacenter: stringPointer("def"), + NodeName: stringPointer("ghi"), + SegmentName: stringPointer("jkl"), + RetryJoinLAN: []string{"10.0.0.1"}, + EncryptKey: stringPointer("blarg"), + EncryptVerifyIncoming: boolPointer(true), + EncryptVerifyOutgoing: boolPointer(true), + TLS: config.TLS{ + Defaults: config.TLSProtocolConfig{ + VerifyOutgoing: boolPointer(true), + TLSCipherSuites: stringPointer("stuff"), + TLSMinVersion: stringPointer("tls13"), + }, + InternalRPC: config.TLSProtocolConfig{ + VerifyServerHostname: boolPointer(true), + }, + }, ACL: config.ACL{ Enabled: boolPointer(true), PolicyTTL: stringPointer("1s"), diff --git a/agent/config/builder.go b/agent/config/builder.go index b32fae30d..478540db8 100644 --- a/agent/config/builder.go +++ b/agent/config/builder.go @@ -747,18 +747,6 @@ func (b *builder) build() (rt RuntimeConfig, err error) { enableRemoteScriptChecks := boolVal(c.EnableScriptChecks) enableLocalScriptChecks := boolValWithDefault(c.EnableLocalScriptChecks, enableRemoteScriptChecks) - // VerifyServerHostname implies VerifyOutgoing - verifyServerName := boolVal(c.VerifyServerHostname) - verifyOutgoing := boolVal(c.VerifyOutgoing) - if verifyServerName { - // Setting only verify_server_hostname is documented to imply - // verify_outgoing. If it doesn't then we risk sending communication over TCP - // when we documented it as forcing TLS for RPCs. Enforce this here rather - // than in several different places through the code that need to reason - // about it. (See CVE-2018-19653) - verifyOutgoing = true - } - var configEntries []structs.ConfigEntry if len(c.ConfigEntries.Bootstrap) > 0 { @@ -963,9 +951,6 @@ func (b *builder) build() (rt RuntimeConfig, err error) { c.Cache.EntryFetchMaxBurst, cache.DefaultEntryFetchMaxBurst, ), }, - CAFile: stringVal(c.CAFile), - CAPath: stringVal(c.CAPath), - CertFile: stringVal(c.CertFile), CheckUpdateInterval: b.durationVal("check_update_interval", c.CheckUpdateInterval), CheckOutputMaxSize: intValWithDefault(c.CheckOutputMaxSize, 4096), Checks: checks, @@ -1012,7 +997,6 @@ func (b *builder) build() (rt RuntimeConfig, err error) { GRPCAddrs: grpcAddrs, HTTPMaxConnsPerClient: intVal(c.Limits.HTTPMaxConnsPerClient), HTTPSHandshakeTimeout: b.durationVal("limits.https_handshake_timeout", c.Limits.HTTPSHandshakeTimeout), - KeyFile: stringVal(c.KeyFile), KVMaxValueSize: uint64Val(c.Limits.KVMaxValueSize), LeaveDrainTime: b.durationVal("performance.leave_drain_time", c.Performance.LeaveDrainTime), LeaveOnTerm: leaveOnTerm, @@ -1026,72 +1010,69 @@ func (b *builder) build() (rt RuntimeConfig, err error) { LogRotateBytes: intVal(c.LogRotateBytes), LogRotateMaxFiles: intVal(c.LogRotateMaxFiles), }, - MaxQueryTime: b.durationVal("max_query_time", c.MaxQueryTime), - NodeID: types.NodeID(stringVal(c.NodeID)), - NodeMeta: c.NodeMeta, - NodeName: b.nodeName(c.NodeName), - ReadReplica: boolVal(c.ReadReplica), - PidFile: stringVal(c.PidFile), - PrimaryDatacenter: primaryDatacenter, - PrimaryGateways: b.expandAllOptionalAddrs("primary_gateways", c.PrimaryGateways), - PrimaryGatewaysInterval: b.durationVal("primary_gateways_interval", c.PrimaryGatewaysInterval), - RPCAdvertiseAddr: rpcAdvertiseAddr, - RPCBindAddr: rpcBindAddr, - RPCHandshakeTimeout: b.durationVal("limits.rpc_handshake_timeout", c.Limits.RPCHandshakeTimeout), - RPCHoldTimeout: b.durationVal("performance.rpc_hold_timeout", c.Performance.RPCHoldTimeout), - RPCMaxBurst: intVal(c.Limits.RPCMaxBurst), - RPCMaxConnsPerClient: intVal(c.Limits.RPCMaxConnsPerClient), - RPCProtocol: intVal(c.RPCProtocol), - RPCRateLimit: rate.Limit(float64Val(c.Limits.RPCRate)), - RPCConfig: consul.RPCConfig{EnableStreaming: boolValWithDefault(c.RPC.EnableStreaming, serverMode)}, - RaftProtocol: intVal(c.RaftProtocol), - RaftSnapshotThreshold: intVal(c.RaftSnapshotThreshold), - RaftSnapshotInterval: b.durationVal("raft_snapshot_interval", c.RaftSnapshotInterval), - RaftTrailingLogs: intVal(c.RaftTrailingLogs), - ReconnectTimeoutLAN: b.durationVal("reconnect_timeout", c.ReconnectTimeoutLAN), - ReconnectTimeoutWAN: b.durationVal("reconnect_timeout_wan", c.ReconnectTimeoutWAN), - RejoinAfterLeave: boolVal(c.RejoinAfterLeave), - RetryJoinIntervalLAN: b.durationVal("retry_interval", c.RetryJoinIntervalLAN), - RetryJoinIntervalWAN: b.durationVal("retry_interval_wan", c.RetryJoinIntervalWAN), - RetryJoinLAN: b.expandAllOptionalAddrs("retry_join", c.RetryJoinLAN), - RetryJoinMaxAttemptsLAN: intVal(c.RetryJoinMaxAttemptsLAN), - RetryJoinMaxAttemptsWAN: intVal(c.RetryJoinMaxAttemptsWAN), - RetryJoinWAN: b.expandAllOptionalAddrs("retry_join_wan", c.RetryJoinWAN), - SegmentName: stringVal(c.SegmentName), - Segments: segments, - SegmentLimit: intVal(c.SegmentLimit), - SerfAdvertiseAddrLAN: serfAdvertiseAddrLAN, - SerfAdvertiseAddrWAN: serfAdvertiseAddrWAN, - SerfAllowedCIDRsLAN: serfAllowedCIDRSLAN, - SerfAllowedCIDRsWAN: serfAllowedCIDRSWAN, - SerfBindAddrLAN: serfBindAddrLAN, - SerfBindAddrWAN: serfBindAddrWAN, - SerfPortLAN: serfPortLAN, - SerfPortWAN: serfPortWAN, - ServerMode: serverMode, - ServerName: stringVal(c.ServerName), - ServerPort: serverPort, - Services: services, - SessionTTLMin: b.durationVal("session_ttl_min", c.SessionTTLMin), - SkipLeaveOnInt: skipLeaveOnInt, - StartJoinAddrsLAN: b.expandAllOptionalAddrs("start_join", c.StartJoinAddrsLAN), - StartJoinAddrsWAN: b.expandAllOptionalAddrs("start_join_wan", c.StartJoinAddrsWAN), - TLSCipherSuites: b.tlsCipherSuites("tls_cipher_suites", c.TLSCipherSuites), - TLSMinVersion: stringVal(c.TLSMinVersion), - TLSPreferServerCipherSuites: boolVal(c.TLSPreferServerCipherSuites), - TaggedAddresses: c.TaggedAddresses, - TranslateWANAddrs: boolVal(c.TranslateWANAddrs), - TxnMaxReqLen: uint64Val(c.Limits.TxnMaxReqLen), - UIConfig: b.uiConfigVal(c.UIConfig), - UnixSocketGroup: stringVal(c.UnixSocket.Group), - UnixSocketMode: stringVal(c.UnixSocket.Mode), - UnixSocketUser: stringVal(c.UnixSocket.User), - VerifyIncoming: boolVal(c.VerifyIncoming), - VerifyIncomingHTTPS: boolVal(c.VerifyIncomingHTTPS), - VerifyIncomingRPC: boolVal(c.VerifyIncomingRPC), - VerifyOutgoing: verifyOutgoing, - VerifyServerHostname: verifyServerName, - Watches: c.Watches, + MaxQueryTime: b.durationVal("max_query_time", c.MaxQueryTime), + NodeID: types.NodeID(stringVal(c.NodeID)), + NodeMeta: c.NodeMeta, + NodeName: b.nodeName(c.NodeName), + ReadReplica: boolVal(c.ReadReplica), + PidFile: stringVal(c.PidFile), + PrimaryDatacenter: primaryDatacenter, + PrimaryGateways: b.expandAllOptionalAddrs("primary_gateways", c.PrimaryGateways), + PrimaryGatewaysInterval: b.durationVal("primary_gateways_interval", c.PrimaryGatewaysInterval), + RPCAdvertiseAddr: rpcAdvertiseAddr, + RPCBindAddr: rpcBindAddr, + RPCHandshakeTimeout: b.durationVal("limits.rpc_handshake_timeout", c.Limits.RPCHandshakeTimeout), + RPCHoldTimeout: b.durationVal("performance.rpc_hold_timeout", c.Performance.RPCHoldTimeout), + RPCMaxBurst: intVal(c.Limits.RPCMaxBurst), + RPCMaxConnsPerClient: intVal(c.Limits.RPCMaxConnsPerClient), + RPCProtocol: intVal(c.RPCProtocol), + RPCRateLimit: rate.Limit(float64Val(c.Limits.RPCRate)), + RPCConfig: consul.RPCConfig{EnableStreaming: boolValWithDefault(c.RPC.EnableStreaming, serverMode)}, + RaftProtocol: intVal(c.RaftProtocol), + RaftSnapshotThreshold: intVal(c.RaftSnapshotThreshold), + RaftSnapshotInterval: b.durationVal("raft_snapshot_interval", c.RaftSnapshotInterval), + RaftTrailingLogs: intVal(c.RaftTrailingLogs), + ReconnectTimeoutLAN: b.durationVal("reconnect_timeout", c.ReconnectTimeoutLAN), + ReconnectTimeoutWAN: b.durationVal("reconnect_timeout_wan", c.ReconnectTimeoutWAN), + RejoinAfterLeave: boolVal(c.RejoinAfterLeave), + RetryJoinIntervalLAN: b.durationVal("retry_interval", c.RetryJoinIntervalLAN), + RetryJoinIntervalWAN: b.durationVal("retry_interval_wan", c.RetryJoinIntervalWAN), + RetryJoinLAN: b.expandAllOptionalAddrs("retry_join", c.RetryJoinLAN), + RetryJoinMaxAttemptsLAN: intVal(c.RetryJoinMaxAttemptsLAN), + RetryJoinMaxAttemptsWAN: intVal(c.RetryJoinMaxAttemptsWAN), + RetryJoinWAN: b.expandAllOptionalAddrs("retry_join_wan", c.RetryJoinWAN), + SegmentName: stringVal(c.SegmentName), + Segments: segments, + SegmentLimit: intVal(c.SegmentLimit), + SerfAdvertiseAddrLAN: serfAdvertiseAddrLAN, + SerfAdvertiseAddrWAN: serfAdvertiseAddrWAN, + SerfAllowedCIDRsLAN: serfAllowedCIDRSLAN, + SerfAllowedCIDRsWAN: serfAllowedCIDRSWAN, + SerfBindAddrLAN: serfBindAddrLAN, + SerfBindAddrWAN: serfBindAddrWAN, + SerfPortLAN: serfPortLAN, + SerfPortWAN: serfPortWAN, + ServerMode: serverMode, + ServerName: stringVal(c.ServerName), + ServerPort: serverPort, + Services: services, + SessionTTLMin: b.durationVal("session_ttl_min", c.SessionTTLMin), + SkipLeaveOnInt: skipLeaveOnInt, + StartJoinAddrsLAN: b.expandAllOptionalAddrs("start_join", c.StartJoinAddrsLAN), + StartJoinAddrsWAN: b.expandAllOptionalAddrs("start_join_wan", c.StartJoinAddrsWAN), + TaggedAddresses: c.TaggedAddresses, + TranslateWANAddrs: boolVal(c.TranslateWANAddrs), + TxnMaxReqLen: uint64Val(c.Limits.TxnMaxReqLen), + UIConfig: b.uiConfigVal(c.UIConfig), + UnixSocketGroup: stringVal(c.UnixSocket.Group), + UnixSocketMode: stringVal(c.UnixSocket.Mode), + UnixSocketUser: stringVal(c.UnixSocket.User), + Watches: c.Watches, + } + + rt.TLS, err = b.buildTLSConfig(rt, c.TLS) + if err != nil { + return RuntimeConfig{}, err } rt.UseStreamingBackend = boolValWithDefault(c.UseStreamingBackend, true) @@ -1477,10 +1458,8 @@ func (b *builder) validate(rt RuntimeConfig) error { b.warn("rpc.enable_streaming = true has no effect when not running in server mode") } - if rt.AutoEncryptAllowTLS { - if !rt.VerifyIncoming && !rt.VerifyIncomingRPC { - b.warn("if auto_encrypt.allow_tls is turned on, either verify_incoming or verify_incoming_rpc should be enabled. It is necessary to turn it off during a migration to TLS, but it should definitely be turned on afterwards.") - } + if rt.AutoEncryptAllowTLS && !rt.TLS.InternalRPC.VerifyIncoming { + b.warn("if auto_encrypt.allow_tls is turned on, tls.internal_rpc.verify_incoming should be enabled (either explicitly or via tls.defaults.verify_incoming). It is necessary to turn it off during a migration to TLS, but it should definitely be turned on afterwards.") } if err := checkLimitsFromMaxConnsPerClient(rt.HTTPMaxConnsPerClient); err != nil { @@ -2319,7 +2298,7 @@ func (b *builder) validateAutoConfig(rt RuntimeConfig) error { // Right now we require TLS as everything we are going to transmit via auto-config is sensitive. Signed Certificates, Tokens // and other encryption keys. This must be transmitted over a secure connection so we don't allow doing otherwise. - if !rt.VerifyOutgoing { + if !rt.TLS.InternalRPC.VerifyOutgoing { return fmt.Errorf("auto_config.enabled cannot be set without configuring TLS for server communications") } @@ -2366,7 +2345,7 @@ func validateAutoConfigAuthorizer(rt RuntimeConfig) error { // Right now we require TLS as everything we are going to transmit via auto-config is sensitive. Signed Certificates, Tokens // and other encryption keys. This must be transmitted over a secure connection so we don't allow doing otherwise. - if rt.CertFile == "" { + if rt.TLS.InternalRPC.CertFile == "" { return fmt.Errorf("auto_config.authorization.enabled cannot be set without providing a TLS certificate for the server") } @@ -2475,3 +2454,75 @@ func validateAbsoluteURLPath(p string) error { return nil } + +func (b *builder) buildTLSConfig(rt RuntimeConfig, t TLS) (tlsutil.Config, error) { + var c tlsutil.Config + + // Consul makes no outgoing connections to the public gRPC port (internal gRPC + // traffic goes through the multiplexed internal RPC port) so return an error + // rather than let the user think this setting is going to do anything useful. + if t.GRPC.VerifyOutgoing != nil { + return c, errors.New("verify_outgoing is not valid in the tls.grpc stanza") + } + + // Similarly, only the internal RPC configuration honors VerifyServerHostname + // so we call it out here too. + if t.Defaults.VerifyServerHostname != nil || t.GRPC.VerifyServerHostname != nil || t.HTTPS.VerifyServerHostname != nil { + return c, errors.New("verify_server_hostname is only valid in the tls.internal_rpc stanza") + } + + // TLS is only enabled on the gRPC listener if there's an HTTPS port configured + // for historic and backwards-compatibility reasons. + if rt.HTTPSPort <= 0 && (t.GRPC != TLSProtocolConfig{}) { + b.warn("tls.grpc was provided but TLS will NOT be enabled on the gRPC listener without an HTTPS listener configured (e.g. via ports.https)") + } + + defaultCipherSuites := b.tlsCipherSuites("tls.defaults.tls_cipher_suites", t.Defaults.TLSCipherSuites) + + mapCommon := func(name string, src TLSProtocolConfig, dst *tlsutil.ProtocolConfig) { + dst.CAPath = stringValWithDefault(src.CAPath, stringVal(t.Defaults.CAPath)) + dst.CAFile = stringValWithDefault(src.CAFile, stringVal(t.Defaults.CAFile)) + dst.CertFile = stringValWithDefault(src.CertFile, stringVal(t.Defaults.CertFile)) + dst.KeyFile = stringValWithDefault(src.KeyFile, stringVal(t.Defaults.KeyFile)) + dst.TLSMinVersion = stringValWithDefault(src.TLSMinVersion, stringVal(t.Defaults.TLSMinVersion)) + dst.VerifyIncoming = boolValWithDefault(src.VerifyIncoming, boolVal(t.Defaults.VerifyIncoming)) + + // We prevent this from being set explicity in the tls.grpc stanza above, but + // let's also prevent it from getting the tls.defaults value to avoid confusion + // if we decide to support it in the future. + if name != "grpc" { + dst.VerifyOutgoing = boolValWithDefault(src.VerifyOutgoing, boolVal(t.Defaults.VerifyOutgoing)) + } + + if src.TLSCipherSuites == nil { + dst.CipherSuites = defaultCipherSuites + } else { + dst.CipherSuites = b.tlsCipherSuites( + fmt.Sprintf("tls.%s.tls_cipher_suites", name), + src.TLSCipherSuites, + ) + } + } + + mapCommon("internal_rpc", t.InternalRPC, &c.InternalRPC) + c.InternalRPC.VerifyServerHostname = boolVal(t.InternalRPC.VerifyServerHostname) + + // Setting only verify_server_hostname is documented to imply verify_outgoing. + // If it doesn't then we risk sending communication over plain TCP when we + // documented it as forcing TLS for RPCs. Enforce this here rather than in + // several different places through the code that need to reason about it. + // + // See: CVE-2018-19653 + c.InternalRPC.VerifyOutgoing = c.InternalRPC.VerifyOutgoing || c.InternalRPC.VerifyServerHostname + + mapCommon("https", t.HTTPS, &c.HTTPS) + mapCommon("grpc", t.GRPC, &c.GRPC) + + c.ServerName = rt.ServerName + c.NodeName = rt.NodeName + c.Domain = rt.DNSDomain + c.EnableAgentTLSForChecks = rt.EnableAgentTLSForChecks + c.AutoTLS = rt.AutoEncryptTLS || rt.AutoConfig.Enabled + + return c, nil +} diff --git a/agent/config/config.go b/agent/config/config.go index 41141cff2..e8caa74b7 100644 --- a/agent/config/config.go +++ b/agent/config/config.go @@ -147,9 +147,6 @@ type Config struct { Bootstrap *bool `mapstructure:"bootstrap"` BootstrapExpect *int `mapstructure:"bootstrap_expect"` Cache Cache `mapstructure:"cache"` - CAFile *string `mapstructure:"ca_file"` - CAPath *string `mapstructure:"ca_path"` - CertFile *string `mapstructure:"cert_file"` Check *CheckDefinition `mapstructure:"check"` // needs to be a pointer to avoid partial merges CheckOutputMaxSize *int `mapstructure:"check_output_max_size"` CheckUpdateInterval *string `mapstructure:"check_update_interval"` @@ -186,7 +183,6 @@ type Config struct { GossipLAN GossipLANConfig `mapstructure:"gossip_lan"` GossipWAN GossipWANConfig `mapstructure:"gossip_wan"` HTTPConfig HTTPConfig `mapstructure:"http_config"` - KeyFile *string `mapstructure:"key_file"` LeaveOnTerm *bool `mapstructure:"leave_on_terminate"` LicensePath *string `mapstructure:"license_path"` Limits Limits `mapstructure:"limits"` @@ -233,9 +229,7 @@ type Config struct { StartJoinAddrsLAN []string `mapstructure:"start_join"` StartJoinAddrsWAN []string `mapstructure:"start_join_wan"` SyslogFacility *string `mapstructure:"syslog_facility"` - TLSCipherSuites *string `mapstructure:"tls_cipher_suites"` - TLSMinVersion *string `mapstructure:"tls_min_version"` - TLSPreferServerCipherSuites *bool `mapstructure:"tls_prefer_server_cipher_suites"` + TLS TLS `mapstructure:"tls"` TaggedAddresses map[string]string `mapstructure:"tagged_addresses"` Telemetry Telemetry `mapstructure:"telemetry"` TranslateWANAddrs *bool `mapstructure:"translate_wan_addrs"` @@ -248,13 +242,8 @@ type Config struct { UIDir *string `mapstructure:"ui_dir"` UIConfig RawUIConfig `mapstructure:"ui_config"` - UnixSocket UnixSocket `mapstructure:"unix_sockets"` - VerifyIncoming *bool `mapstructure:"verify_incoming"` - VerifyIncomingHTTPS *bool `mapstructure:"verify_incoming_https"` - VerifyIncomingRPC *bool `mapstructure:"verify_incoming_rpc"` - VerifyOutgoing *bool `mapstructure:"verify_outgoing"` - VerifyServerHostname *bool `mapstructure:"verify_server_hostname"` - Watches []map[string]interface{} `mapstructure:"watches"` + UnixSocket UnixSocket `mapstructure:"unix_sockets"` + Watches []map[string]interface{} `mapstructure:"watches"` RPC RPC `mapstructure:"rpc"` @@ -859,3 +848,22 @@ type RawUIMetricsProxyAddHeader struct { type RPC struct { EnableStreaming *bool `mapstructure:"enable_streaming"` } + +type TLSProtocolConfig struct { + CAFile *string `mapstructure:"ca_file"` + CAPath *string `mapstructure:"ca_path"` + CertFile *string `mapstructure:"cert_file"` + KeyFile *string `mapstructure:"key_file"` + TLSMinVersion *string `mapstructure:"tls_min_version"` + TLSCipherSuites *string `mapstructure:"tls_cipher_suites"` + VerifyIncoming *bool `mapstructure:"verify_incoming"` + VerifyOutgoing *bool `mapstructure:"verify_outgoing"` + VerifyServerHostname *bool `mapstructure:"verify_server_hostname"` +} + +type TLS struct { + Defaults TLSProtocolConfig `mapstructure:"defaults"` + InternalRPC TLSProtocolConfig `mapstructure:"internal_rpc"` + HTTPS TLSProtocolConfig `mapstructure:"https"` + GRPC TLSProtocolConfig `mapstructure:"grpc"` +} diff --git a/agent/config/default.go b/agent/config/default.go index 691e7f081..3f40766ff 100644 --- a/agent/config/default.go +++ b/agent/config/default.go @@ -55,7 +55,12 @@ func DefaultSource() Source { server = false syslog_facility = "LOCAL0" - tls_min_version = "tls12" + + tls = { + defaults = { + tls_min_version = "tls12" + } + } // TODO (slackpad) - Until #3744 is done, we need to keep these // in sync with agent/consul/config.go. diff --git a/agent/config/deprecated.go b/agent/config/deprecated.go index c026b21e0..d2649d61d 100644 --- a/agent/config/deprecated.go +++ b/agent/config/deprecated.go @@ -1,6 +1,8 @@ package config -import "fmt" +import ( + "fmt" +) type DeprecatedConfig struct { // DEPRECATED (ACL-Legacy-Compat) - moved into the "acl.tokens" stanza @@ -28,6 +30,42 @@ type DeprecatedConfig struct { ACLDownPolicy *string `mapstructure:"acl_down_policy"` // DEPRECATED (ACL-Legacy-Compat) - moved to "acl.token_ttl" ACLTTL *string `mapstructure:"acl_ttl"` + + // DEPRECATED(TLS) - moved to "tls.defaults.ca_file" + CAFile *string `mapstructure:"ca_file"` + + // DEPRECATED(TLS) - moved to "tls.defaults.ca_path" + CAPath *string `mapstructure:"ca_path"` + + // DEPRECATED(TLS) - moved to "tls.defaults.cert_file" + CertFile *string `mapstructure:"cert_file"` + + // DEPRECATED(TLS) - moved to "tls.defaults.key_file" + KeyFile *string `mapstructure:"key_file"` + + // DEPRECATED(TLS) - moved to "tls.defaults.tls_cipher_suites" + TLSCipherSuites *string `mapstructure:"tls_cipher_suites"` + + // DEPRECATED(TLS) - moved to "tls.defaults.tls_min_version" + TLSMinVersion *string `mapstructure:"tls_min_version"` + + // DEPRECATED(TLS) - moved to "tls.defaults.verify_incoming" + VerifyIncoming *bool `mapstructure:"verify_incoming"` + + // DEPRECATED(TLS) - moved to "tls.https.verify_incoming" + VerifyIncomingHTTPS *bool `mapstructure:"verify_incoming_https"` + + // DEPRECATED(TLS) - moved to "tls.internal_rpc.verify_incoming" + VerifyIncomingRPC *bool `mapstructure:"verify_incoming_rpc"` + + // DEPRECATED(TLS) - moved to "tls.defaults.verify_outgoing" + VerifyOutgoing *bool `mapstructure:"verify_outgoing"` + + // DEPRECATED(TLS) - moved to "tls.internal_rpc.verify_server_hostname" + VerifyServerHostname *bool `mapstructure:"verify_server_hostname"` + + // DEPRECATED(TLS) - this isn't honored by crypto/tls anymore. + TLSPreferServerCipherSuites *bool `mapstructure:"tls_prefer_server_cipher_suites"` } func applyDeprecatedConfig(d *decodeTarget) (Config, []string) { @@ -132,9 +170,102 @@ func applyDeprecatedConfig(d *decodeTarget) (Config, []string) { warns = append(warns, deprecationWarning("acl_enable_key_list_policy", "acl.enable_key_list_policy")) } + warns = append(warns, applyDeprecatedTLSConfig(dep, &d.Config)...) + return d.Config, warns } +func applyDeprecatedTLSConfig(dep DeprecatedConfig, cfg *Config) []string { + var warns []string + + defaults := &cfg.TLS.Defaults + internalRPC := &cfg.TLS.InternalRPC + https := &cfg.TLS.HTTPS + + if v := dep.CAFile; v != nil { + if defaults.CAFile == nil { + defaults.CAFile = v + } + warns = append(warns, deprecationWarning("ca_file", "tls.defaults.ca_file")) + } + + if v := dep.CAPath; v != nil { + if defaults.CAPath == nil { + defaults.CAPath = v + } + warns = append(warns, deprecationWarning("ca_path", "tls.defaults.ca_path")) + } + + if v := dep.CertFile; v != nil { + if defaults.CertFile == nil { + defaults.CertFile = v + } + warns = append(warns, deprecationWarning("cert_file", "tls.defaults.cert_file")) + } + + if v := dep.KeyFile; v != nil { + if defaults.KeyFile == nil { + defaults.KeyFile = v + } + warns = append(warns, deprecationWarning("key_file", "tls.defaults.key_file")) + } + + if v := dep.TLSCipherSuites; v != nil { + if defaults.TLSCipherSuites == nil { + defaults.TLSCipherSuites = v + } + warns = append(warns, deprecationWarning("tls_cipher_suites", "tls.defaults.tls_cipher_suites")) + } + + if v := dep.TLSMinVersion; v != nil { + if defaults.TLSMinVersion == nil { + defaults.TLSMinVersion = v + } + warns = append(warns, deprecationWarning("tls_min_version", "tls.defaults.tls_min_version")) + } + + if v := dep.VerifyIncoming; v != nil { + if defaults.VerifyIncoming == nil { + defaults.VerifyIncoming = v + } + warns = append(warns, deprecationWarning("verify_incoming", "tls.defaults.verify_incoming")) + } + + if v := dep.VerifyIncomingHTTPS; v != nil { + if https.VerifyIncoming == nil { + https.VerifyIncoming = v + } + warns = append(warns, deprecationWarning("verify_incoming_https", "tls.https.verify_incoming")) + } + + if v := dep.VerifyIncomingRPC; v != nil { + if internalRPC.VerifyIncoming == nil { + internalRPC.VerifyIncoming = v + } + warns = append(warns, deprecationWarning("verify_incoming_rpc", "tls.internal_rpc.verify_incoming")) + } + + if v := dep.VerifyOutgoing; v != nil { + if defaults.VerifyOutgoing == nil { + defaults.VerifyOutgoing = v + } + warns = append(warns, deprecationWarning("verify_outgoing", "tls.defaults.verify_outgoing")) + } + + if v := dep.VerifyServerHostname; v != nil { + if internalRPC.VerifyServerHostname == nil { + internalRPC.VerifyServerHostname = v + } + warns = append(warns, deprecationWarning("verify_server_hostname", "tls.internal_rpc.verify_server_hostname")) + } + + if dep.TLSPreferServerCipherSuites != nil { + warns = append(warns, "The 'tls_prefer_server_cipher_suites' field is deprecated and will be ignored.") + } + + return warns +} + func deprecationWarning(old, new string) string { return fmt.Sprintf("The '%v' field is deprecated. Use the '%v' field instead.", old, new) } diff --git a/agent/config/deprecated_test.go b/agent/config/deprecated_test.go index 6930c47b5..ce32a3ccd 100644 --- a/agent/config/deprecated_test.go +++ b/agent/config/deprecated_test.go @@ -1,11 +1,14 @@ package config import ( + "crypto/tls" "sort" "testing" "time" "github.com/stretchr/testify/require" + + "github.com/hashicorp/consul/tlsutil" ) func TestLoad_DeprecatedConfig(t *testing.T) { @@ -26,6 +29,18 @@ acl_down_policy = "async-cache" acl_ttl = "3h" acl_enable_key_list_policy = true +ca_file = "some-ca-file" +ca_path = "some-ca-path" +cert_file = "some-cert-file" +key_file = "some-key-file" +tls_cipher_suites = "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256" +tls_min_version = "some-tls-version" +verify_incoming = true +verify_incoming_https = false +verify_incoming_rpc = false +verify_outgoing = true +verify_server_hostname = true +tls_prefer_server_cipher_suites = true `}, } patchLoadOptsShims(&opts) @@ -41,9 +56,20 @@ acl_enable_key_list_policy = true deprecationWarning("acl_replication_token", "acl.tokens.replication"), deprecationWarning("acl_token", "acl.tokens.default"), deprecationWarning("acl_ttl", "acl.token_ttl"), + deprecationWarning("ca_file", "tls.defaults.ca_file"), + deprecationWarning("ca_path", "tls.defaults.ca_path"), + deprecationWarning("cert_file", "tls.defaults.cert_file"), + deprecationWarning("key_file", "tls.defaults.key_file"), + deprecationWarning("tls_cipher_suites", "tls.defaults.tls_cipher_suites"), + deprecationWarning("tls_min_version", "tls.defaults.tls_min_version"), + deprecationWarning("verify_incoming", "tls.defaults.verify_incoming"), + deprecationWarning("verify_incoming_https", "tls.https.verify_incoming"), + deprecationWarning("verify_incoming_rpc", "tls.internal_rpc.verify_incoming"), + deprecationWarning("verify_outgoing", "tls.defaults.verify_outgoing"), + deprecationWarning("verify_server_hostname", "tls.internal_rpc.verify_server_hostname"), + "The 'tls_prefer_server_cipher_suites' field is deprecated and will be ignored.", } - sort.Strings(result.Warnings) - require.Equal(t, expectWarns, result.Warnings) + require.ElementsMatch(t, expectWarns, result.Warnings) // Ideally this would compare against the entire result.RuntimeConfig, but // we have so many non-zero defaults in that response that the noise of those // defaults makes this test difficult to read. So as a workaround, compare @@ -58,6 +84,22 @@ acl_enable_key_list_policy = true require.Equal(t, "async-cache", rt.ACLResolverSettings.ACLDownPolicy) require.Equal(t, 3*time.Hour, rt.ACLResolverSettings.ACLTokenTTL) require.Equal(t, true, rt.ACLEnableKeyListPolicy) + + for _, l := range []tlsutil.ProtocolConfig{rt.TLS.InternalRPC, rt.TLS.GRPC, rt.TLS.HTTPS} { + require.Equal(t, "some-ca-file", l.CAFile) + require.Equal(t, "some-ca-path", l.CAPath) + require.Equal(t, "some-cert-file", l.CertFile) + require.Equal(t, "some-key-file", l.KeyFile) + require.Equal(t, "some-tls-version", l.TLSMinVersion) + require.Equal(t, []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256}, l.CipherSuites) + } + + require.False(t, rt.TLS.InternalRPC.VerifyIncoming) + require.False(t, rt.TLS.HTTPS.VerifyIncoming) + require.True(t, rt.TLS.GRPC.VerifyIncoming) + require.True(t, rt.TLS.InternalRPC.VerifyOutgoing) + require.True(t, rt.TLS.HTTPS.VerifyOutgoing) + require.True(t, rt.TLS.InternalRPC.VerifyServerHostname) } func TestLoad_DeprecatedConfig_ACLReplication(t *testing.T) { diff --git a/agent/config/runtime.go b/agent/config/runtime.go index 6a9695019..af3dd51e1 100644 --- a/agent/config/runtime.go +++ b/agent/config/runtime.go @@ -377,24 +377,6 @@ type RuntimeConfig struct { // Cache represent cache configuration of agent Cache cache.Options - // CAFile is a path to a certificate authority file. This is used with - // VerifyIncoming or VerifyOutgoing to verify the TLS connection. - // - // hcl: ca_file = string - CAFile string - - // CAPath is a path to a directory of certificate authority files. This is - // used with VerifyIncoming or VerifyOutgoing to verify the TLS connection. - // - // hcl: ca_path = string - CAPath string - - // CertFile is used to provide a TLS certificate that is used for serving - // TLS connections. Must be provided to serve TLS connections. - // - // hcl: cert_file = string - CertFile string - // CheckUpdateInterval controls the interval on which the output of a health check // is updated if there is no change to the state. For example, a check in a steady // state may run every 5 second generating a unique output (timestamp, etc), forcing @@ -767,12 +749,6 @@ type RuntimeConfig struct { // flags: -https-port int HTTPSPort int - // KeyFile is used to provide a TLS key that is used for serving TLS - // connections. Must be provided to serve TLS connections. - // - // hcl: key_file = string - KeyFile string - // KVMaxValueSize controls the max allowed value size. If not set defaults // to raft's suggested max value size. // @@ -1338,40 +1314,11 @@ type RuntimeConfig struct { // flag: -join-wan string -join-wan string StartJoinAddrsWAN []string - // TLSCipherSuites is used to specify the list of supported ciphersuites. + // TLS configures certificates, CA, cipher suites, and other TLS settings + // on Consul's listeners (i.e. Internal multiplexed RPC, HTTPS and gRPC). // - // The values should be a list of the following values: - // - // TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA - // TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 - // TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 - // TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA - // TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - // TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA - // TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 - // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - // TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA - // TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - // - // todo(fs): IMHO, we should also support the raw 0xNNNN values from - // todo(fs): https://golang.org/pkg/crypto/tls/#pkg-constants - // todo(fs): since they are standardized by IANA. - // - // hcl: tls_cipher_suites = []string - TLSCipherSuites []uint16 - - // TLSMinVersion is used to set the minimum TLS version used for TLS - // connections. Should be either "tls10", "tls11", "tls12" or "tls13". - // Defaults to tls12. - // - // hcl: tls_min_version = string - TLSMinVersion string - - // TLSPreferServerCipherSuites specifies whether to prefer the server's - // cipher suite over the client cipher suites. - // - // hcl: tls_prefer_server_cipher_suites = (true|false) - TLSPreferServerCipherSuites bool + // hcl: tls { ... } + TLS tlsutil.Config // TaggedAddresses are used to publish a set of addresses for // for a node, which can be used by the remote agent. We currently @@ -1427,49 +1374,6 @@ type RuntimeConfig struct { // hcl: unix_sockets { user = string } UnixSocketUser string - // VerifyIncoming is used to verify the authenticity of incoming - // connections. This means that TCP requests are forbidden, only allowing - // for TLS. TLS connections must match a provided certificate authority. - // This can be used to force client auth. - // - // hcl: verify_incoming = (true|false) - VerifyIncoming bool - - // VerifyIncomingHTTPS is used to verify the authenticity of incoming HTTPS - // connections. This means that TCP requests are forbidden, only allowing - // for TLS. TLS connections must match a provided certificate authority. - // This can be used to force client auth. - // - // hcl: verify_incoming_https = (true|false) - VerifyIncomingHTTPS bool - - // VerifyIncomingRPC is used to verify the authenticity of incoming RPC - // connections. This means that TCP requests are forbidden, only allowing - // for TLS. TLS connections must match a provided certificate authority. - // This can be used to force client auth. - // - // hcl: verify_incoming_rpc = (true|false) - VerifyIncomingRPC bool - - // VerifyOutgoing is used to verify the authenticity of outgoing - // connections. This means that TLS requests are used. TLS connections must - // match a provided certificate authority. This is used to verify - // authenticity of server nodes. - // - // hcl: verify_outgoing = (true|false) - VerifyOutgoing bool - - // VerifyServerHostname is used to enable hostname verification of servers. - // This ensures that the certificate presented is valid for - // server... This prevents a compromised client from - // being restarted as a server, and then intercepting request traffic as - // well as being added as a raft peer. This should be enabled by default - // with VerifyOutgoing, but for legacy reasons we cannot break existing - // clients. - // - // hcl: verify_server_hostname = (true|false) - VerifyServerHostname bool - // Watches are used to monitor various endpoints and to invoke a // handler to act appropriately. These are managed entirely in the // agent layer using the standard APIs. @@ -1676,9 +1580,11 @@ func (c *RuntimeConfig) ConnectCAConfiguration() (*structs.CAConfiguration, erro } func (c *RuntimeConfig) APIConfig(includeClientCerts bool) (*api.Config, error) { + tls := c.TLS.HTTPS + cfg := &api.Config{ Datacenter: c.Datacenter, - TLSConfig: api.TLSConfig{InsecureSkipVerify: !c.VerifyOutgoing}, + TLSConfig: api.TLSConfig{InsecureSkipVerify: !tls.VerifyOutgoing}, } unixAddr, httpAddr, httpsAddr := c.ClientAddress() @@ -1686,11 +1592,11 @@ func (c *RuntimeConfig) APIConfig(includeClientCerts bool) (*api.Config, error) if httpsAddr != "" { cfg.Address = httpsAddr cfg.Scheme = "https" - cfg.TLSConfig.CAFile = c.CAFile - cfg.TLSConfig.CAPath = c.CAPath + cfg.TLSConfig.CAFile = tls.CAFile + cfg.TLSConfig.CAPath = tls.CAPath if includeClientCerts { - cfg.TLSConfig.CertFile = c.CertFile - cfg.TLSConfig.KeyFile = c.KeyFile + cfg.TLSConfig.CertFile = tls.CertFile + cfg.TLSConfig.KeyFile = tls.KeyFile } } else if httpAddr != "" { cfg.Address = httpAddr @@ -1715,28 +1621,6 @@ func (c *RuntimeConfig) Sanitized() map[string]interface{} { return sanitize("rt", reflect.ValueOf(c)).Interface().(map[string]interface{}) } -func (c *RuntimeConfig) ToTLSUtilConfig() tlsutil.Config { - return tlsutil.Config{ - VerifyIncoming: c.VerifyIncoming, - VerifyIncomingRPC: c.VerifyIncomingRPC, - VerifyIncomingHTTPS: c.VerifyIncomingHTTPS, - VerifyOutgoing: c.VerifyOutgoing, - VerifyServerHostname: c.VerifyServerHostname, - CAFile: c.CAFile, - CAPath: c.CAPath, - CertFile: c.CertFile, - KeyFile: c.KeyFile, - NodeName: c.NodeName, - Domain: c.DNSDomain, - ServerName: c.ServerName, - TLSMinVersion: c.TLSMinVersion, - CipherSuites: c.TLSCipherSuites, - PreferServerCipherSuites: c.TLSPreferServerCipherSuites, - EnableAgentTLSForChecks: c.EnableAgentTLSForChecks, - AutoTLS: c.AutoEncryptTLS || c.AutoConfig.Enabled, - } -} - // isSecret determines whether a field name represents a field which // may contain a secret. func isSecret(name string) bool { diff --git a/agent/config/runtime_test.go b/agent/config/runtime_test.go index ab7d0ea67..7c24e71d3 100644 --- a/agent/config/runtime_test.go +++ b/agent/config/runtime_test.go @@ -32,6 +32,7 @@ import ( "github.com/hashicorp/consul/lib" "github.com/hashicorp/consul/logging" "github.com/hashicorp/consul/sdk/testutil" + "github.com/hashicorp/consul/tlsutil" "github.com/hashicorp/consul/types" ) @@ -386,6 +387,7 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { }, expected: func(rt *RuntimeConfig) { rt.DNSDomain = "a" + rt.TLS.Domain = "a" rt.DataDir = dataDir }, }) @@ -590,6 +592,7 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { }, expected: func(rt *RuntimeConfig) { rt.NodeName = "a" + rt.TLS.NodeName = "a" rt.DataDir = dataDir }, }) @@ -2505,6 +2508,98 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { }, expectedErr: `invalid meta for service a: Node metadata cannot contain more than 64 key`, }) + run(t, testCase{ + desc: "verify_outgoing in the grpc stanza", + args: []string{ + `-data-dir=` + dataDir, + }, + hcl: []string{` + tls { + grpc { + verify_outgoing = true + } + } + `}, + json: []string{` + { + "tls": { + "grpc": { + "verify_outgoing": true + } + } + } + `}, + expectedErr: "verify_outgoing is not valid in the tls.grpc stanza", + }) + run(t, testCase{ + desc: "verify_server_hostname in the defaults stanza", + args: []string{ + `-data-dir=` + dataDir, + }, + hcl: []string{` + tls { + defaults { + verify_server_hostname = true + } + } + `}, + json: []string{` + { + "tls": { + "defaults": { + "verify_server_hostname": true + } + } + } + `}, + expectedErr: "verify_server_hostname is only valid in the tls.internal_rpc stanza", + }) + run(t, testCase{ + desc: "verify_server_hostname in the grpc stanza", + args: []string{ + `-data-dir=` + dataDir, + }, + hcl: []string{` + tls { + grpc { + verify_server_hostname = true + } + } + `}, + json: []string{` + { + "tls": { + "grpc": { + "verify_server_hostname": true + } + } + } + `}, + expectedErr: "verify_server_hostname is only valid in the tls.internal_rpc stanza", + }) + run(t, testCase{ + desc: "verify_server_hostname in the https stanza", + args: []string{ + `-data-dir=` + dataDir, + }, + hcl: []string{` + tls { + https { + verify_server_hostname = true + } + } + `}, + json: []string{` + { + "tls": { + "https": { + "verify_server_hostname": true + } + } + } + `}, + expectedErr: "verify_server_hostname is only valid in the tls.internal_rpc stanza", + }) run(t, testCase{ desc: "translated keys", args: []string{ @@ -2920,8 +3015,11 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { `}, expected: func(rt *RuntimeConfig) { rt.DataDir = dataDir - rt.VerifyServerHostname = true - rt.VerifyOutgoing = true + rt.TLS.InternalRPC.VerifyServerHostname = true + rt.TLS.InternalRPC.VerifyOutgoing = true + }, + expectedWarnings: []string{ + deprecationWarning("verify_server_hostname", "tls.internal_rpc.verify_server_hostname"), }, }) run(t, testCase{ @@ -2930,18 +3028,18 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { `-data-dir=` + dataDir, }, json: []string{`{ - "verify_incoming": true, + "tls": { "internal_rpc": { "verify_incoming": true } }, "auto_encrypt": { "allow_tls": true }, "server": true }`}, hcl: []string{` - verify_incoming = true + tls { internal_rpc { verify_incoming = true } } auto_encrypt { allow_tls = true } server = true `}, expected: func(rt *RuntimeConfig) { rt.DataDir = dataDir - rt.VerifyIncoming = true + rt.TLS.InternalRPC.VerifyIncoming = true rt.AutoEncryptAllowTLS = true rt.ConnectEnabled = true @@ -2953,26 +3051,29 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { }, }) run(t, testCase{ - desc: "auto_encrypt.allow_tls works with verify_incoming", + desc: "auto_encrypt.allow_tls works with tls.defaults.verify_incoming", args: []string{ `-data-dir=` + dataDir, }, json: []string{`{ - "verify_incoming": true, + "tls": { "defaults": { "verify_incoming": true } }, "auto_encrypt": { "allow_tls": true }, "server": true }`}, hcl: []string{` - verify_incoming = true + tls { defaults { verify_incoming = true } } auto_encrypt { allow_tls = true } server = true `}, expected: func(rt *RuntimeConfig) { rt.DataDir = dataDir - rt.VerifyIncoming = true rt.AutoEncryptAllowTLS = true rt.ConnectEnabled = true + rt.TLS.InternalRPC.VerifyIncoming = true + rt.TLS.GRPC.VerifyIncoming = true + rt.TLS.HTTPS.VerifyIncoming = true + // server things rt.ServerMode = true rt.LeaveOnTerm = false @@ -2981,25 +3082,25 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { }, }) run(t, testCase{ - desc: "auto_encrypt.allow_tls works with verify_incoming_rpc", + desc: "auto_encrypt.allow_tls works with tls.internal_rpc.verify_incoming", args: []string{ `-data-dir=` + dataDir, }, json: []string{`{ - "verify_incoming_rpc": true, + "tls": { "internal_rpc": { "verify_incoming": true } }, "auto_encrypt": { "allow_tls": true }, "server": true }`}, hcl: []string{` - verify_incoming_rpc = true + tls { internal_rpc { verify_incoming = true } } auto_encrypt { allow_tls = true } server = true `}, expected: func(rt *RuntimeConfig) { rt.DataDir = dataDir - rt.VerifyIncomingRPC = true rt.AutoEncryptAllowTLS = true rt.ConnectEnabled = true + rt.TLS.InternalRPC.VerifyIncoming = true // server things rt.ServerMode = true @@ -3009,7 +3110,7 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { }, }) run(t, testCase{ - desc: "auto_encrypt.allow_tls warns without verify_incoming or verify_incoming_rpc", + desc: "auto_encrypt.allow_tls warns without tls.defaults.verify_incoming or tls.internal_rpc.verify_incoming", args: []string{ `-data-dir=` + dataDir, }, @@ -3021,7 +3122,7 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { auto_encrypt { allow_tls = true } server = true `}, - expectedWarnings: []string{"if auto_encrypt.allow_tls is turned on, either verify_incoming or verify_incoming_rpc should be enabled. It is necessary to turn it off during a migration to TLS, but it should definitely be turned on afterwards."}, + expectedWarnings: []string{"if auto_encrypt.allow_tls is turned on, tls.internal_rpc.verify_incoming should be enabled (either explicitly or via tls.defaults.verify_incoming). It is necessary to turn it off during a migration to TLS, but it should definitely be turned on afterwards."}, expected: func(rt *RuntimeConfig) { rt.DataDir = dataDir rt.AutoEncryptAllowTLS = true @@ -4436,7 +4537,11 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { auto_encrypt { tls = true } - verify_outgoing = true + tls { + internal_rpc { + verify_outgoing = true + } + } `}, json: []string{`{ "auto_config": { @@ -4447,7 +4552,11 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { "auto_encrypt": { "tls": true }, - "verify_outgoing": true + "tls": { + "internal_rpc": { + "verify_outgoing": true + } + } }`}, expectedErr: "both auto_encrypt.tls and auto_config.enabled cannot be set to true.", }) @@ -4463,7 +4572,11 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { intro_token = "blah" server_addresses = ["198.18.0.1"] } - verify_outgoing = true + tls { + internal_rpc { + verify_outgoing = true + } + } `}, json: []string{` { @@ -4473,7 +4586,11 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { "intro_token": "blah", "server_addresses": ["198.18.0.1"] }, - "verify_outgoing": true + "tls": { + "internal_rpc": { + "verify_outgoing": true + } + } }`}, expectedErr: "auto_config.enabled cannot be set to true for server agents", }) @@ -4536,7 +4653,11 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { enabled = true server_addresses = ["198.18.0.1"] } - verify_outgoing = true + tls { + internal_rpc { + verify_outgoing = true + } + } `}, json: []string{` { @@ -4544,7 +4665,11 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { "enabled": true, "server_addresses": ["198.18.0.1"] }, - "verify_outgoing": true + "tls": { + "internal_rpc": { + "verify_outgoing": true + } + } }`}, expectedErr: "One of auto_config.intro_token, auto_config.intro_token_file or the CONSUL_INTRO_TOKEN environment variable must be set to enable auto_config", }) @@ -4559,7 +4684,11 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { enabled = true intro_token = "blah" } - verify_outgoing = true + tls { + internal_rpc { + verify_outgoing = true + } + } `}, json: []string{` { @@ -4567,7 +4696,11 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { "enabled": true, "intro_token": "blah" }, - "verify_outgoing": true + "tls": { + "internal_rpc": { + "verify_outgoing": true + } + } }`}, expectedErr: "auto_config.enabled is set without providing a list of addresses", }) @@ -4586,7 +4719,11 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { dns_sans = ["foo"] ip_sans = ["invalid", "127.0.0.1"] } - verify_outgoing = true + tls { + internal_rpc { + verify_outgoing = true + } + } `}, json: []string{` { @@ -4598,7 +4735,11 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { "dns_sans": ["foo"], "ip_sans": ["invalid", "127.0.0.1"] }, - "verify_outgoing": true + "tls": { + "internal_rpc": { + "verify_outgoing": true + } + } }`}, expectedWarnings: []string{ "Cannot parse ip \"invalid\" from auto_config.ip_sans", @@ -4613,7 +4754,8 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { rt.AutoConfig.DNSSANs = []string{"foo"} rt.AutoConfig.IPSANs = []net.IP{net.IPv4(127, 0, 0, 1)} rt.DataDir = dataDir - rt.VerifyOutgoing = true + rt.TLS.InternalRPC.VerifyOutgoing = true + rt.TLS.AutoTLS = true }, }) @@ -4652,7 +4794,11 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { enabled = true } } - cert_file = "foo" + tls { + internal_rpc { + cert_file = "foo" + } + } `}, json: []string{` { @@ -4661,7 +4807,11 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { "enabled": true } }, - "cert_file": "foo" + "tls": { + "internal_rpc": { + "cert_file": "foo" + } + } }`}, expectedErr: `auto_config.authorization.static has invalid configuration: exactly one of 'JWTValidationPubKeys', 'JWKSURL', or 'OIDCDiscoveryURL' must be set for type "jwt"`, }) @@ -4682,7 +4832,11 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { } } } - cert_file = "foo" + tls { + internal_rpc { + cert_file = "foo" + } + } `}, json: []string{` { @@ -4695,7 +4849,11 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { } } }, - "cert_file": "foo" + "tls": { + "internal_rpc": { + "cert_file": "foo" + } + } }`}, expectedErr: `auto_config.authorization.static has invalid configuration: exactly one of 'JWTValidationPubKeys', 'JWKSURL', or 'OIDCDiscoveryURL' must be set for type "jwt"`, }) @@ -4720,7 +4878,11 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { } } } - cert_file = "foo" + tls { + internal_rpc { + cert_file = "foo" + } + } `}, json: []string{` { @@ -4737,7 +4899,11 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { } } }, - "cert_file": "foo" + "tls": { + "internal_rpc": { + "cert_file": "foo" + } + } }`}, expectedErr: `Enabling auto-config authorization (auto_config.authorization.enabled) in non primary datacenters with ACLs enabled (acl.enabled) requires also enabling ACL token replication (acl.enable_token_replication)`, }) @@ -4760,7 +4926,11 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { } } } - cert_file = "foo" + tls { + internal_rpc { + cert_file = "foo" + } + } `}, json: []string{` { @@ -4775,7 +4945,11 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { } } }, - "cert_file": "foo" + "tls": { + "internal_rpc": { + "cert_file": "foo" + } + } }`}, expectedErr: `auto_config.authorization.static.claim_assertion "values.node == ${node}" is invalid: Selector "values" is not valid`, }) @@ -4800,7 +4974,11 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { } } } - cert_file = "foo" + tls { + internal_rpc { + cert_file = "foo" + } + } `}, json: []string{` { @@ -4818,7 +4996,11 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { } } }, - "cert_file": "foo" + "tls": { + "internal_rpc": { + "cert_file": "foo" + } + } }`}, expected: func(rt *RuntimeConfig) { rt.AutoConfig.Authorizer.Enabled = true @@ -4831,7 +5013,7 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { rt.LeaveOnTerm = false rt.ServerMode = true rt.SkipLeaveOnInt = true - rt.CertFile = "foo" + rt.TLS.InternalRPC.CertFile = "foo" rt.RPCConfig.EnableStreaming = true }, }) @@ -5197,6 +5379,165 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { }`}, expectedErr: "advertise_reconnect_timeout can only be used on a client", }) + + run(t, testCase{ + desc: "TLS defaults and overrides", + args: []string{ + `-data-dir=` + dataDir, + }, + hcl: []string{` + ports { + https = 4321 + } + + tls { + defaults { + ca_file = "default_ca_file" + ca_path = "default_ca_path" + cert_file = "default_cert_file" + tls_min_version = "tls12" + tls_cipher_suites = "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256" + verify_incoming = true + } + + internal_rpc { + ca_file = "internal_rpc_ca_file" + } + + https { + cert_file = "https_cert_file" + tls_min_version = "tls13" + } + + grpc { + verify_incoming = false + tls_cipher_suites = "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA" + } + } + `}, + json: []string{` + { + "ports": { + "https": 4321 + }, + "tls": { + "defaults": { + "ca_file": "default_ca_file", + "ca_path": "default_ca_path", + "cert_file": "default_cert_file", + "tls_min_version": "tls12", + "tls_cipher_suites": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + "verify_incoming": true + }, + "internal_rpc": { + "ca_file": "internal_rpc_ca_file" + }, + "https": { + "cert_file": "https_cert_file", + "tls_min_version": "tls13" + }, + "grpc": { + "verify_incoming": false, + "tls_cipher_suites": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA" + } + } + } + `}, + expected: func(rt *RuntimeConfig) { + rt.DataDir = dataDir + + rt.HTTPSPort = 4321 + rt.HTTPSAddrs = []net.Addr{tcpAddr("127.0.0.1:4321")} + + rt.TLS.Domain = "consul." + rt.TLS.NodeName = "thehostname" + + rt.TLS.InternalRPC.CAFile = "internal_rpc_ca_file" + rt.TLS.InternalRPC.CAPath = "default_ca_path" + rt.TLS.InternalRPC.CertFile = "default_cert_file" + rt.TLS.InternalRPC.TLSMinVersion = "tls12" + rt.TLS.InternalRPC.CipherSuites = []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256} + rt.TLS.InternalRPC.VerifyIncoming = true + + rt.TLS.HTTPS.CAFile = "default_ca_file" + rt.TLS.HTTPS.CAPath = "default_ca_path" + rt.TLS.HTTPS.CertFile = "https_cert_file" + rt.TLS.HTTPS.TLSMinVersion = "tls13" + rt.TLS.HTTPS.CipherSuites = []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256} + rt.TLS.HTTPS.VerifyIncoming = true + + rt.TLS.GRPC.CAFile = "default_ca_file" + rt.TLS.GRPC.CAPath = "default_ca_path" + rt.TLS.GRPC.CertFile = "default_cert_file" + rt.TLS.GRPC.TLSMinVersion = "tls12" + rt.TLS.GRPC.CipherSuites = []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA} + rt.TLS.GRPC.VerifyIncoming = false + }, + }) + run(t, testCase{ + desc: "tls.internal_rpc.verify_server_hostname implies tls.internal_rpc.verify_outgoing", + args: []string{ + `-data-dir=` + dataDir, + }, + json: []string{` + { + "tls": { + "internal_rpc": { + "verify_server_hostname": true + } + } + } + `}, + hcl: []string{` + tls { + internal_rpc { + verify_server_hostname = true + } + } + `}, + expected: func(rt *RuntimeConfig) { + rt.DataDir = dataDir + + rt.TLS.Domain = "consul." + rt.TLS.NodeName = "thehostname" + + rt.TLS.InternalRPC.VerifyServerHostname = true + rt.TLS.InternalRPC.VerifyOutgoing = true + }, + }) + run(t, testCase{ + desc: "tls.grpc without ports.https", + args: []string{ + `-data-dir=` + dataDir, + }, + json: []string{` + { + "tls": { + "grpc": { + "cert_file": "cert-1234" + } + } + } + `}, + hcl: []string{` + tls { + grpc { + cert_file = "cert-1234" + } + } + `}, + expected: func(rt *RuntimeConfig) { + rt.DataDir = dataDir + + rt.TLS.Domain = "consul." + rt.TLS.NodeName = "thehostname" + + rt.TLS.GRPC.CertFile = "cert-1234" + }, + expectedWarnings: []string{ + "tls.grpc was provided but TLS will NOT be enabled on the gRPC listener without an HTTPS listener configured (e.g. via ports.https)", + }, + }) } func (tc testCase) run(format string, dataDir string) func(t *testing.T) { @@ -5382,9 +5723,6 @@ func TestLoad_FullConfig(t *testing.T) { EntryFetchMaxBurst: 42, EntryFetchRate: 0.334, }, - CAFile: "erA7T0PM", - CAPath: "mQEN1Mfp", - CertFile: "7s4QAzDk", CheckOutputMaxSize: checks.DefaultBufSize, Checks: []*structs.CheckDefinition{ { @@ -5592,7 +5930,6 @@ func TestLoad_FullConfig(t *testing.T) { HTTPSHandshakeTimeout: 2391 * time.Millisecond, HTTPSPort: 15127, HTTPUseCache: false, - KeyFile: "IEkkwgIA", KVMaxValueSize: 1234567800, LeaveDrainTime: 8265 * time.Second, LeaveOnTerm: true, @@ -5966,9 +6303,43 @@ func TestLoad_FullConfig(t *testing.T) { Name: "ftO6DySn", // notice this is the same as the metrics prefix }, }, - TLSCipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256}, - TLSMinVersion: "pAOWafkR", - TLSPreferServerCipherSuites: true, + TLS: tlsutil.Config{ + InternalRPC: tlsutil.ProtocolConfig{ + VerifyIncoming: true, + CAFile: "mKl19Utl", + CAPath: "lOp1nhPa", + CertFile: "dfJ4oPln", + KeyFile: "aL1Knkpo", + TLSMinVersion: "lPo1MklP", + CipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256}, + VerifyOutgoing: true, + VerifyServerHostname: true, + }, + GRPC: tlsutil.ProtocolConfig{ + VerifyIncoming: true, + CAFile: "lOp1nhJk", + CAPath: "fLponKpl", + CertFile: "a674klPn", + KeyFile: "1y4prKjl", + TLSMinVersion: "lPo4fNkl", + CipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256}, + VerifyOutgoing: false, + }, + HTTPS: tlsutil.ProtocolConfig{ + VerifyIncoming: true, + CAFile: "7Yu1PolM", + CAPath: "nu4PlHzn", + CertFile: "1yrhPlMk", + KeyFile: "1bHapOkL", + TLSMinVersion: "mK14iOpz", + CipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256}, + VerifyOutgoing: true, + }, + NodeName: "otlLxGaI", + ServerName: "Oerr9n1G", + Domain: "7W1xXSqd", + EnableAgentTLSForChecks: true, + }, TaggedAddresses: map[string]string{ "7MYgHrYH": "dALJAhLD", "h6DdBy6K": "ebrr9zZ8", @@ -5997,14 +6368,9 @@ func TestLoad_FullConfig(t *testing.T) { }, DashboardURLTemplates: map[string]string{"u2eziu2n_lower_case": "http://lkjasd.otr"}, }, - UnixSocketUser: "E0nB1DwA", - UnixSocketGroup: "8pFodrV8", - UnixSocketMode: "E8sAwOv4", - VerifyIncoming: true, - VerifyIncomingHTTPS: true, - VerifyIncomingRPC: true, - VerifyOutgoing: true, - VerifyServerHostname: true, + UnixSocketUser: "E0nB1DwA", + UnixSocketGroup: "8pFodrV8", + UnixSocketMode: "E8sAwOv4", Watches: []map[string]interface{}{ { "type": "key", @@ -6038,6 +6404,18 @@ func TestLoad_FullConfig(t *testing.T) { deprecationWarning("acl_ttl", "acl.token_ttl"), deprecationWarning("acl_enable_key_list_policy", "acl.enable_key_list_policy"), `bootstrap_expect > 0: expecting 53 servers`, + deprecationWarning("ca_file", "tls.defaults.ca_file"), + deprecationWarning("ca_path", "tls.defaults.ca_path"), + deprecationWarning("cert_file", "tls.defaults.cert_file"), + deprecationWarning("key_file", "tls.defaults.key_file"), + deprecationWarning("tls_cipher_suites", "tls.defaults.tls_cipher_suites"), + deprecationWarning("tls_min_version", "tls.defaults.tls_min_version"), + deprecationWarning("verify_incoming", "tls.defaults.verify_incoming"), + deprecationWarning("verify_incoming_https", "tls.https.verify_incoming"), + deprecationWarning("verify_incoming_rpc", "tls.internal_rpc.verify_incoming"), + deprecationWarning("verify_outgoing", "tls.defaults.verify_outgoing"), + deprecationWarning("verify_server_hostname", "tls.internal_rpc.verify_server_hostname"), + "The 'tls_prefer_server_cipher_suites' field is deprecated and will be ignored.", } expectedWarns = append(expectedWarns, enterpriseConfigKeyWarnings...) @@ -6350,34 +6728,38 @@ func TestRuntime_APIConfigHTTPS(t *testing.T) { HTTPSAddrs: []net.Addr{ &net.TCPAddr{IP: net.ParseIP("198.18.0.2"), Port: 5678}, }, - Datacenter: "dc-test", - CAFile: "/etc/consul/ca.crt", - CAPath: "/etc/consul/ca.dir", - CertFile: "/etc/consul/server.crt", - KeyFile: "/etc/consul/ssl/server.key", - VerifyOutgoing: false, + Datacenter: "dc-test", + TLS: tlsutil.Config{ + HTTPS: tlsutil.ProtocolConfig{ + CAFile: "/etc/consul/ca.crt", + CAPath: "/etc/consul/ca.dir", + CertFile: "/etc/consul/server.crt", + KeyFile: "/etc/consul/ssl/server.key", + VerifyOutgoing: false, + }, + }, } cfg, err := rt.APIConfig(false) require.NoError(t, err) require.Equal(t, "198.18.0.2:5678", cfg.Address) require.Equal(t, "https", cfg.Scheme) - require.Equal(t, rt.CAFile, cfg.TLSConfig.CAFile) - require.Equal(t, rt.CAPath, cfg.TLSConfig.CAPath) + require.Equal(t, rt.TLS.HTTPS.CAFile, cfg.TLSConfig.CAFile) + require.Equal(t, rt.TLS.HTTPS.CAPath, cfg.TLSConfig.CAPath) require.Equal(t, "", cfg.TLSConfig.CertFile) require.Equal(t, "", cfg.TLSConfig.KeyFile) require.Equal(t, rt.Datacenter, cfg.Datacenter) require.Equal(t, true, cfg.TLSConfig.InsecureSkipVerify) - rt.VerifyOutgoing = true + rt.TLS.HTTPS.VerifyOutgoing = true cfg, err = rt.APIConfig(true) require.NoError(t, err) require.Equal(t, "198.18.0.2:5678", cfg.Address) require.Equal(t, "https", cfg.Scheme) - require.Equal(t, rt.CAFile, cfg.TLSConfig.CAFile) - require.Equal(t, rt.CAPath, cfg.TLSConfig.CAPath) - require.Equal(t, rt.CertFile, cfg.TLSConfig.CertFile) - require.Equal(t, rt.KeyFile, cfg.TLSConfig.KeyFile) + require.Equal(t, rt.TLS.HTTPS.CAFile, cfg.TLSConfig.CAFile) + require.Equal(t, rt.TLS.HTTPS.CAPath, cfg.TLSConfig.CAPath) + require.Equal(t, rt.TLS.HTTPS.CertFile, cfg.TLSConfig.CertFile) + require.Equal(t, rt.TLS.HTTPS.KeyFile, cfg.TLSConfig.KeyFile) require.Equal(t, rt.Datacenter, cfg.Datacenter) require.Equal(t, false, cfg.TLSConfig.InsecureSkipVerify) } @@ -6515,86 +6897,6 @@ func TestRuntime_ClientAddressAnyV6(t *testing.T) { require.Equal(t, "[::1]:5688", https) } -func TestRuntime_ToTLSUtilConfig(t *testing.T) { - c := &RuntimeConfig{ - VerifyIncoming: true, - VerifyIncomingRPC: true, - VerifyIncomingHTTPS: true, - VerifyOutgoing: true, - VerifyServerHostname: true, - CAFile: "a", - CAPath: "b", - CertFile: "c", - KeyFile: "d", - NodeName: "e", - ServerName: "f", - DNSDomain: "g", - TLSMinVersion: "tls12", - TLSCipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA}, - TLSPreferServerCipherSuites: true, - EnableAgentTLSForChecks: true, - AutoEncryptTLS: true, - } - r := c.ToTLSUtilConfig() - require.True(t, r.VerifyIncoming) - require.True(t, r.VerifyIncomingRPC) - require.True(t, r.VerifyIncomingHTTPS) - require.True(t, r.VerifyOutgoing) - require.True(t, r.EnableAgentTLSForChecks) - require.True(t, r.AutoTLS) - require.True(t, r.VerifyServerHostname) - require.True(t, r.PreferServerCipherSuites) - require.Equal(t, "a", r.CAFile) - require.Equal(t, "b", r.CAPath) - require.Equal(t, "c", r.CertFile) - require.Equal(t, "d", r.KeyFile) - require.Equal(t, "e", r.NodeName) - require.Equal(t, "f", r.ServerName) - require.Equal(t, "g", r.Domain) - require.Equal(t, "tls12", r.TLSMinVersion) - require.Equal(t, []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA}, r.CipherSuites) -} - -func TestRuntime_ToTLSUtilConfig_AutoConfig(t *testing.T) { - c := &RuntimeConfig{ - VerifyIncoming: true, - VerifyIncomingRPC: true, - VerifyIncomingHTTPS: true, - VerifyOutgoing: true, - VerifyServerHostname: true, - CAFile: "a", - CAPath: "b", - CertFile: "c", - KeyFile: "d", - NodeName: "e", - ServerName: "f", - DNSDomain: "g", - TLSMinVersion: "tls12", - TLSCipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA}, - TLSPreferServerCipherSuites: true, - EnableAgentTLSForChecks: true, - AutoConfig: AutoConfig{Enabled: true}, - } - r := c.ToTLSUtilConfig() - require.True(t, r.VerifyIncoming) - require.True(t, r.VerifyIncomingRPC) - require.True(t, r.VerifyIncomingHTTPS) - require.True(t, r.VerifyOutgoing) - require.True(t, r.EnableAgentTLSForChecks) - require.True(t, r.AutoTLS) - require.True(t, r.VerifyServerHostname) - require.True(t, r.PreferServerCipherSuites) - require.Equal(t, "a", r.CAFile) - require.Equal(t, "b", r.CAPath) - require.Equal(t, "c", r.CertFile) - require.Equal(t, "d", r.KeyFile) - require.Equal(t, "e", r.NodeName) - require.Equal(t, "f", r.ServerName) - require.Equal(t, "g", r.Domain) - require.Equal(t, "tls12", r.TLSMinVersion) - require.Equal(t, []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA}, r.CipherSuites) -} - func Test_UIPathBuilder(t *testing.T) { cases := []struct { name string diff --git a/agent/config/testdata/TestRuntimeConfig_Sanitize.golden b/agent/config/testdata/TestRuntimeConfig_Sanitize.golden index 52b900719..0f8d66b8b 100644 --- a/agent/config/testdata/TestRuntimeConfig_Sanitize.golden +++ b/agent/config/testdata/TestRuntimeConfig_Sanitize.golden @@ -74,14 +74,11 @@ "BindAddr": "127.0.0.1", "Bootstrap": false, "BootstrapExpect": 0, - "CAFile": "", - "CAPath": "", "Cache": { "EntryFetchMaxBurst": 42, "EntryFetchRate": 0.334, "Logger": null }, - "CertFile": "", "CheckDeregisterIntervalMin": "0s", "CheckOutputMaxSize": 4096, "CheckReapInterval": "0s", @@ -218,7 +215,6 @@ "HTTPSPort": 0, "HTTPUseCache": false, "KVMaxValueSize": 1234567800000000, - "KeyFile": "hidden", "LeaveDrainTime": "0s", "LeaveOnTerm": false, "Logging": { @@ -354,9 +350,46 @@ "StartJoinAddrsWAN": [], "SyncCoordinateIntervalMin": "0s", "SyncCoordinateRateTarget": 0, - "TLSCipherSuites": [], - "TLSMinVersion": "", - "TLSPreferServerCipherSuites": false, + "TLS": { + "AutoTLS": false, + "Domain": "", + "EnableAgentTLSForChecks": false, + "GRPC": { + "CAFile": "", + "CAPath": "", + "CertFile": "", + "CipherSuites": [], + "KeyFile": "hidden", + "TLSMinVersion": "", + "VerifyIncoming": false, + "VerifyOutgoing": false, + "VerifyServerHostname": false + }, + "HTTPS": { + "CAFile": "", + "CAPath": "", + "CertFile": "", + "CipherSuites": [], + "KeyFile": "hidden", + "TLSMinVersion": "", + "VerifyIncoming": false, + "VerifyOutgoing": false, + "VerifyServerHostname": false + }, + "InternalRPC": { + "CAFile": "", + "CAPath": "", + "CertFile": "", + "CipherSuites": [], + "KeyFile": "hidden", + "TLSMinVersion": "", + "VerifyIncoming": false, + "VerifyOutgoing": false, + "VerifyServerHostname": false + }, + "NodeName": "", + "ServerName": "" + }, "TaggedAddresses": {}, "Telemetry": { "AllowedPrefixes": [], @@ -417,11 +450,6 @@ "UnixSocketMode": "", "UnixSocketUser": "", "UseStreamingBackend": false, - "VerifyIncoming": false, - "VerifyIncomingHTTPS": false, - "VerifyIncomingRPC": false, - "VerifyOutgoing": false, - "VerifyServerHostname": false, "Version": "", "VersionPrerelease": "", "Watches": [] diff --git a/agent/config/testdata/full-config.hcl b/agent/config/testdata/full-config.hcl index 76090e439..bb68055cf 100644 --- a/agent/config/testdata/full-config.hcl +++ b/agent/config/testdata/full-config.hcl @@ -647,6 +647,48 @@ telemetry { statsite_address = "HpFwKB8R" disable_compat_1.9 = true } +tls { + defaults { + ca_file = "a5tY0opl" + ca_path = "bN63LpXu" + cert_file = "hB4PoxkL" + key_file = "Po0hB1tY" + tls_cipher_suites = "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256" + tls_min_version = "yU0uIp1A" + verify_incoming = true + verify_outgoing = true + } + internal_rpc { + ca_file = "mKl19Utl" + ca_path = "lOp1nhPa" + cert_file = "dfJ4oPln" + key_file = "aL1Knkpo" + tls_cipher_suites = "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256" + tls_min_version = "lPo1MklP" + verify_incoming = true + verify_outgoing = true + verify_server_hostname = true + } + https { + ca_file = "7Yu1PolM" + ca_path = "nu4PlHzn" + cert_file = "1yrhPlMk" + key_file = "1bHapOkL" + tls_cipher_suites = "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256" + tls_min_version = "mK14iOpz" + verify_incoming = true + verify_outgoing = true + } + grpc { + ca_file = "lOp1nhJk" + ca_path = "fLponKpl" + cert_file = "a674klPn" + key_file = "1y4prKjl" + tls_cipher_suites = "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256" + tls_min_version = "lPo4fNkl" + verify_incoming = true + } +} tls_cipher_suites = "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256" tls_min_version = "pAOWafkR" tls_prefer_server_cipher_suites = true diff --git a/agent/config/testdata/full-config.json b/agent/config/testdata/full-config.json index 6db4b63c9..574c715f4 100644 --- a/agent/config/testdata/full-config.json +++ b/agent/config/testdata/full-config.json @@ -644,6 +644,47 @@ "statsite_address": "HpFwKB8R", "disable_compat_1.9": true }, + "tls": { + "defaults": { + "ca_file": "a5tY0opl", + "ca_path": "bN63LpXu", + "cert_file": "hB4PoxkL", + "key_file": "Po0hB1tY", + "tls_cipher_suites": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + "tls_min_version": "yU0uIp1A", + "verify_incoming": true, + "verify_outgoing": true + }, + "internal_rpc": { + "ca_file": "mKl19Utl", + "ca_path": "lOp1nhPa", + "cert_file": "dfJ4oPln", + "key_file": "aL1Knkpo", + "tls_cipher_suites": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + "tls_min_version": "lPo1MklP", + "verify_incoming": true, + "verify_outgoing": true + }, + "https": { + "ca_file": "7Yu1PolM", + "ca_path": "nu4PlHzn", + "cert_file": "1yrhPlMk", + "key_file": "1bHapOkL", + "tls_cipher_suites": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + "tls_min_version": "mK14iOpz", + "verify_incoming": true, + "verify_outgoing": true + }, + "grpc": { + "ca_file": "lOp1nhJk", + "ca_path": "fLponKpl", + "cert_file": "a674klPn", + "key_file": "1y4prKjl", + "tls_cipher_suites": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + "tls_min_version": "lPo4fNkl", + "verify_incoming": true + } + }, "tls_cipher_suites": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "tls_min_version": "pAOWafkR", "tls_prefer_server_cipher_suites": true, diff --git a/agent/consul/auto_config_endpoint.go b/agent/consul/auto_config_endpoint.go index e170735c8..781192d6e 100644 --- a/agent/consul/auto_config_endpoint.go +++ b/agent/consul/auto_config_endpoint.go @@ -280,19 +280,8 @@ func (ac *AutoConfig) updateTLSSettingsInConfig(_ AutoConfigOptions, resp *pbaut return nil } - // add in TLS configuration - if resp.Config.TLS == nil { - resp.Config.TLS = &pbconfig.TLS{} - } - - resp.Config.TLS.VerifyServerHostname = ac.tlsConfigurator.VerifyServerHostname() - base := ac.tlsConfigurator.Base() - resp.Config.TLS.VerifyOutgoing = base.VerifyOutgoing - resp.Config.TLS.MinVersion = base.TLSMinVersion - resp.Config.TLS.PreferServerCipherSuites = base.PreferServerCipherSuites - var err error - resp.Config.TLS.CipherSuites, err = tlsutil.CipherString(base.CipherSuites) + resp.Config.TLS, err = ac.tlsConfigurator.AutoConfigTLSSettings() return err } diff --git a/agent/consul/auto_config_endpoint_test.go b/agent/consul/auto_config_endpoint_test.go index fb943cac6..00873b615 100644 --- a/agent/consul/auto_config_endpoint_test.go +++ b/agent/consul/auto_config_endpoint_test.go @@ -11,11 +11,12 @@ import ( "testing" "time" - msgpackrpc "github.com/hashicorp/consul-net-rpc/net-rpc-msgpackrpc" "github.com/hashicorp/memberlist" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + msgpackrpc "github.com/hashicorp/consul-net-rpc/net-rpc-msgpackrpc" + "github.com/hashicorp/consul/agent/connect" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/internal/go-sso/oidcauth/oidcauthtest" @@ -166,14 +167,13 @@ func TestAutoConfigInitialConfiguration(t *testing.T) { err = ioutil.WriteFile(keyfile, []byte(key), 0600) require.NoError(t, err) - c.TLSConfig.CAFile = cafile - c.TLSConfig.CertFile = certfile - c.TLSConfig.KeyFile = keyfile - c.TLSConfig.VerifyOutgoing = true - c.TLSConfig.VerifyIncoming = true - c.TLSConfig.VerifyServerHostname = true - c.TLSConfig.TLSMinVersion = "tls12" - c.TLSConfig.PreferServerCipherSuites = true + c.TLSConfig.InternalRPC.CAFile = cafile + c.TLSConfig.InternalRPC.CertFile = certfile + c.TLSConfig.InternalRPC.KeyFile = keyfile + c.TLSConfig.InternalRPC.VerifyOutgoing = true + c.TLSConfig.InternalRPC.VerifyIncoming = true + c.TLSConfig.InternalRPC.VerifyServerHostname = true + c.TLSConfig.InternalRPC.TLSMinVersion = "tls12" c.ConnectEnabled = true c.AutoEncryptAllowTLS = true @@ -187,10 +187,12 @@ func TestAutoConfigInitialConfiguration(t *testing.T) { // TODO: use s.config.TLSConfig directly instead of creating a new one? conf := tlsutil.Config{ - CAFile: s.config.TLSConfig.CAFile, - VerifyServerHostname: s.config.TLSConfig.VerifyServerHostname, - VerifyOutgoing: s.config.TLSConfig.VerifyOutgoing, - Domain: s.config.TLSConfig.Domain, + InternalRPC: tlsutil.ProtocolConfig{ + CAFile: s.config.TLSConfig.InternalRPC.CAFile, + VerifyServerHostname: s.config.TLSConfig.InternalRPC.VerifyServerHostname, + VerifyOutgoing: s.config.TLSConfig.InternalRPC.VerifyOutgoing, + }, + Domain: s.config.TLSConfig.Domain, } codec, err := insecureRPCClient(s, conf) require.NoError(t, err) @@ -281,10 +283,9 @@ func TestAutoConfigInitialConfiguration(t *testing.T) { RetryJoinLAN: []string{joinAddr.String()}, }, TLS: &pbconfig.TLS{ - VerifyOutgoing: true, - VerifyServerHostname: true, - MinVersion: "tls12", - PreferServerCipherSuites: true, + VerifyOutgoing: true, + VerifyServerHostname: true, + MinVersion: "tls12", }, }, }, @@ -414,42 +415,42 @@ func TestAutoConfig_updateTLSSettingsInConfig(t *testing.T) { cases := map[string]testCase{ "secure": { tlsConfig: tlsutil.Config{ - VerifyOutgoing: true, - VerifyServerHostname: true, - TLSMinVersion: "tls12", - PreferServerCipherSuites: true, - CAFile: cafile, - CipherSuites: parseCiphers(t, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"), + InternalRPC: tlsutil.ProtocolConfig{ + VerifyServerHostname: true, + VerifyOutgoing: true, + TLSMinVersion: "tls12", + CAFile: cafile, + CipherSuites: parseCiphers(t, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"), + }, }, expected: pbautoconf.AutoConfigResponse{ Config: &pbconfig.Config{ TLS: &pbconfig.TLS{ - VerifyOutgoing: true, - VerifyServerHostname: true, - MinVersion: "tls12", - PreferServerCipherSuites: true, - CipherSuites: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + VerifyOutgoing: true, + VerifyServerHostname: true, + MinVersion: "tls12", + CipherSuites: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", }, }, }, }, "less-secure": { tlsConfig: tlsutil.Config{ - VerifyOutgoing: true, - VerifyServerHostname: false, - TLSMinVersion: "tls10", - PreferServerCipherSuites: false, - CAFile: cafile, - CipherSuites: parseCiphers(t, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"), + InternalRPC: tlsutil.ProtocolConfig{ + VerifyServerHostname: false, + VerifyOutgoing: true, + TLSMinVersion: "tls10", + CAFile: cafile, + CipherSuites: parseCiphers(t, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"), + }, }, expected: pbautoconf.AutoConfigResponse{ Config: &pbconfig.Config{ TLS: &pbconfig.TLS{ - VerifyOutgoing: true, - VerifyServerHostname: false, - MinVersion: "tls10", - PreferServerCipherSuites: false, - CipherSuites: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + VerifyOutgoing: true, + VerifyServerHostname: false, + MinVersion: "tls10", + CipherSuites: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", }, }, }, @@ -631,12 +632,13 @@ func TestAutoConfig_updateTLSCertificatesInConfig(t *testing.T) { ConnectEnabled: true, }, tlsConfig: tlsutil.Config{ - VerifyOutgoing: true, - VerifyServerHostname: true, - TLSMinVersion: "tls12", - PreferServerCipherSuites: true, - CAFile: cafile, - CipherSuites: parseCiphers(t, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"), + InternalRPC: tlsutil.ProtocolConfig{ + VerifyServerHostname: true, + VerifyOutgoing: true, + TLSMinVersion: "tls12", + CAFile: cafile, + CipherSuites: parseCiphers(t, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"), + }, }, expected: pbautoconf.AutoConfigResponse{ CARoots: pbroots, @@ -649,12 +651,13 @@ func TestAutoConfig_updateTLSCertificatesInConfig(t *testing.T) { ConnectEnabled: true, }, tlsConfig: tlsutil.Config{ - VerifyOutgoing: true, - VerifyServerHostname: true, - TLSMinVersion: "tls12", - PreferServerCipherSuites: true, - CAFile: cafile, - CipherSuites: parseCiphers(t, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"), + InternalRPC: tlsutil.ProtocolConfig{ + VerifyServerHostname: true, + VerifyOutgoing: true, + TLSMinVersion: "tls12", + CAFile: cafile, + CipherSuites: parseCiphers(t, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"), + }, }, opts: AutoConfigOptions{ NodeName: "test", diff --git a/agent/consul/auto_encrypt_endpoint_test.go b/agent/consul/auto_encrypt_endpoint_test.go index 1605d2c4c..a27cef26b 100644 --- a/agent/consul/auto_encrypt_endpoint_test.go +++ b/agent/consul/auto_encrypt_endpoint_test.go @@ -8,10 +8,11 @@ import ( "strings" "testing" - msgpackrpc "github.com/hashicorp/consul-net-rpc/net-rpc-msgpackrpc" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + msgpackrpc "github.com/hashicorp/consul-net-rpc/net-rpc-msgpackrpc" + "github.com/hashicorp/consul/agent/connect" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/testrpc" @@ -38,11 +39,11 @@ func TestAutoEncryptSign(t *testing.T) { tests := []test{ {Name: "Works with defaults", Config: tlsutil.Config{}, ConnError: false}, - {Name: "Works with good root", Config: tlsutil.Config{CAFile: root}, ConnError: false}, - {Name: "VerifyOutgoing fails because of bad root", Config: tlsutil.Config{CAFile: badRoot}, ConnError: true}, - {Name: "VerifyServerHostname fails", Config: tlsutil.Config{VerifyServerHostname: true, CAFile: root}, ConnError: false, RPCError: true}, + {Name: "Works with good root", Config: tlsutil.Config{InternalRPC: tlsutil.ProtocolConfig{CAFile: root}}, ConnError: false}, + {Name: "VerifyOutgoing fails because of bad root", Config: tlsutil.Config{InternalRPC: tlsutil.ProtocolConfig{CAFile: badRoot}}, ConnError: true}, + {Name: "VerifyServerHostname fails", Config: tlsutil.Config{InternalRPC: tlsutil.ProtocolConfig{CAFile: root, VerifyServerHostname: true}}, ConnError: false, RPCError: true}, {Name: "VerifyServerHostname succeeds", Cert: "../../test/key/ourdomain_server.cer", Key: "../../test/key/ourdomain_server.key", - Config: tlsutil.Config{VerifyServerHostname: true, CAFile: root}, ConnError: false, RPCError: false}, + Config: tlsutil.Config{InternalRPC: tlsutil.ProtocolConfig{VerifyServerHostname: true, CAFile: root}}, ConnError: false, RPCError: false}, } for i, test := range tests { @@ -59,10 +60,10 @@ func TestAutoEncryptSign(t *testing.T) { c.AutoEncryptAllowTLS = true c.PrimaryDatacenter = "dc1" c.Bootstrap = true - c.TLSConfig.CAFile = root - c.TLSConfig.VerifyOutgoing = true - c.TLSConfig.CertFile = cert - c.TLSConfig.KeyFile = key + c.TLSConfig.InternalRPC.CAFile = root + c.TLSConfig.InternalRPC.VerifyOutgoing = true + c.TLSConfig.InternalRPC.CertFile = cert + c.TLSConfig.InternalRPC.KeyFile = key }) defer os.RemoveAll(dir) defer s.Shutdown() diff --git a/agent/consul/client_test.go b/agent/consul/client_test.go index 048df454c..f077f5d5f 100644 --- a/agent/consul/client_test.go +++ b/agent/consul/client_test.go @@ -10,12 +10,13 @@ import ( "testing" "time" - msgpackrpc "github.com/hashicorp/consul-net-rpc/net-rpc-msgpackrpc" "github.com/hashicorp/go-hclog" "github.com/hashicorp/serf/serf" "github.com/stretchr/testify/require" "golang.org/x/time/rate" + msgpackrpc "github.com/hashicorp/consul-net-rpc/net-rpc-msgpackrpc" + "github.com/hashicorp/consul/agent/grpc" "github.com/hashicorp/consul/agent/grpc/resolver" "github.com/hashicorp/consul/agent/pool" @@ -447,8 +448,8 @@ func TestClient_RPC_ConsulServerPing(t *testing.T) { func TestClient_RPC_TLS(t *testing.T) { t.Parallel() _, conf1 := testServerConfig(t) - conf1.TLSConfig.VerifyIncoming = true - conf1.TLSConfig.VerifyOutgoing = true + conf1.TLSConfig.InternalRPC.VerifyIncoming = true + conf1.TLSConfig.InternalRPC.VerifyOutgoing = true configureTLS(conf1) s1, err := newServer(t, conf1) if err != nil { @@ -457,7 +458,7 @@ func TestClient_RPC_TLS(t *testing.T) { defer s1.Shutdown() _, conf2 := testClientConfig(t) - conf2.TLSConfig.VerifyOutgoing = true + conf2.TLSConfig.InternalRPC.VerifyOutgoing = true configureTLS(conf2) c1 := newClient(t, conf2) @@ -660,8 +661,8 @@ func TestClient_SnapshotRPC_TLS(t *testing.T) { t.Parallel() _, conf1 := testServerConfig(t) - conf1.TLSConfig.VerifyIncoming = true - conf1.TLSConfig.VerifyOutgoing = true + conf1.TLSConfig.InternalRPC.VerifyIncoming = true + conf1.TLSConfig.InternalRPC.VerifyOutgoing = true configureTLS(conf1) s1, err := newServer(t, conf1) if err != nil { @@ -670,7 +671,7 @@ func TestClient_SnapshotRPC_TLS(t *testing.T) { defer s1.Shutdown() _, conf2 := testClientConfig(t) - conf2.TLSConfig.VerifyOutgoing = true + conf2.TLSConfig.InternalRPC.VerifyOutgoing = true configureTLS(conf2) c1 := newClient(t, conf2) diff --git a/agent/consul/rpc_test.go b/agent/consul/rpc_test.go index b08c5649b..0f37d4b6f 100644 --- a/agent/consul/rpc_test.go +++ b/agent/consul/rpc_test.go @@ -19,8 +19,6 @@ import ( "testing" "time" - "github.com/hashicorp/consul-net-rpc/go-msgpack/codec" - msgpackrpc "github.com/hashicorp/consul-net-rpc/net-rpc-msgpackrpc" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-memdb" "github.com/hashicorp/raft" @@ -28,6 +26,9 @@ import ( "github.com/stretchr/testify/require" "google.golang.org/grpc" + "github.com/hashicorp/consul-net-rpc/go-msgpack/codec" + msgpackrpc "github.com/hashicorp/consul-net-rpc/net-rpc-msgpackrpc" + "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/connect" "github.com/hashicorp/consul/agent/consul/state" @@ -565,12 +566,12 @@ func TestRPC_TLSHandshakeTimeout(t *testing.T) { dir1, s1 := testServerWithConfig(t, func(c *Config) { c.RPCHandshakeTimeout = 10 * time.Millisecond - c.TLSConfig.CAFile = "../../test/hostname/CertAuth.crt" - c.TLSConfig.CertFile = "../../test/hostname/Alice.crt" - c.TLSConfig.KeyFile = "../../test/hostname/Alice.key" - c.TLSConfig.VerifyServerHostname = true - c.TLSConfig.VerifyOutgoing = true - c.TLSConfig.VerifyIncoming = true + c.TLSConfig.InternalRPC.CAFile = "../../test/hostname/CertAuth.crt" + c.TLSConfig.InternalRPC.CertFile = "../../test/hostname/Alice.crt" + c.TLSConfig.InternalRPC.KeyFile = "../../test/hostname/Alice.key" + c.TLSConfig.InternalRPC.VerifyServerHostname = true + c.TLSConfig.InternalRPC.VerifyOutgoing = true + c.TLSConfig.InternalRPC.VerifyIncoming = true }) defer os.RemoveAll(dir1) defer s1.Shutdown() @@ -661,12 +662,12 @@ func TestRPC_PreventsTLSNesting(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.TLSConfig.CAFile = "../../test/hostname/CertAuth.crt" - c.TLSConfig.CertFile = "../../test/hostname/Alice.crt" - c.TLSConfig.KeyFile = "../../test/hostname/Alice.key" - c.TLSConfig.VerifyServerHostname = true - c.TLSConfig.VerifyOutgoing = true - c.TLSConfig.VerifyIncoming = false // saves us getting client cert setup + c.TLSConfig.InternalRPC.CAFile = "../../test/hostname/CertAuth.crt" + c.TLSConfig.InternalRPC.CertFile = "../../test/hostname/Alice.crt" + c.TLSConfig.InternalRPC.KeyFile = "../../test/hostname/Alice.key" + c.TLSConfig.InternalRPC.VerifyServerHostname = true + c.TLSConfig.InternalRPC.VerifyOutgoing = true + c.TLSConfig.InternalRPC.VerifyIncoming = false // saves us getting client cert setup c.TLSConfig.Domain = "consul" }) defer os.RemoveAll(dir1) @@ -818,12 +819,12 @@ func TestRPC_RPCMaxConnsPerClient(t *testing.T) { dir1, s1 := testServerWithConfig(t, func(c *Config) { c.RPCMaxConnsPerClient = 2 if tc.tlsEnabled { - c.TLSConfig.CAFile = "../../test/hostname/CertAuth.crt" - c.TLSConfig.CertFile = "../../test/hostname/Alice.crt" - c.TLSConfig.KeyFile = "../../test/hostname/Alice.key" - c.TLSConfig.VerifyServerHostname = true - c.TLSConfig.VerifyOutgoing = true - c.TLSConfig.VerifyIncoming = false // saves us getting client cert setup + c.TLSConfig.InternalRPC.CAFile = "../../test/hostname/CertAuth.crt" + c.TLSConfig.InternalRPC.CertFile = "../../test/hostname/Alice.crt" + c.TLSConfig.InternalRPC.KeyFile = "../../test/hostname/Alice.key" + c.TLSConfig.InternalRPC.VerifyServerHostname = true + c.TLSConfig.InternalRPC.VerifyOutgoing = true + c.TLSConfig.InternalRPC.VerifyIncoming = false // saves us getting client cert setup c.TLSConfig.Domain = "consul" } }) @@ -1416,11 +1417,11 @@ func TestRPC_AuthorizeRaftRPC(t *testing.T) { _, srv := testServerWithConfig(t, func(c *Config) { c.TLSConfig.Domain = "consul." // consul. is the default value in agent/config - c.TLSConfig.CAFile = filepath.Join(dir, "ca.pem") - c.TLSConfig.CertFile = filepath.Join(dir, "srv1-server.dc1.consul.pem") - c.TLSConfig.KeyFile = filepath.Join(dir, "srv1-server.dc1.consul.key") - c.TLSConfig.VerifyIncoming = true - c.TLSConfig.VerifyServerHostname = true + c.TLSConfig.InternalRPC.CAFile = filepath.Join(dir, "ca.pem") + c.TLSConfig.InternalRPC.CertFile = filepath.Join(dir, "srv1-server.dc1.consul.pem") + c.TLSConfig.InternalRPC.KeyFile = filepath.Join(dir, "srv1-server.dc1.consul.key") + c.TLSConfig.InternalRPC.VerifyIncoming = true + c.TLSConfig.InternalRPC.VerifyServerHostname = true // Enable Auto-Encrypt so that Connect CA roots are added to the // tlsutil.Configurator. c.AutoEncryptAllowTLS = true @@ -1509,12 +1510,14 @@ func TestRPC_AuthorizeRaftRPC(t *testing.T) { certPath := tc.setupCert(t) cfg := tlsutil.Config{ - VerifyOutgoing: true, - VerifyServerHostname: true, - CAFile: filepath.Join(dir, "ca.pem"), - CertFile: certPath + ".pem", - KeyFile: certPath + ".key", - Domain: "consul", + InternalRPC: tlsutil.ProtocolConfig{ + VerifyOutgoing: true, + VerifyServerHostname: true, + CAFile: filepath.Join(dir, "ca.pem"), + CertFile: certPath + ".pem", + KeyFile: certPath + ".key", + }, + Domain: "consul", } c, err := tlsutil.NewConfigurator(cfg, hclog.New(nil)) require.NoError(t, err) diff --git a/agent/consul/server.go b/agent/consul/server.go index 2c48f5557..96db9fa1a 100644 --- a/agent/consul/server.go +++ b/agent/consul/server.go @@ -22,7 +22,6 @@ import ( "go.etcd.io/bbolt" "github.com/armon/go-metrics" - "github.com/hashicorp/consul-net-rpc/net/rpc" connlimit "github.com/hashicorp/go-connlimit" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-memdb" @@ -33,6 +32,8 @@ import ( "golang.org/x/time/rate" "google.golang.org/grpc" + "github.com/hashicorp/consul-net-rpc/net/rpc" + "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/consul/authmethod" "github.com/hashicorp/consul/agent/consul/authmethod/ssoauth" diff --git a/agent/consul/server_serf.go b/agent/consul/server_serf.go index 56af75fef..3db25c155 100644 --- a/agent/consul/server_serf.go +++ b/agent/consul/server_serf.go @@ -115,7 +115,7 @@ func (s *Server) setupSerfConfig(opts setupSerfOptions) (*serf.Config, error) { conf.Tags["nonvoter"] = "1" conf.Tags["read_replica"] = "1" } - if s.config.TLSConfig.CAPath != "" || s.config.TLSConfig.CAFile != "" { + if s.config.TLSConfig.InternalRPC.CAPath != "" || s.config.TLSConfig.InternalRPC.CAFile != "" { conf.Tags["use_tls"] = "1" } diff --git a/agent/consul/server_test.go b/agent/consul/server_test.go index ea4ea177f..90d6d6440 100644 --- a/agent/consul/server_test.go +++ b/agent/consul/server_test.go @@ -16,10 +16,11 @@ import ( "github.com/hashicorp/consul/ipaddr" - "github.com/hashicorp/consul-net-rpc/net/rpc" "github.com/hashicorp/go-uuid" "golang.org/x/time/rate" + "github.com/hashicorp/consul-net-rpc/net/rpc" + "github.com/hashicorp/consul/agent/connect" "github.com/hashicorp/consul/agent/metadata" "github.com/hashicorp/consul/agent/structs" @@ -75,9 +76,9 @@ func testServerACLConfig(c *Config) { } func configureTLS(config *Config) { - config.TLSConfig.CAFile = "../../test/ca/root.cer" - config.TLSConfig.CertFile = "../../test/key/ourdomain.cer" - config.TLSConfig.KeyFile = "../../test/key/ourdomain.key" + config.TLSConfig.InternalRPC.CAFile = "../../test/ca/root.cer" + config.TLSConfig.InternalRPC.CertFile = "../../test/key/ourdomain.cer" + config.TLSConfig.InternalRPC.KeyFile = "../../test/key/ourdomain.key" } var id int64 @@ -709,12 +710,12 @@ func TestServer_JoinWAN_viaMeshGateway(t *testing.T) { c.PrimaryDatacenter = "dc1" c.Bootstrap = true // tls - c.TLSConfig.CAFile = "../../test/hostname/CertAuth.crt" - c.TLSConfig.CertFile = "../../test/hostname/Bob.crt" - c.TLSConfig.KeyFile = "../../test/hostname/Bob.key" - c.TLSConfig.VerifyIncoming = true - c.TLSConfig.VerifyOutgoing = true - c.TLSConfig.VerifyServerHostname = true + c.TLSConfig.InternalRPC.CAFile = "../../test/hostname/CertAuth.crt" + c.TLSConfig.InternalRPC.CertFile = "../../test/hostname/Bob.crt" + c.TLSConfig.InternalRPC.KeyFile = "../../test/hostname/Bob.key" + c.TLSConfig.InternalRPC.VerifyIncoming = true + c.TLSConfig.InternalRPC.VerifyOutgoing = true + c.TLSConfig.InternalRPC.VerifyServerHostname = true // wanfed c.ConnectMeshGatewayWANFederationEnabled = true }) @@ -728,12 +729,12 @@ func TestServer_JoinWAN_viaMeshGateway(t *testing.T) { c.PrimaryDatacenter = "dc1" c.Bootstrap = true // tls - c.TLSConfig.CAFile = "../../test/hostname/CertAuth.crt" - c.TLSConfig.CertFile = "../../test/hostname/Betty.crt" - c.TLSConfig.KeyFile = "../../test/hostname/Betty.key" - c.TLSConfig.VerifyIncoming = true - c.TLSConfig.VerifyOutgoing = true - c.TLSConfig.VerifyServerHostname = true + c.TLSConfig.InternalRPC.CAFile = "../../test/hostname/CertAuth.crt" + c.TLSConfig.InternalRPC.CertFile = "../../test/hostname/Betty.crt" + c.TLSConfig.InternalRPC.KeyFile = "../../test/hostname/Betty.key" + c.TLSConfig.InternalRPC.VerifyIncoming = true + c.TLSConfig.InternalRPC.VerifyOutgoing = true + c.TLSConfig.InternalRPC.VerifyServerHostname = true // wanfed c.ConnectMeshGatewayWANFederationEnabled = true }) @@ -747,12 +748,12 @@ func TestServer_JoinWAN_viaMeshGateway(t *testing.T) { c.PrimaryDatacenter = "dc1" c.Bootstrap = true // tls - c.TLSConfig.CAFile = "../../test/hostname/CertAuth.crt" - c.TLSConfig.CertFile = "../../test/hostname/Bonnie.crt" - c.TLSConfig.KeyFile = "../../test/hostname/Bonnie.key" - c.TLSConfig.VerifyIncoming = true - c.TLSConfig.VerifyOutgoing = true - c.TLSConfig.VerifyServerHostname = true + c.TLSConfig.InternalRPC.CAFile = "../../test/hostname/CertAuth.crt" + c.TLSConfig.InternalRPC.CertFile = "../../test/hostname/Bonnie.crt" + c.TLSConfig.InternalRPC.KeyFile = "../../test/hostname/Bonnie.key" + c.TLSConfig.InternalRPC.VerifyIncoming = true + c.TLSConfig.InternalRPC.VerifyOutgoing = true + c.TLSConfig.InternalRPC.VerifyServerHostname = true // wanfed c.ConnectMeshGatewayWANFederationEnabled = true }) @@ -1136,8 +1137,8 @@ func TestServer_JoinLAN_TLS(t *testing.T) { t.Parallel() _, conf1 := testServerConfig(t) - conf1.TLSConfig.VerifyIncoming = true - conf1.TLSConfig.VerifyOutgoing = true + conf1.TLSConfig.InternalRPC.VerifyIncoming = true + conf1.TLSConfig.InternalRPC.VerifyOutgoing = true configureTLS(conf1) s1, err := newServer(t, conf1) if err != nil { @@ -1148,8 +1149,8 @@ func TestServer_JoinLAN_TLS(t *testing.T) { _, conf2 := testServerConfig(t) conf2.Bootstrap = false - conf2.TLSConfig.VerifyIncoming = true - conf2.TLSConfig.VerifyOutgoing = true + conf2.TLSConfig.InternalRPC.VerifyIncoming = true + conf2.TLSConfig.InternalRPC.VerifyOutgoing = true configureTLS(conf2) s2, err := newServer(t, conf2) if err != nil { @@ -1410,9 +1411,9 @@ func TestServer_TLSToNoTLS(t *testing.T) { // Add a second server with TLS configured dir2, s2 := testServerWithConfig(t, func(c *Config) { c.Bootstrap = false - c.TLSConfig.CAFile = "../../test/client_certs/rootca.crt" - c.TLSConfig.CertFile = "../../test/client_certs/server.crt" - c.TLSConfig.KeyFile = "../../test/client_certs/server.key" + c.TLSConfig.InternalRPC.CAFile = "../../test/client_certs/rootca.crt" + c.TLSConfig.InternalRPC.CertFile = "../../test/client_certs/server.crt" + c.TLSConfig.InternalRPC.KeyFile = "../../test/client_certs/server.key" }) defer os.RemoveAll(dir2) defer s2.Shutdown() @@ -1442,10 +1443,10 @@ func TestServer_TLSForceOutgoingToNoTLS(t *testing.T) { // Add a second server with TLS and VerifyOutgoing set dir2, s2 := testServerWithConfig(t, func(c *Config) { c.Bootstrap = false - c.TLSConfig.CAFile = "../../test/client_certs/rootca.crt" - c.TLSConfig.CertFile = "../../test/client_certs/server.crt" - c.TLSConfig.KeyFile = "../../test/client_certs/server.key" - c.TLSConfig.VerifyOutgoing = true + c.TLSConfig.InternalRPC.CAFile = "../../test/client_certs/rootca.crt" + c.TLSConfig.InternalRPC.CertFile = "../../test/client_certs/server.crt" + c.TLSConfig.InternalRPC.KeyFile = "../../test/client_certs/server.key" + c.TLSConfig.InternalRPC.VerifyOutgoing = true }) defer os.RemoveAll(dir2) defer s2.Shutdown() @@ -1464,10 +1465,10 @@ func TestServer_TLSToFullVerify(t *testing.T) { t.Parallel() // Set up a server with TLS and VerifyIncoming set dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.TLSConfig.CAFile = "../../test/client_certs/rootca.crt" - c.TLSConfig.CertFile = "../../test/client_certs/server.crt" - c.TLSConfig.KeyFile = "../../test/client_certs/server.key" - c.TLSConfig.VerifyOutgoing = true + c.TLSConfig.InternalRPC.CAFile = "../../test/client_certs/rootca.crt" + c.TLSConfig.InternalRPC.CertFile = "../../test/client_certs/server.crt" + c.TLSConfig.InternalRPC.KeyFile = "../../test/client_certs/server.key" + c.TLSConfig.InternalRPC.VerifyOutgoing = true }) defer os.RemoveAll(dir1) defer s1.Shutdown() @@ -1477,9 +1478,9 @@ func TestServer_TLSToFullVerify(t *testing.T) { // Add a second server with TLS configured dir2, s2 := testServerWithConfig(t, func(c *Config) { c.Bootstrap = false - c.TLSConfig.CAFile = "../../test/client_certs/rootca.crt" - c.TLSConfig.CertFile = "../../test/client_certs/server.crt" - c.TLSConfig.KeyFile = "../../test/client_certs/server.key" + c.TLSConfig.InternalRPC.CAFile = "../../test/client_certs/rootca.crt" + c.TLSConfig.InternalRPC.CertFile = "../../test/client_certs/server.crt" + c.TLSConfig.InternalRPC.KeyFile = "../../test/client_certs/server.key" }) defer os.RemoveAll(dir2) defer s2.Shutdown() diff --git a/agent/consul/status_endpoint_test.go b/agent/consul/status_endpoint_test.go index 6ae7111e8..c531de778 100644 --- a/agent/consul/status_endpoint_test.go +++ b/agent/consul/status_endpoint_test.go @@ -6,9 +6,10 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + msgpackrpc "github.com/hashicorp/consul-net-rpc/net-rpc-msgpackrpc" "github.com/hashicorp/consul-net-rpc/net/rpc" - "github.com/stretchr/testify/require" "github.com/hashicorp/consul/agent/pool" "github.com/hashicorp/consul/agent/structs" diff --git a/agent/consul/subscribe_backend_test.go b/agent/consul/subscribe_backend_test.go index de80a2088..5235f0b96 100644 --- a/agent/consul/subscribe_backend_test.go +++ b/agent/consul/subscribe_backend_test.go @@ -29,8 +29,8 @@ func TestSubscribeBackend_IntegrationWithServer_TLSEnabled(t *testing.T) { // TODO(rb): add tests for the wanfed/alpn variations _, conf1 := testServerConfig(t) - conf1.TLSConfig.VerifyIncoming = true - conf1.TLSConfig.VerifyOutgoing = true + conf1.TLSConfig.InternalRPC.VerifyIncoming = true + conf1.TLSConfig.InternalRPC.VerifyOutgoing = true conf1.RPCConfig.EnableStreaming = true configureTLS(conf1) server, err := newServer(t, conf1) @@ -161,11 +161,11 @@ func TestSubscribeBackend_IntegrationWithServer_TLSReload(t *testing.T) { // Set up a server with initially bad certificates. _, conf1 := testServerConfig(t) - conf1.TLSConfig.VerifyIncoming = true - conf1.TLSConfig.VerifyOutgoing = true - conf1.TLSConfig.CAFile = "../../test/ca/root.cer" - conf1.TLSConfig.CertFile = "../../test/key/ssl-cert-snakeoil.pem" - conf1.TLSConfig.KeyFile = "../../test/key/ssl-cert-snakeoil.key" + conf1.TLSConfig.InternalRPC.VerifyIncoming = true + conf1.TLSConfig.InternalRPC.VerifyOutgoing = true + conf1.TLSConfig.InternalRPC.CAFile = "../../test/ca/root.cer" + conf1.TLSConfig.InternalRPC.CertFile = "../../test/key/ssl-cert-snakeoil.pem" + conf1.TLSConfig.InternalRPC.KeyFile = "../../test/key/ssl-cert-snakeoil.key" conf1.RPCConfig.EnableStreaming = true server, err := newServer(t, conf1) @@ -199,8 +199,8 @@ func TestSubscribeBackend_IntegrationWithServer_TLSReload(t *testing.T) { // Reload the server with valid certs newConf := server.config.TLSConfig - newConf.CertFile = "../../test/key/ourdomain.cer" - newConf.KeyFile = "../../test/key/ourdomain.key" + newConf.InternalRPC.CertFile = "../../test/key/ourdomain.cer" + newConf.InternalRPC.KeyFile = "../../test/key/ourdomain.key" server.tlsConfigurator.Update(newConf) // Try the subscribe call again @@ -212,7 +212,7 @@ func TestSubscribeBackend_IntegrationWithServer_TLSReload(t *testing.T) { } func clientConfigVerifyOutgoing(config *Config) { - config.TLSConfig.VerifyOutgoing = true + config.TLSConfig.InternalRPC.VerifyOutgoing = true } // retryFailedConn forces the ClientConn to reset its backoff timer and retry the connection, diff --git a/agent/grpc/client_test.go b/agent/grpc/client_test.go index cd4c827dd..371452ff4 100644 --- a/agent/grpc/client_test.go +++ b/agent/grpc/client_test.go @@ -143,11 +143,13 @@ func TestNewDialer_IntegrationWithTLSEnabledHandler(t *testing.T) { registerWithGRPC(t, res) tlsConf, err := tlsutil.NewConfigurator(tlsutil.Config{ - VerifyIncoming: true, - VerifyOutgoing: true, - CAFile: "../../test/hostname/CertAuth.crt", - CertFile: "../../test/hostname/Alice.crt", - KeyFile: "../../test/hostname/Alice.key", + InternalRPC: tlsutil.ProtocolConfig{ + VerifyIncoming: true, + CAFile: "../../test/hostname/CertAuth.crt", + CertFile: "../../test/hostname/Alice.crt", + KeyFile: "../../test/hostname/Alice.key", + VerifyOutgoing: true, + }, }, hclog.New(nil)) require.NoError(t, err) @@ -188,14 +190,16 @@ func TestNewDialer_IntegrationWithTLSEnabledHandler_viaMeshGateway(t *testing.T) registerWithGRPC(t, res) tlsConf, err := tlsutil.NewConfigurator(tlsutil.Config{ - VerifyIncoming: true, - VerifyOutgoing: true, - VerifyServerHostname: true, - CAFile: "../../test/hostname/CertAuth.crt", - CertFile: "../../test/hostname/Bob.crt", - KeyFile: "../../test/hostname/Bob.key", - Domain: "consul", - NodeName: "bob", + InternalRPC: tlsutil.ProtocolConfig{ + VerifyIncoming: true, + CAFile: "../../test/hostname/CertAuth.crt", + CertFile: "../../test/hostname/Bob.crt", + KeyFile: "../../test/hostname/Bob.key", + VerifyOutgoing: true, + VerifyServerHostname: true, + }, + Domain: "consul", + NodeName: "bob", }, hclog.New(nil)) require.NoError(t, err) @@ -216,14 +220,16 @@ func TestNewDialer_IntegrationWithTLSEnabledHandler_viaMeshGateway(t *testing.T) t.Cleanup(srv.shutdown) clientTLSConf, err := tlsutil.NewConfigurator(tlsutil.Config{ - VerifyIncoming: true, - VerifyOutgoing: true, - VerifyServerHostname: true, - CAFile: "../../test/hostname/CertAuth.crt", - CertFile: "../../test/hostname/Betty.crt", - KeyFile: "../../test/hostname/Betty.key", - Domain: "consul", - NodeName: "betty", + InternalRPC: tlsutil.ProtocolConfig{ + VerifyIncoming: true, + CAFile: "../../test/hostname/CertAuth.crt", + CertFile: "../../test/hostname/Betty.crt", + KeyFile: "../../test/hostname/Betty.key", + VerifyOutgoing: true, + VerifyServerHostname: true, + }, + Domain: "consul", + NodeName: "betty", }, hclog.New(nil)) require.NoError(t, err) diff --git a/agent/grpc/server_test.go b/agent/grpc/server_test.go index eb56b8933..043eb4500 100644 --- a/agent/grpc/server_test.go +++ b/agent/grpc/server_test.go @@ -14,11 +14,12 @@ import ( "golang.org/x/sync/errgroup" "google.golang.org/grpc" + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/consul/agent/grpc/internal/testservice" "github.com/hashicorp/consul/agent/metadata" "github.com/hashicorp/consul/agent/pool" "github.com/hashicorp/consul/tlsutil" - "github.com/hashicorp/go-hclog" ) type testServer struct { diff --git a/agent/pool/pool.go b/agent/pool/pool.go index 4df55966d..1f21850e3 100644 --- a/agent/pool/pool.go +++ b/agent/pool/pool.go @@ -11,9 +11,10 @@ import ( "sync/atomic" "time" + "github.com/hashicorp/yamux" + msgpackrpc "github.com/hashicorp/consul-net-rpc/net-rpc-msgpackrpc" "github.com/hashicorp/consul-net-rpc/net/rpc" - "github.com/hashicorp/yamux" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/lib" diff --git a/agent/setup.go b/agent/setup.go index 82543e7fa..1ce7f40d1 100644 --- a/agent/setup.go +++ b/agent/setup.go @@ -93,7 +93,7 @@ func NewBaseDeps(configLoader ConfigLoader, logOut io.Writer) (BaseDeps, error) return d, fmt.Errorf("failed to initialize telemetry: %w", err) } - d.TLSConfigurator, err = tlsutil.NewConfigurator(cfg.ToTLSUtilConfig(), d.Logger) + d.TLSConfigurator, err = tlsutil.NewConfigurator(cfg.TLS, d.Logger) if err != nil { return d, err } diff --git a/agent/xds/server.go b/agent/xds/server.go index efab8923c..86aa6ec73 100644 --- a/agent/xds/server.go +++ b/agent/xds/server.go @@ -222,11 +222,9 @@ func NewGRPCServer(s *Server, tlsConfigurator *tlsutil.Configurator) *grpc.Serve recovery.StreamServerInterceptor(recoveryOpts...), ), } - if tlsConfigurator != nil { - if tlsConfigurator.Cert() != nil { - creds := credentials.NewTLS(tlsConfigurator.IncomingGRPCConfig()) - opts = append(opts, grpc.Creds(creds)) - } + if tlsConfigurator != nil && tlsConfigurator.GRPCTLSConfigured() { + creds := credentials.NewTLS(tlsConfigurator.IncomingGRPCConfig()) + opts = append(opts, grpc.Creds(creds)) } srv := grpc.NewServer(opts...) envoy_discovery_v3.RegisterAggregatedDiscoveryServiceServer(srv, s) diff --git a/command/agent/agent.go b/command/agent/agent.go index 1525c2819..6a8d042c3 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -207,7 +207,7 @@ func (c *cmd) run(args []string) int { ui.Info(fmt.Sprintf(" Cluster Addr: %v (LAN: %d, WAN: %d)", config.AdvertiseAddrLAN, config.SerfPortLAN, config.SerfPortWAN)) ui.Info(fmt.Sprintf(" Encrypt: Gossip: %v, TLS-Outgoing: %v, TLS-Incoming: %v, Auto-Encrypt-TLS: %t", - config.EncryptKey != "", config.VerifyOutgoing, config.VerifyIncoming, config.AutoEncryptTLS || config.AutoEncryptAllowTLS)) + config.EncryptKey != "", config.TLS.InternalRPC.VerifyOutgoing, config.TLS.InternalRPC.VerifyIncoming, config.AutoEncryptTLS || config.AutoEncryptAllowTLS)) // Enable log streaming ui.Output("") ui.Output("Log data will now stream in as it occurs:\n") diff --git a/proto/pbconfig/config.pb.go b/proto/pbconfig/config.pb.go index 458fc5482..5828b88ac 100644 --- a/proto/pbconfig/config.pb.go +++ b/proto/pbconfig/config.pb.go @@ -252,14 +252,16 @@ func (m *GossipEncryption) GetVerifyOutgoing() bool { } type TLS struct { - VerifyOutgoing bool `protobuf:"varint,1,opt,name=VerifyOutgoing,proto3" json:"VerifyOutgoing,omitempty"` - VerifyServerHostname bool `protobuf:"varint,2,opt,name=VerifyServerHostname,proto3" json:"VerifyServerHostname,omitempty"` - CipherSuites string `protobuf:"bytes,3,opt,name=CipherSuites,proto3" json:"CipherSuites,omitempty"` - MinVersion string `protobuf:"bytes,4,opt,name=MinVersion,proto3" json:"MinVersion,omitempty"` - PreferServerCipherSuites bool `protobuf:"varint,5,opt,name=PreferServerCipherSuites,proto3" json:"PreferServerCipherSuites,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + VerifyOutgoing bool `protobuf:"varint,1,opt,name=VerifyOutgoing,proto3" json:"VerifyOutgoing,omitempty"` + VerifyServerHostname bool `protobuf:"varint,2,opt,name=VerifyServerHostname,proto3" json:"VerifyServerHostname,omitempty"` + CipherSuites string `protobuf:"bytes,3,opt,name=CipherSuites,proto3" json:"CipherSuites,omitempty"` + MinVersion string `protobuf:"bytes,4,opt,name=MinVersion,proto3" json:"MinVersion,omitempty"` + // Deprecated_PreferServerCipherSuites is deprecated. It is no longer + // populated and should be ignored by clients. + Deprecated_PreferServerCipherSuites bool `protobuf:"varint,5,opt,name=Deprecated_PreferServerCipherSuites,json=DeprecatedPreferServerCipherSuites,proto3" json:"Deprecated_PreferServerCipherSuites,omitempty"` // Deprecated: Do not use. + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *TLS) Reset() { *m = TLS{} } @@ -323,9 +325,10 @@ func (m *TLS) GetMinVersion() string { return "" } -func (m *TLS) GetPreferServerCipherSuites() bool { +// Deprecated: Do not use. +func (m *TLS) GetDeprecated_PreferServerCipherSuites() bool { if m != nil { - return m.PreferServerCipherSuites + return m.Deprecated_PreferServerCipherSuites } return false } @@ -687,59 +690,59 @@ func init() { func init() { proto.RegisterFile("proto/pbconfig/config.proto", fileDescriptor_aefa824db7b74d77) } var fileDescriptor_aefa824db7b74d77 = []byte{ - // 823 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x55, 0xdd, 0x6e, 0xe3, 0x44, - 0x14, 0xc6, 0xf1, 0xd6, 0x6d, 0x26, 0xb0, 0xda, 0x9d, 0x5d, 0x8a, 0xc5, 0x4f, 0x88, 0x2c, 0xb4, - 0x2a, 0x08, 0xb5, 0xa8, 0x08, 0x04, 0x88, 0x9b, 0x34, 0x59, 0x41, 0xd8, 0x34, 0x44, 0x76, 0x58, - 0x24, 0x6e, 0x90, 0xe3, 0x9c, 0x24, 0x23, 0x9c, 0x19, 0x6b, 0x3c, 0xe9, 0xca, 0xaf, 0xc0, 0x13, - 0xf0, 0x2e, 0xbc, 0x00, 0x77, 0xf0, 0x08, 0x50, 0x5e, 0x64, 0x75, 0x66, 0xc6, 0x8e, 0xdd, 0x26, - 0x57, 0xc9, 0xf9, 0xbe, 0x6f, 0xce, 0x9c, 0x33, 0xe7, 0xc7, 0xe4, 0xbd, 0x4c, 0x0a, 0x25, 0x2e, - 0xb2, 0x79, 0x22, 0xf8, 0x92, 0xad, 0x2e, 0xcc, 0xcf, 0xb9, 0x46, 0xa9, 0x67, 0xac, 0xe0, 0xef, - 0x16, 0xf1, 0x06, 0xfa, 0x2f, 0xed, 0x12, 0x32, 0x8c, 0x55, 0x9c, 0x00, 0x57, 0x20, 0x7d, 0xa7, - 0xe7, 0x9c, 0xb5, 0xc3, 0x1a, 0x42, 0x3f, 0x25, 0x8f, 0xa7, 0x92, 0x6d, 0x62, 0x59, 0xd4, 0x64, - 0x2d, 0x2d, 0xbb, 0x4f, 0xd0, 0x77, 0xc9, 0xc9, 0x44, 0x2c, 0x60, 0x12, 0x6f, 0xc0, 0x77, 0xb5, - 0xa8, 0xb2, 0x69, 0x8f, 0x74, 0x22, 0x58, 0x6d, 0x80, 0x2b, 0x4d, 0x3f, 0xd0, 0x74, 0x1d, 0xa2, - 0xef, 0x93, 0xf6, 0x34, 0x96, 0x8a, 0x29, 0x26, 0xb8, 0xdf, 0xd6, 0xfc, 0x0e, 0xa0, 0x1f, 0x10, - 0xb7, 0x3f, 0x18, 0xfb, 0x47, 0x3d, 0xe7, 0xac, 0x73, 0xd9, 0x39, 0xb7, 0x89, 0xf5, 0x07, 0xe3, - 0x10, 0x71, 0xfa, 0x05, 0xe9, 0xf4, 0xb7, 0x4a, 0x3c, 0xe7, 0x89, 0x2c, 0x32, 0xe5, 0x7b, 0x5a, - 0xf6, 0xa4, 0x92, 0xed, 0xa8, 0xb0, 0xae, 0xa3, 0xcf, 0x88, 0xf7, 0x9d, 0xc8, 0x73, 0x96, 0xf9, - 0xc7, 0xfa, 0xc4, 0xc3, 0xf2, 0x84, 0x41, 0x43, 0xcb, 0xe2, 0xed, 0xb3, 0x71, 0xe4, 0x9f, 0x34, - 0x6f, 0x9f, 0x8d, 0xa3, 0x10, 0xf1, 0x60, 0x59, 0xba, 0xa1, 0x5f, 0x11, 0x62, 0x7d, 0x63, 0x16, - 0x8e, 0xd6, 0xfb, 0x4d, 0xa7, 0x3b, 0x3e, 0xac, 0x69, 0x69, 0x40, 0xde, 0x0c, 0x41, 0xc9, 0xe2, - 0x07, 0xc1, 0xf8, 0xb8, 0x3f, 0xf1, 0x5b, 0x3d, 0xf7, 0xac, 0x1d, 0x36, 0xb0, 0x40, 0x91, 0x47, - 0x77, 0x7d, 0xd0, 0x47, 0xc4, 0x7d, 0x01, 0x85, 0xad, 0x1d, 0xfe, 0xa5, 0xcf, 0xc8, 0xc3, 0x97, - 0x20, 0xd9, 0xb2, 0x18, 0xf1, 0x44, 0x6c, 0x18, 0x5f, 0xe9, 0x8a, 0x9d, 0x84, 0x77, 0xd0, 0x9d, - 0xee, 0xc7, 0xad, 0x5a, 0x09, 0xd4, 0xb9, 0x75, 0x5d, 0x89, 0x06, 0xff, 0x39, 0x3a, 0xfb, 0x3d, - 0x7a, 0x67, 0x9f, 0x9e, 0x5e, 0x92, 0xa7, 0x06, 0x89, 0x40, 0xde, 0x80, 0xfc, 0x5e, 0xe4, 0x8a, - 0x63, 0xcd, 0x4d, 0x14, 0x7b, 0x39, 0xcc, 0x7e, 0xc0, 0xb2, 0x35, 0xc8, 0x68, 0xcb, 0x14, 0xe4, - 0xb6, 0x7d, 0x1a, 0x18, 0x36, 0xeb, 0x35, 0xe3, 0x2f, 0x41, 0xe6, 0xf8, 0xb6, 0xa6, 0x83, 0x6a, - 0x08, 0xfd, 0x86, 0xf8, 0x53, 0x09, 0x4b, 0x90, 0xc6, 0x77, 0xc3, 0xdf, 0x91, 0xbe, 0xfb, 0x20, - 0x1f, 0xfc, 0xe9, 0xea, 0xfe, 0xa2, 0x3e, 0x39, 0x7e, 0xce, 0xe3, 0x79, 0x0a, 0x0b, 0x9b, 0x5c, - 0x69, 0xea, 0xf6, 0x14, 0x29, 0x4b, 0x8a, 0xd9, 0x6c, 0x6c, 0x47, 0x60, 0x07, 0xe0, 0xb9, 0x50, - 0xa4, 0x80, 0x9c, 0x09, 0xbd, 0x34, 0x71, 0x28, 0x66, 0xe2, 0x37, 0xe0, 0x48, 0x99, 0x98, 0x2b, - 0x5b, 0x8f, 0x9f, 0x78, 0xc5, 0x8d, 0x1b, 0x1d, 0x23, 0x8e, 0x5f, 0x85, 0xd0, 0x8f, 0xc8, 0x5b, - 0x43, 0x58, 0xc6, 0xdb, 0x54, 0x59, 0x89, 0xa7, 0x25, 0x4d, 0x90, 0x7e, 0x46, 0x9e, 0x98, 0x20, - 0x5f, 0x40, 0x31, 0x66, 0x79, 0xa9, 0x3d, 0xd6, 0xf1, 0xef, 0xa3, 0xe8, 0xc7, 0xc4, 0xd3, 0x31, - 0xe4, 0xb6, 0xa3, 0x1f, 0xd7, 0xe6, 0xc9, 0x10, 0xa1, 0x15, 0xd0, 0xaf, 0xc9, 0xe9, 0x10, 0x32, - 0x09, 0x49, 0xac, 0x60, 0xf1, 0xeb, 0x90, 0xe5, 0xfa, 0x35, 0x30, 0x19, 0x3d, 0xa2, 0x57, 0x2d, - 0xdf, 0x09, 0xdf, 0xde, 0x29, 0x6a, 0x02, 0xfa, 0x25, 0x39, 0x35, 0x97, 0x6b, 0x57, 0x53, 0xac, - 0x52, 0xae, 0x80, 0x27, 0xe0, 0x13, 0x1d, 0xda, 0x01, 0x16, 0xf3, 0xb9, 0x8e, 0xa6, 0xd6, 0xd3, - 0x95, 0x10, 0x2a, 0x57, 0x32, 0xce, 0xfc, 0x8e, 0xc9, 0x67, 0x0f, 0x15, 0xfc, 0xde, 0x22, 0xed, - 0x2a, 0x74, 0x5c, 0x5a, 0x23, 0xce, 0x14, 0x8b, 0xd3, 0xeb, 0x98, 0xc7, 0x2b, 0xc0, 0x0d, 0x63, - 0xe7, 0xe3, 0x3e, 0x81, 0x8b, 0x29, 0x84, 0x2c, 0x65, 0x49, 0xac, 0x47, 0xd6, 0x54, 0xb6, 0x0e, - 0x61, 0x15, 0xfa, 0x2b, 0xe0, 0x2a, 0x84, 0x44, 0xdc, 0x80, 0x2c, 0x6c, 0x85, 0x9b, 0x20, 0x76, - 0x80, 0x2d, 0x8b, 0x2d, 0x73, 0x69, 0xd2, 0xa7, 0xe4, 0x48, 0x4b, 0x6d, 0x81, 0x8d, 0x41, 0x7f, - 0x26, 0xa7, 0x26, 0x8a, 0x05, 0xb6, 0x23, 0x4b, 0x60, 0x2a, 0xc5, 0x0d, 0x5b, 0x80, 0xf4, 0xbd, - 0x9e, 0x7b, 0xd6, 0xb9, 0xfc, 0xb0, 0x56, 0x93, 0x3b, 0x0a, 0x9d, 0x67, 0x78, 0xe0, 0x78, 0xf0, - 0x13, 0x79, 0xe7, 0xc0, 0x11, 0xec, 0xb7, 0x7e, 0x92, 0x40, 0x9e, 0x0b, 0x39, 0x1a, 0x96, 0xeb, - 0x7e, 0x87, 0x60, 0xaf, 0x46, 0x90, 0x48, 0x50, 0xa3, 0xa1, 0x7d, 0x88, 0xca, 0x0e, 0x58, 0x63, - 0xc3, 0xe2, 0xda, 0xc1, 0x8d, 0x68, 0x86, 0x44, 0xaf, 0x87, 0x53, 0xe2, 0x0d, 0x27, 0x51, 0x54, - 0xad, 0x2e, 0x6b, 0x61, 0xfa, 0xa3, 0x29, 0xc2, 0xae, 0x86, 0x8d, 0x81, 0x57, 0xf5, 0xd3, 0x54, - 0xbc, 0x42, 0x27, 0x0f, 0xb4, 0x93, 0xca, 0xbe, 0xfa, 0xf6, 0xaf, 0xdb, 0xae, 0xf3, 0xcf, 0x6d, - 0xd7, 0xf9, 0xf7, 0xb6, 0xeb, 0xfc, 0xf1, 0x7f, 0xf7, 0x8d, 0x5f, 0x3e, 0x59, 0x31, 0xb5, 0xde, - 0xce, 0xcf, 0x13, 0xb1, 0xb9, 0x58, 0xc7, 0xf9, 0x9a, 0x25, 0x42, 0x66, 0xf8, 0x75, 0xcb, 0xb7, - 0xe9, 0x45, 0xf3, 0x9b, 0x37, 0xf7, 0xb4, 0xfd, 0xf9, 0xeb, 0x00, 0x00, 0x00, 0xff, 0xff, 0x5d, - 0x66, 0xca, 0x39, 0x0c, 0x07, 0x00, 0x00, + // 831 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x55, 0xdd, 0x8e, 0xdb, 0x44, + 0x14, 0xc6, 0x71, 0xeb, 0xdd, 0x4c, 0xa0, 0x6a, 0xa7, 0x65, 0xb1, 0xf8, 0x09, 0x91, 0x41, 0xd5, + 0x82, 0xd0, 0x2e, 0x5a, 0x04, 0x02, 0x89, 0x9b, 0xec, 0xa6, 0x82, 0xd0, 0x6c, 0x88, 0xec, 0x50, + 0x24, 0x6e, 0x90, 0xe3, 0x9c, 0x24, 0x23, 0x9c, 0x19, 0x6b, 0x3c, 0xd9, 0xca, 0xaf, 0xc0, 0x13, + 0xf0, 0x2e, 0xbc, 0x00, 0x77, 0xf0, 0x08, 0x68, 0x79, 0x0b, 0xae, 0xaa, 0x33, 0x33, 0xfe, 0xdb, + 0x26, 0x57, 0xc9, 0xf9, 0xbe, 0x6f, 0xce, 0x9c, 0x33, 0xe7, 0xc7, 0xe4, 0xbd, 0x4c, 0x0a, 0x25, + 0xce, 0xb3, 0x45, 0x22, 0xf8, 0x8a, 0xad, 0xcf, 0xcd, 0xcf, 0x99, 0x46, 0xa9, 0x67, 0xac, 0xe0, + 0xef, 0x0e, 0xf1, 0xae, 0xf4, 0x5f, 0xda, 0x27, 0x64, 0x14, 0xab, 0x38, 0x01, 0xae, 0x40, 0xfa, + 0xce, 0xc0, 0x39, 0xed, 0x86, 0x0d, 0x84, 0x7e, 0x46, 0x1e, 0xcd, 0x24, 0xdb, 0xc6, 0xb2, 0x68, + 0xc8, 0x3a, 0x5a, 0xf6, 0x3a, 0x41, 0xdf, 0x25, 0xc7, 0x53, 0xb1, 0x84, 0x69, 0xbc, 0x05, 0xdf, + 0xd5, 0xa2, 0xca, 0xa6, 0x03, 0xd2, 0x8b, 0x60, 0xbd, 0x05, 0xae, 0x34, 0x7d, 0x4f, 0xd3, 0x4d, + 0x88, 0xbe, 0x4f, 0xba, 0xb3, 0x58, 0x2a, 0xa6, 0x98, 0xe0, 0x7e, 0x57, 0xf3, 0x35, 0x40, 0x3f, + 0x20, 0xee, 0xf0, 0x6a, 0xe2, 0xdf, 0x1f, 0x38, 0xa7, 0xbd, 0x8b, 0xde, 0x99, 0x4d, 0x6c, 0x78, + 0x35, 0x09, 0x11, 0xa7, 0x5f, 0x92, 0xde, 0x70, 0xa7, 0xc4, 0x33, 0x9e, 0xc8, 0x22, 0x53, 0xbe, + 0xa7, 0x65, 0x8f, 0x2b, 0x59, 0x4d, 0x85, 0x4d, 0x1d, 0x7d, 0x4a, 0xbc, 0xef, 0x44, 0x9e, 0xb3, + 0xcc, 0x3f, 0xd2, 0x27, 0x1e, 0x94, 0x27, 0x0c, 0x1a, 0x5a, 0x16, 0x6f, 0x9f, 0x4f, 0x22, 0xff, + 0xb8, 0x7d, 0xfb, 0x7c, 0x12, 0x85, 0x88, 0x07, 0xab, 0xd2, 0x0d, 0xfd, 0x9a, 0x10, 0xeb, 0x1b, + 0xb3, 0x70, 0xb4, 0xde, 0x6f, 0x3b, 0xad, 0xf9, 0xb0, 0xa1, 0xa5, 0x01, 0x79, 0x33, 0x04, 0x25, + 0x8b, 0x1f, 0x04, 0xe3, 0x93, 0xe1, 0xd4, 0xef, 0x0c, 0xdc, 0xd3, 0x6e, 0xd8, 0xc2, 0x02, 0x45, + 0x1e, 0xde, 0xf5, 0x41, 0x1f, 0x12, 0xf7, 0x39, 0x14, 0xb6, 0x76, 0xf8, 0x97, 0x3e, 0x25, 0x0f, + 0x5e, 0x80, 0x64, 0xab, 0x62, 0xcc, 0x13, 0xb1, 0x65, 0x7c, 0xad, 0x2b, 0x76, 0x1c, 0xde, 0x41, + 0x6b, 0xdd, 0x8f, 0x3b, 0xb5, 0x16, 0xa8, 0x73, 0x9b, 0xba, 0x12, 0x0d, 0xfe, 0x77, 0x74, 0xf6, + 0x7b, 0xf4, 0xce, 0x3e, 0x3d, 0xbd, 0x20, 0x4f, 0x0c, 0x12, 0x81, 0xbc, 0x01, 0xf9, 0xbd, 0xc8, + 0x15, 0xc7, 0x9a, 0x9b, 0x28, 0xf6, 0x72, 0x98, 0xfd, 0x15, 0xcb, 0x36, 0x20, 0xa3, 0x1d, 0x53, + 0x90, 0xdb, 0xf6, 0x69, 0x61, 0xd8, 0xac, 0xd7, 0x8c, 0xbf, 0x00, 0x99, 0xe3, 0xdb, 0x9a, 0x0e, + 0x6a, 0x20, 0x34, 0x22, 0x1f, 0x8d, 0x20, 0x93, 0x90, 0xc4, 0x0a, 0x96, 0xbf, 0xce, 0x24, 0xac, + 0x40, 0x9a, 0x6b, 0x5a, 0xae, 0xb1, 0x85, 0x8e, 0x2f, 0x3b, 0xbe, 0x13, 0x06, 0xb5, 0xfc, 0x90, + 0x3a, 0xf8, 0xd3, 0xd5, 0x8d, 0x47, 0x7d, 0x72, 0xf4, 0x8c, 0xc7, 0x8b, 0x14, 0x96, 0x36, 0xeb, + 0xd2, 0xd4, 0x7d, 0x2b, 0x52, 0x96, 0x14, 0xf3, 0xf9, 0xc4, 0xce, 0x46, 0x0d, 0xe0, 0xb9, 0x50, + 0xa4, 0x80, 0x9c, 0xc9, 0xa9, 0x34, 0x71, 0x5a, 0xe6, 0xe2, 0x37, 0xe0, 0x48, 0x99, 0x64, 0x2a, + 0x5b, 0xcf, 0xa5, 0x78, 0xc9, 0x8d, 0x1b, 0x1d, 0x31, 0xce, 0x65, 0x85, 0xd0, 0x8f, 0xc9, 0x5b, + 0x23, 0x58, 0xc5, 0xbb, 0x54, 0x59, 0x89, 0xa7, 0x25, 0x6d, 0x90, 0x7e, 0x4e, 0x1e, 0x9b, 0x20, + 0x9f, 0x43, 0x31, 0x61, 0x79, 0xa9, 0x3d, 0xd2, 0xf1, 0xef, 0xa3, 0xe8, 0x27, 0xc4, 0xd3, 0x31, + 0xe4, 0xb6, 0xd5, 0x1f, 0x35, 0x06, 0xcd, 0x10, 0xa1, 0x15, 0xd0, 0x6f, 0xc8, 0x49, 0xe3, 0xb5, + 0x47, 0x2c, 0xd7, 0xaf, 0x81, 0xc9, 0xe8, 0xd9, 0xd5, 0x0f, 0xfc, 0x76, 0xad, 0x68, 0x08, 0xe8, + 0x57, 0xe4, 0xc4, 0x5c, 0xae, 0x5d, 0xcd, 0xb0, 0x7c, 0xb9, 0x02, 0x9e, 0x80, 0x4f, 0x74, 0x68, + 0x07, 0x58, 0xcc, 0xe7, 0x3a, 0x9a, 0x59, 0x4f, 0x97, 0x42, 0xa8, 0x5c, 0xc9, 0x38, 0xf3, 0x7b, + 0x26, 0x9f, 0x3d, 0x54, 0xf0, 0x7b, 0x87, 0x74, 0xab, 0xd0, 0x71, 0x9b, 0x8d, 0x39, 0x53, 0x2c, + 0x4e, 0xaf, 0x63, 0x1e, 0xaf, 0x01, 0x57, 0x8f, 0x1d, 0x9c, 0xd7, 0x09, 0xdc, 0x58, 0x21, 0x64, + 0x29, 0x4b, 0x62, 0x3d, 0xcb, 0xa6, 0xb2, 0x4d, 0x08, 0xab, 0x30, 0x5c, 0x03, 0x57, 0x21, 0x24, + 0xe2, 0x06, 0x64, 0x61, 0x2b, 0xdc, 0x06, 0xb1, 0x03, 0x6c, 0x59, 0x6c, 0x99, 0x4b, 0x93, 0x3e, + 0x21, 0xf7, 0xb5, 0xd4, 0x16, 0xd8, 0x18, 0xf4, 0x67, 0x72, 0x62, 0xa2, 0x58, 0x62, 0x3b, 0xb2, + 0x04, 0x66, 0x52, 0xdc, 0xb0, 0x25, 0x48, 0xdf, 0x1b, 0xb8, 0xa7, 0xbd, 0x8b, 0x0f, 0x1b, 0x35, + 0xb9, 0xa3, 0xd0, 0x79, 0x86, 0x07, 0x8e, 0x07, 0x3f, 0x91, 0x77, 0x0e, 0x1c, 0xc1, 0x7e, 0x1b, + 0x26, 0x09, 0xe4, 0xb9, 0x90, 0xe3, 0x51, 0xf9, 0x1d, 0xa8, 0x11, 0xec, 0xd5, 0x08, 0x12, 0x09, + 0x6a, 0x3c, 0xb2, 0x0f, 0x51, 0xd9, 0x01, 0x6b, 0xad, 0x5e, 0xdc, 0x47, 0xb8, 0x2a, 0xcd, 0x90, + 0xe8, 0xbd, 0x71, 0x42, 0xbc, 0xd1, 0x34, 0x8a, 0xaa, 0x9d, 0x66, 0x2d, 0x4c, 0x7f, 0x3c, 0x43, + 0xd8, 0xd5, 0xb0, 0x31, 0xf0, 0xaa, 0x61, 0x9a, 0x8a, 0x97, 0xe8, 0xe4, 0x9e, 0x76, 0x52, 0xd9, + 0x97, 0xdf, 0xfe, 0x75, 0xdb, 0x77, 0xfe, 0xb9, 0xed, 0x3b, 0xff, 0xde, 0xf6, 0x9d, 0x3f, 0xfe, + 0xeb, 0xbf, 0xf1, 0xcb, 0xa7, 0x6b, 0xa6, 0x36, 0xbb, 0xc5, 0x59, 0x22, 0xb6, 0xe7, 0x9b, 0x38, + 0xdf, 0xb0, 0x44, 0xc8, 0x0c, 0x3f, 0x7b, 0xf9, 0x2e, 0x3d, 0x6f, 0x7f, 0x0c, 0x17, 0x9e, 0xb6, + 0xbf, 0x78, 0x15, 0x00, 0x00, 0xff, 0xff, 0x4f, 0xaf, 0xda, 0x68, 0x25, 0x07, 0x00, 0x00, } func (m *Config) Marshal() (dAtA []byte, err error) { @@ -978,9 +981,9 @@ func (m *TLS) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } - if m.PreferServerCipherSuites { + if m.Deprecated_PreferServerCipherSuites { i-- - if m.PreferServerCipherSuites { + if m.Deprecated_PreferServerCipherSuites { dAtA[i] = 1 } else { dAtA[i] = 0 @@ -1451,7 +1454,7 @@ func (m *TLS) Size() (n int) { if l > 0 { n += 1 + l + sovConfig(uint64(l)) } - if m.PreferServerCipherSuites { + if m.Deprecated_PreferServerCipherSuites { n += 2 } if m.XXX_unrecognized != nil { @@ -2338,7 +2341,7 @@ func (m *TLS) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 5: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field PreferServerCipherSuites", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Deprecated_PreferServerCipherSuites", wireType) } var v int for shift := uint(0); ; shift += 7 { @@ -2355,7 +2358,7 @@ func (m *TLS) Unmarshal(dAtA []byte) error { break } } - m.PreferServerCipherSuites = bool(v != 0) + m.Deprecated_PreferServerCipherSuites = bool(v != 0) default: iNdEx = preIndex skippy, err := skipConfig(dAtA[iNdEx:]) diff --git a/proto/pbconfig/config.proto b/proto/pbconfig/config.proto index 8483c9626..08a3fe5ec 100644 --- a/proto/pbconfig/config.proto +++ b/proto/pbconfig/config.proto @@ -32,7 +32,9 @@ message TLS { bool VerifyServerHostname = 2; string CipherSuites = 3; string MinVersion = 4; - bool PreferServerCipherSuites = 5; + // Deprecated_PreferServerCipherSuites is deprecated. It is no longer + // populated and should be ignored by clients. + bool Deprecated_PreferServerCipherSuites = 5 [deprecated = true]; } message ACL { diff --git a/tlsutil/config.go b/tlsutil/config.go index 4d0bd0525..c6157da93 100644 --- a/tlsutil/config.go +++ b/tlsutil/config.go @@ -18,6 +18,7 @@ import ( "github.com/hashicorp/go-multierror" "github.com/hashicorp/consul/logging" + "github.com/hashicorp/consul/proto/pbconfig" ) // ALPNWrapper is a function that is used to wrap a non-TLS connection and @@ -44,42 +45,14 @@ var tlsLookup = map[string]uint16{ "tls13": tls.VersionTLS13, } -// Config used to create tls.Config -type Config struct { +// ProtocolConfig contains configuration for a given protocol. +type ProtocolConfig struct { // VerifyIncoming is used to verify the authenticity of incoming // connections. This means that TCP requests are forbidden, only // allowing for TLS. TLS connections must match a provided certificate // authority. This can be used to force client auth. VerifyIncoming bool - // VerifyIncomingRPC is used to verify the authenticity of incoming RPC - // connections. This means that TCP requests are forbidden, only - // allowing for TLS. TLS connections must match a provided certificate - // authority. This can be used to force client auth. - VerifyIncomingRPC bool - - // VerifyIncomingHTTPS is used to verify the authenticity of incoming - // HTTPS connections. This means that TCP requests are forbidden, only - // allowing for TLS. TLS connections must match a provided certificate - // authority. This can be used to force client auth. - VerifyIncomingHTTPS bool - - // VerifyOutgoing is used to verify the authenticity of outgoing - // connections. This means that TLS requests are used, and TCP - // requests are not made. TLS connections must match a provided - // certificate authority. This is used to verify authenticity of server - // nodes. - VerifyOutgoing bool - - // VerifyServerHostname is used to enable hostname verification of - // servers. This ensures that the certificate presented is valid for - // server... This prevents a compromised client - // from being restarted as a server, and then intercepting request - // traffic as well as being added as a raft peer. This should be - // enabled by default with VerifyOutgoing, but for legacy reasons we - // cannot break existing clients. - VerifyServerHostname bool - // CAFile is a path to a certificate authority file. This is used with // VerifyIncoming or VerifyOutgoing to verify the TLS connection. CAFile string @@ -97,6 +70,62 @@ type Config struct { // connections. Must be provided to serve TLS connections. KeyFile string + // TLSMinVersion is the minimum accepted TLS version that can be used. + TLSMinVersion string + + // CipherSuites is the list of TLS cipher suites to use. + // + // The values should be a list of the following values: + // + // TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA + // TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 + // TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + // TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA + // TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + // TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA + // TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 + // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + // TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA + // TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + // + // todo(fs): IMHO, we should also support the raw 0xNNNN values from + // todo(fs): https://golang.org/pkg/crypto/tls/#pkg-constants + // todo(fs): since they are standardized by IANA. + CipherSuites []uint16 + + // VerifyOutgoing is used to verify the authenticity of outgoing + // connections. This means that TLS requests are used, and TCP + // requests are not made. TLS connections must match a provided + // certificate authority. This is used to verify authenticity of server + // nodes. + // + // Note: this setting doesn't apply to the gRPC configuration, as Consul + // makes no outgoing connections using this protocol. + VerifyOutgoing bool + + // VerifyServerHostname is used to enable hostname verification of + // servers. This ensures that the certificate presented is valid for + // server... This prevents a compromised client + // from being restarted as a server, and then intercepting request + // traffic as well as being added as a raft peer. This should be + // enabled by default with VerifyOutgoing, but for legacy reasons we + // cannot break existing clients. + // + // Note: this setting only applies to the Internal RPC configuration. + VerifyServerHostname bool +} + +// Config configures the Configurator. +type Config struct { + // InternalRPC is used to configure the internal multiplexed RPC protocol. + InternalRPC ProtocolConfig + + // GRPC is used to configure the external (e.g. xDS) gRPC protocol. + GRPC ProtocolConfig + + // HTTPS is used to configure the external HTTPS protocol. + HTTPS ProtocolConfig + // Node name is the name we use to advertise. Defaults to hostname. NodeName string @@ -107,16 +136,6 @@ type Config struct { // Domain is the Consul TLD being used. Defaults to "consul." Domain string - // TLSMinVersion is the minimum accepted TLS version that can be used. - TLSMinVersion string - - // CipherSuites is the list of TLS cipher suites to use. - CipherSuites []uint16 - - // PreferServerCipherSuites specifies whether to prefer the server's - // ciphersuite over the client ciphersuites. - PreferServerCipherSuites bool - // EnableAgentTLSForChecks is used to apply the agent's TLS settings in // order to configure the HTTP client used for health checks. Enabling // this allows HTTP checks to present a client certificate and verify @@ -151,27 +170,30 @@ func SpecificDC(dc string, tlsWrap DCWrapper) Wrapper { } } -// autoTLS stores configuration that is received from the auto-encrypt or -// auto-config features. -type autoTLS struct { - extraCAPems []string - connectCAPems []string - cert *tls.Certificate - verifyServerHostname bool -} +// protocolConfig contains the loaded state (e.g. x509 certificates) for a given +// ProtocolConfig. +type protocolConfig struct { + // cert is the TLS certificate configured manually by the cert_file/key_file + // options in the configuration file. + cert *tls.Certificate -// manual stores the TLS CA and cert received from Configurator.Update which -// generally comes from the agent configuration. -type manual struct { - caPems []string - cert *tls.Certificate - // caPool containing only the caPems. This CertPool should be used instead of - // the Configurator.caPool when only the Agent TLS CA is allowed. - caPool *x509.CertPool + // manualCAPEMs contains the PEM-encoded CA certificates provided manually by + // the ca_file/ca_path options in the configuration file. + manualCAPEMs []string + + // manualCAPool is a pool containing only manualCAPEM, for cases where it is + // not appropriate to trust the Connect CA (e.g. when verifying server identity + // in AuthorizeServerConn). + manualCAPool *x509.CertPool + + // combinedCAPool is a pool containing both manualCAPEMs and the certificates + // received from auto-config/auto-encrypt. + combinedCAPool *x509.CertPool } // Configurator provides tls.Config and net.Dial wrappers to enable TLS for -// clients and servers, for both HTTPS and RPC requests. +// clients and servers, for internal RPC, and external gRPC and HTTPS connections. +// // Configurator receives an initial TLS configuration from agent configuration, // and receives updates from config reloads, auto-encrypt, and auto-config. type Configurator struct { @@ -181,15 +203,25 @@ type Configurator struct { version uint64 // lock synchronizes access to all fields on this struct except for logger and version. - lock sync.RWMutex - base *Config - autoTLS autoTLS - manual manual - caPool *x509.CertPool + lock sync.RWMutex + base *Config // peerDatacenterUseTLS is a map of DC name to a bool indicating if the DC // uses TLS for RPC requests. peerDatacenterUseTLS map[string]bool + grpc protocolConfig + https protocolConfig + internalRPC protocolConfig + + // autoTLS stores configuration that is received from the auto-encrypt or + // auto-config features. + autoTLS struct { + extraCAPems []string + connectCAPems []string + cert *tls.Certificate + verifyServerHostname bool + } + // logger is not protected by a lock. It must never be changed after // Configurator is created. logger hclog.Logger @@ -215,11 +247,12 @@ func NewConfigurator(config Config, logger hclog.Logger) (*Configurator, error) return c, nil } -// ManualCAPems returns the currently loaded CAs in PEM format. +// ManualCAPems returns the currently loaded CAs for the internal RPC protocol +// in PEM format. It is used in the auto-config/auto-encrypt endpoints. func (c *Configurator) ManualCAPems() []string { c.lock.RLock() defer c.lock.RUnlock() - return c.manual.caPems + return c.internalRPC.manualCAPEMs } // Update updates the internal configuration which is used to generate @@ -229,36 +262,92 @@ func (c *Configurator) Update(config Config) error { c.lock.Lock() defer c.lock.Unlock() - cert, err := loadKeyPair(config.CertFile, config.KeyFile) + grpc, err := c.loadProtocolConfig(config, config.GRPC) if err != nil { return err } - pems, err := LoadCAs(config.CAFile, config.CAPath) + + https, err := c.loadProtocolConfig(config, config.HTTPS) if err != nil { return err } - caPool, err := newX509CertPool(pems, c.autoTLS.extraCAPems, c.autoTLS.connectCAPems) - if err != nil { - return err - } - if err = validateConfig(config, caPool, cert); err != nil { - return err - } - manualCAPool, err := newX509CertPool(pems) + + internalRPC, err := c.loadProtocolConfig(config, config.InternalRPC) if err != nil { return err } c.base = &config - c.manual.cert = cert - c.manual.caPems = pems - c.manual.caPool = manualCAPool - c.caPool = caPool + c.grpc = *grpc + c.https = *https + c.internalRPC = *internalRPC + atomic.AddUint64(&c.version, 1) c.log("Update") return nil } +// loadProtocolConfig loads the certificates etc. for a given ProtocolConfig +// and performs validation. +func (c *Configurator) loadProtocolConfig(base Config, lc ProtocolConfig) (*protocolConfig, error) { + if min := lc.TLSMinVersion; min != "" { + if _, ok := tlsLookup[min]; !ok { + versions := strings.Join(tlsVersions(), ", ") + return nil, fmt.Errorf("TLSMinVersion: value %s not supported, please specify one of [%s]", min, versions) + } + } + + cert, err := loadKeyPair(lc.CertFile, lc.KeyFile) + if err != nil { + return nil, err + } + pems, err := LoadCAs(lc.CAFile, lc.CAPath) + if err != nil { + return nil, err + } + manualPool, err := newX509CertPool(pems) + if err != nil { + return nil, err + } + combinedPool, err := newX509CertPool(pems, c.autoTLS.connectCAPems, c.autoTLS.extraCAPems) + if err != nil { + return nil, err + } + + if lc.VerifyIncoming { + // Both auto-config and auto-encrypt require verifying the connection from the + // client to the server for secure operation. In order to be able to verify the + // server's certificate we must have some CA certs already provided. Therefore, + // even though both of those features can push down extra CA certificates which + // could be used to verify incoming connections, we still must consider it an + // error if none are provided in the initial configuration as those features + // cannot be successfully enabled without providing CA certificates to use those + // features. + if combinedPool == nil { + return nil, fmt.Errorf("VerifyIncoming set but no CA certificates were provided") + } + + // We will use the auto_encrypt/auto_config cert for TLS in the incoming APIs + // when available. Therefore the check here will ensure that either we enabled + // one of those two features or a certificate and key were provided manually + if cert == nil && !base.AutoTLS { + return nil, fmt.Errorf("VerifyIncoming requires either a Cert and Key pair in the configuration file, or auto_encrypt/auto_config be enabled") + } + } + + // Ensure we have a CA if VerifyOutgoing is set. + if lc.VerifyOutgoing && combinedPool == nil { + return nil, fmt.Errorf("VerifyOutgoing set but no CA certificates were provided") + } + + return &protocolConfig{ + cert: cert, + manualCAPEMs: pems, + manualCAPool: manualPool, + combinedCAPool: combinedPool, + }, nil +} + // UpdateAutoTLSCA updates the autoEncrypt.caPems. This is supposed to be called // from the server in order to be able to accept TLS connections with TLS // certificates. @@ -267,15 +356,30 @@ func (c *Configurator) UpdateAutoTLSCA(connectCAPems []string) error { c.lock.Lock() defer c.lock.Unlock() - pool, err := newX509CertPool(c.manual.caPems, c.autoTLS.extraCAPems, connectCAPems) + makePool := func(l protocolConfig) (*x509.CertPool, error) { + return newX509CertPool(l.manualCAPEMs, c.autoTLS.extraCAPems, connectCAPems) + } + + // Make all of the pools up-front (before assigning anything) so that if any of + // them fails, we aren't left in a half-applied state. + internalRPCPool, err := makePool(c.internalRPC) if err != nil { return err } - if err = validateConfig(*c.base, pool, c.manual.cert); err != nil { + grpcPool, err := makePool(c.grpc) + if err != nil { return err } + httpsPool, err := makePool(c.https) + if err != nil { + return err + } + c.autoTLS.connectCAPems = connectCAPems - c.caPool = pool + c.internalRPC.combinedCAPool = internalRPCPool + c.grpc.combinedCAPool = grpcPool + c.https.combinedCAPool = httpsPool + atomic.AddUint64(&c.version, 1) c.log("UpdateAutoTLSCA") return nil @@ -308,15 +412,33 @@ func (c *Configurator) UpdateAutoTLS(manualCAPems, connectCAPems []string, pub, c.lock.Lock() defer c.lock.Unlock() - pool, err := newX509CertPool(c.manual.caPems, manualCAPems, connectCAPems) + makePool := func(l protocolConfig) (*x509.CertPool, error) { + return newX509CertPool(l.manualCAPEMs, manualCAPems, connectCAPems) + } + + // Make all of the pools up-front (before assigning anything) so that if any of + // them fails, we aren't left in a half-applied state. + internalRPCPool, err := makePool(c.internalRPC) if err != nil { return err } + grpcPool, err := makePool(c.grpc) + if err != nil { + return err + } + httpsPool, err := makePool(c.https) + if err != nil { + return err + } + c.autoTLS.extraCAPems = manualCAPems c.autoTLS.connectCAPems = connectCAPems c.autoTLS.cert = &cert - c.caPool = pool c.autoTLS.verifyServerHostname = verifyServerHostname + c.internalRPC.combinedCAPool = internalRPCPool + c.grpc.combinedCAPool = grpcPool + c.https.combinedCAPool = httpsPool + atomic.AddUint64(&c.version, 1) c.log("UpdateAutoTLS") return nil @@ -368,46 +490,6 @@ func newX509CertPool(groups ...[]string) (*x509.CertPool, error) { return pool, nil } -// validateConfig checks that config is valid and does not conflict with the pool -// or cert. -func validateConfig(config Config, pool *x509.CertPool, cert *tls.Certificate) error { - // Check if a minimum TLS version was set - if config.TLSMinVersion != "" { - if _, ok := tlsLookup[config.TLSMinVersion]; !ok { - versions := strings.Join(tlsVersions(), ", ") - return fmt.Errorf("TLSMinVersion: value %s not supported, please specify one of [%s]", config.TLSMinVersion, versions) - } - } - - // Ensure we have a CA if VerifyOutgoing is set - if config.VerifyOutgoing && pool == nil { - return fmt.Errorf("VerifyOutgoing set, and no CA certificate provided!") - } - - // Ensure we have a CA and cert if VerifyIncoming is set - if config.anyVerifyIncoming() { - if pool == nil { - // both auto-config and auto-encrypt require verifying the connection from the client to the server for secure - // operation. In order to be able to verify the servers certificate we must have some CA certs already provided. - // Therefore, even though both of those features can push down extra CA certificates which could be used to - // verify incoming connections, we still must consider it an error if none are provided in the initial configuration - // as those features cannot be successfully enabled without providing CA certificates to use those features. - return fmt.Errorf("VerifyIncoming set but no CA certificates were provided") - } - - // We will use the auto_encrypt/auto_config cert for TLS in the incoming APIs when available. Therefore the check - // here will ensure that either we enabled one of those two features or a certificate and key were provided manually - if cert == nil && !config.AutoTLS { - return fmt.Errorf("VerifyIncoming requires either a Cert and Key pair in the configuration file, or auto_encrypt/auto_config be enabled") - } - } - return nil -} - -func (c Config) anyVerifyIncoming() bool { - return c.VerifyIncoming || c.VerifyIncomingRPC || c.VerifyIncomingHTTPS -} - func loadKeyPair(certFile, keyFile string) (*tls.Certificate, error) { if certFile == "" || keyFile == "" { return nil, nil @@ -465,33 +547,43 @@ func LoadCAs(caFile, caPath string) ([]string, error) { return pems, nil } +// internalRPCTLSConfig generates a *tls.Config for the internal RPC protocol. +// +// This function acquires a read lock because it reads from the config. +func (c *Configurator) internalRPCTLSConfig(verifyIncoming bool) *tls.Config { + c.lock.RLock() + defer c.lock.RUnlock() + + config := c.commonTLSConfig( + c.internalRPC, + c.base.InternalRPC, + verifyIncoming, + ) + config.InsecureSkipVerify = !c.base.InternalRPC.VerifyServerHostname + + return config +} + // commonTLSConfig generates a *tls.Config from the base configuration the // Configurator has. It accepts an additional flag in case a config is needed // for incoming TLS connections. -// This function acquires a read lock because it reads from the config. -func (c *Configurator) commonTLSConfig(verifyIncoming bool) *tls.Config { - // this needs to be outside of RLock because it acquires an RLock itself - verifyServerHostname := c.VerifyServerHostname() - - c.lock.RLock() - defer c.lock.RUnlock() - tlsConfig := &tls.Config{ - InsecureSkipVerify: !verifyServerHostname, - } +func (c *Configurator) commonTLSConfig(state protocolConfig, cfg ProtocolConfig, verifyIncoming bool) *tls.Config { + var tlsConfig tls.Config // Set the cipher suites - if len(c.base.CipherSuites) != 0 { - tlsConfig.CipherSuites = c.base.CipherSuites + if len(cfg.CipherSuites) != 0 { + tlsConfig.CipherSuites = cfg.CipherSuites } - tlsConfig.PreferServerCipherSuites = c.base.PreferServerCipherSuites - // GetCertificate is used when acting as a server and responding to // client requests. Default to the manually configured cert, but allow // autoEncrypt cert too so that a client can encrypt incoming // connections without having a manual cert configured. tlsConfig.GetCertificate = func(*tls.ClientHelloInfo) (*tls.Certificate, error) { - return c.Cert(), nil + if state.cert != nil { + return state.cert, nil + } + return c.autoTLS.cert, nil } // GetClientCertificate is used when acting as a client and responding @@ -500,7 +592,7 @@ func (c *Configurator) commonTLSConfig(verifyIncoming bool) *tls.Config { tlsConfig.GetClientCertificate = func(*tls.CertificateRequestInfo) (*tls.Certificate, error) { cert := c.autoTLS.cert if cert == nil { - cert = c.manual.cert + cert = state.cert } if cert == nil { @@ -512,39 +604,53 @@ func (c *Configurator) commonTLSConfig(verifyIncoming bool) *tls.Config { return cert, nil } - tlsConfig.ClientCAs = c.caPool - tlsConfig.RootCAs = c.caPool + tlsConfig.ClientCAs = state.combinedCAPool + tlsConfig.RootCAs = state.combinedCAPool // This is possible because tlsLookup also contains "" with golang's // default (tls10). And because the initial check makes sure the // version correctly matches. - tlsConfig.MinVersion = tlsLookup[c.base.TLSMinVersion] + tlsConfig.MinVersion = tlsLookup[cfg.TLSMinVersion] // Set ClientAuth if necessary if verifyIncoming { tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert } - return tlsConfig + return &tlsConfig } +// Cert returns the certificate used for connections on the internal RPC protocol. +// // This function acquires a read lock because it reads from the config. func (c *Configurator) Cert() *tls.Certificate { c.lock.RLock() defer c.lock.RUnlock() - cert := c.manual.cert + cert := c.internalRPC.cert if cert == nil { cert = c.autoTLS.cert } return cert } -// VerifyIncomingRPC returns true if the configuration has enabled either -// VerifyIncoming, or VerifyIncomingRPC +// GRPCTLSConfigured returns whether there's a TLS certificate configured for +// gRPC (either manually or by auto-config/auto-encrypt). It is checked, along +// with the presence of an HTTPS port, to determine whether to enable TLS on +// incoming gRPC connections. +// +// This function acquires a read lock because it reads from the config. +func (c *Configurator) GRPCTLSConfigured() bool { + c.lock.RLock() + defer c.lock.RUnlock() + return c.grpc.cert != nil || c.autoTLS.cert != nil +} + +// VerifyIncomingRPC returns true if we should verify incoming connnections to +// the internal RPC protocol. func (c *Configurator) VerifyIncomingRPC() bool { c.lock.RLock() defer c.lock.RUnlock() - return c.base.VerifyIncoming || c.base.VerifyIncomingRPC + return c.base.InternalRPC.VerifyIncoming } // This function acquires a read lock because it reads from the config. @@ -553,15 +659,15 @@ func (c *Configurator) outgoingRPCTLSEnabled() bool { defer c.lock.RUnlock() // use TLS if AutoEncrypt or VerifyOutgoing are enabled. - return c.base.AutoTLS || c.base.VerifyOutgoing + return c.base.AutoTLS || c.base.InternalRPC.VerifyOutgoing } -// MutualTLSCapable returns true if Configurator has a CA and a local TLS -// certificate configured. +// MutualTLSCapable returns true if Configurator has a CA and a local TL +// certificate configured on the internal RPC protocol. func (c *Configurator) MutualTLSCapable() bool { c.lock.RLock() defer c.lock.RUnlock() - return c.caPool != nil && (c.autoTLS.cert != nil || c.manual.cert != nil) + return c.internalRPC.combinedCAPool != nil && (c.autoTLS.cert != nil || c.internalRPC.cert != nil) } // This function acquires a read lock because it reads from the config. @@ -571,13 +677,14 @@ func (c *Configurator) verifyOutgoing() bool { // If AutoEncryptTLS is enabled and there is a CA, then verify // outgoing. - if c.base.AutoTLS && c.caPool != nil { + if c.base.AutoTLS && c.internalRPC.combinedCAPool != nil { return true } - return c.base.VerifyOutgoing + return c.base.InternalRPC.VerifyOutgoing } +// This function acquires a read lock because it reads from the config. func (c *Configurator) ServerSNI(dc, nodeName string) string { // Strip the trailing '.' from the domain if any domain := strings.TrimSuffix(c.domain(), ".") @@ -610,19 +717,45 @@ func (c *Configurator) serverNameOrNodeName() string { func (c *Configurator) VerifyServerHostname() bool { c.lock.RLock() defer c.lock.RUnlock() - return c.base.VerifyServerHostname || c.autoTLS.verifyServerHostname + return c.base.InternalRPC.VerifyServerHostname || c.autoTLS.verifyServerHostname } -// IncomingGRPCConfig generates a *tls.Config for incoming GRPC connections. -func (c *Configurator) IncomingGRPCConfig() *tls.Config { - c.log("IncomingGRPCConfig") +// AutoConfigTLSSettings constructs the pbconfig.TLS that will be returned by +// servers in the auto-config endpoint. +func (c *Configurator) AutoConfigTLSSettings() (*pbconfig.TLS, error) { + c.lock.RLock() + defer c.lock.RUnlock() - // false has the effect that this config doesn't require a client cert - // verification. This is because there is no verify_incoming_grpc - // configuration option. And using verify_incoming would be backwards - // incompatible, because even if it was set before, it didn't have an - // effect on the grpc server. - config := c.commonTLSConfig(false) + cfg := c.base.InternalRPC + + cipherString, err := CipherString(cfg.CipherSuites) + if err != nil { + return nil, err + } + + return &pbconfig.TLS{ + VerifyOutgoing: cfg.VerifyOutgoing, + VerifyServerHostname: cfg.VerifyServerHostname || c.autoTLS.verifyServerHostname, + MinVersion: cfg.TLSMinVersion, + CipherSuites: cipherString, + }, nil +} + +// IncomingGRPCConfig generates a *tls.Config for incoming external (e.g. xDS) +// GRPC connections. +// +// This function acquires a read lock because it reads from the config. +func (c *Configurator) IncomingGRPCConfig() *tls.Config { + c.log("IncomingGRPConfig") + + c.lock.RLock() + defer c.lock.RUnlock() + + config := c.commonTLSConfig( + c.grpc, + c.base.GRPC, + c.base.GRPC.VerifyIncoming, + ) config.GetConfigForClient = func(*tls.ClientHelloInfo) (*tls.Config, error) { return c.IncomingGRPCConfig(), nil } @@ -632,7 +765,7 @@ func (c *Configurator) IncomingGRPCConfig() *tls.Config { // IncomingRPCConfig generates a *tls.Config for incoming RPC connections. func (c *Configurator) IncomingRPCConfig() *tls.Config { c.log("IncomingRPCConfig") - config := c.commonTLSConfig(c.VerifyIncomingRPC()) + config := c.internalRPCTLSConfig(c.base.InternalRPC.VerifyIncoming) config.GetConfigForClient = func(*tls.ClientHelloInfo) (*tls.Config, error) { return c.IncomingRPCConfig(), nil } @@ -645,7 +778,7 @@ func (c *Configurator) IncomingALPNRPCConfig(alpnProtos []string) *tls.Config { c.log("IncomingALPNRPCConfig") // Since the ALPN-RPC variation is indirectly exposed to the internet via // mesh gateways we force mTLS and full server name verification. - config := c.commonTLSConfig(true) + config := c.internalRPCTLSConfig(true) config.InsecureSkipVerify = false config.GetConfigForClient = func(*tls.ClientHelloInfo) (*tls.Config, error) { @@ -662,7 +795,7 @@ func (c *Configurator) IncomingALPNRPCConfig(alpnProtos []string) *tls.Config { // usecase ever. func (c *Configurator) IncomingInsecureRPCConfig() *tls.Config { c.log("IncomingInsecureRPCConfig") - config := c.commonTLSConfig(false) + config := c.internalRPCTLSConfig(false) config.GetConfigForClient = func(*tls.ClientHelloInfo) (*tls.Config, error) { return c.IncomingInsecureRPCConfig(), nil } @@ -674,10 +807,13 @@ func (c *Configurator) IncomingHTTPSConfig() *tls.Config { c.log("IncomingHTTPSConfig") c.lock.RLock() - verifyIncoming := c.base.VerifyIncoming || c.base.VerifyIncomingHTTPS - c.lock.RUnlock() + defer c.lock.RUnlock() - config := c.commonTLSConfig(verifyIncoming) + config := c.commonTLSConfig( + c.https, + c.base.HTTPS, + c.base.HTTPS.VerifyIncoming, + ) config.NextProtos = []string{"h2", "http/1.1"} config.GetConfigForClient = func(*tls.ClientHelloInfo) (*tls.Config, error) { return c.IncomingHTTPSConfig(), nil @@ -706,22 +842,22 @@ func (c *Configurator) OutgoingTLSConfigForCheck(skipVerify bool, serverName str if serverName == "" { serverName = c.serverNameOrNodeName() } - config := c.commonTLSConfig(false) + config := c.internalRPCTLSConfig(false) config.InsecureSkipVerify = skipVerify config.ServerName = serverName return config } -// OutgoingRPCConfig generates a *tls.Config for outgoing RPC connections. If -// there is a CA or VerifyOutgoing is set, a *tls.Config will be provided, -// otherwise we assume that no TLS should be used. +// OutgoingRPCConfig generates a *tls.Config for outgoing internal RPC +// connections. If there is a CA or VerifyOutgoing is set, a *tls.Config +// will be provided, otherwise we assume that no TLS should be used. func (c *Configurator) OutgoingRPCConfig() *tls.Config { c.log("OutgoingRPCConfig") if !c.outgoingRPCTLSEnabled() { return nil } - return c.commonTLSConfig(false) + return c.internalRPCTLSConfig(false) } // outgoingALPNRPCConfig generates a *tls.Config for outgoing RPC connections @@ -737,7 +873,7 @@ func (c *Configurator) outgoingALPNRPCConfig() *tls.Config { // Since the ALPN-RPC variation is indirectly exposed to the internet via // mesh gateways we force mTLS and full server name verification. - config := c.commonTLSConfig(true) + config := c.internalRPCTLSConfig(true) config.InsecureSkipVerify = false return config } @@ -912,7 +1048,7 @@ func (c *Configurator) AuthorizeServerConn(dc string, conn TLSConn) error { } c.lock.RLock() - caPool := c.manual.caPool + caPool := c.internalRPC.manualCAPool c.lock.RUnlock() expected := c.ServerSNI(dc, "") diff --git a/tlsutil/config_test.go b/tlsutil/config_test.go index cad4baac5..abcade402 100644 --- a/tlsutil/config_test.go +++ b/tlsutil/config_test.go @@ -21,242 +21,729 @@ import ( "github.com/hashicorp/consul/sdk/testutil" ) -func startRPCTLSServer(config *Config) (net.Conn, chan error) { - return startTLSServer(config, nil, false) +func TestConfigurator_IncomingConfig_Common(t *testing.T) { + // if this test is failing because of expired certificates + // use the procedure in test/CA-GENERATION.md + testCases := map[string]struct { + setupFn func(ProtocolConfig) Config + configFn func(*Configurator) *tls.Config + }{ + "Internal RPC": { + func(lc ProtocolConfig) Config { return Config{InternalRPC: lc} }, + func(c *Configurator) *tls.Config { return c.IncomingRPCConfig() }, + }, + "gRPC": { + func(lc ProtocolConfig) Config { return Config{GRPC: lc} }, + func(c *Configurator) *tls.Config { return c.IncomingGRPCConfig() }, + }, + "HTTPS": { + func(lc ProtocolConfig) Config { return Config{HTTPS: lc} }, + func(c *Configurator) *tls.Config { return c.IncomingHTTPSConfig() }, + }, + } + + for desc, tc := range testCases { + t.Run(desc, func(t *testing.T) { + t.Run("MinTLSVersion", func(t *testing.T) { + cfg := ProtocolConfig{ + TLSMinVersion: "tls13", + CertFile: "../test/hostname/Alice.crt", + KeyFile: "../test/hostname/Alice.key", + } + c := makeConfigurator(t, tc.setupFn(cfg)) + + client, errc, _ := startTLSServer(tc.configFn(c)) + if client == nil { + t.Fatalf("startTLSServer err: %v", <-errc) + } + + tlsClient := tls.Client(client, &tls.Config{ + InsecureSkipVerify: true, + MaxVersion: tls.VersionTLS12, + }) + + err := tlsClient.Handshake() + require.Error(t, err) + require.Contains(t, err.Error(), "version not supported") + }) + + t.Run("CipherSuites", func(t *testing.T) { + cfg := ProtocolConfig{ + CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384}, + CertFile: "../test/hostname/Alice.crt", + KeyFile: "../test/hostname/Alice.key", + } + c := makeConfigurator(t, tc.setupFn(cfg)) + + client, errc, _ := startTLSServer(tc.configFn(c)) + if client == nil { + t.Fatalf("startTLSServer err: %v", <-errc) + } + + tlsClient := tls.Client(client, &tls.Config{ + InsecureSkipVerify: true, + MaxVersion: tls.VersionTLS12, // TLS 1.3 cipher suites are not configurable. + }) + require.NoError(t, tlsClient.Handshake()) + + cipherSuite := tlsClient.ConnectionState().CipherSuite + require.Equal(t, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, cipherSuite) + }) + + t.Run("manually configured certificate is preferred over AutoTLS", func(t *testing.T) { + // Manually configure Alice's certifcate. + cfg := ProtocolConfig{ + CertFile: "../test/hostname/Alice.crt", + KeyFile: "../test/hostname/Alice.key", + } + c := makeConfigurator(t, tc.setupFn(cfg)) + + // Set Bob's certificate via auto TLS. + bobCert := loadFile(t, "../test/hostname/Bob.crt") + bobKey := loadFile(t, "../test/hostname/Bob.key") + require.NoError(t, c.UpdateAutoTLSCert(bobCert, bobKey)) + + client, errc, _ := startTLSServer(tc.configFn(c)) + if client == nil { + t.Fatalf("startTLSServer err: %v", <-errc) + } + + // Perform a handshake and check the server presented Alice's certificate. + tlsClient := tls.Client(client, &tls.Config{InsecureSkipVerify: true}) + require.NoError(t, tlsClient.Handshake()) + + certificates := tlsClient.ConnectionState().PeerCertificates + require.NotEmpty(t, certificates) + require.Equal(t, "Alice", certificates[0].Subject.CommonName) + + // Check the server side of the handshake succeded. + require.NoError(t, <-errc) + }) + + t.Run("AutoTLS certificate is presented if no certificate was configured manually", func(t *testing.T) { + // No manually configured certificate. + c := makeConfigurator(t, Config{}) + + // Set Bob's certificate via auto TLS. + bobCert := loadFile(t, "../test/hostname/Bob.crt") + bobKey := loadFile(t, "../test/hostname/Bob.key") + require.NoError(t, c.UpdateAutoTLSCert(bobCert, bobKey)) + + client, errc, _ := startTLSServer(tc.configFn(c)) + if client == nil { + t.Fatalf("startTLSServer err: %v", <-errc) + } + + // Perform a handshake and check the server presented Bobs's certificate. + tlsClient := tls.Client(client, &tls.Config{InsecureSkipVerify: true}) + require.NoError(t, tlsClient.Handshake()) + + certificates := tlsClient.ConnectionState().PeerCertificates + require.NotEmpty(t, certificates) + require.Equal(t, "Bob", certificates[0].Subject.CommonName) + + // Check the server side of the handshake succeded. + require.NoError(t, <-errc) + }) + + t.Run("VerifyIncoming enabled - successful handshake", func(t *testing.T) { + cfg := ProtocolConfig{ + CAFile: "../test/hostname/CertAuth.crt", + CertFile: "../test/hostname/Alice.crt", + KeyFile: "../test/hostname/Alice.key", + VerifyIncoming: true, + } + c := makeConfigurator(t, tc.setupFn(cfg)) + + client, errc, _ := startTLSServer(tc.configFn(c)) + if client == nil { + t.Fatalf("startTLSServer err: %v", <-errc) + } + + tlsClient := tls.Client(client, &tls.Config{ + InsecureSkipVerify: true, + GetClientCertificate: func(*tls.CertificateRequestInfo) (*tls.Certificate, error) { + cert, err := tls.LoadX509KeyPair("../test/hostname/Bob.crt", "../test/hostname/Bob.key") + return &cert, err + }, + }) + require.NoError(t, tlsClient.Handshake()) + require.NoError(t, <-errc) + }) + + t.Run("VerifyIncoming enabled - client provides no certificate", func(t *testing.T) { + cfg := ProtocolConfig{ + CAFile: "../test/hostname/CertAuth.crt", + CertFile: "../test/hostname/Alice.crt", + KeyFile: "../test/hostname/Alice.key", + VerifyIncoming: true, + } + c := makeConfigurator(t, tc.setupFn(cfg)) + + client, errc, _ := startTLSServer(tc.configFn(c)) + if client == nil { + t.Fatalf("startTLSServer err: %v", <-errc) + } + + tlsClient := tls.Client(client, &tls.Config{InsecureSkipVerify: true}) + require.NoError(t, tlsClient.Handshake()) + + err := <-errc + require.Error(t, err) + require.Contains(t, err.Error(), "client didn't provide a certificate") + }) + + t.Run("VerifyIncoming enabled - client certificate signed by an unknown CA", func(t *testing.T) { + cfg := ProtocolConfig{ + CAFile: "../test/ca/root.cer", + CertFile: "../test/hostname/Alice.crt", + KeyFile: "../test/hostname/Alice.key", + VerifyIncoming: true, + } + c := makeConfigurator(t, tc.setupFn(cfg)) + + client, errc, _ := startTLSServer(tc.configFn(c)) + if client == nil { + t.Fatalf("startTLSServer err: %v", <-errc) + } + + tlsClient := tls.Client(client, &tls.Config{ + InsecureSkipVerify: true, + GetClientCertificate: func(*tls.CertificateRequestInfo) (*tls.Certificate, error) { + cert, err := tls.LoadX509KeyPair("../test/hostname/Bob.crt", "../test/hostname/Bob.key") + return &cert, err + }, + }) + require.NoError(t, tlsClient.Handshake()) + + err := <-errc + require.Error(t, err) + require.Contains(t, err.Error(), "signed by unknown authority") + }) + }) + } } -func startALPNRPCTLSServer(config *Config, alpnProtos []string) (net.Conn, chan error) { - return startTLSServer(config, alpnProtos, true) +func TestConfigurator_IncomingInsecureRPCConfig(t *testing.T) { + // if this test is failing because of expired certificates + // use the procedure in test/CA-GENERATION.md + cfg := Config{ + InternalRPC: ProtocolConfig{ + CAFile: "../test/hostname/CertAuth.crt", + CertFile: "../test/hostname/Alice.crt", + KeyFile: "../test/hostname/Alice.key", + VerifyIncoming: true, + }, + } + + c := makeConfigurator(t, cfg) + + client, errc, _ := startTLSServer(c.IncomingInsecureRPCConfig()) + if client == nil { + t.Fatalf("startTLSServer err: %v", <-errc) + } + + tlsClient := tls.Client(client, &tls.Config{InsecureSkipVerify: true}) + require.NoError(t, tlsClient.Handshake()) + + // Check the server side of the handshake succeded. + require.NoError(t, <-errc) } -func startTLSServer(config *Config, alpnProtos []string, doAlpnVariant bool) (net.Conn, chan error) { - errc := make(chan error, 1) +func TestConfigurator_ALPNRPCConfig(t *testing.T) { + // if this test is failing because of expired certificates + // use the procedure in test/CA-GENERATION.md + t.Run("successful protocol negotiation", func(t *testing.T) { + serverCfg := makeConfigurator(t, Config{ + InternalRPC: ProtocolConfig{ + CAFile: "../test/hostname/CertAuth.crt", + CertFile: "../test/hostname/Bob.crt", + KeyFile: "../test/hostname/Bob.key", + }, + }) - c, err := NewConfigurator(*config, nil) - if err != nil { - errc <- err - return nil, errc - } - var tlsConfigServer *tls.Config - if doAlpnVariant { - tlsConfigServer = c.IncomingALPNRPCConfig(alpnProtos) - } else { - tlsConfigServer = c.IncomingRPCConfig() - } - client, server := net.Pipe() - - // Use yamux to buffer the reads, otherwise it's easy to deadlock - muxConf := yamux.DefaultConfig() - serverSession, _ := yamux.Server(server, muxConf) - clientSession, _ := yamux.Client(client, muxConf) - clientConn, _ := clientSession.Open() - serverConn, _ := serverSession.Accept() - - go func() { - tlsServer := tls.Server(serverConn, tlsConfigServer) - if err := tlsServer.Handshake(); err != nil { - errc <- err + client, errc, _ := startTLSServer(serverCfg.IncomingALPNRPCConfig([]string{"some-protocol"})) + if client == nil { + t.Fatalf("startTLSServer err: %v", <-errc) } - close(errc) - // Because net.Pipe() is unbuffered, if both sides - // Close() simultaneously, we will deadlock as they - // both send an alert and then block. So we make the - // server read any data from the client until error or - // EOF, which will allow the client to Close(), and - // *then* we Close() the server. - io.Copy(ioutil.Discard, tlsServer) - tlsServer.Close() - }() - return clientConn, errc + clientCfg := makeConfigurator(t, Config{ + InternalRPC: ProtocolConfig{ + CAFile: "../test/hostname/CertAuth.crt", + CertFile: "../test/hostname/Alice.crt", + KeyFile: "../test/hostname/Alice.key", + }, + Domain: "consul", + }) + wrap := clientCfg.OutgoingALPNRPCWrapper() + + tlsClient, err := wrap("dc1", "bob", "some-protocol", client) + require.NoError(t, err) + defer tlsClient.Close() + + tlsConn := tlsClient.(*tls.Conn) + require.NoError(t, tlsConn.Handshake()) + require.Equal(t, "some-protocol", tlsConn.ConnectionState().NegotiatedProtocol) + + // Check the server side of the handshake succeded. + require.NoError(t, <-errc) + }) + + t.Run("protocol negotiation fails", func(t *testing.T) { + serverCfg := makeConfigurator(t, Config{ + InternalRPC: ProtocolConfig{ + CAFile: "../test/hostname/CertAuth.crt", + CertFile: "../test/hostname/Bob.crt", + KeyFile: "../test/hostname/Bob.key", + }, + }) + + client, errc, _ := startTLSServer(serverCfg.IncomingALPNRPCConfig([]string{"some-protocol"})) + if client == nil { + t.Fatalf("startTLSServer err: %v", <-errc) + } + + clientCfg := makeConfigurator(t, Config{ + InternalRPC: ProtocolConfig{ + CAFile: "../test/hostname/CertAuth.crt", + CertFile: "../test/hostname/Alice.crt", + KeyFile: "../test/hostname/Alice.key", + }, + Domain: "consul", + }) + wrap := clientCfg.OutgoingALPNRPCWrapper() + + _, err := wrap("dc1", "bob", "other-protocol", client) + require.Error(t, err) + require.Error(t, <-errc) + }) + + t.Run("no node name in SAN", func(t *testing.T) { + // Note: Alice.crt has server.dc1.consul as its SAN (as apposed to alice.server.dc1.consul). + serverCfg := makeConfigurator(t, Config{ + InternalRPC: ProtocolConfig{ + CAFile: "../test/hostname/CertAuth.crt", + CertFile: "../test/hostname/Alice.crt", + KeyFile: "../test/hostname/Alice.key", + }, + }) + + client, errc, _ := startTLSServer(serverCfg.IncomingALPNRPCConfig([]string{"some-protocol"})) + if client == nil { + t.Fatalf("startTLSServer err: %v", <-errc) + } + + clientCfg := makeConfigurator(t, Config{ + InternalRPC: ProtocolConfig{ + CAFile: "../test/hostname/CertAuth.crt", + CertFile: "../test/hostname/Bob.crt", + KeyFile: "../test/hostname/Bob.key", + }, + Domain: "consul", + }) + wrap := clientCfg.OutgoingALPNRPCWrapper() + + _, err := wrap("dc1", "alice", "some-protocol", client) + require.Error(t, err) + require.Error(t, <-errc) + }) + + t.Run("client certificate is always required", func(t *testing.T) { + cfg := Config{ + InternalRPC: ProtocolConfig{ + VerifyIncoming: false, // this setting is ignored + CAFile: "../test/hostname/CertAuth.crt", + CertFile: "../test/hostname/Alice.crt", + KeyFile: "../test/hostname/Alice.key", + }, + } + c := makeConfigurator(t, cfg) + + client, errc, _ := startTLSServer(c.IncomingALPNRPCConfig([]string{"some-protocol"})) + if client == nil { + t.Fatalf("startTLSServer err: %v", <-errc) + } + + tlsClient := tls.Client(client, &tls.Config{ + InsecureSkipVerify: true, + NextProtos: []string{"some-protocol"}, + }) + require.NoError(t, tlsClient.Handshake()) + + err := <-errc + require.Error(t, err) + require.Contains(t, err.Error(), "client didn't provide a certificate") + }) + + t.Run("bad DC", func(t *testing.T) { + serverCfg := makeConfigurator(t, Config{ + InternalRPC: ProtocolConfig{ + CAFile: "../test/hostname/CertAuth.crt", + CertFile: "../test/hostname/Alice.crt", + KeyFile: "../test/hostname/Alice.key", + }, + }) + + client, errc, _ := startTLSServer(serverCfg.IncomingALPNRPCConfig([]string{"some-protocol"})) + if client == nil { + t.Fatalf("startTLSServer err: %v", <-errc) + } + + clientCfg := makeConfigurator(t, Config{ + InternalRPC: ProtocolConfig{ + CAFile: "../test/hostname/CertAuth.crt", + CertFile: "../test/hostname/Bob.crt", + KeyFile: "../test/hostname/Bob.key", + }, + Domain: "consul", + }) + wrap := clientCfg.OutgoingALPNRPCWrapper() + + _, err := wrap("dc2", "*", "some-protocol", client) + require.Error(t, err) + require.Error(t, <-errc) + }) } -func TestConfigurator_outgoingWrapper_OK(t *testing.T) { +func TestConfigurator_OutgoingInternalRPCWrapper(t *testing.T) { // if this test is failing because of expired certificates // use the procedure in test/CA-GENERATION.md - config := Config{ - CAFile: "../test/hostname/CertAuth.crt", - CertFile: "../test/hostname/Alice.crt", - KeyFile: "../test/hostname/Alice.key", - VerifyServerHostname: true, - VerifyOutgoing: true, - Domain: "consul", - } + t.Run("AutoTLS", func(t *testing.T) { + serverCfg := makeConfigurator(t, Config{ + InternalRPC: ProtocolConfig{ + CAFile: "../test/hostname/CertAuth.crt", + CertFile: "../test/hostname/Alice.crt", + KeyFile: "../test/hostname/Alice.key", + VerifyIncoming: true, + }, + }) - client, errc := startRPCTLSServer(&config) - if client == nil { - t.Fatalf("startTLSServer err: %v", <-errc) - } + client, errc, _ := startTLSServer(serverCfg.IncomingRPCConfig()) + if client == nil { + t.Fatalf("startTLSServer err: %v", <-errc) + } - c, err := NewConfigurator(config, nil) - require.NoError(t, err) - wrap := c.OutgoingRPCWrapper() - require.NotNil(t, wrap) + clientCfg := makeConfigurator(t, Config{ + AutoTLS: true, + }) + bobCert := loadFile(t, "../test/hostname/Bob.crt") + bobKey := loadFile(t, "../test/hostname/Bob.key") + require.NoError(t, clientCfg.UpdateAutoTLSCert(bobCert, bobKey)) - tlsClient, err := wrap("dc1", client) - require.NoError(t, err) + wrap := clientCfg.OutgoingRPCWrapper() + require.NotNil(t, wrap) - defer tlsClient.Close() - err = tlsClient.(*tls.Conn).Handshake() - require.NoError(t, err) + tlsClient, err := wrap("dc1", client) + require.NoError(t, err) + defer tlsClient.Close() - err = <-errc - require.NoError(t, err) -} + err = tlsClient.(*tls.Conn).Handshake() + require.NoError(t, err) -func TestConfigurator_outgoingWrapper_noverify_OK(t *testing.T) { - // if this test is failing because of expired certificates - // use the procedure in test/CA-GENERATION.md - config := Config{ - VerifyOutgoing: true, - CAFile: "../test/hostname/CertAuth.crt", - CertFile: "../test/hostname/Alice.crt", - KeyFile: "../test/hostname/Alice.key", - Domain: "consul", - } + err = <-errc + require.NoError(t, err) + }) - client, errc := startRPCTLSServer(&config) - if client == nil { - t.Fatalf("startTLSServer err: %v", <-errc) - } + t.Run("VerifyOutgoing and a manually configured certificate", func(t *testing.T) { + serverCfg := makeConfigurator(t, Config{ + InternalRPC: ProtocolConfig{ + CAFile: "../test/hostname/CertAuth.crt", + CertFile: "../test/hostname/Alice.crt", + KeyFile: "../test/hostname/Alice.key", + VerifyIncoming: true, + }, + }) - c, err := NewConfigurator(config, nil) - require.NoError(t, err) - wrap := c.OutgoingRPCWrapper() - require.NotNil(t, wrap) + client, errc, _ := startTLSServer(serverCfg.IncomingRPCConfig()) + if client == nil { + t.Fatalf("startTLSServer err: %v", <-errc) + } - tlsClient, err := wrap("dc1", client) - require.NoError(t, err) + clientCfg := makeConfigurator(t, Config{ + InternalRPC: ProtocolConfig{ + VerifyOutgoing: true, + CAFile: "../test/hostname/CertAuth.crt", + CertFile: "../test/hostname/Bob.crt", + KeyFile: "../test/hostname/Bob.key", + }, + }) - defer tlsClient.Close() - err = tlsClient.(*tls.Conn).Handshake() - require.NoError(t, err) + wrap := clientCfg.OutgoingRPCWrapper() + require.NotNil(t, wrap) - err = <-errc - require.NoError(t, err) -} + tlsClient, err := wrap("dc1", client) + require.NoError(t, err) + defer tlsClient.Close() -func TestConfigurator_outgoingWrapper_BadDC(t *testing.T) { - // if this test is failing because of expired certificates - // use the procedure in test/CA-GENERATION.md - config := Config{ - CAFile: "../test/hostname/CertAuth.crt", - CertFile: "../test/hostname/Alice.crt", - KeyFile: "../test/hostname/Alice.key", - VerifyServerHostname: true, - VerifyOutgoing: true, - Domain: "consul", - } + err = tlsClient.(*tls.Conn).Handshake() + require.NoError(t, err) - client, errc := startRPCTLSServer(&config) - if client == nil { - t.Fatalf("startTLSServer err: %v", <-errc) - } + err = <-errc + require.NoError(t, err) + }) - c, err := NewConfigurator(config, nil) - require.NoError(t, err) - wrap := c.OutgoingRPCWrapper() + t.Run("outgoing TLS not enabled", func(t *testing.T) { + serverCfg := makeConfigurator(t, Config{ + InternalRPC: ProtocolConfig{ + CAFile: "../test/hostname/CertAuth.crt", + CertFile: "../test/hostname/Alice.crt", + KeyFile: "../test/hostname/Alice.key", + VerifyIncoming: true, + }, + }) - tlsClient, err := wrap("dc2", client) - require.NoError(t, err) + client, errc, _ := startTLSServer(serverCfg.IncomingRPCConfig()) + if client == nil { + t.Fatalf("startTLSServer err: %v", <-errc) + } - err = tlsClient.(*tls.Conn).Handshake() - _, ok := err.(x509.HostnameError) - require.True(t, ok) - tlsClient.Close() + clientCfg := makeConfigurator(t, Config{}) - <-errc -} + wrap := clientCfg.OutgoingRPCWrapper() + require.NotNil(t, wrap) -func TestConfigurator_outgoingWrapper_BadCert(t *testing.T) { - config := Config{ - CAFile: "../test/ca/root.cer", - CertFile: "../test/key/ourdomain.cer", - KeyFile: "../test/key/ourdomain.key", - VerifyServerHostname: true, - VerifyOutgoing: true, - Domain: "consul", - } + client, err := wrap("dc1", client) + require.NoError(t, err) + defer client.Close() - client, errc := startRPCTLSServer(&config) - if client == nil { - t.Fatalf("startTLSServer err: %v", <-errc) - } + _, isTLS := client.(*tls.Conn) + require.False(t, isTLS) + }) - c, err := NewConfigurator(config, nil) - require.NoError(t, err) - wrap := c.OutgoingRPCWrapper() + t.Run("VerifyServerHostname = true", func(t *testing.T) { + serverCfg := makeConfigurator(t, Config{ + InternalRPC: ProtocolConfig{ + CAFile: "../test/client_certs/rootca.crt", + CertFile: "../test/client_certs/client.crt", + KeyFile: "../test/client_certs/client.key", + }, + }) - tlsClient, err := wrap("dc1", client) - require.NoError(t, err) + client, errc, _ := startTLSServer(serverCfg.IncomingRPCConfig()) + if client == nil { + t.Fatalf("startTLSServer err: %v", <-errc) + } - err = tlsClient.(*tls.Conn).Handshake() - if _, ok := err.(x509.HostnameError); !ok { - t.Fatalf("should get hostname err: %v", err) - } - tlsClient.Close() + clientCfg := makeConfigurator(t, Config{ + InternalRPC: ProtocolConfig{ + VerifyOutgoing: true, + VerifyServerHostname: true, + CAFile: "../test/client_certs/rootca.crt", + CertFile: "../test/client_certs/client.crt", + KeyFile: "../test/client_certs/client.key", + }, + Domain: "consul", + }) - <-errc -} + wrap := clientCfg.OutgoingRPCWrapper() + require.NotNil(t, wrap) -func TestConfigurator_outgoingWrapperALPN_OK(t *testing.T) { - // if this test is failing because of expired certificates - // use the procedure in test/CA-GENERATION.md - config := Config{ - CAFile: "../test/hostname/CertAuth.crt", - CertFile: "../test/hostname/Bob.crt", - KeyFile: "../test/hostname/Bob.key", - VerifyServerHostname: false, // doesn't matter - VerifyOutgoing: false, // doesn't matter - Domain: "consul", - } + tlsClient, err := wrap("dc1", client) + require.NoError(t, err) + defer tlsClient.Close() - client, errc := startALPNRPCTLSServer(&config, []string{"foo", "bar"}) - if client == nil { - t.Fatalf("startTLSServer err: %v", <-errc) - } + err = tlsClient.(*tls.Conn).Handshake() + require.Error(t, err) + require.Regexp(t, `certificate is valid for ([a-z].+) not server.dc1.consul`, err.Error()) + }) - c, err := NewConfigurator(config, nil) - require.NoError(t, err) - wrap := c.OutgoingALPNRPCWrapper() - require.NotNil(t, wrap) + t.Run("VerifyServerHostname = true and incorrect DC name", func(t *testing.T) { + serverCfg := makeConfigurator(t, Config{ + InternalRPC: ProtocolConfig{ + CAFile: "../test/client_certs/rootca.crt", + CertFile: "../test/client_certs/client.crt", + KeyFile: "../test/client_certs/client.key", + }, + }) - tlsClient, err := wrap("dc1", "bob", "foo", client) - require.NoError(t, err) - defer tlsClient.Close() + client, errc, _ := startTLSServer(serverCfg.IncomingRPCConfig()) + if client == nil { + t.Fatalf("startTLSServer err: %v", <-errc) + } - tlsConn := tlsClient.(*tls.Conn) - cs := tlsConn.ConnectionState() - require.Equal(t, "foo", cs.NegotiatedProtocol) + clientCfg := makeConfigurator(t, Config{ + InternalRPC: ProtocolConfig{ + VerifyServerHostname: true, + VerifyOutgoing: true, + CAFile: "../test/client_certs/rootca.crt", + CertFile: "../test/client_certs/client.crt", + KeyFile: "../test/client_certs/client.key", + }, + Domain: "consul", + }) - err = <-errc - require.NoError(t, err) + wrap := clientCfg.OutgoingRPCWrapper() + require.NotNil(t, wrap) + + tlsClient, err := wrap("dc2", client) + require.NoError(t, err) + defer tlsClient.Close() + + err = tlsClient.(*tls.Conn).Handshake() + require.Error(t, err) + require.Regexp(t, `certificate is valid for ([a-z].+) not server.dc2.consul`, err.Error()) + }) + + t.Run("VerifyServerHostname = false", func(t *testing.T) { + serverCfg := makeConfigurator(t, Config{ + InternalRPC: ProtocolConfig{ + CAFile: "../test/client_certs/rootca.crt", + CertFile: "../test/client_certs/client.crt", + KeyFile: "../test/client_certs/client.key", + }, + }) + + client, errc, _ := startTLSServer(serverCfg.IncomingRPCConfig()) + if client == nil { + t.Fatalf("startTLSServer err: %v", <-errc) + } + + clientCfg := makeConfigurator(t, Config{ + InternalRPC: ProtocolConfig{ + VerifyServerHostname: false, + VerifyOutgoing: true, + CAFile: "../test/client_certs/rootca.crt", + CertFile: "../test/client_certs/client.crt", + KeyFile: "../test/client_certs/client.key", + }, + Domain: "other", + }) + + wrap := clientCfg.OutgoingRPCWrapper() + require.NotNil(t, wrap) + + tlsClient, err := wrap("dc1", client) + require.NoError(t, err) + defer tlsClient.Close() + + err = tlsClient.(*tls.Conn).Handshake() + require.NoError(t, err) + + // Check the server side of the handshake succeded. + require.NoError(t, <-errc) + }) + + t.Run("AutoTLS certificate preferred over manually configured certificate", func(t *testing.T) { + serverCfg := makeConfigurator(t, Config{ + InternalRPC: ProtocolConfig{ + CAFile: "../test/hostname/CertAuth.crt", + CertFile: "../test/hostname/Alice.crt", + KeyFile: "../test/hostname/Alice.key", + VerifyIncoming: true, + }, + }) + + client, errc, certc := startTLSServer(serverCfg.IncomingRPCConfig()) + if client == nil { + t.Fatalf("startTLSServer err: %v", <-errc) + } + + clientCfg := makeConfigurator(t, Config{ + InternalRPC: ProtocolConfig{ + VerifyServerHostname: true, + VerifyOutgoing: true, + CAFile: "../test/hostname/CertAuth.crt", + CertFile: "../test/hostname/Bob.crt", + KeyFile: "../test/hostname/Bob.key", + }, + Domain: "consul", + }) + + bettyCert := loadFile(t, "../test/hostname/Betty.crt") + bettyKey := loadFile(t, "../test/hostname/Betty.key") + require.NoError(t, clientCfg.UpdateAutoTLSCert(bettyCert, bettyKey)) + + wrap := clientCfg.OutgoingRPCWrapper() + require.NotNil(t, wrap) + + tlsClient, err := wrap("dc1", client) + require.NoError(t, err) + defer tlsClient.Close() + + err = tlsClient.(*tls.Conn).Handshake() + require.NoError(t, err) + + err = <-errc + require.NoError(t, err) + + clientCerts := <-certc + require.NotEmpty(t, clientCerts) + require.Equal(t, "Betty", clientCerts[0].Subject.CommonName) + }) + + t.Run("manually configured certificate is presented if there's no AutoTLS certificate", func(t *testing.T) { + serverCfg := makeConfigurator(t, Config{ + InternalRPC: ProtocolConfig{ + CAFile: "../test/hostname/CertAuth.crt", + CertFile: "../test/hostname/Alice.crt", + KeyFile: "../test/hostname/Alice.key", + VerifyIncoming: true, + }, + }) + + client, errc, certc := startTLSServer(serverCfg.IncomingRPCConfig()) + if client == nil { + t.Fatalf("startTLSServer err: %v", <-errc) + } + + clientCfg := makeConfigurator(t, Config{ + InternalRPC: ProtocolConfig{ + VerifyServerHostname: true, + VerifyOutgoing: true, + CAFile: "../test/hostname/CertAuth.crt", + CertFile: "../test/hostname/Bob.crt", + KeyFile: "../test/hostname/Bob.key", + }, + Domain: "consul", + }) + + wrap := clientCfg.OutgoingRPCWrapper() + require.NotNil(t, wrap) + + tlsClient, err := wrap("dc1", client) + require.NoError(t, err) + defer tlsClient.Close() + + err = tlsClient.(*tls.Conn).Handshake() + require.NoError(t, err) + + err = <-errc + require.NoError(t, err) + + clientCerts := <-certc + require.NotEmpty(t, clientCerts) + require.Equal(t, "Bob", clientCerts[0].Subject.CommonName) + }) } func TestConfigurator_outgoingWrapperALPN_serverHasNoNodeNameInSAN(t *testing.T) { // if this test is failing because of expired certificates // use the procedure in test/CA-GENERATION.md srvConfig := Config{ - CAFile: "../test/hostname/CertAuth.crt", - CertFile: "../test/hostname/Alice.crt", - KeyFile: "../test/hostname/Alice.key", - VerifyServerHostname: false, // doesn't matter - VerifyOutgoing: false, // doesn't matter - Domain: "consul", + InternalRPC: ProtocolConfig{ + CAFile: "../test/hostname/CertAuth.crt", + CertFile: "../test/hostname/Alice.crt", + KeyFile: "../test/hostname/Alice.key", + VerifyOutgoing: false, // doesn't matter + VerifyServerHostname: false, // doesn't matter + }, + Domain: "consul", } - client, errc := startALPNRPCTLSServer(&srvConfig, []string{"foo", "bar"}) + client, errc := startALPNRPCTLSServer(t, &srvConfig, []string{"foo", "bar"}) if client == nil { t.Fatalf("startTLSServer err: %v", <-errc) } config := Config{ - CAFile: "../test/hostname/CertAuth.crt", - CertFile: "../test/hostname/Bob.crt", - KeyFile: "../test/hostname/Bob.key", - VerifyServerHostname: false, // doesn't matter - VerifyOutgoing: false, // doesn't matter - Domain: "consul", + InternalRPC: ProtocolConfig{ + CAFile: "../test/hostname/CertAuth.crt", + CertFile: "../test/hostname/Bob.crt", + KeyFile: "../test/hostname/Bob.key", + VerifyOutgoing: false, // doesn't matter + VerifyServerHostname: false, // doesn't matter + }, + Domain: "consul", } c, err := NewConfigurator(config, nil) @@ -273,114 +760,6 @@ func TestConfigurator_outgoingWrapperALPN_serverHasNoNodeNameInSAN(t *testing.T) <-errc } -func TestConfigurator_outgoingWrapperALPN_BadDC(t *testing.T) { - // if this test is failing because of expired certificates - // use the procedure in test/CA-GENERATION.md - config := Config{ - CAFile: "../test/hostname/CertAuth.crt", - CertFile: "../test/hostname/Bob.crt", - KeyFile: "../test/hostname/Bob.key", - VerifyServerHostname: false, // doesn't matter - VerifyOutgoing: false, // doesn't matter - Domain: "consul", - } - - client, errc := startALPNRPCTLSServer(&config, []string{"foo", "bar"}) - if client == nil { - t.Fatalf("startTLSServer err: %v", <-errc) - } - - c, err := NewConfigurator(config, nil) - require.NoError(t, err) - wrap := c.OutgoingALPNRPCWrapper() - - _, err = wrap("dc2", "bob", "foo", client) - require.Error(t, err) - _, ok := err.(x509.HostnameError) - require.True(t, ok) - client.Close() - - <-errc -} - -func TestConfigurator_outgoingWrapperALPN_BadCert(t *testing.T) { - config := Config{ - CAFile: "../test/ca/root.cer", - CertFile: "../test/key/ourdomain.cer", - KeyFile: "../test/key/ourdomain.key", - VerifyServerHostname: false, // doesn't matter - VerifyOutgoing: false, // doesn't matter - Domain: "consul", - } - - client, errc := startALPNRPCTLSServer(&config, []string{"foo", "bar"}) - if client == nil { - t.Fatalf("startTLSServer err: %v", <-errc) - } - - c, err := NewConfigurator(config, nil) - require.NoError(t, err) - wrap := c.OutgoingALPNRPCWrapper() - - _, err = wrap("dc1", "bob", "foo", client) - require.Error(t, err) - _, ok := err.(x509.HostnameError) - require.True(t, ok) - client.Close() - - <-errc -} - -func TestConfigurator_wrapTLS_OK(t *testing.T) { - config := Config{ - CAFile: "../test/ca/root.cer", - CertFile: "../test/key/ourdomain.cer", - KeyFile: "../test/key/ourdomain.key", - VerifyOutgoing: true, - } - - client, errc := startRPCTLSServer(&config) - if client == nil { - t.Fatalf("startTLSServer err: %v", <-errc) - } - - c, err := NewConfigurator(config, nil) - require.NoError(t, err) - - tlsClient, err := c.wrapTLSClient("dc1", client) - require.NoError(t, err) - - tlsClient.Close() - err = <-errc - require.NoError(t, err) -} - -func TestConfigurator_wrapTLS_BadCert(t *testing.T) { - serverConfig := &Config{ - CertFile: "../test/key/ssl-cert-snakeoil.pem", - KeyFile: "../test/key/ssl-cert-snakeoil.key", - } - - client, errc := startRPCTLSServer(serverConfig) - if client == nil { - t.Fatalf("startTLSServer err: %v", <-errc) - } - - clientConfig := Config{ - CAFile: "../test/ca/root.cer", - VerifyOutgoing: true, - } - - c, err := NewConfigurator(clientConfig, nil) - require.NoError(t, err) - tlsClient, err := c.wrapTLSClient("dc1", client) - require.Error(t, err) - require.Nil(t, tlsClient) - - err = <-errc - require.NoError(t, err) -} - func TestConfig_ParseCiphers(t *testing.T) { testOk := strings.Join([]string{ "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", @@ -464,93 +843,207 @@ func TestConfig_SpecifyDC(t *testing.T) { require.Nil(t, conn) } -func TestConfigurator_NewConfigurator(t *testing.T) { - logger := testutil.Logger(t) - c, err := NewConfigurator(Config{}, logger) - require.NoError(t, err) - require.NotNil(t, c) +func TestConfigurator_Validation(t *testing.T) { + // if this test is failing because of expired certificates + // use the procedure in test/CA-GENERATION.md + const ( + caFile = "../test/ca/root.cer" + caPath = "../test/ca_path" + certFile = "../test/key/ourdomain.cer" + keyFile = "../test/key/ourdomain.key" + ) - c, err = NewConfigurator(Config{VerifyOutgoing: true}, nil) - require.Error(t, err) - require.Nil(t, c) -} + t.Run("empty config", func(t *testing.T) { + _, err := NewConfigurator(Config{}, nil) + require.NoError(t, err) + require.NoError(t, new(Configurator).Update(Config{})) + }) -func TestConfigurator_ErrorPropagation(t *testing.T) { - type variant struct { - config Config - shouldErr bool - excludeCheck bool - } - cafile := "../test/ca/root.cer" - capath := "../test/ca_path" - certfile := "../test/key/ourdomain.cer" - keyfile := "../test/key/ourdomain.key" - variants := []variant{ - {Config{}, false, false}, // 1 - {Config{TLSMinVersion: "tls9"}, true, false}, // 1 - {Config{TLSMinVersion: ""}, false, false}, // 2 - {Config{VerifyOutgoing: true, CAFile: "", CAPath: ""}, true, false}, // 6 - {Config{VerifyOutgoing: false, CAFile: "", CAPath: ""}, false, false}, // 7 - {Config{VerifyOutgoing: false, CAFile: cafile, CAPath: ""}, - false, false}, // 8 - {Config{VerifyOutgoing: false, CAFile: "", CAPath: capath}, - false, false}, // 9 - {Config{VerifyOutgoing: false, CAFile: cafile, CAPath: capath}, - false, false}, // 10 - {Config{VerifyOutgoing: true, CAFile: cafile, CAPath: ""}, - false, false}, // 11 - {Config{VerifyOutgoing: true, CAFile: "", CAPath: capath}, - false, false}, // 12 - {Config{VerifyOutgoing: true, CAFile: cafile, CAPath: capath}, - false, false}, // 13 - {Config{VerifyIncoming: true, CAFile: "", CAPath: ""}, true, false}, // 14 - {Config{VerifyIncomingRPC: true, CAFile: "", CAPath: ""}, - true, false}, // 15 - {Config{VerifyIncomingHTTPS: true, CAFile: "", CAPath: ""}, - true, false}, // 16 - {Config{VerifyIncoming: true, CAFile: cafile, CAPath: ""}, true, false}, // 17 - {Config{VerifyIncoming: true, CAFile: "", CAPath: capath}, true, false}, // 18 - {Config{VerifyIncoming: true, CAFile: "", CAPath: capath, - CertFile: certfile, KeyFile: keyfile}, false, false}, // 19 - {Config{CertFile: "bogus", KeyFile: "bogus"}, true, true}, // 20 - {Config{CAFile: "bogus"}, true, true}, // 21 - {Config{CAPath: "bogus"}, true, true}, // 22 - {Config{VerifyIncoming: true, CAFile: cafile, AutoTLS: true}, false, false}, // 22 - } - for _, v := range tlsVersions() { - variants = append(variants, variant{Config{TLSMinVersion: v}, false, false}) - } - - c := Configurator{} - for i, v := range variants { - info := fmt.Sprintf("case %d, config: %+v", i, v.config) - _, err1 := NewConfigurator(v.config, nil) - err2 := c.Update(v.config) - - var err3 error - if !v.excludeCheck { - cert, err := loadKeyPair(v.config.CertFile, v.config.KeyFile) - require.NoError(t, err, info) - pems, err := LoadCAs(v.config.CAFile, v.config.CAPath) - require.NoError(t, err, info) - pool, err := newX509CertPool(pems) - require.NoError(t, err, info) - err3 = validateConfig(v.config, pool, cert) + t.Run("common fields", func(t *testing.T) { + type testCase struct { + config ProtocolConfig + isValid bool } - if v.shouldErr { - require.Error(t, err1, info) - require.Error(t, err2, info) - if !v.excludeCheck { - require.Error(t, err3, info) - } - } else { - require.NoError(t, err1, info) - require.NoError(t, err2, info) - if !v.excludeCheck { - require.NoError(t, err3, info) + + testCases := map[string]testCase{ + "invalid TLSMinVersion": { + ProtocolConfig{TLSMinVersion: "tls9"}, + false, + }, + "default TLSMinVersion": { + ProtocolConfig{TLSMinVersion: ""}, + true, + }, + "invalid CAFile": { + ProtocolConfig{CAFile: "bogus"}, + false, + }, + "invalid CAPath": { + ProtocolConfig{CAPath: "bogus"}, + false, + }, + "invalid CertFile": { + ProtocolConfig{ + CertFile: "bogus", + KeyFile: keyFile, + }, + false, + }, + "invalid KeyFile": { + ProtocolConfig{ + CertFile: certFile, + KeyFile: "bogus", + }, + false, + }, + "VerifyIncoming set but no CA": { + ProtocolConfig{ + VerifyIncoming: true, + CAFile: "", + CAPath: "", + CertFile: certFile, + KeyFile: keyFile, + }, + false, + }, + "VerifyIncoming set but no CertFile": { + ProtocolConfig{ + VerifyIncoming: true, + CAFile: caFile, + CertFile: "", + KeyFile: keyFile, + }, + false, + }, + "VerifyIncoming set but no KeyFile": { + ProtocolConfig{ + VerifyIncoming: true, + CAFile: caFile, + CertFile: certFile, + KeyFile: "", + }, + false, + }, + "VerifyIncoming + CAFile": { + ProtocolConfig{ + VerifyIncoming: true, + CAFile: caFile, + CertFile: certFile, + KeyFile: keyFile, + }, + true, + }, + "VerifyIncoming + CAPath": { + ProtocolConfig{ + VerifyIncoming: true, + CAPath: caPath, + CertFile: certFile, + KeyFile: keyFile, + }, + true, + }, + "VerifyIncoming + invalid CAFile": { + ProtocolConfig{ + VerifyIncoming: true, + CAFile: "bogus", + CertFile: certFile, + KeyFile: keyFile, + }, + false, + }, + "VerifyIncoming + invalid CAPath": { + ProtocolConfig{ + VerifyIncoming: true, + CAPath: "bogus", + CertFile: certFile, + KeyFile: keyFile, + }, + false, + }, + "VerifyOutgoing + CAFile": { + ProtocolConfig{VerifyOutgoing: true, CAFile: caFile}, + true, + }, + "VerifyOutgoing + CAPath": { + ProtocolConfig{VerifyOutgoing: true, CAPath: caPath}, + true, + }, + "VerifyOutgoing + CAFile + CAPath": { + ProtocolConfig{ + VerifyOutgoing: true, + CAFile: caFile, + CAPath: caPath, + }, + true, + }, + "VerifyOutgoing but no CA": { + ProtocolConfig{ + VerifyOutgoing: true, + CAFile: "", + CAPath: "", + }, + false, + }, + } + + for _, v := range tlsVersions() { + testCases[fmt.Sprintf("MinTLSVersion(%s)", v)] = testCase{ + ProtocolConfig{TLSMinVersion: v}, + true, } } - } + + for desc, tc := range testCases { + for _, p := range []string{"internal", "grpc", "https"} { + info := fmt.Sprintf("%s => %s", p, desc) + + var cfg Config + switch p { + case "internal": + cfg.InternalRPC = tc.config + case "grpc": + cfg.GRPC = tc.config + case "https": + cfg.HTTPS = tc.config + default: + t.Fatalf("unknown protocol: %s", p) + } + + _, err1 := NewConfigurator(cfg, nil) + err2 := new(Configurator).Update(cfg) + + if tc.isValid { + require.NoError(t, err1, info) + require.NoError(t, err2, info) + } else { + require.Error(t, err1, info) + require.Error(t, err2, info) + } + } + } + }) + + t.Run("VerifyIncoming + AutoTLS", func(t *testing.T) { + cfg := Config{ + InternalRPC: ProtocolConfig{ + VerifyIncoming: true, + CAFile: caFile, + }, + GRPC: ProtocolConfig{ + VerifyIncoming: true, + CAFile: caFile, + }, + HTTPS: ProtocolConfig{ + VerifyIncoming: true, + CAFile: caFile, + }, + AutoTLS: true, + } + + _, err := NewConfigurator(cfg, nil) + require.NoError(t, err) + require.NoError(t, new(Configurator).Update(cfg)) + }) } func TestConfigurator_CommonTLSConfigServerNameNodeName(t *testing.T) { @@ -569,7 +1062,7 @@ func TestConfigurator_CommonTLSConfigServerNameNodeName(t *testing.T) { for _, v := range variants { c, err := NewConfigurator(v.config, nil) require.NoError(t, err) - tlsConf := c.commonTLSConfig(false) + tlsConf := c.internalRPCTLSConfig(false) require.Empty(t, tlsConf.ServerName) } } @@ -613,166 +1106,7 @@ func TestConfigurator_LoadCAs(t *testing.T) { } } -func TestConfigurator_CommonTLSConfigInsecureSkipVerify(t *testing.T) { - c, err := NewConfigurator(Config{}, nil) - require.NoError(t, err) - tlsConf := c.commonTLSConfig(false) - require.True(t, tlsConf.InsecureSkipVerify) - - require.NoError(t, c.Update(Config{VerifyServerHostname: false})) - tlsConf = c.commonTLSConfig(false) - require.True(t, tlsConf.InsecureSkipVerify) - - require.NoError(t, c.Update(Config{VerifyServerHostname: true})) - tlsConf = c.commonTLSConfig(false) - require.False(t, tlsConf.InsecureSkipVerify) -} - -func TestConfigurator_CommonTLSConfigPreferServerCipherSuites(t *testing.T) { - c, err := NewConfigurator(Config{}, nil) - require.NoError(t, err) - tlsConf := c.commonTLSConfig(false) - require.False(t, tlsConf.PreferServerCipherSuites) - - require.NoError(t, c.Update(Config{PreferServerCipherSuites: false})) - tlsConf = c.commonTLSConfig(false) - require.False(t, tlsConf.PreferServerCipherSuites) - - require.NoError(t, c.Update(Config{PreferServerCipherSuites: true})) - tlsConf = c.commonTLSConfig(false) - require.True(t, tlsConf.PreferServerCipherSuites) -} - -func TestConfigurator_CommonTLSConfigCipherSuites(t *testing.T) { - c, err := NewConfigurator(Config{}, nil) - require.NoError(t, err) - tlsConf := c.commonTLSConfig(false) - require.Empty(t, tlsConf.CipherSuites) - - conf := Config{CipherSuites: []uint16{ - tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305}} - require.NoError(t, c.Update(conf)) - tlsConf = c.commonTLSConfig(false) - require.Equal(t, conf.CipherSuites, tlsConf.CipherSuites) -} - -func TestConfigurator_CommonTLSConfigGetClientCertificate(t *testing.T) { - c, err := NewConfigurator(Config{}, nil) - require.NoError(t, err) - - cert, err := c.commonTLSConfig(false).GetClientCertificate(nil) - require.NoError(t, err) - require.NotNil(t, cert) - require.Empty(t, cert.Certificate) - - c1, err := loadKeyPair("../test/key/something_expired.cer", "../test/key/something_expired.key") - require.NoError(t, err) - c.manual.cert = c1 - cert, err = c.commonTLSConfig(false).GetClientCertificate(nil) - require.NoError(t, err) - require.Equal(t, c.manual.cert, cert) - - c2, err := loadKeyPair("../test/key/ourdomain.cer", "../test/key/ourdomain.key") - require.NoError(t, err) - c.autoTLS.cert = c2 - cert, err = c.commonTLSConfig(false).GetClientCertificate(nil) - require.NoError(t, err) - require.Equal(t, c.autoTLS.cert, cert) -} - -func TestConfigurator_CommonTLSConfigGetCertificate(t *testing.T) { - c, err := NewConfigurator(Config{}, nil) - require.NoError(t, err) - - cert, err := c.commonTLSConfig(false).GetCertificate(nil) - require.NoError(t, err) - require.Nil(t, cert) - - // Setting a certificate as the auto-encrypt cert will return it as the regular server certificate - c1, err := loadKeyPair("../test/key/something_expired.cer", "../test/key/something_expired.key") - require.NoError(t, err) - c.autoTLS.cert = c1 - cert, err = c.commonTLSConfig(false).GetCertificate(nil) - require.NoError(t, err) - require.Equal(t, c.autoTLS.cert, cert) - - // Setting a different certificate as a manual cert will override the auto-encrypt cert and instead return the manual cert - c2, err := loadKeyPair("../test/key/ourdomain.cer", "../test/key/ourdomain.key") - require.NoError(t, err) - c.manual.cert = c2 - cert, err = c.commonTLSConfig(false).GetCertificate(nil) - require.NoError(t, err) - require.Equal(t, c.manual.cert, cert) -} - -func TestConfigurator_CommonTLSConfigCAs(t *testing.T) { - c, err := NewConfigurator(Config{}, nil) - require.NoError(t, err) - require.Nil(t, c.commonTLSConfig(false).ClientCAs) - require.Nil(t, c.commonTLSConfig(false).RootCAs) - - c.caPool = &x509.CertPool{} - require.Equal(t, c.caPool, c.commonTLSConfig(false).ClientCAs) - require.Equal(t, c.caPool, c.commonTLSConfig(false).RootCAs) -} - -func TestConfigurator_CommonTLSConfigTLSMinVersion(t *testing.T) { - c, err := NewConfigurator(Config{TLSMinVersion: ""}, nil) - require.NoError(t, err) - require.Equal(t, c.commonTLSConfig(false).MinVersion, tlsLookup["tls10"]) - - for _, version := range tlsVersions() { - require.NoError(t, c.Update(Config{TLSMinVersion: version})) - require.Equal(t, c.commonTLSConfig(false).MinVersion, - tlsLookup[version]) - } - - require.Error(t, c.Update(Config{TLSMinVersion: "tlsBOGUS"})) -} - -func TestConfigurator_CommonTLSConfigVerifyIncoming(t *testing.T) { - c := Configurator{base: &Config{}} - type variant struct { - verify bool - expected tls.ClientAuthType - } - variants := []variant{ - {true, tls.RequireAndVerifyClientCert}, - {false, tls.NoClientCert}, - } - for _, v := range variants { - require.Equal(t, v.expected, c.commonTLSConfig(v.verify).ClientAuth) - } -} - -func TestConfigurator_OutgoingRPCTLSDisabled(t *testing.T) { - c := Configurator{base: &Config{}} - type variant struct { - verify bool - autoEncryptTLS bool - pool *x509.CertPool - expected bool - } - variants := []variant{ - {false, false, nil, false}, - {true, false, nil, true}, - {false, true, nil, true}, - {true, true, nil, true}, - - {true, false, &x509.CertPool{}, true}, - {false, true, &x509.CertPool{}, true}, - {true, true, &x509.CertPool{}, true}, - } - for i, v := range variants { - info := fmt.Sprintf("case %d", i) - c.caPool = v.pool - c.base.VerifyOutgoing = v.verify - c.base.AutoTLS = v.autoEncryptTLS - require.Equal(t, v.expected, c.outgoingRPCTLSEnabled(), info) - } -} - -func TestConfigurator_MutualTLSCapable(t *testing.T) { +func TestConfigurator_InternalRPCMutualTLSCapable(t *testing.T) { // if this test is failing because of expired certificates // use the procedure in test/CA-GENERATION.md t.Run("no ca", func(t *testing.T) { @@ -787,7 +1121,9 @@ func TestConfigurator_MutualTLSCapable(t *testing.T) { t.Run("ca and no keys", func(t *testing.T) { config := Config{ - CAFile: "../test/hostname/CertAuth.crt", + InternalRPC: ProtocolConfig{ + CAFile: "../test/hostname/CertAuth.crt", + }, Domain: "consul", } c, err := NewConfigurator(config, nil) @@ -798,10 +1134,12 @@ func TestConfigurator_MutualTLSCapable(t *testing.T) { t.Run("ca and manual key", func(t *testing.T) { config := Config{ - CAFile: "../test/hostname/CertAuth.crt", - CertFile: "../test/hostname/Bob.crt", - KeyFile: "../test/hostname/Bob.key", - Domain: "consul", + InternalRPC: ProtocolConfig{ + CAFile: "../test/hostname/CertAuth.crt", + CertFile: "../test/hostname/Bob.crt", + KeyFile: "../test/hostname/Bob.key", + }, + Domain: "consul", } c, err := NewConfigurator(config, nil) require.NoError(t, err) @@ -809,12 +1147,6 @@ func TestConfigurator_MutualTLSCapable(t *testing.T) { require.True(t, c.MutualTLSCapable()) }) - loadFile := func(t *testing.T, path string) string { - data, err := ioutil.ReadFile(path) - require.NoError(t, err) - return string(data) - } - t.Run("autoencrypt ca and no autoencrypt keys", func(t *testing.T) { config := Config{ Domain: "consul", @@ -857,104 +1189,9 @@ func TestConfigurator_UpdateAutoTLSCA_DoesNotPanic(t *testing.T) { } func TestConfigurator_VerifyIncomingRPC(t *testing.T) { - c := Configurator{base: &Config{ - VerifyIncomingRPC: true, - }} - verify := c.VerifyIncomingRPC() - require.Equal(t, c.base.VerifyIncomingRPC, verify) -} - -func TestConfigurator_IncomingRPCConfig(t *testing.T) { - c, err := NewConfigurator(Config{ - VerifyIncomingRPC: true, - CAFile: "../test/ca/root.cer", - CertFile: "../test/key/ourdomain.cer", - KeyFile: "../test/key/ourdomain.key", - }, nil) - require.NoError(t, err) - tlsConf := c.IncomingRPCConfig() - require.Equal(t, tls.RequireAndVerifyClientCert, tlsConf.ClientAuth) - require.Empty(t, tlsConf.NextProtos) - require.Empty(t, tlsConf.ServerName) - - require.NotNil(t, tlsConf.GetConfigForClient) - tlsConf, err = tlsConf.GetConfigForClient(nil) - require.NoError(t, err) - require.Equal(t, tls.RequireAndVerifyClientCert, tlsConf.ClientAuth) - require.Empty(t, tlsConf.NextProtos) - require.Empty(t, tlsConf.ServerName) -} - -func TestConfigurator_IncomingALPNRPCConfig(t *testing.T) { - c, err := NewConfigurator(Config{ - VerifyIncomingRPC: false, // ignored, assumed true - CAFile: "../test/ca/root.cer", - CertFile: "../test/key/ourdomain.cer", - KeyFile: "../test/key/ourdomain.key", - }, nil) - require.NoError(t, err) - tlsConf := c.IncomingALPNRPCConfig([]string{"foo/1", "bar/2"}) - require.Equal(t, tls.RequireAndVerifyClientCert, tlsConf.ClientAuth) - require.False(t, tlsConf.InsecureSkipVerify) - require.Equal(t, []string{"foo/1", "bar/2"}, tlsConf.NextProtos) - require.Empty(t, tlsConf.ServerName) - - require.NotNil(t, tlsConf.GetConfigForClient) - tlsConf, err = tlsConf.GetConfigForClient(nil) - require.NoError(t, err) - require.Equal(t, tls.RequireAndVerifyClientCert, tlsConf.ClientAuth) - require.False(t, tlsConf.InsecureSkipVerify) - require.Equal(t, []string{"foo/1", "bar/2"}, tlsConf.NextProtos) - require.Empty(t, tlsConf.ServerName) -} - -func TestConfigurator_IncomingHTTPSConfig(t *testing.T) { - - // compare tls.Config.GetConfigForClient by nil/not-nil, since Go can not compare - // functions any other way. - cmpClientFunc := cmp.Comparer(func(x, y func(*tls.ClientHelloInfo) (*tls.Config, error)) bool { - return (x == nil && y == nil) || (x != nil && y != nil) - }) - - t.Run("default", func(t *testing.T) { - c, err := NewConfigurator(Config{}, nil) - require.NoError(t, err) - - cfg := c.IncomingHTTPSConfig() - - expected := &tls.Config{ - NextProtos: []string{"h2", "http/1.1"}, - InsecureSkipVerify: true, - MinVersion: tls.VersionTLS10, - GetConfigForClient: func(info *tls.ClientHelloInfo) (*tls.Config, error) { - return nil, nil - }, - } - assertDeepEqual(t, expected, cfg, cmpTLSConfig, cmpClientFunc) - }) - - t.Run("verify incoming", func(t *testing.T) { - c := Configurator{base: &Config{VerifyIncoming: true}} - - cfg := c.IncomingHTTPSConfig() - - expected := &tls.Config{ - NextProtos: []string{"h2", "http/1.1"}, - InsecureSkipVerify: true, - MinVersion: tls.VersionTLS10, - GetConfigForClient: func(info *tls.ClientHelloInfo) (*tls.Config, error) { - return nil, nil - }, - ClientAuth: tls.RequireAndVerifyClientCert, - } - assertDeepEqual(t, expected, cfg, cmpTLSConfig, cmpClientFunc) - }) - -} - -var cmpTLSConfig = cmp.Options{ - cmpopts.IgnoreFields(tls.Config{}, "GetCertificate", "GetClientCertificate"), - cmpopts.IgnoreUnexported(tls.Config{}), + c := Configurator{base: &Config{}} + c.base.InternalRPC.VerifyIncoming = true + require.True(t, c.VerifyIncomingRPC()) } func TestConfigurator_OutgoingTLSConfigForCheck(t *testing.T) { @@ -970,7 +1207,13 @@ func TestConfigurator_OutgoingTLSConfigForCheck(t *testing.T) { configurator, err := tc.conf() require.NoError(t, err) c := configurator.OutgoingTLSConfigForCheck(tc.skipVerify, tc.serverName) - assertDeepEqual(t, tc.expected, c, cmpTLSConfig) + + if diff := cmp.Diff(tc.expected, c, cmp.Options{ + cmpopts.IgnoreFields(tls.Config{}, "GetCertificate", "GetClientCertificate"), + cmpopts.IgnoreUnexported(tls.Config{}), + }); diff != "" { + t.Fatalf("assertion failed: values are not equal\n--- expected\n+++ actual\n%v", diff) + } } testCases := []testCase{ @@ -985,7 +1228,9 @@ func TestConfigurator_OutgoingTLSConfigForCheck(t *testing.T) { name: "default tls, skip verify, no server name", conf: func() (*Configurator, error) { return NewConfigurator(Config{ - TLSMinVersion: "tls12", + InternalRPC: ProtocolConfig{ + TLSMinVersion: "tls12", + }, EnableAgentTLSForChecks: false, }, nil) }, @@ -996,7 +1241,9 @@ func TestConfigurator_OutgoingTLSConfigForCheck(t *testing.T) { name: "default tls, skip verify, default server name", conf: func() (*Configurator, error) { return NewConfigurator(Config{ - TLSMinVersion: "tls12", + InternalRPC: ProtocolConfig{ + TLSMinVersion: "tls12", + }, EnableAgentTLSForChecks: false, ServerName: "servername", NodeName: "nodename", @@ -1009,7 +1256,9 @@ func TestConfigurator_OutgoingTLSConfigForCheck(t *testing.T) { name: "default tls, skip verify, check server name", conf: func() (*Configurator, error) { return NewConfigurator(Config{ - TLSMinVersion: "tls12", + InternalRPC: ProtocolConfig{ + TLSMinVersion: "tls12", + }, EnableAgentTLSForChecks: false, ServerName: "servername", }, nil) @@ -1025,7 +1274,9 @@ func TestConfigurator_OutgoingTLSConfigForCheck(t *testing.T) { name: "agent tls, default server name", conf: func() (*Configurator, error) { return NewConfigurator(Config{ - TLSMinVersion: "tls12", + InternalRPC: ProtocolConfig{ + TLSMinVersion: "tls12", + }, EnableAgentTLSForChecks: true, NodeName: "nodename", ServerName: "servername", @@ -1040,7 +1291,9 @@ func TestConfigurator_OutgoingTLSConfigForCheck(t *testing.T) { name: "agent tls, skip verify, node name for server name", conf: func() (*Configurator, error) { return NewConfigurator(Config{ - TLSMinVersion: "tls12", + InternalRPC: ProtocolConfig{ + TLSMinVersion: "tls12", + }, EnableAgentTLSForChecks: true, NodeName: "nodename", }, nil) @@ -1056,7 +1309,9 @@ func TestConfigurator_OutgoingTLSConfigForCheck(t *testing.T) { name: "agent tls, skip verify, with server name override", conf: func() (*Configurator, error) { return NewConfigurator(Config{ - TLSMinVersion: "tls12", + InternalRPC: ProtocolConfig{ + TLSMinVersion: "tls12", + }, EnableAgentTLSForChecks: true, ServerName: "servername", }, nil) @@ -1078,130 +1333,6 @@ func TestConfigurator_OutgoingTLSConfigForCheck(t *testing.T) { } } -func assertDeepEqual(t *testing.T, x, y interface{}, opts ...cmp.Option) { - t.Helper() - if diff := cmp.Diff(x, y, opts...); diff != "" { - t.Fatalf("assertion failed: values are not equal\n--- expected\n+++ actual\n%v", diff) - } -} - -func TestConfigurator_OutgoingRPCConfig(t *testing.T) { - c := &Configurator{base: &Config{}} - require.Nil(t, c.OutgoingRPCConfig()) - - c, err := NewConfigurator(Config{ - VerifyOutgoing: true, - CAFile: "../test/ca/root.cer", - }, nil) - require.NoError(t, err) - - tlsConf := c.OutgoingRPCConfig() - require.NotNil(t, tlsConf) - require.Equal(t, tls.NoClientCert, tlsConf.ClientAuth) - require.True(t, tlsConf.InsecureSkipVerify) - require.Empty(t, tlsConf.NextProtos) - require.Empty(t, tlsConf.ServerName) -} - -func TestConfigurator_OutgoingALPNRPCConfig(t *testing.T) { - c := &Configurator{base: &Config{}} - require.Nil(t, c.outgoingALPNRPCConfig()) - - c, err := NewConfigurator(Config{ - VerifyOutgoing: false, // ignored, assumed true - CAFile: "../test/ca/root.cer", - CertFile: "../test/key/ourdomain.cer", - KeyFile: "../test/key/ourdomain.key", - }, nil) - require.NoError(t, err) - - tlsConf := c.outgoingALPNRPCConfig() - require.NotNil(t, tlsConf) - require.Equal(t, tls.RequireAndVerifyClientCert, tlsConf.ClientAuth) - require.False(t, tlsConf.InsecureSkipVerify) - require.Empty(t, tlsConf.NextProtos) - require.Empty(t, tlsConf.ServerName) -} - -func TestConfigurator_OutgoingRPCWrapper(t *testing.T) { - c := &Configurator{base: &Config{}} - wrapper := c.OutgoingRPCWrapper() - require.NotNil(t, wrapper) - conn := &net.TCPConn{} - cWrap, err := wrapper("", conn) - require.NoError(t, err) - require.Equal(t, conn, cWrap) - - c, err = NewConfigurator(Config{ - VerifyOutgoing: true, - CAFile: "../test/ca/root.cer", - }, nil) - require.NoError(t, err) - - wrapper = c.OutgoingRPCWrapper() - require.NotNil(t, wrapper) - cWrap, err = wrapper("", conn) - require.EqualError(t, err, "invalid argument") - require.NotEqual(t, conn, cWrap) -} - -func TestConfigurator_OutgoingALPNRPCWrapper(t *testing.T) { - c := &Configurator{base: &Config{}} - wrapper := c.OutgoingRPCWrapper() - require.NotNil(t, wrapper) - conn := &net.TCPConn{} - cWrap, err := wrapper("", conn) - require.NoError(t, err) - require.Equal(t, conn, cWrap) - - c, err = NewConfigurator(Config{ - VerifyOutgoing: true, - CAFile: "../test/ca/root.cer", - }, nil) - require.NoError(t, err) - - wrapper = c.OutgoingRPCWrapper() - require.NotNil(t, wrapper) - cWrap, err = wrapper("", conn) - require.EqualError(t, err, "invalid argument") - require.NotEqual(t, conn, cWrap) -} - -func TestConfigurator_UpdateChecks(t *testing.T) { - c, err := NewConfigurator(Config{}, nil) - require.NoError(t, err) - require.NoError(t, c.Update(Config{})) - require.Error(t, c.Update(Config{VerifyOutgoing: true})) - require.Error(t, c.Update(Config{VerifyIncoming: true, CAFile: "../test/ca/root.cer"})) - require.False(t, c.base.VerifyIncoming) - require.False(t, c.base.VerifyOutgoing) - require.Equal(t, uint64(2), c.version) -} - -func TestConfigurator_UpdateSetsStuff(t *testing.T) { - c, err := NewConfigurator(Config{}, nil) - require.NoError(t, err) - require.Nil(t, c.caPool) - require.Nil(t, c.manual.cert) - require.Equal(t, c.base, &Config{}) - require.Equal(t, uint64(1), c.version) - - require.Error(t, c.Update(Config{VerifyOutgoing: true})) - require.Equal(t, uint64(1), c.version) - - config := Config{ - CAFile: "../test/ca/root.cer", - CertFile: "../test/key/ourdomain.cer", - KeyFile: "../test/key/ourdomain.key", - } - require.NoError(t, c.Update(config)) - require.NotNil(t, c.caPool) - require.Len(t, c.caPool.Subjects(), 1) - require.NotNil(t, c.manual.cert) - require.Equal(t, c.base, &config) - require.Equal(t, uint64(2), c.version) -} - func TestConfigurator_ServerNameOrNodeName(t *testing.T) { c := Configurator{base: &Config{}} type variant struct { @@ -1220,52 +1351,19 @@ func TestConfigurator_ServerNameOrNodeName(t *testing.T) { } } -func TestConfigurator_VerifyOutgoing(t *testing.T) { - c := Configurator{base: &Config{}} - type variant struct { - verify bool - autoEncryptTLS bool - pool *x509.CertPool - expected bool - } - variants := []variant{ - {false, false, nil, false}, - {true, false, nil, true}, - {false, true, nil, false}, - {true, true, nil, true}, - - {false, false, &x509.CertPool{}, false}, - {true, false, &x509.CertPool{}, true}, - {false, true, &x509.CertPool{}, true}, - {true, true, &x509.CertPool{}, true}, - } - for i, v := range variants { - info := fmt.Sprintf("case %d", i) - c.caPool = v.pool - c.base.VerifyOutgoing = v.verify - c.base.AutoTLS = v.autoEncryptTLS - require.Equal(t, v.expected, c.verifyOutgoing(), info) - } -} - -func TestConfigurator_Domain(t *testing.T) { - c := Configurator{base: &Config{Domain: "something"}} - require.Equal(t, "something", c.domain()) -} - -func TestConfigurator_VerifyServerHostname(t *testing.T) { +func TestConfigurator_InternalRPCVerifyServerHostname(t *testing.T) { c := Configurator{base: &Config{}} require.False(t, c.VerifyServerHostname()) - c.base.VerifyServerHostname = true + c.base.InternalRPC.VerifyServerHostname = true c.autoTLS.verifyServerHostname = false require.True(t, c.VerifyServerHostname()) - c.base.VerifyServerHostname = false + c.base.InternalRPC.VerifyServerHostname = false c.autoTLS.verifyServerHostname = true require.True(t, c.VerifyServerHostname()) - c.base.VerifyServerHostname = true + c.base.InternalRPC.VerifyServerHostname = true c.autoTLS.verifyServerHostname = true require.True(t, c.VerifyServerHostname()) } @@ -1285,7 +1383,7 @@ func TestConfigurator_AutoEncryptCert(t *testing.T) { require.Equal(t, int64(4679716209), c.AutoEncryptCert().NotAfter.Unix()) } -func TestConfigurator_AuthorizeServerConn(t *testing.T) { +func TestConfigurator_AuthorizeInternalRPCServerConn(t *testing.T) { caPEM, caPK, err := GenerateCA(CAOpts{Days: 5, Domain: "consul"}) require.NoError(t, err) @@ -1294,7 +1392,7 @@ func TestConfigurator_AuthorizeServerConn(t *testing.T) { err = ioutil.WriteFile(caPath, []byte(caPEM), 0600) require.NoError(t, err) - // Cert and key are not used, but required to get past validateConfig + // Cert and key are not used, but required to get past validation. signer, err := ParseSigner(caPK) require.NoError(t, err) pub, pk, err := GenerateCert(CertOpts{ @@ -1310,15 +1408,16 @@ func TestConfigurator_AuthorizeServerConn(t *testing.T) { require.NoError(t, err) cfg := Config{ - VerifyServerHostname: true, - VerifyIncomingRPC: true, - Domain: "consul", - CAFile: caPath, - CertFile: certFile, - KeyFile: keyFile, + InternalRPC: ProtocolConfig{ + VerifyServerHostname: true, + VerifyIncoming: true, + CAFile: caPath, + CertFile: certFile, + KeyFile: keyFile, + }, + Domain: "consul", } - c, err := NewConfigurator(cfg, hclog.New(nil)) - require.NoError(t, err) + c := makeConfigurator(t, cfg) t.Run("wrong DNSName", func(t *testing.T) { signer, err := ParseSigner(caPK) @@ -1402,10 +1501,12 @@ func TestConfigurator_AuthorizeServerConn(t *testing.T) { t.Run("disabled by verify_incoming_rpc", func(t *testing.T) { cfg := Config{ - VerifyServerHostname: true, - VerifyIncomingRPC: false, - Domain: "consul", - CAFile: caPath, + InternalRPC: ProtocolConfig{ + VerifyServerHostname: true, + VerifyIncoming: false, + CAFile: caPath, + }, + Domain: "consul", } c, err := NewConfigurator(cfg, hclog.New(nil)) require.NoError(t, err) @@ -1414,7 +1515,39 @@ func TestConfigurator_AuthorizeServerConn(t *testing.T) { err = c.AuthorizeServerConn("dc1", s) require.NoError(t, err) }) +} +func TestConfig_tlsVersions(t *testing.T) { + require.Equal(t, []string{"tls10", "tls11", "tls12", "tls13"}, tlsVersions()) + expected := "tls10, tls11, tls12, tls13" + require.Equal(t, expected, strings.Join(tlsVersions(), ", ")) +} + +func TestConfigurator_GRPCTLSConfigured(t *testing.T) { + t.Run("certificate manually configured", func(t *testing.T) { + c := makeConfigurator(t, Config{ + GRPC: ProtocolConfig{ + CertFile: "../test/hostname/Alice.crt", + KeyFile: "../test/hostname/Alice.key", + }, + }) + require.True(t, c.GRPCTLSConfigured()) + }) + + t.Run("AutoTLS", func(t *testing.T) { + c := makeConfigurator(t, Config{}) + + bobCert := loadFile(t, "../test/hostname/Bob.crt") + bobKey := loadFile(t, "../test/hostname/Bob.key") + require.NoError(t, c.UpdateAutoTLSCert(bobCert, bobKey)) + + require.True(t, c.GRPCTLSConfigured()) + }) + + t.Run("no certificate", func(t *testing.T) { + c := makeConfigurator(t, Config{}) + require.False(t, c.GRPCTLSConfigured()) + }) } type fakeTLSConn struct { @@ -1438,8 +1571,63 @@ func certChain(t *testing.T, certs ...string) []*x509.Certificate { return result } -func TestConfig_tlsVersions(t *testing.T) { - require.Equal(t, []string{"tls10", "tls11", "tls12", "tls13"}, tlsVersions()) - expected := "tls10, tls11, tls12, tls13" - require.Equal(t, expected, strings.Join(tlsVersions(), ", ")) +func startRPCTLSServer(t *testing.T, c *Configurator) (net.Conn, <-chan error) { + client, errc, _ := startTLSServer(c.IncomingRPCConfig()) + return client, errc +} + +func startALPNRPCTLSServer(t *testing.T, config *Config, alpnProtos []string) (net.Conn, <-chan error) { + cfg := makeConfigurator(t, *config).IncomingALPNRPCConfig(alpnProtos) + client, errc, _ := startTLSServer(cfg) + return client, errc +} + +func makeConfigurator(t *testing.T, config Config) *Configurator { + t.Helper() + + c, err := NewConfigurator(config, nil) + require.NoError(t, err) + + return c +} + +func startTLSServer(tlsConfigServer *tls.Config) (net.Conn, <-chan error, <-chan []*x509.Certificate) { + errc := make(chan error, 1) + certc := make(chan []*x509.Certificate, 1) + + client, server := net.Pipe() + + // Use yamux to buffer the reads, otherwise it's easy to deadlock + muxConf := yamux.DefaultConfig() + serverSession, _ := yamux.Server(server, muxConf) + clientSession, _ := yamux.Client(client, muxConf) + clientConn, _ := clientSession.Open() + serverConn, _ := serverSession.Accept() + + go func() { + tlsServer := tls.Server(serverConn, tlsConfigServer) + if err := tlsServer.Handshake(); err != nil { + errc <- err + } + certc <- tlsServer.ConnectionState().PeerCertificates + close(errc) + + // Because net.Pipe() is unbuffered, if both sides + // Close() simultaneously, we will deadlock as they + // both send an alert and then block. So we make the + // server read any data from the client until error or + // EOF, which will allow the client to Close(), and + // *then* we Close() the server. + io.Copy(ioutil.Discard, tlsServer) + tlsServer.Close() + }() + return clientConn, errc, certc +} + +func loadFile(t *testing.T, path string) string { + t.Helper() + + data, err := ioutil.ReadFile(path) + require.NoError(t, err) + return string(data) } diff --git a/website/content/docs/agent/index.mdx b/website/content/docs/agent/index.mdx index 6d3896951..b937214a8 100644 --- a/website/content/docs/agent/index.mdx +++ b/website/content/docs/agent/index.mdx @@ -245,7 +245,7 @@ Refer to the [Security](/docs/security) chapter for additional information about ```hcl node_name = "consul-server" -server = true +server = true ui_config { enabled = true } @@ -257,13 +257,21 @@ retry_join = [ "consul-server2", "consul-server3" ] -encrypt = "aPuGh+5UDskRAbkLaXRzFoSOcSM+5vAK+NEYOWHJH7w=" -verify_incoming = true -verify_outgoing = true -verify_server_hostname = true -ca_file = "/consul/config/certs/consul-agent-ca.pem" -cert_file = "/consul/config/certs/dc1-server-consul-0.pem" -key_file = "/consul/config/certs/dc1-server-consul-0-key.pem" +encrypt = "aPuGh+5UDskRAbkLaXRzFoSOcSM+5vAK+NEYOWHJH7w=" + +tls { + defaults { + verify_incoming = true + verify_outgoing = true + ca_file = "/consul/config/certs/consul-agent-ca.pem" + cert_file = "/consul/config/certs/dc1-server-consul-0.pem" + key_file = "/consul/config/certs/dc1-server-consul-0-key.pem" + } + + internal_rpc { + verify_server_hostname = true + } +} ``` @@ -281,12 +289,18 @@ key_file = "/consul/config/certs/dc1-server-consul-0-key.pem" }, "retry_join": ["consul-server1", "consul-server2"], "encrypt": "aPuGh+5UDskRAbkLaXRzFoSOcSM+5vAK+NEYOWHJH7w=", - "verify_incoming": true, - "verify_outgoing": true, - "verify_server_hostname": true, - "ca_file": "/consul/config/certs/consul-agent-ca.pem", - "cert_file": "/consul/config/certs/dc1-server-consul-0.pem", - "key_file": "/consul/config/certs/dc1-server-consul-0-key.pem" + "tls": { + "defaults": { + "verify_incoming": true, + "verify_outgoing": true, + "ca_file": "/consul/config/certs/consul-agent-ca.pem", + "cert_file": "/consul/config/certs/dc1-server-consul-0.pem", + "key_file": "/consul/config/certs/dc1-server-consul-0-key.pem" + }, + "internal_rpc": { + "verify_server_hostname": true + } + } } ``` diff --git a/website/content/docs/agent/options.mdx b/website/content/docs/agent/options.mdx index 1cd3c5d27..452b2e51c 100644 --- a/website/content/docs/agent/options.mdx +++ b/website/content/docs/agent/options.mdx @@ -2446,114 +2446,197 @@ There are also a number of common configuration options supported by all provide ## TLS Configuration Reference This section documents all of the configuration settings that apply to Agent TLS. Agent -TLS is used by the HTTP API, server RPC, and xDS interfaces. Some of these settings may also be -applied automatically by [auto_config](#auto_config) or [auto_encrypt](#auto_encrypt). +TLS is used by the HTTP API, internal RPC, and gRPC/xDS interfaces. Some of these settings +may also be applied automatically by [auto_config](#auto_config) or [auto_encrypt](#auto_encrypt). -~> **Security Note:** The Certificate Authority (CA) specified by `ca_file` or `ca_path` -should be a private CA, not a public one. We recommend using a dedicated CA -which should not be used with any other systems. Any certificate signed by the -CA will be allowed to communicate with the cluster and a specially crafted certificate -signed by the CA can be used to gain full access to Consul. +~> **Security Note:** The Certificate Authority (CA) configured on the internal RPC interface +(either explicitly by `tls.internal_rpc` or implicitly by `tls.defaults`) should be a private +CA, not a public one. We recommend using a dedicated CA which should not be used with any other +systems. Any certificate signed by the CA will be allowed to communicate with the cluster and a +specially crafted certificate signed by the CA can be used to gain full access to Consul. -- `ca_file` This provides a file path to a PEM-encoded certificate - authority. The certificate authority is used to check the authenticity of client - and server connections with the appropriate [`verify_incoming`](#verify_incoming) - or [`verify_outgoing`](#verify_outgoing) flags. +- `tls` Added in Consul 1.12, for previous versions see + [Deprecated Options](#tls_deprecated_options). -- `ca_path` This provides a path to a directory of PEM-encoded - certificate authority files. These certificate authorities are used to check the - authenticity of client and server connections with the appropriate [`verify_incoming`](#verify_incoming) or [`verify_outgoing`](#verify_outgoing) flags. + - `defaults` ((#tls_defaults)) Provides default settings that will be applied + to every interface unless explicitly overridden by `tls.grpc`, `tls.https`, + or `tls.internal_rpc`. -- `cert_file` This provides a file path to a PEM-encoded - certificate. The certificate is provided to clients or servers to verify the agent's - authenticity. It must be provided along with [`key_file`](#key_file). + - `ca_file` ((#tls_defaults_ca_file)) This provides a file path to a + PEM-encoded certificate authority. The certificate authority is used to + check the authenticity of client and server connections with the + appropriate [`verify_incoming`](#tls_defaults_verify_incoming) or + [`verify_outgoing`](#tls_defaults_verify_outgoing) flags. -- `key_file` This provides a the file path to a PEM-encoded - private key. The key is used with the certificate to verify the agent's authenticity. - This must be provided along with [`cert_file`](#cert_file). + - `ca_path` ((#tls_defaults_ca_path)) This provides a path to a directory + of PEM-encoded certificate authority files. These certificate authorities + are used to check the authenticity of client and server connections with + the appropriate [`verify_incoming`](#tls_defaults_verify_incoming) or + [`verify_outgoing`](#tls_defaults_verify_outgoing) flags. + + - `cert_file` ((#tls_defaults_cert_file)) This provides a file path to a + PEM-encoded certificate. The certificate is provided to clients or servers + to verify the agent's authenticity. It must be provided along with + [`key_file`](#tls_defaults_key_file). + + - `key_file` ((#tls_defaults_key_file)) This provides a the file path to a + PEM-encoded private key. The key is used with the certificate to verify + the agent's authenticity. This must be provided along with + [`cert_file`](#tls_defaults_cert_file). + + - `tls_min_version` ((#tls_defaults_tls_min_version)) This specifies the + minimum supported version of TLS. Accepted values are "tls10", "tls11", + "tls12", or "tls13". This defaults to "tls12". **WARNING: TLS 1.1 and + lower are generally considered less secure; avoid using these if + possible.** + + - `tls_cipher_suites` ((#tls_defaults_tls_cipher_suites)) This specifies + the list of supported ciphersuites as a comma-separated-list. Applicable + to TLS 1.2 and below only. The list of all supported ciphersuites is + available through [this search](https://github.com/hashicorp/consul/search?q=cipherMap+%3A%3D+map&unscoped_q=cipherMap+%3A%3D+map). + + ~> **Note:** The ordering of cipher suites will not be guaranteed from + Consul 1.11 onwards. See this [post](https://go.dev/blog/tls-cipher-suites) + for details. + + - `verify_incoming` - ((#tls_defaults_verify_incoming)) If set to true, + Consul requires that all incoming connections make use of TLS and that + the client provides a certificate signed by a Certificate Authority from + the [`ca_file`](#tls_defaults_ca_file) or [`ca_path`](#tls_defaults_ca_path). + By default, this is false, and Consul will not enforce the use of TLS or + verify a client's authenticity. + + - `verify_outgoing` - ((#tls_defaults_verify_outgoing)) If set to true, + Consul requires that all outgoing connections from this agent make use + of TLS and that the server provides a certificate that is signed by a + Certificate Authority from the [`ca_file`](#tls_defaults_ca_file) or + [`ca_path`](#tls_defaults_ca_path). By default, this is false, and Consul + will not make use of TLS for outgoing connections. This applies to clients + and servers as both will make outgoing connections. This setting *does not* + apply to the gRPC interface as Consul makes no outgoing connections on this + interface. + + - `grpc` ((#tls_grpc)) Provides settings for the gRPC/xDS interface. To enable + the gRPC interface you must define a port via [`ports.grpc`](#grpc_port). + To enable TLS on the gRPC interface you also must define an HTTPS port via + [`ports.https`](#https_port). + + - `ca_file` ((#tls_grpc_ca_file)) Overrides [`tls.defaults.ca_file`](#tls_defaults_ca_file). + + - `ca_path` ((#tls_grpc_ca_path)) Overrides [`tls.defaults.ca_path`](#tls_defaults_ca_path). + + - `cert_file` ((#tls_grpc_cert_file)) Overrides [`tls.defaults.cert_file`](#tls_defaults_cert_file). + + - `key_file` ((#tls_grpc_key_file)) Overrides [`tls.defaults.key_file`](#tls_defaults_key_file). + + - `tls_min_version` ((#tls_grpc_tls_min_version)) Overrides [`tls.defaults.tls_min_version`](#tls_defaults_tls_min_version). + + - `tls_cipher_suites` ((#tls_grpc_tls_cipher_suites)) Overrides [`tls.defaults.tls_cipher_suites`](#tls_defaults_tls_cipher_suites). + + - `verify_incoming` - ((#tls_grpc_verify_incoming)) Overrides [`tls.defaults.verify_incoming`](#tls_defaults_verify_incoming). + + - `https` ((#tls_https)) Provides settings for the HTTPS interface. To enable + the HTTPS interface you must define a port via [`ports.https`](#https_port). + + - `ca_file` ((#tls_https_ca_file)) Overrides [`tls.defaults.ca_file`](#tls_defaults_ca_file). + + - `ca_path` ((#tls_https_ca_path)) Overrides [`tls.defaults.ca_path`](#tls_defaults_ca_path). + + - `cert_file` ((#tls_https_cert_file)) Overrides [`tls.defaults.cert_file`](#tls_defaults_cert_file). + + - `key_file` ((#tls_https_key_file)) Overrides [`tls.defaults.key_file`](#tls_defaults_key_file). + + - `tls_min_version` ((#tls_https_tls_min_version)) Overrides [`tls.defaults.tls_min_version`](#tls_defaults_tls_min_version). + + - `tls_cipher_suites` ((#tls_https_tls_cipher_suites)) Overrides [`tls.defaults.tls_cipher_suites`](#tls_defaults_tls_cipher_suites). + + - `verify_incoming` - ((#tls_https_verify_incoming)) Overrides [`tls.defaults.verify_incoming`](#tls_defaults_verify_incoming). + + - `verify_outgoing` - ((#tls_https_verify_outgoing)) Overrides [`tls.defaults.verify_outgoing`](#tls_defaults_verify_outgoing). + + - `internal_rpc` ((#tls_internal_rpc)) Provides settings for the internal + "server" RPC interface configured by [`ports.server`](#server_rpc_port). + + - `ca_file` ((#tls_internal_rpc_ca_file)) Overrides [`tls.defaults.ca_file`](#tls_defaults_ca_file). + + - `ca_path` ((#tls_internal_rpc_ca_path)) Overrides [`tls.defaults.ca_path`](#tls_defaults_ca_path). + + - `cert_file` ((#tls_internal_rpc_cert_file)) Overrides [`tls.defaults.cert_file`](#tls_defaults_cert_file). + + - `key_file` ((#tls_internal_rpc_key_file)) Overrides [`tls.defaults.key_file`](#tls_defaults_key_file). + + - `tls_min_version` ((#tls_internal_rpc_tls_min_version)) Overrides [`tls.defaults.tls_min_version`](#tls_defaults_tls_min_version). + + - `tls_cipher_suites` ((#tls_internal_rpc_tls_cipher_suites)) Overrides [`tls.defaults.tls_cipher_suites`](#tls_defaults_tls_cipher_suites). + + - `verify_incoming` - ((#tls_internal_rpc_verify_incoming)) Overrides [`tls.defaults.verify_incoming`](#tls_defaults_verify_incoming). + + ~> **Security Note:** `verify_incoming` *must* be set to true to prevent + anyone with access to the internal RPC port from gaining full access to + the Consul cluster. + + - `verify_outgoing` ((#tls_internal_rpc_verify_outgoing)) Overrides [`tls.defaults.verify_outgoing`](#tls_defaults_verify_outgoing). + + ~> **Security Note:** Servers that specify `verify_outgoing = true` will + always talk to other servers over TLS, but they still _accept_ non-TLS + connections to allow for a transition of all clients to TLS. Currently the + only way to enforce that no client can communicate with a server unencrypted + is to also enable `verify_incoming` which requires client certificates too. + + - `verify_server_hostname` ((#tls_internal_rpc_verify_server_hostname)) When + set to true, Consul verifies the TLS certificate presented by the servers + match the hostname `server..`. By default this is false, + and Consul does not verify the hostname of the certificate, only that it + is signed by a trusted CA. This setting *must* be enabled to prevent a + compromised client from gaining full read and write access to all cluster + data *including all ACL tokens and Connect CA root keys*. - `server_name` When provided, this overrides the [`node_name`](#_node) for the TLS certificate. It can be used to ensure that the certificate name matches the hostname we declare. -- `tls_min_version` Added in Consul 0.7.4, this specifies - the minimum supported version of TLS. Accepted values are "tls10", "tls11", "tls12", - or "tls13". This defaults to "tls12". WARNING: TLS 1.1 and lower are generally - considered less secure; avoid using these if possible. +### Deprecated Options ((#tls_deprecated_options)) -- `tls_cipher_suites` Added in Consul 0.8.2, this specifies the list of - supported ciphersuites as a comma-separated-list. Applicable to TLS 1.2 and below only. - The list of all supported ciphersuites is available through - [this search](https://github.com/hashicorp/consul/search?q=cipherMap+%3A%3D+map&unscoped_q=cipherMap+%3A%3D+map). +The following options were deprecated in Consul 1.12, please use the +[`tls`](#tls) stanza instead. - ~> **Note:** The ordering of cipher suites will not be guaranteed from Consul 1.11 onwards. See this - [post](https://go.dev/blog/tls-cipher-suites) for details. +- `ca_file` See: [`tls.defaults.ca_file`](#tls_defaults_ca_file). -- `tls_prefer_server_cipher_suites` Added in Consul 0.8.2, this - will cause Consul to prefer the server's ciphersuite over the client ciphersuites. +- `ca_path` See: [`tls.defaults.ca_path`](#tls_defaults_ca_path). - ~> **Note:** This config will be deprecated in Consul 1.11. See this - [post](https://go.dev/blog/tls-cipher-suites) for details. +- `cert_file` See: [`tls.defaults.cert_file`](#tls_defaults_cert_file). -- `verify_incoming` - If set to true, Consul - requires that all incoming connections make use of TLS and that the client - provides a certificate signed by a Certificate Authority from the - [`ca_file`](#ca_file) or [`ca_path`](#ca_path). This applies to both server - RPC and to the HTTPS API. By default, this is false, and Consul will not - enforce the use of TLS or verify a client's authenticity. Turning on - `verify_incoming` on consul clients protects the HTTPS endpoint, by ensuring - that the certificate that is presented by a 3rd party tool to the HTTPS - endpoint was created by the CA that the consul client was setup with. If the - UI is served, the same checks are performed. +- `key_file` See: [`tls.defaults.key_file`](#tls_defaults_key_file). -- `verify_incoming_rpc` - When set to true, Consul - requires that all incoming RPC connections use TLS and that the client - provides a certificate signed by a Certificate Authority from the [`ca_file`](#ca_file) - or [`ca_path`](#ca_path). By default, this is false, and Consul will not enforce - the use of TLS or verify a client's authenticity. +- `tls_min_version` Added in Consul 0.7.4. + See: [`tls.defaults.tls_min_version`](#tls_defaults_tls_min_version). - ~> **Security Note:** `verify_incoming_rpc` _must_ be set to true to prevent anyone - with access to the RPC port from gaining full access to the Consul cluster. +- `tls_cipher_suites` Added in Consul 0.8.2. + See: [`tls.defaults.tls_cipher_suites`](#tls_defaults_tls_cipher_suites). -- `verify_incoming_https` - If set to true, - Consul requires that all incoming HTTPS connections make use of TLS and that the - client provides a certificate signed by a Certificate Authority from the [`ca_file`](#ca_file) - or [`ca_path`](#ca_path). By default, this is false, and Consul will not enforce - the use of TLS or verify a client's authenticity. To enable the HTTPS API, you - must define an HTTPS port via the [`ports`](#ports) configuration. By default, - HTTPS is disabled. +- `tls_prefer_server_cipher_suites` Added in Consul 0.8.2. This setting will + be ignored (see [this post](https://go.dev/blog/tls-cipher-suites) for details). -- `verify_outgoing` - If set to true, Consul requires - that all outgoing connections from this agent make use of TLS and that the server - provides a certificate that is signed by a Certificate Authority from the [`ca_file`](#ca_file) - or [`ca_path`](#ca_path). By default, this is false, and Consul will not make use - of TLS for outgoing connections. This applies to clients and servers as both will - make outgoing connections. +- `verify_incoming` See: [`tls.defaults.verify_incoming`](#tls_defaults_verify_incoming). - ~> **Security Note:** Note that servers that specify `verify_outgoing = true` will always talk to other servers over TLS, but they still _accept_ - non-TLS connections to allow for a transition of all clients to TLS. - Currently the only way to enforce that no client can communicate with a - server unencrypted is to also enable `verify_incoming` which requires client - certificates too. +- `verify_incoming_rpc` See: [`tls.internal_rpc.verify_incoming`](#tls_internal_rpc_verify_incoming). -- `verify_server_hostname` - When set to true, Consul verifies the TLS certificate - presented by the servers match the hostname `server..`. - By default this is false, and Consul does not verify the hostname - of the certificate, only that it is signed by a trusted CA. This setting _must_ be enabled - to prevent a compromised client from gaining full read and write access to all - cluster data _including all ACL tokens and Connect CA root keys_. This is new in 0.5.1. +- `verify_incoming_https` See: [`tls.https.verify_incoming`](#tls_https_verify_incoming). - ~> **Security Note:** From versions 0.5.1 to 1.4.0, due to a bug, setting - this flag alone _does not_ imply `verify_outgoing` and leaves client to server - and server to server RPCs unencrypted despite the documentation stating otherwise. See - [CVE-2018-19653](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-19653) - for more details. For those versions you **must also set `verify_outgoing = true`** to ensure encrypted RPC connections. +- `verify_outgoing` See: [`tls.defaults.verify_outgoing`](#tls_defaults_verify_outgoing). + +- `verify_server_hostname` See: [`tls.internal_rpc.verify_server_hostname`](#tls_internal_rpc_verify_server_hostname). ### Example Configuration File, with TLS -~> **Security Note:** all three verify options should be set as `true` to enable secure mTLS communication, enabling both -encryption and authentication. Failing to set [`verify_incoming`](#verify_incoming) or [`verify_outgoing`](#verify_outgoing) -will result in TLS not being enabled at all, even when specifying a [`ca_file`](#ca_file), [`cert_file`](#cert_file), and [`key_file`](#key_file). +~> **Security Note:** all three verify options should be set as `true` to enable +secure mTLS communication, enabling both encryption and authentication. Failing +to set [`verify_incoming`](#tls_defaults_verify_incoming) or +[`verify_outgoing`](#tls_defaults_verify_outgoing) either in the +interface-specific stanza (e.g. `tls.internal_rpc`, `tls.https`) or in +`tls.defaults` will result in TLS not being enabled at all, even when specifying +a [`ca_file`](#tls_defaults_ca_file), [`cert_file`](#tls_defaults_cert_file), +and [`key_file`](#tls_defaults_key_file). See, especially, the use of the `ports` setting highlighted below. @@ -2575,12 +2658,19 @@ ports { https = 8501 } -key_file = "/etc/pki/tls/private/my.key" -cert_file = "/etc/pki/tls/certs/my.crt" -ca_file = "/etc/pki/tls/certs/ca-bundle.crt" -verify_incoming = true -verify_outgoing = true -verify_server_hostname = true +tls { + defaults { + key_file = "/etc/pki/tls/private/my.key" + cert_file = "/etc/pki/tls/certs/my.crt" + ca_file = "/etc/pki/tls/certs/ca-bundle.crt" + verify_incoming = true + verify_outgoing = true + } + + internal_rpc { + verify_server_hostname = true + } +} ``` @@ -2600,12 +2690,18 @@ verify_server_hostname = true "ports": { "https": 8501 }, - "key_file": "/etc/pki/tls/private/my.key", - "cert_file": "/etc/pki/tls/certs/my.crt", - "ca_file": "/etc/pki/tls/certs/ca-bundle.crt", - "verify_incoming": true, - "verify_outgoing": true, - "verify_server_hostname": true + "tls": { + "defaults": { + "key_file": "/etc/pki/tls/private/my.key", + "cert_file": "/etc/pki/tls/certs/my.crt", + "ca_file": "/etc/pki/tls/certs/ca-bundle.crt", + "verify_incoming": true, + "verify_outgoing": true + }, + "internal_rpc": { + "verify_server_hostname": true + } + } } ``` @@ -2613,8 +2709,8 @@ verify_server_hostname = true -Consul will not enable TLS for the HTTP API unless the `https` port has been -assigned a port number `> 0`. We recommend using `8501` for `https` as this +Consul will not enable TLS for the HTTP or gRPC API unless the `https` port has +been assigned a port number `> 0`. We recommend using `8501` for `https` as this default will automatically work with some tooling. ## Ports Used diff --git a/website/content/docs/connect/configuration.mdx b/website/content/docs/connect/configuration.mdx index ba6a60740..e6ae57508 100644 --- a/website/content/docs/connect/configuration.mdx +++ b/website/content/docs/connect/configuration.mdx @@ -46,7 +46,7 @@ configuration file include: - [certificate authority settings](/docs/agent/options#connect) - [token replication](/docs/agent/options#acl_tokens_replication) - [dev mode](/docs/agent/options#_dev) -- [server host name verification](/docs/agent/options#verify_server_hostname) +- [server host name verification](/docs/agent/options#tls_internal_rpc_verify_server_hostname) If you would like to use Envoy as your Connect proxy you will need to [enable gRPC](/docs/agent/options#grpc_port). diff --git a/website/content/docs/ecs/manual/secure-configuration.mdx b/website/content/docs/ecs/manual/secure-configuration.mdx index 3e2722a79..1fc7345e7 100644 --- a/website/content/docs/ecs/manual/secure-configuration.mdx +++ b/website/content/docs/ecs/manual/secure-configuration.mdx @@ -157,8 +157,12 @@ auto_encrypt = { tls = true ip_san = ["$ECS_IPV4"] } -ca_file = "/tmp/consul-ca-cert.pem" -verify_outgoing = true +tls { + defaults { + ca_file = "/tmp/consul-ca-cert.pem" + verify_outgoing = true + } +} # Configure ACLs acl { @@ -177,12 +181,12 @@ EOF The following table describes the additional fields that must be included in the Consul client configuration file. -| Field name | Type | Description | -| --------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------ | -| [`encrypt`](/docs/agent/options#_encrypt) | string | Specifies the gossip encryption key | -| [`ca_file`](/docs/agent/options#ca_file) | string | Specifies the Consul server CA cert for TLS verification. | -| [`acl.enabled`](/docs/agent/options#acl_enabled) | boolen | Enable ACLs for this agent. | -| [`acl.tokens.agent`](/docs/agent/options#acl_tokens_agent) | string | Specifies the Consul client token which authorizes this agent with Consul servers. | +| Field name | Type | Description | +| --------------------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------ | +| [`encrypt`](/docs/agent/options#_encrypt) | string | Specifies the gossip encryption key | +| [`tls.defaults.ca_file`](/docs/agent/options#tls_defaults_ca_file) | string | Specifies the Consul server CA cert for TLS verification. | +| [`acl.enabled`](/docs/agent/options#acl_enabled) | boolean | Enable ACLs for this agent. | +| [`acl.tokens.agent`](/docs/agent/options#acl_tokens_agent) | string | Specifies the Consul client token which authorizes this agent with Consul servers. | ## Configure `consul-ecs-mesh-init` and `consul-ecs-health-sync` diff --git a/website/content/docs/k8s/helm.mdx b/website/content/docs/k8s/helm.mdx index afff97859..009e43348 100644 --- a/website/content/docs/k8s/helm.mdx +++ b/website/content/docs/k8s/helm.mdx @@ -294,11 +294,11 @@ Use these links to navigate to a particular top-level stanza. in the server certificate. This is useful when you need to access the Consul server(s) externally, for example, if you're using the UI. - - `verify` ((#v-global-tls-verify)) (`boolean: true`) - If true, `verify_outgoing`, `verify_server_hostname`, - and `verify_incoming_rpc` will be set to `true` for Consul servers and clients. - Set this to false to incrementally roll out TLS on an existing Consul cluster. - Please see https://consul.io/docs/k8s/operations/tls-on-existing-cluster - for more details. + - `verify` ((#v-global-tls-verify)) (`boolean: true`) - If true, `tls.defaults.verify_outgoing`, + `tls.internal_rpc.verify_server_hostname`, and `tls.internal_rpc.verify_incoming` will be set + to `true` for Consul servers and clients. Set this to false to incrementally roll out TLS + on an existing Consul cluster. + Please see https://consul.io/docs/k8s/operations/tls-on-existing-cluster for more details. - `httpsOnly` ((#v-global-tls-httpsonly)) (`boolean: true`) - If true, the Helm chart will configure Consul to disable the HTTP port on both clients and servers and to only accept HTTPS connections. diff --git a/website/content/docs/k8s/installation/deployment-configurations/servers-outside-kubernetes.mdx b/website/content/docs/k8s/installation/deployment-configurations/servers-outside-kubernetes.mdx index 2e5c75801..7c021e06a 100644 --- a/website/content/docs/k8s/installation/deployment-configurations/servers-outside-kubernetes.mdx +++ b/website/content/docs/k8s/installation/deployment-configurations/servers-outside-kubernetes.mdx @@ -56,7 +56,7 @@ You may also consider adopting Consul Enterprise for -> **Note:** Consul on Kubernetes currently does not support external servers that require mutual authentication for the HTTPS clients of the Consul servers, that is when servers have either -`verify_incoming` or `verify_incoming_https` set to `true`. +`tls.defaults.verify_incoming` or `tls.https.verify_incoming` set to `true`. As noted in the [Security Model](/docs/security#secure-configuration), that setting isn't strictly necessary to support Consul's threat model as it is recommended that all requests contain a valid ACL token. diff --git a/website/content/docs/k8s/installation/multi-cluster/vms-and-kubernetes.mdx b/website/content/docs/k8s/installation/multi-cluster/vms-and-kubernetes.mdx index e88be62b8..73e0c079e 100644 --- a/website/content/docs/k8s/installation/multi-cluster/vms-and-kubernetes.mdx +++ b/website/content/docs/k8s/installation/multi-cluster/vms-and-kubernetes.mdx @@ -75,9 +75,13 @@ The following sections detail how to export this data. ```hcl - cert_file = "vm-dc-server-consul-0.pem" - key_file = "vm-dc-server-consul-0-key.pem" - ca_file = "consul-agent-ca.pem" + tls { + defaults { + cert_file = "vm-dc-server-consul-0.pem" + key_file = "vm-dc-server-consul-0-key.pem" + ca_file = "consul-agent-ca.pem" + } + } ``` @@ -160,9 +164,19 @@ A final example server config file might look like: ```hcl # From above -cert_file = "vm-dc-server-consul-0.pem" -key_file = "vm-dc-server-consul-0-key.pem" -ca_file = "consul-agent-ca.pem" +tls { + defaults { + cert_file = "vm-dc-server-consul-0.pem" + key_file = "vm-dc-server-consul-0-key.pem" + ca_file = "consul-agent-ca.pem" + } + + internal_rpc { + verify_incoming = true + verify_outgoing = true + verify_server_hostname = true + } +} primary_gateways = ["1.2.3.4:443"] acl { enabled = true @@ -185,9 +199,6 @@ connect { enabled = true enable_mesh_gateway_wan_federation = true } -verify_incoming_rpc = true -verify_outgoing = true -verify_server_hostname = true ports { https = 8501 http = -1 diff --git a/website/content/docs/security/encryption.mdx b/website/content/docs/security/encryption.mdx index 0d71e8654..5d33117cb 100644 --- a/website/content/docs/security/encryption.mdx +++ b/website/content/docs/security/encryption.mdx @@ -75,17 +75,17 @@ CA then signs keys for each of the agents, as in ~> Certificates need to be created with x509v3 extendedKeyUsage attributes for both clientAuth and serverAuth since Consul uses a single cert/key pair for both server and client communications. TLS can be used to verify the authenticity of the servers or verify the authenticity of clients. -These modes are controlled by the [`verify_outgoing`](/docs/agent/options#verify_outgoing), -[`verify_server_hostname`](/docs/agent/options#verify_server_hostname), -and [`verify_incoming`](/docs/agent/options#verify_incoming) options, respectively. +These modes are controlled by the [`verify_outgoing`](/docs/agent/options#tls_internal_rpc_verify_outgoing), +[`verify_server_hostname`](/docs/agent/options#tls_internal_rpc_verify_server_hostname), +and [`verify_incoming`](/docs/agent/options#tls_internal_rpc_verify_incoming) options, respectively. -If [`verify_outgoing`](/docs/agent/options#verify_outgoing) is set, agents verify the +If [`verify_outgoing`](/docs/agent/options#tls_internal_rpc_verify_outgoing) is set, agents verify the authenticity of Consul for outgoing connections. Server nodes must present a certificate signed by a common certificate authority present on all agents, set via the agent's -[`ca_file`](/docs/agent/options#ca_file) and [`ca_path`](/docs/agent/options#ca_path) -options. All server nodes must have an appropriate key pair set using [`cert_file`](/docs/agent/options#cert_file) and [`key_file`](/docs/agent/options#key_file). +[`ca_file`](/docs/agent/options#tls_internal_rpc_ca_file) and [`ca_path`](/docs/agent/options#tls_internal_rpc_ca_path) +options. All server nodes must have an appropriate key pair set using [`cert_file`](/docs/agent/options#tls_internal_rpc_cert_file) and [`key_file`](/docs/agent/options#tls_internal_rpc_key_file). -If [`verify_server_hostname`](/docs/agent/options#verify_server_hostname) is set, then +If [`verify_server_hostname`](/docs/agent/options#tls_internal_rpc_verify_server_hostname) is set, then outgoing connections perform hostname verification. All servers must have a certificate valid for `server..` or the client will reject the handshake. This is a new configuration as of 0.5.1, and it is used to prevent a compromised client from being @@ -93,12 +93,12 @@ able to restart in server mode and perform a MITM (Man-In-The-Middle) attack. Ne to true, and generate the proper certificates, but this is defaulted to false to avoid breaking existing deployments. -If [`verify_incoming`](/docs/agent/options#verify_incoming) is set, the servers verify the +If [`verify_incoming`](/docs/agent/options#tls_internal_rpc_verify_incoming) is set, the servers verify the authenticity of all incoming connections. All clients must have a valid key pair set using -[`cert_file`](/docs/agent/options#cert_file) and -[`key_file`](/docs/agent/options#key_file). Servers will +[`cert_file`](/docs/agent/options#tls_internal_rpc_cert_file) and +[`key_file`](/docs/agent/options#tls_internal_rpc_key_file). Servers will also disallow any non-TLS connections. To force clients to use TLS, -[`verify_outgoing`](/docs/agent/options#verify_outgoing) must also be set. +[`verify_outgoing`](/docs/agent/options#tls_internal_rpc_verify_outgoing) must also be set. TLS is used to secure the RPC calls between agents, but gossip between nodes is done over UDP and is secured using a symmetric key. See above for enabling gossip encryption. diff --git a/website/content/docs/security/security-models/core.mdx b/website/content/docs/security/security-models/core.mdx index 75080942d..b11f5da30 100644 --- a/website/content/docs/security/security-models/core.mdx +++ b/website/content/docs/security/security-models/core.mdx @@ -72,29 +72,33 @@ environment and adapt these configurations accordingly. - **mTLS** - Mutual authentication of both the TLS server and client x509 certificates prevents internal abuse through unauthorized access to Consul agents within the cluster. - - [`verify_incoming`](/docs/agent/options#verify_incoming) - By default this is false, and should almost always be set - to true to require TLS verification for incoming client connections. This applies to both server RPC and to the - HTTPS API. + - [`tls.defaults.verify_incoming`](/docs/agent/options#tls_defaults_verify_incoming) - By default this is false, and + should almost always be set to true to require TLS verification for incoming client connections. This applies to the + internal RPC, HTTPS and gRPC APIs. - - [`verify_incoming_https`](/docs/agent/options#verify_incoming_https) - By default this is false, and should be set - to true to require clients to provide a valid TLS certificate when the Consul HTTPS API is enabled. TLS for the API - may be not be necessary if it is exclusively served over a loopback interface such as `localhost`. + - [`tls.https.verify_incoming`](/docs/agent/options#tls_https_verify_incoming) - By default this is false, and should + be set to true to require clients to provide a valid TLS certificate when the Consul HTTPS API is enabled. TLS for + the API may be not be necessary if it is exclusively served over a loopback interface such as `localhost`. - - [`verify_incoming_rpc`](/docs/agent/options#verify_incoming_rpc) - By default this is false, and should almost - always be set to true to require clients to provide a valid TLS certificate for Consul agent RPCs. + - [`tls.internal_rpc.verify_incoming`](/docs/agent/options#tls_internal_rpc_verify_incoming) - By default this is false, + and should almost always be set to true to require clients to provide a valid TLS certificate for Consul agent RPCs. - - [`verify_outgoing`](/docs/agent/options#verify_outgoing) - By default this is false, and should be set to true to - require TLS for outgoing connections from server or client agents. Servers that specify `verify_outgoing = true` - will always talk to other servers over TLS, but they still accept non-TLS connections to allow for a transition of - all clients to TLS. Currently the only way to enforce that no client can communicate with a server unencrypted is - to also enable `verify_incoming` which requires client certificates too. + - [`tls.grpc.verify_incoming`](/docs/agent/options#tls_grpc_verify_incoming) - By default this is false, and should + be set to true to require clients to provide a valid TLS certificate when the Consul gRPC API is enabled. TLS for + the API may be not be necessary if it is exclusively served over a loopback interface such as `localhost`. + + - [`tls.internal_rpc.verify_outgoing`](/docs/agent/options#tls_internal_rpc_verify_outgoing) - By default this is false, + and should be set to true to require TLS for outgoing connections from server or client agents. Servers that specify + `verify_outgoing = true` will always talk to other servers over TLS, but they still accept non-TLS connections to allow + for a transition of all clients to TLS. Currently the only way to enforce that no client can communicate with a server + unencrypted is to also enable `verify_incoming` which requires client certificates too. - [`enable_agent_tls_for_checks`](/docs/agent/options#enable_agent_tls_for_checks) - By default this is false, and should almost always be set to true to require mTLS to set up the client for HTTP or gRPC health checks. This was added in Consul 1.0.1. - - [`verify_server_hostname`](/docs/agent/options#verify_server_hostname) - By default this is false, and should be - set to true to require that the TLS certificate presented by the servers matches + - [`tls.internal_rpc.verify_server_hostname`](/docs/agent/options#tls_internal_rpc_verify_server_hostname) - By default + this is false, and should be set to true to require that the TLS certificate presented by the servers matches `server..` hostname for outgoing TLS connections. The default configuration does not verify the hostname of the certificate, only that it is signed by a trusted CA. This setting is critical to prevent a compromised client agent from being restarted as a server and having all cluster state including all ACL tokens and @@ -105,7 +109,7 @@ environment and adapt these configurations accordingly. in 1.4.1. - [`auto_encrypt`](/docs/agent/options#auto_encrypt) - Enables automated TLS certificate distribution for client - agent RPC communication using the Connect CA. Using this configuration a [`ca_file`](/docs/agent/options#ca_file) + agent RPC communication using the Connect CA. Using this configuration a [`ca_file`](/docs/agent/options#tls_defaults_ca_file) and ACL token would still need to be distributed to client agents. - [`allow_tls`](/docs/agent/options#allow_tls) - By default this is false, and should be set to true on server @@ -117,35 +121,48 @@ environment and adapt these configurations accordingly. **Example Server Agent TLS Configuration** ```hcl - verify_incoming = true - verify_outgoing = true - verify_server_hostname = true + tls { + defaults { + verify_incoming = true + verify_outgoing = true + ca_file = "consul-agent-ca.pem" + cert_file = "dc1-server-consul-0.pem" + key_file = "dc1-server-consul-0-key.pem" + } - ca_file = "consul-agent-ca.pem" - cert_file = "dc1-server-consul-0.pem" - key_file = "dc1-server-consul-0-key.pem" + internal_rpc { + verify_server_hostname = true + } + } auto_encrypt { - allow_tls = true + allow_tls = true } ``` **Example Client Agent TLS Configuration** ```hcl - verify_incoming = false - verify_outgoing = true - verify_server_hostname = true + tls { + defaults { + verify_incoming = false + verify_outgoing = true + ca_file = "consul-agent-ca.pem" + } + + internal_rpc { + verify_server_hostname = true + } + } - ca_file = "consul-agent-ca.pem" auto_encrypt { - tls = true + tls = true } ``` - -> The client agent TLS configuration from above sets [`verify_incoming`](/docs/agent/options#verify_incoming) to - false which assumes all incoming traffic is restricted to `localhost`. The primary benefit for this configuration + -> The client agent TLS configuration from above sets [`verify_incoming`](/docs/agent/options#tls_defaults_verify_incoming) + to false which assumes all incoming traffic is restricted to `localhost`. The primary benefit for this configuration would be to avoid provisioning client TLS certificates (in addition to ACL tokens) for all tools or applications using the local Consul agent. In this case ACLs should be enabled to provide authorization and only ACL tokens would need to be distributed. @@ -223,15 +240,12 @@ environment and adapt these configurations accordingly. - **Linux Security Modules** - Use of security modules that can be directly integrated into operating systems such as AppArmor, SElinux, and Seccomp on Consul agent hosts. -- **Customize TLS Settings** - TLS settings such as the [available cipher suites](/docs/agent/options#tls_cipher_suites), +- **Customize TLS Settings** - TLS settings such as the [available cipher suites](/docs/agent/options#tls_defaults_tls_cipher_suites), should be tuned to fit the needs of your environment. - - [`tls_min_version`](/docs/agent/options#tls_min_version) - Used to specify the minimum TLS version to use. + - [`tls_min_version`](/docs/agent/options#tls_defaults_tls_min_version) - Used to specify the minimum TLS version to use. - - [`tls_cipher_suites`](/docs/agent/options#tls_cipher_suites) - Used to specify which TLS cipher suites are allowed. - - - [`tls_prefer_server_cipher_suites`](/docs/agent/options#tls_prefer_server_cipher_suites) - Used to specify which TLS - cipher suites are preferred on the server side. + - [`tls_cipher_suites`](/docs/agent/options#tls_defaults_tls_cipher_suites) - Used to specify which TLS cipher suites are allowed. - **Customize HTTP Response Headers** - Additional security headers, such as [`X-XSS-Protection`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection), can be