133a6d99f2
The fallback method would still work but it would get into a state where it would let the certificate expire for 10s before getting a new one. And the new one used the less secure RPC endpoint. This is also a pretty large refactoring of the auto encrypt code. I was going to write some tests around the certificate monitoring but it was going to be impossible to get a TestAgent configured in such a way that I could write a test that ran in less than an hour or two to exercise the functionality. Moving the certificate monitoring into its own package will allow for dependency injection and in particular mocking the cache types to control how it hands back certificates and how long those certificates should live. This will allow for exercising the main loop more than would be possible with it coupled so tightly with the Agent.
206 lines
4.7 KiB
Go
206 lines
4.7 KiB
Go
package consul
|
|
|
|
import (
|
|
"context"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/asn1"
|
|
"net"
|
|
"net/url"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/consul/agent/connect"
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
"github.com/hashicorp/consul/sdk/testutil"
|
|
"github.com/hashicorp/go-hclog"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestAutoEncrypt_resolveAddr(t *testing.T) {
|
|
type args struct {
|
|
rawHost string
|
|
logger hclog.Logger
|
|
}
|
|
logger := testutil.Logger(t)
|
|
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
ips []net.IP
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "host without port",
|
|
args: args{
|
|
"127.0.0.1",
|
|
logger,
|
|
},
|
|
ips: []net.IP{net.IPv4(127, 0, 0, 1)},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "host with port",
|
|
args: args{
|
|
"127.0.0.1:1234",
|
|
logger,
|
|
},
|
|
ips: []net.IP{net.IPv4(127, 0, 0, 1)},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "host with broken port",
|
|
args: args{
|
|
"127.0.0.1:xyz",
|
|
logger,
|
|
},
|
|
ips: []net.IP{net.IPv4(127, 0, 0, 1)},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "not an address",
|
|
args: args{
|
|
"abc",
|
|
logger,
|
|
},
|
|
ips: nil,
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ips, err := resolveAddr(tt.args.rawHost, tt.args.logger)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("resolveAddr error: %v, wantErr: %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
require.Equal(t, tt.ips, ips)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAutoEncrypt_missingPortError(t *testing.T) {
|
|
host := "127.0.0.1"
|
|
_, _, err := net.SplitHostPort(host)
|
|
require.True(t, missingPortError(host, err))
|
|
|
|
host = "127.0.0.1:1234"
|
|
_, _, err = net.SplitHostPort(host)
|
|
require.False(t, missingPortError(host, err))
|
|
}
|
|
|
|
func TestAutoEncrypt_RequestAutoEncryptCerts(t *testing.T) {
|
|
dir1, c1 := testClient(t)
|
|
defer os.RemoveAll(dir1)
|
|
defer c1.Shutdown()
|
|
servers := []string{"localhost"}
|
|
port := 8301
|
|
token := ""
|
|
|
|
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(75*time.Millisecond))
|
|
defer cancel()
|
|
|
|
doneCh := make(chan struct{})
|
|
var err error
|
|
go func() {
|
|
_, err = c1.RequestAutoEncryptCerts(ctx, servers, port, token, nil, nil)
|
|
close(doneCh)
|
|
}()
|
|
select {
|
|
case <-doneCh:
|
|
// since there are no servers at this port, we shouldn't be
|
|
// done and this should be an error of some sorts that happened
|
|
// in the setup phase before entering the for loop in
|
|
// RequestAutoEncryptCerts.
|
|
require.NoError(t, err)
|
|
case <-ctx.Done():
|
|
// this is the happy case since auto encrypt is in its loop to
|
|
// try to request certs.
|
|
}
|
|
}
|
|
|
|
func TestAutoEncrypt_autoEncryptCSR(t *testing.T) {
|
|
type testCase struct {
|
|
conf *Config
|
|
extraDNSSANs []string
|
|
extraIPSANs []net.IP
|
|
err string
|
|
|
|
// to validate the csr
|
|
expectedSubject pkix.Name
|
|
expectedSigAlg x509.SignatureAlgorithm
|
|
expectedPubAlg x509.PublicKeyAlgorithm
|
|
expectedDNSNames []string
|
|
expectedIPs []net.IP
|
|
expectedURIs []*url.URL
|
|
}
|
|
|
|
cases := map[string]testCase{
|
|
"sans": {
|
|
conf: &Config{
|
|
Datacenter: "dc1",
|
|
NodeName: "test-node",
|
|
CAConfig: &structs.CAConfiguration{},
|
|
},
|
|
extraDNSSANs: []string{"foo.local", "bar.local"},
|
|
extraIPSANs: []net.IP{net.IPv4(198, 18, 0, 1), net.IPv4(198, 18, 0, 2)},
|
|
expectedSubject: pkix.Name{
|
|
CommonName: connect.AgentCN("test-node", dummyTrustDomain),
|
|
Names: []pkix.AttributeTypeAndValue{
|
|
{
|
|
// 2,5,4,3 is the CommonName type ASN1 identifier
|
|
Type: asn1.ObjectIdentifier{2, 5, 4, 3},
|
|
Value: "testnode.agnt.dummy.tr.consul",
|
|
},
|
|
},
|
|
},
|
|
expectedSigAlg: x509.ECDSAWithSHA256,
|
|
expectedPubAlg: x509.ECDSA,
|
|
expectedDNSNames: []string{
|
|
"localhost",
|
|
"foo.local",
|
|
"bar.local",
|
|
},
|
|
expectedIPs: []net.IP{
|
|
{127, 0, 0, 1},
|
|
net.ParseIP("::1"),
|
|
{198, 18, 0, 1},
|
|
{198, 18, 0, 2},
|
|
},
|
|
expectedURIs: []*url.URL{
|
|
{
|
|
Scheme: "spiffe",
|
|
Host: dummyTrustDomain,
|
|
Path: "/agent/client/dc/dc1/id/test-node",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for name, tcase := range cases {
|
|
t.Run(name, func(t *testing.T) {
|
|
client := Client{config: tcase.conf}
|
|
|
|
_, csr, err := client.autoEncryptCSR(tcase.extraDNSSANs, tcase.extraIPSANs)
|
|
if tcase.err == "" {
|
|
require.NoError(t, err)
|
|
|
|
request, err := connect.ParseCSR(csr)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, request)
|
|
|
|
require.Equal(t, tcase.expectedSubject, request.Subject)
|
|
require.Equal(t, tcase.expectedSigAlg, request.SignatureAlgorithm)
|
|
require.Equal(t, tcase.expectedPubAlg, request.PublicKeyAlgorithm)
|
|
require.Equal(t, tcase.expectedDNSNames, request.DNSNames)
|
|
require.Equal(t, tcase.expectedIPs, request.IPAddresses)
|
|
require.Equal(t, tcase.expectedURIs, request.URIs)
|
|
} else {
|
|
require.Error(t, err)
|
|
require.Empty(t, csr)
|
|
}
|
|
})
|
|
}
|
|
}
|