Added new auto_encrypt.grpc_server_tls config option to control AutoTLS enabling of GRPC Server's TLS usage

Fix for #14253

Co-authored-by: trujillo-adam <47586768+trujillo-adam@users.noreply.github.com>
This commit is contained in:
Pablo Ruiz García 2022-08-24 18:31:38 +02:00 committed by GitHub
parent ca228aad8d
commit 1f293e5244
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 162 additions and 33 deletions

View File

@ -2531,10 +2531,9 @@ func (b *builder) buildTLSConfig(rt RuntimeConfig, t TLS) (tlsutil.Config, error
return c, errors.New("verify_server_hostname is only valid in the tls.internal_rpc stanza") 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 // And UseAutoCert right now only applies to external gRPC interface.
// for historic and backwards-compatibility reasons. if t.Defaults.UseAutoCert != nil || t.HTTPS.UseAutoCert != nil || t.InternalRPC.UseAutoCert != nil {
if rt.HTTPSPort <= 0 && (t.GRPC != TLSProtocolConfig{} && t.GRPCModifiedByDeprecatedConfig == nil) { return c, errors.New("use_auto_cert is only valid in the tls.grpc stanza")
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)")
} }
defaultTLSMinVersion := b.tlsVersion("tls.defaults.tls_min_version", t.Defaults.TLSMinVersion) defaultTLSMinVersion := b.tlsVersion("tls.defaults.tls_min_version", t.Defaults.TLSMinVersion)
@ -2591,6 +2590,7 @@ func (b *builder) buildTLSConfig(rt RuntimeConfig, t TLS) (tlsutil.Config, error
mapCommon("https", t.HTTPS, &c.HTTPS) mapCommon("https", t.HTTPS, &c.HTTPS)
mapCommon("grpc", t.GRPC, &c.GRPC) mapCommon("grpc", t.GRPC, &c.GRPC)
c.GRPC.UseAutoCert = boolValWithDefault(t.GRPC.UseAutoCert, false)
c.ServerName = rt.ServerName c.ServerName = rt.ServerName
c.NodeName = rt.NodeName c.NodeName = rt.NodeName

View File

@ -867,6 +867,7 @@ type TLSProtocolConfig struct {
VerifyIncoming *bool `mapstructure:"verify_incoming"` VerifyIncoming *bool `mapstructure:"verify_incoming"`
VerifyOutgoing *bool `mapstructure:"verify_outgoing"` VerifyOutgoing *bool `mapstructure:"verify_outgoing"`
VerifyServerHostname *bool `mapstructure:"verify_server_hostname"` VerifyServerHostname *bool `mapstructure:"verify_server_hostname"`
UseAutoCert *bool `mapstructure:"use_auto_cert"`
} }
type TLS struct { type TLS struct {

View File

@ -5516,7 +5516,70 @@ func TestLoad_IntegrationWithFlags(t *testing.T) {
}, },
}) })
run(t, testCase{ run(t, testCase{
desc: "tls.grpc without ports.https", desc: "tls.grpc.use_auto_cert defaults to false",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`
{
"tls": {
"grpc": {}
}
}
`},
hcl: []string{`
tls {
grpc {}
}
`},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.TLS.Domain = "consul."
rt.TLS.NodeName = "thehostname"
rt.TLS.GRPC.UseAutoCert = false
},
})
run(t, testCase{
desc: "tls.grpc.use_auto_cert defaults to false (II)",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`
{
"tls": {}
}
`},
hcl: []string{`
tls {
}
`},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.TLS.Domain = "consul."
rt.TLS.NodeName = "thehostname"
rt.TLS.GRPC.UseAutoCert = false
},
})
run(t, testCase{
desc: "tls.grpc.use_auto_cert defaults to false (III)",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`
{
}
`},
hcl: []string{`
`},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.TLS.Domain = "consul."
rt.TLS.NodeName = "thehostname"
rt.TLS.GRPC.UseAutoCert = false
},
})
run(t, testCase{
desc: "tls.grpc.use_auto_cert enabled when true",
args: []string{ args: []string{
`-data-dir=` + dataDir, `-data-dir=` + dataDir,
}, },
@ -5524,7 +5587,7 @@ func TestLoad_IntegrationWithFlags(t *testing.T) {
{ {
"tls": { "tls": {
"grpc": { "grpc": {
"cert_file": "cert-1234" "use_auto_cert": true
} }
} }
} }
@ -5532,20 +5595,43 @@ func TestLoad_IntegrationWithFlags(t *testing.T) {
hcl: []string{` hcl: []string{`
tls { tls {
grpc { grpc {
cert_file = "cert-1234" use_auto_cert = true
} }
} }
`}, `},
expected: func(rt *RuntimeConfig) { expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir rt.DataDir = dataDir
rt.TLS.Domain = "consul." rt.TLS.Domain = "consul."
rt.TLS.NodeName = "thehostname" rt.TLS.NodeName = "thehostname"
rt.TLS.GRPC.UseAutoCert = true
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)", run(t, testCase{
desc: "tls.grpc.use_auto_cert disabled when false",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{`
{
"tls": {
"grpc": {
"use_auto_cert": false
}
}
}
`},
hcl: []string{`
tls {
grpc {
use_auto_cert = false
}
}
`},
expected: func(rt *RuntimeConfig) {
rt.DataDir = dataDir
rt.TLS.Domain = "consul."
rt.TLS.NodeName = "thehostname"
rt.TLS.GRPC.UseAutoCert = false
}, },
}) })
} }
@ -6340,6 +6426,7 @@ func TestLoad_FullConfig(t *testing.T) {
TLSMinVersion: types.TLSv1_0, TLSMinVersion: types.TLSv1_0,
CipherSuites: []types.TLSCipherSuite{types.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, types.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA}, CipherSuites: []types.TLSCipherSuite{types.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, types.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA},
VerifyOutgoing: false, VerifyOutgoing: false,
UseAutoCert: true,
}, },
HTTPS: tlsutil.ProtocolConfig{ HTTPS: tlsutil.ProtocolConfig{
VerifyIncoming: true, VerifyIncoming: true,

View File

@ -374,7 +374,8 @@
"TLSMinVersion": "", "TLSMinVersion": "",
"VerifyIncoming": false, "VerifyIncoming": false,
"VerifyOutgoing": false, "VerifyOutgoing": false,
"VerifyServerHostname": false "VerifyServerHostname": false,
"UseAutoCert": false
}, },
"HTTPS": { "HTTPS": {
"CAFile": "", "CAFile": "",
@ -385,7 +386,8 @@
"TLSMinVersion": "", "TLSMinVersion": "",
"VerifyIncoming": false, "VerifyIncoming": false,
"VerifyOutgoing": false, "VerifyOutgoing": false,
"VerifyServerHostname": false "VerifyServerHostname": false,
"UseAutoCert": false
}, },
"InternalRPC": { "InternalRPC": {
"CAFile": "", "CAFile": "",
@ -396,7 +398,8 @@
"TLSMinVersion": "", "TLSMinVersion": "",
"VerifyIncoming": false, "VerifyIncoming": false,
"VerifyOutgoing": false, "VerifyOutgoing": false,
"VerifyServerHostname": false "VerifyServerHostname": false,
"UseAutoCert": false
}, },
"NodeName": "", "NodeName": "",
"ServerName": "" "ServerName": ""
@ -466,4 +469,4 @@
"VersionMetadata": "", "VersionMetadata": "",
"VersionPrerelease": "", "VersionPrerelease": "",
"Watches": [] "Watches": []
} }

