568da5918b
The `nomad tls cert` command did not create certificates with the correct SANs for them to work with non default domain and region names. This changset updates the code to support non default domains and regions in the certificates.
1081 lines
27 KiB
Go
1081 lines
27 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package tlsutil
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/nomad/ci"
|
|
"github.com/hashicorp/nomad/nomad/structs/config"
|
|
"github.com/hashicorp/yamux"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
const (
|
|
// See README.md for documentation
|
|
cacert = "./testdata/nomad-agent-ca.pem"
|
|
fooclientcert = "./testdata/regionFoo-client-nomad.pem"
|
|
fooclientkey = "./testdata/regionFoo-client-nomad-key.pem"
|
|
fooservercert = "./testdata/regionFoo-server-nomad.pem"
|
|
fooserverkey = "./testdata/regionFoo-server-nomad-key.pem"
|
|
badcert = "./testdata/badRegion-client-bad.pem"
|
|
badkey = "./testdata/badRegion-client-bad-key.pem"
|
|
)
|
|
|
|
func TestConfig_AppendCA_None(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
require := require.New(t)
|
|
|
|
conf := &Config{}
|
|
pool := x509.NewCertPool()
|
|
err := conf.AppendCA(pool)
|
|
|
|
require.Nil(err)
|
|
}
|
|
|
|
func TestConfig_AppendCA_Valid(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
require := require.New(t)
|
|
|
|
conf := &Config{
|
|
CAFile: cacert,
|
|
}
|
|
pool := x509.NewCertPool()
|
|
err := conf.AppendCA(pool)
|
|
|
|
require.Nil(err)
|
|
}
|
|
|
|
func TestConfig_AppendCA_Valid_MultipleCerts(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
require := require.New(t)
|
|
|
|
certs := `
|
|
-----BEGIN CERTIFICATE-----
|
|
MIICMzCCAdqgAwIBAgIUNZ9L86Xp9EuDH0/qyAesh599LXQwCgYIKoZIzj0EAwIw
|
|
eDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh
|
|
biBGcmFuY2lzY28xEjAQBgNVBAoTCUhhc2hpQ29ycDEOMAwGA1UECxMFTm9tYWQx
|
|
GDAWBgNVBAMTD25vbWFkLmhhc2hpY29ycDAeFw0xNjExMTAxOTQ4MDBaFw0yMTEx
|
|
MDkxOTQ4MDBaMHgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYw
|
|
FAYDVQQHEw1TYW4gRnJhbmNpc2NvMRIwEAYDVQQKEwlIYXNoaUNvcnAxDjAMBgNV
|
|
BAsTBU5vbWFkMRgwFgYDVQQDEw9ub21hZC5oYXNoaWNvcnAwWTATBgcqhkjOPQIB
|
|
BggqhkjOPQMBBwNCAARfJmTdHzYIMPD8SK+kj5Gc79fmpOcg6wnb4JNVwCqWw9O+
|
|
uNdZJZWSi4Q/4HojM5FTSBqYxNgSrmY/o3oQrCPlo0IwQDAOBgNVHQ8BAf8EBAMC
|
|
AQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUOjVq/BectnhcKn6EHUD4NJFm
|
|
/UAwCgYIKoZIzj0EAwIDRwAwRAIgTemDJGSGtcQPXLWKiQNw4SKO9wAPhn/WoKW4
|
|
Ln2ZUe8CIDsQswBQS7URbqnKYDye2Y4befJkr4fmhhmMQb2ex9A4
|
|
-----END CERTIFICATE-----
|
|
-----BEGIN CERTIFICATE-----
|
|
MIICNTCCAZagAwIBAgIRANjgoh5iVZI26+Hz/K65G0UwCgYIKoZIzj0EAwQwNjEb
|
|
MBkGA1UEChMSSGFzaGlDb3JwIFRyYWluaW5nMRcwFQYDVQQDEw5zZXJ2aWNlLmNv
|
|
bnN1bDAeFw0xODA4MjMxNzM0NTBaFw0xODA5MjIxNzM0NTBaMDYxGzAZBgNVBAoT
|
|
Ekhhc2hpQ29ycCBUcmFpbmluZzEXMBUGA1UEAxMOc2VydmljZS5jb25zdWwwgZsw
|
|
EAYHKoZIzj0CAQYFK4EEACMDgYYABAGjC4sWsOfirS/DQ9/e7PdQeJwlOjziiOx/
|
|
CALjS6ryEDkZPqRqMuoFXfudAmfdk6tl8AT1IKMVcgiQU5jkm7fliwFIk48uh+n2
|
|
obqZjwDyM76VYBVSYi6i3BPXown1ivIMJNQS1txnWZLZHsv+WxbHydS+GNOAwKDK
|
|
KsXj9dEhd36pvaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w
|
|
HQYDVR0OBBYEFIk3oG2hu0FxueW4e7fL+FdMOquBMAoGCCqGSM49BAMEA4GMADCB
|
|
iAJCAPIPwPyk+8Ymj7Zlvb5qIUQg+UxoacAeJtFZrJ8xQjro0YjsM33O86rAfw+x
|
|
sWWGul4Ews93KFBXvhbKCwb0F0PhAkIAh2z7COsKcQzvBoIy+Kx92+9j/sUjlzzl
|
|
TttDu+g2VdbcBwVDZ49X2Md6OY2N3G8Irdlj+n+mCQJaHwVt52DRzz0=
|
|
-----END CERTIFICATE-----
|
|
`
|
|
|
|
tmpCAFile, err := os.CreateTemp("/tmp", "test_ca_file")
|
|
require.NoError(err)
|
|
defer os.Remove(tmpCAFile.Name())
|
|
|
|
_, err = tmpCAFile.Write([]byte(certs))
|
|
require.NoError(err)
|
|
tmpCAFile.Close()
|
|
|
|
conf := &Config{
|
|
CAFile: tmpCAFile.Name(),
|
|
}
|
|
pool := x509.NewCertPool()
|
|
require.NoError(conf.AppendCA(pool))
|
|
|
|
require.Len(pool.Subjects(), 2)
|
|
}
|
|
|
|
// TestConfig_AppendCA_Valid_Whitespace asserts that a PEM file containing
|
|
// trailing whitespace is valid.
|
|
func TestConfig_AppendCA_Valid_Whitespace(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
require := require.New(t)
|
|
|
|
const cacertWhitespace = "./testdata/whitespace-agent-ca.pem"
|
|
conf := &Config{
|
|
CAFile: cacertWhitespace,
|
|
}
|
|
pool := x509.NewCertPool()
|
|
require.NoError(conf.AppendCA(pool))
|
|
|
|
require.Len(pool.Subjects(), 1)
|
|
}
|
|
|
|
// TestConfig_AppendCA_Invalid_MultipleCerts_Whitespace asserts that a PEM file
|
|
// containing non-PEM data between certificate blocks is still valid.
|
|
func TestConfig_AppendCA_Valid_MultipleCerts_ExtraData(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
require := require.New(t)
|
|
|
|
certs := `
|
|
Did you know...
|
|
-----BEGIN CERTIFICATE-----
|
|
MIICMzCCAdqgAwIBAgIUNZ9L86Xp9EuDH0/qyAesh599LXQwCgYIKoZIzj0EAwIw
|
|
eDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh
|
|
biBGcmFuY2lzY28xEjAQBgNVBAoTCUhhc2hpQ29ycDEOMAwGA1UECxMFTm9tYWQx
|
|
GDAWBgNVBAMTD25vbWFkLmhhc2hpY29ycDAeFw0xNjExMTAxOTQ4MDBaFw0yMTEx
|
|
MDkxOTQ4MDBaMHgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYw
|
|
FAYDVQQHEw1TYW4gRnJhbmNpc2NvMRIwEAYDVQQKEwlIYXNoaUNvcnAxDjAMBgNV
|
|
BAsTBU5vbWFkMRgwFgYDVQQDEw9ub21hZC5oYXNoaWNvcnAwWTATBgcqhkjOPQIB
|
|
BggqhkjOPQMBBwNCAARfJmTdHzYIMPD8SK+kj5Gc79fmpOcg6wnb4JNVwCqWw9O+
|
|
uNdZJZWSi4Q/4HojM5FTSBqYxNgSrmY/o3oQrCPlo0IwQDAOBgNVHQ8BAf8EBAMC
|
|
AQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUOjVq/BectnhcKn6EHUD4NJFm
|
|
/UAwCgYIKoZIzj0EAwIDRwAwRAIgTemDJGSGtcQPXLWKiQNw4SKO9wAPhn/WoKW4
|
|
Ln2ZUe8CIDsQswBQS7URbqnKYDye2Y4befJkr4fmhhmMQb2ex9A4
|
|
-----END CERTIFICATE-----
|
|
|
|
...PEM parsers don't care about data...
|
|
|
|
-----BEGIN CERTIFICATE-----
|
|
MIICNTCCAZagAwIBAgIRANjgoh5iVZI26+Hz/K65G0UwCgYIKoZIzj0EAwQwNjEb
|
|
MBkGA1UEChMSSGFzaGlDb3JwIFRyYWluaW5nMRcwFQYDVQQDEw5zZXJ2aWNlLmNv
|
|
bnN1bDAeFw0xODA4MjMxNzM0NTBaFw0xODA5MjIxNzM0NTBaMDYxGzAZBgNVBAoT
|
|
Ekhhc2hpQ29ycCBUcmFpbmluZzEXMBUGA1UEAxMOc2VydmljZS5jb25zdWwwgZsw
|
|
EAYHKoZIzj0CAQYFK4EEACMDgYYABAGjC4sWsOfirS/DQ9/e7PdQeJwlOjziiOx/
|
|
CALjS6ryEDkZPqRqMuoFXfudAmfdk6tl8AT1IKMVcgiQU5jkm7fliwFIk48uh+n2
|
|
obqZjwDyM76VYBVSYi6i3BPXown1ivIMJNQS1txnWZLZHsv+WxbHydS+GNOAwKDK
|
|
KsXj9dEhd36pvaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w
|
|
HQYDVR0OBBYEFIk3oG2hu0FxueW4e7fL+FdMOquBMAoGCCqGSM49BAMEA4GMADCB
|
|
iAJCAPIPwPyk+8Ymj7Zlvb5qIUQg+UxoacAeJtFZrJ8xQjro0YjsM33O86rAfw+x
|
|
sWWGul4Ews93KFBXvhbKCwb0F0PhAkIAh2z7COsKcQzvBoIy+Kx92+9j/sUjlzzl
|
|
TttDu+g2VdbcBwVDZ49X2Md6OY2N3G8Irdlj+n+mCQJaHwVt52DRzz0=
|
|
-----END CERTIFICATE-----
|
|
|
|
...outside of -----XXX----- blocks?
|
|
`
|
|
|
|
tmpCAFile, err := os.CreateTemp("/tmp", "test_ca_file_extra")
|
|
require.NoError(err)
|
|
defer os.Remove(tmpCAFile.Name())
|
|
_, err = tmpCAFile.Write([]byte(certs))
|
|
require.NoError(err)
|
|
tmpCAFile.Close()
|
|
|
|
conf := &Config{
|
|
CAFile: tmpCAFile.Name(),
|
|
}
|
|
pool := x509.NewCertPool()
|
|
err = conf.AppendCA(pool)
|
|
|
|
require.NoError(err)
|
|
require.Len(pool.Subjects(), 2)
|
|
}
|
|
|
|
// TestConfig_AppendCA_Invalid_MultipleCerts asserts only the valid certificate
|
|
// is returned.
|
|
func TestConfig_AppendCA_Invalid_MultipleCerts(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
require := require.New(t)
|
|
|
|
certs := `
|
|
-----BEGIN CERTIFICATE-----
|
|
MIICMzCCAdqgAwIBAgIUNZ9L86Xp9EuDH0/qyAesh599LXQwCgYIKoZIzj0EAwIw
|
|
eDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh
|
|
biBGcmFuY2lzY28xEjAQBgNVBAoTCUhhc2hpQ29ycDEOMAwGA1UECxMFTm9tYWQx
|
|
GDAWBgNVBAMTD25vbWFkLmhhc2hpY29ycDAeFw0xNjExMTAxOTQ4MDBaFw0yMTEx
|
|
MDkxOTQ4MDBaMHgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYw
|
|
FAYDVQQHEw1TYW4gRnJhbmNpc2NvMRIwEAYDVQQKEwlIYXNoaUNvcnAxDjAMBgNV
|
|
BAsTBU5vbWFkMRgwFgYDVQQDEw9ub21hZC5oYXNoaWNvcnAwWTATBgcqhkjOPQIB
|
|
BggqhkjOPQMBBwNCAARfJmTdHzYIMPD8SK+kj5Gc79fmpOcg6wnb4JNVwCqWw9O+
|
|
uNdZJZWSi4Q/4HojM5FTSBqYxNgSrmY/o3oQrCPlo0IwQDAOBgNVHQ8BAf8EBAMC
|
|
AQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUOjVq/BectnhcKn6EHUD4NJFm
|
|
/UAwCgYIKoZIzj0EAwIDRwAwRAIgTemDJGSGtcQPXLWKiQNw4SKO9wAPhn/WoKW4
|
|
Ln2ZUe8CIDsQswBQS7URbqnKYDye2Y4befJkr4fmhhmMQb2ex9A4
|
|
-----END CERTIFICATE-----
|
|
-----BEGIN CERTIFICATE-----
|
|
Invalid
|
|
-----END CERTIFICATE-----`
|
|
|
|
tmpCAFile, err := os.CreateTemp("/tmp", "test_ca_file")
|
|
require.NoError(err)
|
|
defer os.Remove(tmpCAFile.Name())
|
|
_, err = tmpCAFile.Write([]byte(certs))
|
|
require.NoError(err)
|
|
tmpCAFile.Close()
|
|
|
|
conf := &Config{
|
|
CAFile: tmpCAFile.Name(),
|
|
}
|
|
pool := x509.NewCertPool()
|
|
require.NoError(conf.AppendCA(pool))
|
|
|
|
require.Len(pool.Subjects(), 1)
|
|
}
|
|
|
|
func TestConfig_AppendCA_Invalid(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
require := require.New(t)
|
|
{
|
|
conf := &Config{
|
|
CAFile: "invalidFile",
|
|
}
|
|
pool := x509.NewCertPool()
|
|
err := conf.AppendCA(pool)
|
|
require.NotNil(err)
|
|
require.Contains(err.Error(), "Failed to read CA file")
|
|
require.Equal(len(pool.Subjects()), 0)
|
|
}
|
|
|
|
{
|
|
tmpFile, err := os.CreateTemp("/tmp", "test_ca_file")
|
|
require.Nil(err)
|
|
defer os.Remove(tmpFile.Name())
|
|
_, err = tmpFile.Write([]byte("Invalid CA Content!"))
|
|
require.Nil(err)
|
|
|
|
conf := &Config{
|
|
CAFile: tmpFile.Name(),
|
|
}
|
|
pool := x509.NewCertPool()
|
|
err = conf.AppendCA(pool)
|
|
require.Error(err)
|
|
require.Contains(err.Error(), "Failed to parse any valid certificates in CA file:")
|
|
require.Equal(len(pool.Subjects()), 0)
|
|
}
|
|
}
|
|
|
|
func TestConfig_CACertificate_Valid(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
conf := &Config{
|
|
CAFile: cacert,
|
|
}
|
|
pool := x509.NewCertPool()
|
|
err := conf.AppendCA(pool)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if len(pool.Subjects()) == 0 {
|
|
t.Fatalf("expected cert")
|
|
}
|
|
}
|
|
|
|
func TestConfig_LoadKeyPair_None(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
conf := &Config{
|
|
KeyLoader: &config.KeyLoader{},
|
|
}
|
|
cert, err := conf.LoadKeyPair()
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if cert != nil {
|
|
t.Fatalf("bad: %v", cert)
|
|
}
|
|
}
|
|
|
|
func TestConfig_LoadKeyPair_Valid(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
conf := &Config{
|
|
CertFile: fooclientcert,
|
|
KeyFile: fooclientkey,
|
|
KeyLoader: &config.KeyLoader{},
|
|
}
|
|
cert, err := conf.LoadKeyPair()
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if cert == nil {
|
|
t.Fatalf("expected cert")
|
|
}
|
|
}
|
|
|
|
func TestConfig_OutgoingTLS_MissingCA(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
conf := &Config{
|
|
VerifyOutgoing: true,
|
|
}
|
|
tls, err := conf.OutgoingTLSConfig()
|
|
if err == nil {
|
|
t.Fatalf("expected err")
|
|
}
|
|
if tls != nil {
|
|
t.Fatalf("bad: %v", tls)
|
|
}
|
|
}
|
|
|
|
func TestConfig_OutgoingTLS_OnlyCA(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
conf := &Config{
|
|
CAFile: cacert,
|
|
}
|
|
tls, err := conf.OutgoingTLSConfig()
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if tls != nil {
|
|
t.Fatalf("expected no config")
|
|
}
|
|
}
|
|
|
|
func TestConfig_OutgoingTLS_VerifyOutgoing(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
conf := &Config{
|
|
VerifyOutgoing: true,
|
|
CAFile: cacert,
|
|
}
|
|
tls, err := conf.OutgoingTLSConfig()
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if tls == nil {
|
|
t.Fatalf("expected config")
|
|
}
|
|
if len(tls.RootCAs.Subjects()) != 1 {
|
|
t.Fatalf("expect root cert")
|
|
}
|
|
if !tls.InsecureSkipVerify {
|
|
t.Fatalf("should skip built-in verification")
|
|
}
|
|
}
|
|
|
|
func TestConfig_OutgoingTLS_VerifyHostname(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
conf := &Config{
|
|
VerifyServerHostname: true,
|
|
CAFile: cacert,
|
|
}
|
|
tls, err := conf.OutgoingTLSConfig()
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if tls == nil {
|
|
t.Fatalf("expected config")
|
|
}
|
|
if len(tls.RootCAs.Subjects()) != 1 {
|
|
t.Fatalf("expect root cert")
|
|
}
|
|
if tls.InsecureSkipVerify {
|
|
t.Fatalf("should not skip built-in verification")
|
|
}
|
|
}
|
|
|
|
func TestConfig_OutgoingTLS_WithKeyPair(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
assert := assert.New(t)
|
|
|
|
conf := &Config{
|
|
VerifyOutgoing: true,
|
|
CAFile: cacert,
|
|
CertFile: fooclientcert,
|
|
KeyFile: fooclientkey,
|
|
KeyLoader: &config.KeyLoader{},
|
|
}
|
|
tlsConf, err := conf.OutgoingTLSConfig()
|
|
assert.Nil(err)
|
|
assert.NotNil(tlsConf)
|
|
assert.Equal(len(tlsConf.RootCAs.Subjects()), 1)
|
|
assert.True(tlsConf.InsecureSkipVerify)
|
|
|
|
clientHelloInfo := &tls.ClientHelloInfo{}
|
|
cert, err := tlsConf.GetCertificate(clientHelloInfo)
|
|
assert.Nil(err)
|
|
assert.NotNil(cert)
|
|
}
|
|
|
|
func TestConfig_OutgoingTLS_PreferServerCipherSuites(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
require := require.New(t)
|
|
|
|
{
|
|
conf := &Config{
|
|
VerifyOutgoing: true,
|
|
CAFile: cacert,
|
|
}
|
|
tlsConfig, err := conf.OutgoingTLSConfig()
|
|
require.Nil(err)
|
|
require.Equal(tlsConfig.PreferServerCipherSuites, false)
|
|
}
|
|
{
|
|
conf := &Config{
|
|
VerifyOutgoing: true,
|
|
CAFile: cacert,
|
|
PreferServerCipherSuites: true,
|
|
}
|
|
tlsConfig, err := conf.OutgoingTLSConfig()
|
|
require.Nil(err)
|
|
require.Equal(tlsConfig.PreferServerCipherSuites, true)
|
|
}
|
|
}
|
|
|
|
func TestConfig_OutgoingTLS_TLSCipherSuites(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
require := require.New(t)
|
|
|
|
{
|
|
defaultCiphers := []uint16{
|
|
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
|
}
|
|
conf := &Config{
|
|
VerifyOutgoing: true,
|
|
CAFile: cacert,
|
|
CipherSuites: defaultCiphers,
|
|
}
|
|
tlsConfig, err := conf.OutgoingTLSConfig()
|
|
require.Nil(err)
|
|
require.Equal(tlsConfig.CipherSuites, defaultCiphers)
|
|
}
|
|
{
|
|
conf := &Config{
|
|
VerifyOutgoing: true,
|
|
CAFile: cacert,
|
|
CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305},
|
|
}
|
|
tlsConfig, err := conf.OutgoingTLSConfig()
|
|
require.Nil(err)
|
|
require.Equal(tlsConfig.CipherSuites, []uint16{tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305})
|
|
}
|
|
}
|
|
|
|
func startTLSServer(config *Config) (net.Conn, chan error) {
|
|
errc := make(chan error, 1)
|
|
|
|
tlsConfigServer, err := config.IncomingTLSConfig()
|
|
if err != nil {
|
|
errc <- err
|
|
return nil, errc
|
|
}
|
|
|
|
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
|
|
}
|
|
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
|
|
}
|
|
|
|
// TODO sign the certificates for "server.regionFoo.nomad
|
|
func TestConfig_outgoingWrapper_OK(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
config := &Config{
|
|
CAFile: cacert,
|
|
CertFile: fooservercert,
|
|
KeyFile: fooserverkey,
|
|
VerifyServerHostname: true,
|
|
VerifyOutgoing: true,
|
|
KeyLoader: &config.KeyLoader{},
|
|
}
|
|
|
|
client, errc := startTLSServer(config)
|
|
if client == nil {
|
|
t.Fatalf("startTLSServer err: %v", <-errc)
|
|
}
|
|
|
|
wrap, err := config.OutgoingTLSWrapper()
|
|
if err != nil {
|
|
t.Fatalf("OutgoingTLSWrapper err: %v", err)
|
|
}
|
|
|
|
tlsClient, err := wrap("regionFoo", client)
|
|
if err != nil {
|
|
t.Fatalf("wrapTLS err: %v", err)
|
|
}
|
|
defer tlsClient.Close()
|
|
if err := tlsClient.(*tls.Conn).Handshake(); err != nil {
|
|
t.Fatalf("write err: %v", err)
|
|
}
|
|
|
|
err = <-errc
|
|
if err != nil {
|
|
t.Fatalf("server: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestConfig_outgoingWrapper_BadCert(t *testing.T) {
|
|
ci.Parallel(t)
|
|
// TODO this test is currently hanging, need to investigate more.
|
|
t.SkipNow()
|
|
config := &Config{
|
|
CAFile: cacert,
|
|
CertFile: fooclientcert,
|
|
KeyFile: fooclientkey,
|
|
VerifyServerHostname: true,
|
|
VerifyOutgoing: true,
|
|
}
|
|
|
|
client, errc := startTLSServer(config)
|
|
if client == nil {
|
|
t.Fatalf("startTLSServer err: %v", <-errc)
|
|
}
|
|
|
|
wrap, err := config.OutgoingTLSWrapper()
|
|
if err != nil {
|
|
t.Fatalf("OutgoingTLSWrapper err: %v", err)
|
|
}
|
|
|
|
tlsClient, err := wrap("regionFoo", client)
|
|
if err != nil {
|
|
t.Fatalf("wrapTLS err: %v", err)
|
|
}
|
|
defer tlsClient.Close()
|
|
err = tlsClient.(*tls.Conn).Handshake()
|
|
|
|
if _, ok := err.(x509.HostnameError); !ok {
|
|
t.Fatalf("should get hostname err: %v", err)
|
|
}
|
|
|
|
<-errc
|
|
}
|
|
|
|
func TestConfig_wrapTLS_OK(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
config := &Config{
|
|
CAFile: cacert,
|
|
CertFile: fooclientcert,
|
|
KeyFile: fooclientkey,
|
|
VerifyOutgoing: true,
|
|
KeyLoader: &config.KeyLoader{},
|
|
}
|
|
|
|
client, errc := startTLSServer(config)
|
|
if client == nil {
|
|
t.Fatalf("startTLSServer err: %v", <-errc)
|
|
}
|
|
|
|
clientConfig, err := config.OutgoingTLSConfig()
|
|
if err != nil {
|
|
t.Fatalf("OutgoingTLSConfig err: %v", err)
|
|
}
|
|
|
|
tlsClient, err := WrapTLSClient(client, clientConfig)
|
|
if err != nil {
|
|
t.Fatalf("wrapTLS err: %v", err)
|
|
} else {
|
|
tlsClient.Close()
|
|
}
|
|
err = <-errc
|
|
if err != nil {
|
|
t.Fatalf("server: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestConfig_wrapTLS_BadCert(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
serverConfig := &Config{
|
|
CAFile: cacert,
|
|
CertFile: badcert,
|
|
KeyFile: badkey,
|
|
KeyLoader: &config.KeyLoader{},
|
|
}
|
|
|
|
client, errc := startTLSServer(serverConfig)
|
|
if client == nil {
|
|
t.Fatalf("startTLSServer err: %v", <-errc)
|
|
}
|
|
|
|
clientConfig := &Config{
|
|
CAFile: cacert,
|
|
VerifyOutgoing: true,
|
|
}
|
|
|
|
clientTLSConfig, err := clientConfig.OutgoingTLSConfig()
|
|
if err != nil {
|
|
t.Fatalf("OutgoingTLSConfig err: %v", err)
|
|
}
|
|
|
|
tlsClient, err := WrapTLSClient(client, clientTLSConfig)
|
|
if err == nil {
|
|
t.Fatalf("wrapTLS no err")
|
|
}
|
|
if tlsClient != nil {
|
|
t.Fatalf("returned a client")
|
|
}
|
|
|
|
err = <-errc
|
|
if err != nil {
|
|
t.Fatalf("server: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestConfig_IncomingTLS(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
assert := assert.New(t)
|
|
|
|
conf := &Config{
|
|
VerifyIncoming: true,
|
|
CAFile: cacert,
|
|
CertFile: fooclientcert,
|
|
KeyFile: fooclientkey,
|
|
KeyLoader: &config.KeyLoader{},
|
|
}
|
|
tlsC, err := conf.IncomingTLSConfig()
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if tlsC == nil {
|
|
t.Fatalf("expected config")
|
|
}
|
|
if len(tlsC.ClientCAs.Subjects()) != 1 {
|
|
t.Fatalf("expect client cert")
|
|
}
|
|
if tlsC.ClientAuth != tls.RequireAndVerifyClientCert {
|
|
t.Fatalf("should not skip verification")
|
|
}
|
|
|
|
clientHelloInfo := &tls.ClientHelloInfo{}
|
|
cert, err := tlsC.GetCertificate(clientHelloInfo)
|
|
assert.Nil(err)
|
|
assert.NotNil(cert)
|
|
}
|
|
|
|
func TestConfig_IncomingTLS_MissingCA(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
conf := &Config{
|
|
VerifyIncoming: true,
|
|
CertFile: fooclientcert,
|
|
KeyFile: fooclientkey,
|
|
KeyLoader: &config.KeyLoader{},
|
|
}
|
|
_, err := conf.IncomingTLSConfig()
|
|
if err == nil {
|
|
t.Fatalf("expected err")
|
|
}
|
|
}
|
|
|
|
func TestConfig_IncomingTLS_MissingKey(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
conf := &Config{
|
|
VerifyIncoming: true,
|
|
CAFile: cacert,
|
|
}
|
|
_, err := conf.IncomingTLSConfig()
|
|
if err == nil {
|
|
t.Fatalf("expected err")
|
|
}
|
|
}
|
|
|
|
func TestConfig_IncomingTLS_NoVerify(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
conf := &Config{}
|
|
tlsC, err := conf.IncomingTLSConfig()
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if tlsC == nil {
|
|
t.Fatalf("expected config")
|
|
}
|
|
if len(tlsC.ClientCAs.Subjects()) != 0 {
|
|
t.Fatalf("do not expect client cert")
|
|
}
|
|
if tlsC.ClientAuth != tls.NoClientCert {
|
|
t.Fatalf("should skip verification")
|
|
}
|
|
if len(tlsC.Certificates) != 0 {
|
|
t.Fatalf("unexpected client cert")
|
|
}
|
|
}
|
|
|
|
func TestConfig_IncomingTLS_PreferServerCipherSuites(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
require := require.New(t)
|
|
|
|
{
|
|
conf := &Config{}
|
|
tlsConfig, err := conf.IncomingTLSConfig()
|
|
require.Nil(err)
|
|
require.Equal(tlsConfig.PreferServerCipherSuites, false)
|
|
}
|
|
{
|
|
conf := &Config{
|
|
PreferServerCipherSuites: true,
|
|
}
|
|
tlsConfig, err := conf.IncomingTLSConfig()
|
|
require.Nil(err)
|
|
require.Equal(tlsConfig.PreferServerCipherSuites, true)
|
|
}
|
|
}
|
|
|
|
func TestConfig_IncomingTLS_TLSCipherSuites(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
require := require.New(t)
|
|
|
|
{
|
|
defaultCiphers := []uint16{
|
|
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
|
}
|
|
conf := &Config{
|
|
CipherSuites: defaultCiphers,
|
|
}
|
|
tlsConfig, err := conf.IncomingTLSConfig()
|
|
require.Nil(err)
|
|
require.Equal(tlsConfig.CipherSuites, defaultCiphers)
|
|
}
|
|
{
|
|
conf := &Config{
|
|
CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305},
|
|
}
|
|
tlsConfig, err := conf.IncomingTLSConfig()
|
|
require.Nil(err)
|
|
require.Equal(tlsConfig.CipherSuites, []uint16{tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305})
|
|
}
|
|
}
|
|
|
|
// This test relies on the fact that the specified certificate has an ECDSA
|
|
// signature algorithm
|
|
func TestConfig_ParseCiphers_Valid(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
require := require.New(t)
|
|
|
|
tlsConfig := &config.TLSConfig{
|
|
CertFile: fooclientcert,
|
|
KeyFile: fooclientkey,
|
|
KeyLoader: &config.KeyLoader{},
|
|
TLSCipherSuites: strings.Join([]string{
|
|
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
|
|
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
|
|
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
|
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
|
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
|
|
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
|
|
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
|
|
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
|
|
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
|
|
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
|
|
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
|
|
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
|
|
"TLS_RSA_WITH_AES_128_GCM_SHA256",
|
|
"TLS_RSA_WITH_AES_256_GCM_SHA384",
|
|
"TLS_RSA_WITH_AES_128_CBC_SHA256",
|
|
"TLS_RSA_WITH_AES_128_CBC_SHA",
|
|
"TLS_RSA_WITH_AES_256_CBC_SHA",
|
|
}, ","),
|
|
}
|
|
|
|
expectedCiphers := []uint16{
|
|
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
|
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
|
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
|
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
|
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
|
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
|
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
|
tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
|
|
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
|
tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
|
|
tls.TLS_RSA_WITH_AES_128_CBC_SHA,
|
|
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
|
}
|
|
|
|
parsedCiphers, err := ParseCiphers(tlsConfig)
|
|
require.Nil(err)
|
|
require.Equal(parsedCiphers, expectedCiphers)
|
|
}
|
|
|
|
// This test relies on the fact that the specified certificate has an ECDSA
|
|
// signature algorithm
|
|
func TestConfig_ParseCiphers_Default(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
require := require.New(t)
|
|
|
|
expectedCiphers := []uint16{
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
|
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
|
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
|
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
|
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
|
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
|
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
|
}
|
|
|
|
empty := &config.TLSConfig{
|
|
CertFile: fooclientcert,
|
|
KeyFile: fooclientkey,
|
|
KeyLoader: &config.KeyLoader{},
|
|
}
|
|
parsedCiphers, err := ParseCiphers(empty)
|
|
require.Nil(err)
|
|
require.Equal(parsedCiphers, expectedCiphers)
|
|
}
|
|
|
|
// This test relies on the fact that the specified certificate has an ECDSA
|
|
// signature algorithm
|
|
func TestConfig_ParseCiphers_Invalid(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
require := require.New(t)
|
|
|
|
invalidCiphers := []string{
|
|
"TLS_RSA_RSA_WITH_RC4_128_SHA",
|
|
"INVALID_CIPHER",
|
|
}
|
|
|
|
for _, cipher := range invalidCiphers {
|
|
tlsConfig := &config.TLSConfig{
|
|
TLSCipherSuites: cipher,
|
|
CertFile: fooclientcert,
|
|
KeyFile: fooclientkey,
|
|
KeyLoader: &config.KeyLoader{},
|
|
}
|
|
parsedCiphers, err := ParseCiphers(tlsConfig)
|
|
require.NotNil(err)
|
|
require.Equal(fmt.Sprintf("unsupported TLS cipher %q", cipher), err.Error())
|
|
require.Equal(0, len(parsedCiphers))
|
|
}
|
|
}
|
|
|
|
// This test relies on the fact that the specified certificate has an ECDSA
|
|
// signature algorithm
|
|
func TestConfig_ParseCiphers_SupportedSignature(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
require := require.New(t)
|
|
|
|
// Supported signature
|
|
{
|
|
tlsConfig := &config.TLSConfig{
|
|
TLSCipherSuites: "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
|
|
CertFile: fooclientcert,
|
|
KeyFile: fooclientkey,
|
|
KeyLoader: &config.KeyLoader{},
|
|
}
|
|
parsedCiphers, err := ParseCiphers(tlsConfig)
|
|
require.Nil(err)
|
|
require.Equal(1, len(parsedCiphers))
|
|
}
|
|
|
|
// Unsupported signature
|
|
{
|
|
tlsConfig := &config.TLSConfig{
|
|
TLSCipherSuites: "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
|
|
CertFile: fooclientcert,
|
|
KeyFile: fooclientkey,
|
|
KeyLoader: &config.KeyLoader{},
|
|
}
|
|
parsedCiphers, err := ParseCiphers(tlsConfig)
|
|
require.NotNil(err)
|
|
require.Equal(0, len(parsedCiphers))
|
|
}
|
|
}
|
|
|
|
func TestConfig_ParseMinVersion_Valid(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
require := require.New(t)
|
|
|
|
validVersions := []string{"tls10",
|
|
"tls11",
|
|
"tls12",
|
|
}
|
|
|
|
expected := map[string]uint16{
|
|
"tls10": tls.VersionTLS10,
|
|
"tls11": tls.VersionTLS11,
|
|
"tls12": tls.VersionTLS12,
|
|
}
|
|
|
|
for _, version := range validVersions {
|
|
parsedVersion, err := ParseMinVersion(version)
|
|
require.Nil(err)
|
|
require.Equal(expected[version], parsedVersion)
|
|
}
|
|
}
|
|
|
|
func TestConfig_ParseMinVersion_Invalid(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
require := require.New(t)
|
|
|
|
invalidVersions := []string{"tls13",
|
|
"tls15",
|
|
}
|
|
|
|
for _, version := range invalidVersions {
|
|
parsedVersion, err := ParseMinVersion(version)
|
|
require.NotNil(err)
|
|
require.Equal(fmt.Sprintf("unsupported TLS version %q", version), err.Error())
|
|
require.Equal(uint16(0), parsedVersion)
|
|
}
|
|
}
|
|
|
|
func TestConfig_NewTLSConfiguration(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
require := require.New(t)
|
|
|
|
conf := &config.TLSConfig{
|
|
TLSCipherSuites: "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
|
CertFile: fooclientcert,
|
|
KeyFile: fooclientkey,
|
|
KeyLoader: &config.KeyLoader{},
|
|
}
|
|
|
|
tlsConf, err := NewTLSConfiguration(conf, true, true)
|
|
require.Nil(err)
|
|
require.True(tlsConf.VerifyIncoming)
|
|
require.True(tlsConf.VerifyOutgoing)
|
|
|
|
expectedCiphers := []uint16{
|
|
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
|
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
|
}
|
|
require.Equal(tlsConf.CipherSuites, expectedCiphers)
|
|
}
|
|
|
|
func TestConfig_ShouldReloadRPCConnections(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
require := require.New(t)
|
|
|
|
type shouldReloadTestInput struct {
|
|
old *config.TLSConfig
|
|
new *config.TLSConfig
|
|
shouldReload bool
|
|
errorStr string
|
|
}
|
|
|
|
testInput := []*shouldReloadTestInput{
|
|
{
|
|
old: &config.TLSConfig{
|
|
CAFile: cacert,
|
|
CertFile: badcert,
|
|
KeyFile: badkey,
|
|
},
|
|
new: &config.TLSConfig{
|
|
CAFile: cacert,
|
|
CertFile: badcert,
|
|
KeyFile: badkey,
|
|
},
|
|
shouldReload: false,
|
|
errorStr: "Same TLS Configuration should not reload",
|
|
},
|
|
{
|
|
old: &config.TLSConfig{
|
|
CAFile: cacert,
|
|
CertFile: badcert,
|
|
KeyFile: badkey,
|
|
},
|
|
new: &config.TLSConfig{
|
|
CAFile: cacert,
|
|
CertFile: fooclientcert,
|
|
KeyFile: fooclientkey,
|
|
},
|
|
shouldReload: true,
|
|
errorStr: "Different TLS Configuration should reload",
|
|
},
|
|
{
|
|
old: &config.TLSConfig{
|
|
CAFile: cacert,
|
|
CertFile: badcert,
|
|
KeyFile: badkey,
|
|
EnableRPC: true,
|
|
},
|
|
new: &config.TLSConfig{
|
|
CAFile: cacert,
|
|
CertFile: badcert,
|
|
KeyFile: badkey,
|
|
EnableRPC: false,
|
|
},
|
|
shouldReload: true,
|
|
errorStr: "Downgrading RPC connections should force reload",
|
|
},
|
|
{
|
|
old: nil,
|
|
new: &config.TLSConfig{
|
|
CAFile: cacert,
|
|
CertFile: badcert,
|
|
KeyFile: badkey,
|
|
EnableRPC: true,
|
|
},
|
|
shouldReload: true,
|
|
errorStr: "Upgrading RPC connections should force reload",
|
|
},
|
|
{
|
|
old: &config.TLSConfig{
|
|
CAFile: cacert,
|
|
CertFile: badcert,
|
|
KeyFile: badkey,
|
|
EnableRPC: true,
|
|
},
|
|
new: nil,
|
|
shouldReload: true,
|
|
errorStr: "Downgrading RPC connections should force reload",
|
|
},
|
|
}
|
|
|
|
for _, testCase := range testInput {
|
|
shouldReload, err := ShouldReloadRPCConnections(testCase.old, testCase.new)
|
|
require.NoError(err)
|
|
require.Equal(shouldReload, testCase.shouldReload, testCase.errorStr)
|
|
}
|
|
}
|