// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
//go:build !fips
package tlsutil
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"net"
"os"
"path"
"path/filepath"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/yamux"
"github.com/stretchr/testify/require"
"github.com/hashicorp/consul/sdk/testutil"
"github.com/hashicorp/consul/types"
)
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 : "TLSv1_3" ,
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 : [ ] types . TLSCipherSuite { types . 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 TestConfigurator_IncomingGRPCConfig_Peering ( t * testing . T ) {
// Manually configure Alice's certificates
cfg := Config {
GRPC : ProtocolConfig {
CertFile : "../test/hostname/Alice.crt" ,
KeyFile : "../test/hostname/Alice.key" ,
} ,
}
c := makeConfigurator ( t , 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 ) )
peeringServerName := "server.dc1.peering.1234"
c . UpdateAutoTLSPeeringServerName ( peeringServerName )
testutil . RunStep ( t , "with peering name" , func ( t * testing . T ) {
client , errc , _ := startTLSServer ( c . IncomingGRPCConfig ( ) )
if client == nil {
t . Fatalf ( "startTLSServer err: %v" , <- errc )
}
tlsClient := tls . Client ( client , & tls . Config {
// When the peering server name is provided the server should present
// the certificates configured via AutoTLS (Bob).
ServerName : peeringServerName ,
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 )
} )
testutil . RunStep ( t , "without name" , func ( t * testing . T ) {
client , errc , _ := startTLSServer ( c . IncomingGRPCConfig ( ) )
if client == nil {
t . Fatalf ( "startTLSServer err: %v" , <- errc )
}
tlsClient := tls . Client ( client , & tls . Config {
// ServerName: peeringServerName,
InsecureSkipVerify : true ,
} )
require . NoError ( t , tlsClient . Handshake ( ) )
certificates := tlsClient . ConnectionState ( ) . PeerCertificates
require . NotEmpty ( t , certificates )
// Should default to presenting the manually configured certificates.
require . Equal ( t , "Alice" , certificates [ 0 ] . Subject . CommonName )
// Check the server side of the handshake succeded.
require . NoError ( t , <- errc )
} )
}
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 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" ,
} ,
} )
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 ( )
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_OutgoingRPC_ServerMode ( t * testing . T ) {
type testCase struct {
clientConfig Config
expectName string
}
run := func ( t * testing . T , tc testCase ) {
serverCfg := makeConfigurator ( t , Config {
InternalRPC : ProtocolConfig {
CAFile : "../test/hostname/CertAuth.crt" ,
CertFile : "../test/hostname/Alice.crt" ,
KeyFile : "../test/hostname/Alice.key" ,
VerifyIncoming : true ,
} ,
ServerMode : true ,
} )
serverConn , errc , certc := startTLSServer ( serverCfg . IncomingRPCConfig ( ) )
if serverConn == nil {
t . Fatalf ( "startTLSServer err: %v" , <- errc )
}
clientCfg := makeConfigurator ( t , tc . clientConfig )
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" , serverConn )
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 , tc . expectName , clientCerts [ 0 ] . Subject . CommonName )
// Check the server side of the handshake succeeded.
require . NoError ( t , <- errc )
}
tt := map [ string ] testCase {
"server with manual cert" : {
clientConfig : Config {
InternalRPC : ProtocolConfig {
VerifyOutgoing : true ,
CAFile : "../test/hostname/CertAuth.crt" ,
CertFile : "../test/hostname/Bob.crt" ,
KeyFile : "../test/hostname/Bob.key" ,
} ,
ServerMode : true ,
} ,
// Even though an AutoTLS cert is configured, the server will prefer the manually configured cert.
expectName : "Bob" ,
} ,
"client with manual cert" : {
clientConfig : Config {
InternalRPC : ProtocolConfig {
VerifyOutgoing : true ,
CAFile : "../test/hostname/CertAuth.crt" ,
CertFile : "../test/hostname/Bob.crt" ,
KeyFile : "../test/hostname/Bob.key" ,
} ,
ServerMode : false ,
} ,
expectName : "Betty" ,
} ,
"client with auto-TLS" : {
clientConfig : Config {
ServerMode : false ,
AutoTLS : true ,
} ,
expectName : "Betty" ,
} ,
}
for name , tc := range tt {
t . Run ( name , func ( t * testing . T ) {
run ( t , tc )
} )
}
}
func TestConfigurator_OutgoingInternalRPCWrapper ( t * testing . T ) {
// if this test is failing because of expired certificates
// use the procedure in test/CA-GENERATION.md
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 , _ := startTLSServer ( serverCfg . IncomingRPCConfig ( ) )
if client == nil {
t . Fatalf ( "startTLSServer err: %v" , <- errc )
}
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 ) )
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 )
} )
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 ,
} ,
} )
client , errc , _ := startTLSServer ( serverCfg . IncomingRPCConfig ( ) )
if client == nil {
t . Fatalf ( "startTLSServer err: %v" , <- errc )
}
clientCfg := makeConfigurator ( t , Config {
InternalRPC : ProtocolConfig {
VerifyOutgoing : true ,
CAFile : "../test/hostname/CertAuth.crt" ,
CertFile : "../test/hostname/Bob.crt" ,
KeyFile : "../test/hostname/Bob.key" ,
} ,
} )
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 )
} )
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 ,
} ,
} )
client , errc , _ := startTLSServer ( serverCfg . IncomingRPCConfig ( ) )
if client == nil {
t . Fatalf ( "startTLSServer err: %v" , <- errc )
}
clientCfg := makeConfigurator ( t , Config { } )
wrap := clientCfg . OutgoingRPCWrapper ( )
require . NotNil ( t , wrap )
client , err := wrap ( "dc1" , client )
require . NoError ( t , err )
defer client . Close ( )
_ , isTLS := client . ( * tls . Conn )
require . False ( t , isTLS )
} )
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" ,
} ,
} )
client , errc , _ := startTLSServer ( serverCfg . IncomingRPCConfig ( ) )
if client == nil {
t . Fatalf ( "startTLSServer err: %v" , <- errc )
}
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" ,
} )
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 . Error ( t , err )
require . Regexp ( t , ` certificate is valid for ([a-z].+) not server.dc1.consul ` , err . Error ( ) )
} )
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" ,
} ,
} )
client , errc , _ := startTLSServer ( serverCfg . IncomingRPCConfig ( ) )
if client == nil {
t . Fatalf ( "startTLSServer err: %v" , <- errc )
}
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" ,
} )
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 {
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 ( t , & srvConfig , [ ] string { "foo" , "bar" } )
if client == nil {
t . Fatalf ( "startTLSServer err: %v" , <- errc )
}
config := Config {
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 )
require . NoError ( t , err )
wrap := c . OutgoingALPNRPCWrapper ( )
require . NotNil ( t , wrap )
_ , err = wrap ( "dc1" , "bob" , "foo" , client )
require . Error ( t , err )
_ , ok := err . ( * tls . CertificateVerificationError )
require . True ( t , ok )
client . Close ( )
<- errc
}
func TestLoadKeyPair ( t * testing . T ) {
type variant struct {
cert , key string
shoulderr bool
isnil bool
}
variants := [ ] variant {
{ "" , "" , false , true } ,
{ "bogus" , "" , false , true } ,
{ "" , "bogus" , false , true } ,
{ "../test/key/ourdomain.cer" , "" , false , true } ,
{ "" , "../test/key/ourdomain.key" , false , true } ,
{ "bogus" , "bogus" , true , true } ,
{ "../test/key/ourdomain.cer" , "../test/key/ourdomain.key" ,
false , false } ,
}
for i , v := range variants {
t . Run ( fmt . Sprintf ( "case %d" , i ) , func ( t * testing . T ) {
cert , err := loadKeyPair ( v . cert , v . key )
if v . shoulderr {
require . Error ( t , err )
} else {
require . NoError ( t , err )
}
if v . isnil {
require . Nil ( t , cert )
} else {
require . NotNil ( t , cert )
}
} )
}
}
func TestConfig_SpecifyDC ( t * testing . T ) {
require . Nil ( t , SpecificDC ( "" , nil ) )
dcwrap := func ( dc string , conn net . Conn ) ( net . Conn , error ) { return nil , nil }
wrap := SpecificDC ( "" , dcwrap )
require . NotNil ( t , wrap )
conn , err := wrap ( nil )
require . NoError ( t , err )
require . Nil ( t , conn )
}
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"
)
t . Run ( "empty config" , func ( t * testing . T ) {
_ , err := NewConfigurator ( Config { } , nil )
require . NoError ( t , err )
require . NoError ( t , new ( Configurator ) . Update ( Config { } ) )
} )
t . Run ( "common fields" , func ( t * testing . T ) {
type testCase struct {
config ProtocolConfig
isValid bool
}
testCases := map [ string ] testCase {
"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 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 ) {
type variant struct {
config Config
result string
}
variants := [ ] variant {
{ config : Config { NodeName : "node" , ServerName : "server" } ,
result : "server" } ,
{ config : Config { ServerName : "server" } ,
result : "server" } ,
{ config : Config { NodeName : "node" } ,
result : "node" } ,
}
for _ , v := range variants {
c , err := NewConfigurator ( v . config , nil )
require . NoError ( t , err )
tlsConf := c . internalRPCTLSConfig ( false )
require . Empty ( t , tlsConf . ServerName )
}
}
func TestConfigurator_LoadCAs ( t * testing . T ) {
type variant struct {
cafile , capath string
shouldErr bool
isNil bool
count int
expectedCaPool * x509 . CertPool
}
variants := [ ] variant {
{ "" , "" , false , true , 0 , nil } ,
{ "bogus" , "" , true , true , 0 , nil } ,
{ "" , "bogus" , true , true , 0 , nil } ,
{ "" , "../test/bin" , true , true , 0 , nil } ,
{ "../test/ca/root.cer" , "" , false , false , 1 , getExpectedCaPoolByFile ( t ) } ,
{ "" , "../test/ca_path" , false , false , 2 , getExpectedCaPoolByDir ( t ) } ,
{ "../test/ca/root.cer" , "../test/ca_path" , false , false , 1 , getExpectedCaPoolByFile ( t ) } ,
}
for i , v := range variants {
pems , err1 := LoadCAs ( v . cafile , v . capath )
pool , err2 := newX509CertPool ( pems )
info := fmt . Sprintf ( "case %d" , i )
if v . shouldErr {
if err1 == nil && err2 == nil {
t . Fatal ( "An error is expected but got nil." )
}
} else {
require . NoError ( t , err1 , info )
require . NoError ( t , err2 , info )
}
if v . isNil {
require . Nil ( t , pool , info )
} else {
require . NotEmpty ( t , pems , info )
require . NotNil ( t , pool , info )
assertDeepEqual ( t , v . expectedCaPool , pool , cmpCertPool )
require . Len ( t , pems , v . count , info )
}
}
}
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 ) {
config := Config {
Domain : "consul" ,
}
c , err := NewConfigurator ( config , nil )
require . NoError ( t , err )
require . False ( t , c . MutualTLSCapable ( ) )
} )
t . Run ( "ca and no keys" , func ( t * testing . T ) {
config := Config {
InternalRPC : ProtocolConfig {
CAFile : "../test/hostname/CertAuth.crt" ,
} ,
Domain : "consul" ,
}
c , err := NewConfigurator ( config , nil )
require . NoError ( t , err )
require . False ( t , c . MutualTLSCapable ( ) )
} )
t . Run ( "ca and manual key" , func ( t * testing . T ) {
config := Config {
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 )
require . True ( t , c . MutualTLSCapable ( ) )
} )
t . Run ( "autoencrypt ca and no autoencrypt keys" , func ( t * testing . T ) {
config := Config {
Domain : "consul" ,
}
c , err := NewConfigurator ( config , nil )
require . NoError ( t , err )
caPEM := loadFile ( t , "../test/hostname/CertAuth.crt" )
require . NoError ( t , c . UpdateAutoTLSCA ( [ ] string { caPEM } ) )
require . False ( t , c . MutualTLSCapable ( ) )
} )
t . Run ( "autoencrypt ca and autoencrypt key" , func ( t * testing . T ) {
config := Config {
Domain : "consul" ,
}
c , err := NewConfigurator ( config , nil )
require . NoError ( t , err )
caPEM := loadFile ( t , "../test/hostname/CertAuth.crt" )
certPEM := loadFile ( t , "../test/hostname/Bob.crt" )
keyPEM := loadFile ( t , "../test/hostname/Bob.key" )
require . NoError ( t , c . UpdateAutoTLSCA ( [ ] string { caPEM } ) )
require . NoError ( t , c . UpdateAutoTLSCert ( certPEM , keyPEM ) )
require . True ( t , c . MutualTLSCapable ( ) )
} )
}
func TestConfigurator_UpdateAutoTLSCA_DoesNotPanic ( t * testing . T ) {
config := Config {
Domain : "consul" ,
}
c , err := NewConfigurator ( config , hclog . New ( nil ) )
require . NoError ( t , err )
err = c . UpdateAutoTLSCA ( [ ] string { "invalid pem" } )
require . Error ( t , err )
}
func TestConfigurator_VerifyIncomingRPC ( t * testing . T ) {
c := Configurator { base : & Config { } }
c . base . InternalRPC . VerifyIncoming = true
require . True ( t , c . VerifyIncomingRPC ( ) )
}
func TestConfigurator_OutgoingTLSConfigForCheck ( t * testing . T ) {
type testCase struct {
name string
conf func ( ) ( * Configurator , error )
skipVerify bool
serverName string
expected * tls . Config
}
run := func ( t * testing . T , tc testCase ) {
configurator , err := tc . conf ( )
require . NoError ( t , err )
c := configurator . OutgoingTLSConfigForCheck ( tc . skipVerify , tc . serverName )
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 {
{
name : "default tls" ,
conf : func ( ) ( * Configurator , error ) {
return NewConfigurator ( Config { } , nil )
} ,
expected : & tls . Config { } ,
} ,
{
name : "default tls, skip verify, no server name" ,
conf : func ( ) ( * Configurator , error ) {
return NewConfigurator ( Config {
InternalRPC : ProtocolConfig {
TLSMinVersion : types . TLSv1_2 ,
} ,
EnableAgentTLSForChecks : false ,
} , nil )
} ,
skipVerify : true ,
expected : & tls . Config { InsecureSkipVerify : true } ,
} ,
{
name : "default tls, skip verify, default server name" ,
conf : func ( ) ( * Configurator , error ) {
return NewConfigurator ( Config {
InternalRPC : ProtocolConfig {
TLSMinVersion : types . TLSv1_2 ,
} ,
EnableAgentTLSForChecks : false ,
ServerName : "servername" ,
NodeName : "nodename" ,
} , nil )
} ,
skipVerify : true ,
expected : & tls . Config { InsecureSkipVerify : true } ,
} ,
{
name : "default tls, skip verify, check server name" ,
conf : func ( ) ( * Configurator , error ) {
return NewConfigurator ( Config {
InternalRPC : ProtocolConfig {
TLSMinVersion : types . TLSv1_2 ,
} ,
EnableAgentTLSForChecks : false ,
ServerName : "servername" ,
} , nil )
} ,
skipVerify : true ,
serverName : "check-server-name" ,
expected : & tls . Config {
InsecureSkipVerify : true ,
ServerName : "check-server-name" ,
} ,
} ,
{
name : "agent tls, default consul server name, no override" ,
conf : func ( ) ( * Configurator , error ) {
return NewConfigurator ( Config {
InternalRPC : ProtocolConfig {
TLSMinVersion : types . TLSv1_2 ,
} ,
EnableAgentTLSForChecks : true ,
NodeName : "nodename" ,
ServerName : "servername" ,
} , nil )
} ,
expected : & tls . Config {
MinVersion : tls . VersionTLS12 ,
ServerName : "" ,
} ,
} ,
{
name : "agent tls, skip verify, consul node name for server name, no override" ,
conf : func ( ) ( * Configurator , error ) {
return NewConfigurator ( Config {
InternalRPC : ProtocolConfig {
TLSMinVersion : types . TLSv1_2 ,
} ,
EnableAgentTLSForChecks : true ,
NodeName : "nodename" ,
} , nil )
} ,
skipVerify : true ,
expected : & tls . Config {
InsecureSkipVerify : true ,
MinVersion : tls . VersionTLS12 ,
ServerName : "" ,
} ,
} ,
{
name : "agent tls, skip verify, with server name override" ,
conf : func ( ) ( * Configurator , error ) {
return NewConfigurator ( Config {
InternalRPC : ProtocolConfig {
TLSMinVersion : types . TLSv1_2 ,
} ,
EnableAgentTLSForChecks : true ,
ServerName : "servername" ,
} , nil )
} ,
skipVerify : true ,
serverName : "override" ,
expected : & tls . Config {
InsecureSkipVerify : true ,
MinVersion : tls . VersionTLS12 ,
ServerName : "override" ,
} ,
} ,
}
for _ , tc := range testCases {
t . Run ( tc . name , func ( t * testing . T ) {
run ( t , tc )
} )
}
}
func TestConfigurator_ServerNameOrNodeName ( t * testing . T ) {
c := Configurator { base : & Config { } }
type variant struct {
server , node , expected string
}
variants := [ ] variant {
{ "" , "" , "" } ,
{ "a" , "" , "a" } ,
{ "" , "b" , "b" } ,
{ "a" , "b" , "a" } ,
}
for _ , v := range variants {
c . base . ServerName = v . server
c . base . NodeName = v . node
require . Equal ( t , v . expected , c . serverNameOrNodeName ( ) )
}
}
func TestConfigurator_InternalRPCVerifyServerHostname ( t * testing . T ) {
c := Configurator { base : & Config { } }
require . False ( t , c . VerifyServerHostname ( ) )
c . base . InternalRPC . VerifyServerHostname = true
c . autoTLS . verifyServerHostname = false
require . True ( t , c . VerifyServerHostname ( ) )
c . base . InternalRPC . VerifyServerHostname = false
c . autoTLS . verifyServerHostname = true
require . True ( t , c . VerifyServerHostname ( ) )
c . base . InternalRPC . VerifyServerHostname = true
c . autoTLS . verifyServerHostname = true
require . True ( t , c . VerifyServerHostname ( ) )
}
func TestConfigurator_AutoEncryptCert ( t * testing . T ) {
c := Configurator { base : & Config { } }
require . Nil ( t , c . AutoEncryptCert ( ) )
cert , err := loadKeyPair ( "../test/key/something_expired.cer" , "../test/key/something_expired.key" )
require . NoError ( t , err )
c . autoTLS . cert = cert
require . Equal ( t , int64 ( 1561561551 ) , c . AutoEncryptCert ( ) . NotAfter . Unix ( ) )
cert , err = loadKeyPair ( "../test/key/ourdomain.cer" , "../test/key/ourdomain.key" )
require . NoError ( t , err )
c . autoTLS . cert = cert
require . Equal ( t , int64 ( 4852545616 ) , c . AutoEncryptCert ( ) . NotAfter . Unix ( ) )
}
func TestConfigurator_AuthorizeInternalRPCServerConn ( t * testing . T ) {
caPEM , caPK , err := GenerateCA ( CAOpts { Days : 5 , Domain : "consul" } )
require . NoError ( t , err )
dir := testutil . TempDir ( t , "ca" )
caPath := filepath . Join ( dir , "ca.pem" )
err = os . WriteFile ( caPath , [ ] byte ( caPEM ) , 0600 )
require . NoError ( t , err )
// 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 {
Signer : signer ,
CA : caPEM ,
} )
require . NoError ( t , err )
certFile := filepath . Join ( "cert.pem" )
err = os . WriteFile ( certFile , [ ] byte ( pub ) , 0600 )
require . NoError ( t , err )
keyFile := filepath . Join ( "cert.key" )
err = os . WriteFile ( keyFile , [ ] byte ( pk ) , 0600 )
require . NoError ( t , err )
cfg := Config {
InternalRPC : ProtocolConfig {
VerifyServerHostname : true ,
VerifyIncoming : true ,
CAFile : caPath ,
CertFile : certFile ,
KeyFile : keyFile ,
} ,
Domain : "consul" ,
}
c := makeConfigurator ( t , cfg )
t . Run ( "wrong DNSName" , func ( t * testing . T ) {
signer , err := ParseSigner ( caPK )
require . NoError ( t , err )
pem , _ , err := GenerateCert ( CertOpts {
Signer : signer ,
CA : caPEM ,
Name : "server.dc1.consul" ,
Days : 5 ,
DNSNames : [ ] string { "this-name-is-wrong" , "localhost" } ,
ExtKeyUsage : [ ] x509 . ExtKeyUsage { x509 . ExtKeyUsageClientAuth } ,
} )
require . NoError ( t , err )
s := fakeTLSConn {
state : tls . ConnectionState {
VerifiedChains : [ ] [ ] * x509 . Certificate { certChain ( t , pem , caPEM ) } ,
PeerCertificates : certChain ( t , pem , caPEM ) ,
} ,
}
err = c . AuthorizeServerConn ( "dc1" , s )
testutil . RequireErrorContains ( t , err , "is valid for this-name-is-wrong, localhost, not server.dc1.consul" )
} )
t . Run ( "wrong CA" , func ( t * testing . T ) {
caPEM , caPK , err := GenerateCA ( CAOpts { Days : 5 , Domain : "consul" } )
require . NoError ( t , err )
dir := testutil . TempDir ( t , "other" )
caPath := filepath . Join ( dir , "ca.pem" )
err = os . WriteFile ( caPath , [ ] byte ( caPEM ) , 0600 )
require . NoError ( t , err )
signer , err := ParseSigner ( caPK )
require . NoError ( t , err )
pem , _ , err := GenerateCert ( CertOpts {
Signer : signer ,
CA : caPEM ,
Name : "server.dc1.consul" ,
Days : 5 ,
DNSNames : [ ] string { "server.dc1.consul" , "localhost" } ,
ExtKeyUsage : [ ] x509 . ExtKeyUsage { x509 . ExtKeyUsageClientAuth } ,
} )
require . NoError ( t , err )
s := fakeTLSConn {
state : tls . ConnectionState {
VerifiedChains : [ ] [ ] * x509 . Certificate { certChain ( t , pem , caPEM ) } ,
PeerCertificates : certChain ( t , pem , caPEM ) ,
} ,
}
err = c . AuthorizeServerConn ( "dc1" , s )
testutil . RequireErrorContains ( t , err , "signed by unknown authority" )
} )
t . Run ( "missing ext key usage" , func ( t * testing . T ) {
signer , err := ParseSigner ( caPK )
require . NoError ( t , err )
pem , _ , err := GenerateCert ( CertOpts {
Signer : signer ,
CA : caPEM ,
Name : "server.dc1.consul" ,
Days : 5 ,
DNSNames : [ ] string { "server.dc1.consul" , "localhost" } ,
ExtKeyUsage : [ ] x509 . ExtKeyUsage { x509 . ExtKeyUsageEmailProtection } ,
} )
require . NoError ( t , err )
s := fakeTLSConn {
state : tls . ConnectionState {
VerifiedChains : [ ] [ ] * x509 . Certificate { certChain ( t , pem , caPEM ) } ,
PeerCertificates : certChain ( t , pem , caPEM ) ,
} ,
}
err = c . AuthorizeServerConn ( "dc1" , s )
testutil . RequireErrorContains ( t , err , "certificate specifies an incompatible key usage" )
} )
t . Run ( "disabled by verify_incoming_rpc" , func ( t * testing . T ) {
cfg := Config {
InternalRPC : ProtocolConfig {
VerifyServerHostname : true ,
VerifyIncoming : false ,
CAFile : caPath ,
} ,
Domain : "consul" ,
}
c , err := NewConfigurator ( cfg , hclog . New ( nil ) )
require . NoError ( t , err )
s := fakeTLSConn { }
err = c . AuthorizeServerConn ( "dc1" , s )
require . NoError ( t , err )
} )
}
func TestConfigurator_GRPCServerUseTLS ( 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 . GRPCServerUseTLS ( ) )
} )
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 { } )
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 Disabled" , func ( t * testing . T ) {
c := makeConfigurator ( t , Config {
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 ( ) )
} )
}
type fakeTLSConn struct {
state tls . ConnectionState
}
func ( f fakeTLSConn ) ConnectionState ( ) tls . ConnectionState {
return f . state
}
func certChain ( t * testing . T , certs ... string ) [ ] * x509 . Certificate {
t . Helper ( )
result := make ( [ ] * x509 . Certificate , 0 , len ( certs ) )
for i , c := range certs {
cert , err := parseCert ( c )
require . NoError ( t , err , "cert %d" , i )
result = append ( result , cert )
}
return result
}
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 ( io . Discard , tlsServer )
tlsServer . Close ( )
} ( )
return clientConn , errc , certc
}
func loadFile ( t * testing . T , path string ) string {
t . Helper ( )
data , err := os . ReadFile ( path )
require . NoError ( t , err )
return string ( data )
}
func getExpectedCaPoolByFile ( t * testing . T ) * x509 . CertPool {
pool := x509 . NewCertPool ( )
data , err := os . ReadFile ( "../test/ca/root.cer" )
if err != nil {
t . Fatal ( "could not open test file ../test/ca/root.cer for reading" )
}
if ! pool . AppendCertsFromPEM ( data ) {
t . Fatal ( "could not add test ca ../test/ca/root.cer to pool" )
}
return pool
}
func getExpectedCaPoolByDir ( t * testing . T ) * x509 . CertPool {
pool := x509 . NewCertPool ( )
entries , err := os . ReadDir ( "../test/ca_path" )
if err != nil {
t . Fatal ( "could not open test dir ../test/ca_path for reading" )
}
for _ , entry := range entries {
filename := path . Join ( "../test/ca_path" , entry . Name ( ) )
data , err := os . ReadFile ( filename )
if err != nil {
t . Fatalf ( "could not open test file %s for reading" , filename )
}
if ! pool . AppendCertsFromPEM ( data ) {
t . Fatalf ( "could not add test ca %s to pool" , filename )
}
}
return pool
}
// lazyCerts has a func field which can't be compared.
var cmpCertPool = cmp . Options {
cmpopts . IgnoreFields ( x509 . CertPool { } , "lazyCerts" ) ,
cmp . AllowUnexported ( x509 . CertPool { } ) ,
}
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 )
}
}