View File

@ -697,6 +697,7 @@ tls {
tls_cipher_suites = "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA" tls_cipher_suites = "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA"
tls_min_version = "TLSv1_0" tls_min_version = "TLSv1_0"
verify_incoming = true verify_incoming = true
use_auto_cert = true
} }
} }
tls_cipher_suites = "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" tls_cipher_suites = "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"

View File

@ -692,7 +692,8 @@
"key_file": "1y4prKjl", "key_file": "1y4prKjl",
"tls_cipher_suites": "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "tls_cipher_suites": "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"tls_min_version": "TLSv1_0", "tls_min_version": "TLSv1_0",
"verify_incoming": true "verify_incoming": true,
"use_auto_cert": true
} }
}, },
"tls_cipher_suites": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "tls_cipher_suites": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",

View File

@ -1,12 +1,13 @@
package external package external
import ( import (
"time"
middleware "github.com/grpc-ecosystem/go-grpc-middleware" middleware "github.com/grpc-ecosystem/go-grpc-middleware"
recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery" recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
"google.golang.org/grpc/keepalive" "google.golang.org/grpc/keepalive"
"time"
agentmiddleware "github.com/hashicorp/consul/agent/grpc-middleware" agentmiddleware "github.com/hashicorp/consul/agent/grpc-middleware"
"github.com/hashicorp/consul/tlsutil" "github.com/hashicorp/consul/tlsutil"
@ -34,7 +35,7 @@ func NewServer(logger agentmiddleware.Logger, tls *tlsutil.Configurator) *grpc.S
MinTime: 15 * time.Second, MinTime: 15 * time.Second,
}), }),
} }
if tls != nil && tls.GRPCTLSConfigured() { if tls != nil && tls.GRPCServerUseTLS() {
creds := credentials.NewTLS(tls.IncomingGRPCConfig()) creds := credentials.NewTLS(tls.IncomingGRPCConfig())
opts = append(opts, grpc.Creds(creds)) opts = append(opts, grpc.Creds(creds))
} }

