tlsutil: only AuthorizerServerConn when VerifyIncomingRPC is true

See github.com/hashicorp/consul/issues/11207

When VerifyIncomingRPC is false the TLS conn will not have the required certificates.
This commit is contained in:
Daniel Nephin 2021-10-08 12:08:56 -04:00
parent d8ae915160
commit 6e9dd995eb
5 changed files with 59 additions and 18 deletions

3
.changelog/11255.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:bug
rpc: only attempt to authorize the DNSName in the client cert when verify_incoming_rpc=true
```

View File

@ -904,10 +904,10 @@ type TLSConn interface {
// presented is signed by the Agent TLS CA, and has a DNSName that matches the // presented is signed by the Agent TLS CA, and has a DNSName that matches the
// local ServerSNI name. // local ServerSNI name.
// //
// Note this check is only performed if VerifyServerHostname is enabled, otherwise // Note this check is only performed if VerifyServerHostname and VerifyIncomingRPC
// it does no authorization. // are both enabled, otherwise it does no authorization.
func (c *Configurator) AuthorizeServerConn(dc string, conn TLSConn) error { func (c *Configurator) AuthorizeServerConn(dc string, conn TLSConn) error {
if !c.VerifyServerHostname() { if !c.VerifyIncomingRPC() || !c.VerifyServerHostname() {
return nil return nil
} }
@ -937,6 +937,9 @@ func (c *Configurator) AuthorizeServerConn(dc string, conn TLSConn) error {
} }
errs = multierror.Append(errs, err) errs = multierror.Append(errs, err)
} }
if errs == nil {
errs = fmt.Errorf("no verified chains")
}
return fmt.Errorf("AuthorizeServerConn failed certificate validation for certificate with a SAN.DNSName of %v: %w", expected, errs) return fmt.Errorf("AuthorizeServerConn failed certificate validation for certificate with a SAN.DNSName of %v: %w", expected, errs)
} }

View File

@ -1271,7 +1271,7 @@ func TestConfigurator_AutoEncryptCert(t *testing.T) {
require.Equal(t, int64(4679716209), c.AutoEncryptCert().NotAfter.Unix()) require.Equal(t, int64(4679716209), c.AutoEncryptCert().NotAfter.Unix())
} }
func TestConfigurator_AuthorizeServerConn_Error(t *testing.T) { func TestConfigurator_AuthorizeServerConn(t *testing.T) {
caPEM, caPK, err := GenerateCA(CAOpts{Days: 5, Domain: "consul"}) caPEM, caPK, err := GenerateCA(CAOpts{Days: 5, Domain: "consul"})
require.NoError(t, err) require.NoError(t, err)
@ -1280,10 +1280,28 @@ func TestConfigurator_AuthorizeServerConn_Error(t *testing.T) {
err = ioutil.WriteFile(caPath, []byte(caPEM), 0600) err = ioutil.WriteFile(caPath, []byte(caPEM), 0600)
require.NoError(t, err) require.NoError(t, err)
// Cert and key are not used, but required to get past validateConfig
signer, err := ParseSigner(caPK)
require.NoError(t, err)
pub, pk, err := GenerateCert(CertOpts{
Signer: signer,
CA: caPEM,
})
require.NoError(t, err)
certFile := filepath.Join("cert.pem")
err = ioutil.WriteFile(certFile, []byte(pub), 0600)
require.NoError(t, err)
keyFile := filepath.Join("cert.key")
err = ioutil.WriteFile(keyFile, []byte(pk), 0600)
require.NoError(t, err)
cfg := Config{ cfg := Config{
VerifyServerHostname: true, VerifyServerHostname: true,
VerifyIncomingRPC: true,
Domain: "consul", Domain: "consul",
CAFile: caPath, CAFile: caPath,
CertFile: certFile,
KeyFile: keyFile,
} }
c, err := NewConfigurator(cfg, hclog.New(nil)) c, err := NewConfigurator(cfg, hclog.New(nil))
require.NoError(t, err) require.NoError(t, err)
@ -1367,6 +1385,22 @@ func TestConfigurator_AuthorizeServerConn_Error(t *testing.T) {
err = c.AuthorizeServerConn("dc1", s) err = c.AuthorizeServerConn("dc1", s)
testutil.RequireErrorContains(t, err, "certificate specifies an incompatible key usage") testutil.RequireErrorContains(t, err, "certificate specifies an incompatible key usage")
}) })
t.Run("disabled by verify_incoming_rpc", func(t *testing.T) {
cfg := Config{
VerifyServerHostname: true,
VerifyIncomingRPC: false,
Domain: "consul",
CAFile: caPath,
}
c, err := NewConfigurator(cfg, hclog.New(nil))
require.NoError(t, err)
s := fakeTLSConn{}
err = c.AuthorizeServerConn("dc1", s)
require.NoError(t, err)
})
} }
type fakeTLSConn struct { type fakeTLSConn struct {

View File

@ -143,17 +143,17 @@ func GenerateCA(opts CAOpts) (string, string, error) {
func GenerateCert(opts CertOpts) (string, string, error) { func GenerateCert(opts CertOpts) (string, string, error) {
parent, err := parseCert(opts.CA) parent, err := parseCert(opts.CA)
if err != nil { if err != nil {
return "", "", err return "", "", fmt.Errorf("failed to parse CA: %w", err)
} }
signee, pk, err := GeneratePrivateKey() signee, pk, err := GeneratePrivateKey()
if err != nil { if err != nil {
return "", "", err return "", "", fmt.Errorf("failed to generate private key: %w", err)
} }
id, err := keyID(signee.Public()) id, err := keyID(signee.Public())
if err != nil { if err != nil {
return "", "", err return "", "", fmt.Errorf("failed to get keyID from public key: %w", err)
} }
sn := opts.Serial sn := opts.Serial
@ -185,7 +185,7 @@ func GenerateCert(opts CertOpts) (string, string, error) {
bs, err := x509.CreateCertificate(rand.Reader, &template, parent, signee.Public(), opts.Signer) bs, err := x509.CreateCertificate(rand.Reader, &template, parent, signee.Public(), opts.Signer)
if err != nil { if err != nil {
return "", "", err return "", "", fmt.Errorf("failed to create certificate: %w", err)
} }
var buf bytes.Buffer var buf bytes.Buffer

View File

@ -2276,12 +2276,15 @@ signed by the CA can be used to gain full access to Consul.
endpoint was created by the CA that the consul client was setup with. If the endpoint was created by the CA that the consul client was setup with. If the
UI is served, the same checks are performed. UI is served, the same checks are performed.
- `verify_incoming_rpc` - If set to true, Consul - `verify_incoming_rpc` - When set to true, Consul
requires that all incoming RPC connections make use of TLS and that the client 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) 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 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. the use of TLS or verify a client's authenticity.
~> **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.
- `verify_incoming_https` - If set to true, - `verify_incoming_https` - If set to true,
Consul requires that all incoming HTTPS connections make use of TLS and that the 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) client provides a certificate signed by a Certificate Authority from the [`ca_file`](#ca_file)
@ -2303,14 +2306,12 @@ signed by the CA can be used to gain full access to Consul.
server unencrypted is to also enable `verify_incoming` which requires client server unencrypted is to also enable `verify_incoming` which requires client
certificates too. certificates too.
- `verify_server_hostname` - If set to true, - `verify_server_hostname` - When set to true, Consul verifies the TLS certificate
Consul verifies for all outgoing TLS connections that the TLS certificate presented by the servers match the hostname `server.<datacenter>.<domain>`.
presented by the servers matches `server.<datacenter>.<domain>` By default this is false, and Consul does not verify the hostname
hostname. 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
of the certificate, only that it is signed by a trusted CA. This setting is to prevent a compromised client from gaining full read and write access to all
_critical_ to prevent a compromised client from being restarted as a server cluster data _including all ACL tokens and Connect CA root keys_. This is new in 0.5.1.
and having all cluster state _including all ACL tokens and Connect CA root keys_
replicated to it. This is new in 0.5.1.
~> **Security Note:** From versions 0.5.1 to 1.4.0, due to a bug, setting ~> **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 this flag alone _does not_ imply `verify_outgoing` and leaves client to server