View File

@ -102,6 +102,10 @@ type ProtocolConfig struct {
// //
// Note: this setting only applies to the Internal RPC configuration. // Note: this setting only applies to the Internal RPC configuration.
VerifyServerHostname bool VerifyServerHostname bool
// UseAutoCert is used to enable usage of auto_encrypt/auto_config generated
// certificate & key material on external gRPC listener.
UseAutoCert bool
} }
// Config configures the Configurator. // Config configures the Configurator.
@ -167,6 +171,10 @@ type protocolConfig struct {
// combinedCAPool is a pool containing both manualCAPEMs and the certificates // combinedCAPool is a pool containing both manualCAPEMs and the certificates
// received from auto-config/auto-encrypt. // received from auto-config/auto-encrypt.
combinedCAPool *x509.CertPool combinedCAPool *x509.CertPool
// useAutoCert indicates wether we should use auto-encrypt/config data
// for TLS server/listener. NOTE: Only applies to external GRPC Server.
useAutoCert bool
} }
// Configurator provides tls.Config and net.Dial wrappers to enable TLS for // Configurator provides tls.Config and net.Dial wrappers to enable TLS for
@ -323,6 +331,7 @@ func (c *Configurator) loadProtocolConfig(base Config, pc ProtocolConfig) (*prot
manualCAPEMs: pems, manualCAPEMs: pems,
manualCAPool: manualPool, manualCAPool: manualPool,
combinedCAPool: combinedPool, combinedCAPool: combinedPool,
useAutoCert: pc.UseAutoCert,
}, nil }, nil
} }
@ -620,16 +629,15 @@ func (c *Configurator) Cert() *tls.Certificate {
return cert return cert
} }
// GRPCTLSConfigured returns whether there's a TLS certificate configured for // GRPCServerUseTLS returns whether there's a TLS certificate configured for
// gRPC (either manually or by auto-config/auto-encrypt). It is checked, along // (external) gRPC (either manually or by auto-config/auto-encrypt), and use
// with the presence of an HTTPS port, to determine whether to enable TLS on // of TLS for gRPC has not been explicitly disabled at auto-encrypt.
// incoming gRPC connections.
// //
// This function acquires a read lock because it reads from the config. // This function acquires a read lock because it reads from the config.
func (c *Configurator) GRPCTLSConfigured() bool { func (c *Configurator) GRPCServerUseTLS() bool {
c.lock.RLock() c.lock.RLock()
defer c.lock.RUnlock() defer c.lock.RUnlock()
return c.grpc.cert != nil || c.autoTLS.cert != nil return c.grpc.cert != nil || (c.grpc.useAutoCert && c.autoTLS.cert != nil)
} }
// VerifyIncomingRPC returns true if we should verify incoming connnections to // VerifyIncomingRPC returns true if we should verify incoming connnections to

View File

@ -1465,7 +1465,7 @@ func TestConfigurator_AuthorizeInternalRPCServerConn(t *testing.T) {
}) })
} }
func TestConfigurator_GRPCTLSConfigured(t *testing.T) { func TestConfigurator_GRPCServerUseTLS(t *testing.T) {
t.Run("certificate manually configured", func(t *testing.T) { t.Run("certificate manually configured", func(t *testing.T) {
c := makeConfigurator(t, Config{ c := makeConfigurator(t, Config{
GRPC: ProtocolConfig{ GRPC: ProtocolConfig{
@ -1473,22 +1473,47 @@ func TestConfigurator_GRPCTLSConfigured(t *testing.T) {
KeyFile: "../test/hostname/Alice.key", KeyFile: "../test/hostname/Alice.key",
}, },
}) })
require.True(t, c.GRPCTLSConfigured()) require.True(t, c.GRPCServerUseTLS())
}) })
t.Run("AutoTLS", func(t *testing.T) { t.Run("no certificate", func(t *testing.T) {
c := makeConfigurator(t, Config{})
require.False(t, c.GRPCServerUseTLS())
})
t.Run("AutoTLS (default)", func(t *testing.T) {
c := makeConfigurator(t, Config{}) c := makeConfigurator(t, Config{})
bobCert := loadFile(t, "../test/hostname/Bob.crt") bobCert := loadFile(t, "../test/hostname/Bob.crt")
bobKey := loadFile(t, "../test/hostname/Bob.key") bobKey := loadFile(t, "../test/hostname/Bob.key")
require.NoError(t, c.UpdateAutoTLSCert(bobCert, bobKey)) require.NoError(t, c.UpdateAutoTLSCert(bobCert, bobKey))
require.False(t, c.GRPCServerUseTLS())
require.True(t, c.GRPCTLSConfigured())
}) })
t.Run("no certificate", func(t *testing.T) { t.Run("AutoTLS w/ UseAutoCert Disabled", func(t *testing.T) {
c := makeConfigurator(t, Config{}) c := makeConfigurator(t, Config{
require.False(t, c.GRPCTLSConfigured()) GRPC: ProtocolConfig{
UseAutoCert: false,
},
})
bobCert := loadFile(t, "../test/hostname/Bob.crt")
bobKey := loadFile(t, "../test/hostname/Bob.key")
require.NoError(t, c.UpdateAutoTLSCert(bobCert, bobKey))
require.False(t, c.GRPCServerUseTLS())
})
t.Run("AutoTLS w/ UseAutoCert Enabled", func(t *testing.T) {
c := makeConfigurator(t, Config{
GRPC: ProtocolConfig{
UseAutoCert: true,
},
})
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.GRPCServerUseTLS())
}) })
} }

View File

@ -2019,6 +2019,8 @@ specially crafted certificate signed by the CA can be used to gain full access t
- `verify_incoming` - ((#tls_grpc_verify_incoming)) Overrides [`tls.defaults.verify_incoming`](#tls_defaults_verify_incoming). - `verify_incoming` - ((#tls_grpc_verify_incoming)) Overrides [`tls.defaults.verify_incoming`](#tls_defaults_verify_incoming).
- `use_auto_cert` - (Defaults to `false`) Enables or disables TLS on gRPC servers. Set to `true` to allow `auto_encrypt` TLS settings to apply to gRPC listeners. We recommend disabling TLS on gRPC servers if you are using `auto_encrypt` for other TLS purposes, such as enabling HTTPS.
- `https` ((#tls_https)) Provides settings for the HTTPS interface. To enable - `https` ((#tls_https)) Provides settings for the HTTPS interface. To enable
the HTTPS interface you must define a port via [`ports.https`](#https_port). the HTTPS interface you must define a port via [`ports.https`](#https_port).